Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor code to use Uint8Array instead of Buffer #85

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 50 additions & 55 deletions src/bip32.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@ const crypto = require("./crypto");
const testecc_1 = require("./testecc");
const base_1 = require("@scure/base");
const sha256_1 = require("@noble/hashes/sha256");
const uint8array_utils_1 = require("./uint8array-utils");
const typeforce = require('typeforce');
const wif = require('wif');
const _bs58check = (0, base_1.base58check)(sha256_1.sha256);
const bs58check = {
encode: (data) => _bs58check.encode(Uint8Array.from(data)),
decode: (str) => Buffer.from(_bs58check.decode(str)),
};
const bs58check = (0, base_1.base58check)(sha256_1.sha256);
function BIP32Factory(ecc) {
(0, testecc_1.testEcc)(ecc);
const UINT256_TYPE = typeforce.BufferN(32);
const UINT256_TYPE = (0, uint8array_utils_1.Uint8ArrayTypeN)(32);
const NETWORK_TYPE = typeforce.compile({
wif: typeforce.UInt8,
bip32: {
Expand Down Expand Up @@ -42,7 +38,7 @@ function BIP32Factory(ecc) {
return typeforce.UInt32(value) && value <= UINT31_MAX;
}
function toXOnly(pubKey) {
return pubKey.length === 32 ? pubKey : pubKey.slice(1, 33);
return pubKey.length === 32 ? pubKey : pubKey.subarray(1, 33);
}
class Bip32Signer {
constructor(__D, __Q) {
Expand All @@ -52,7 +48,7 @@ function BIP32Factory(ecc) {
}
get publicKey() {
if (this.__Q === undefined)
this.__Q = Buffer.from(ecc.pointFromScalar(this.__D, true));
this.__Q = ecc.pointFromScalar(this.__D, true);
return this.__Q;
}
get privateKey() {
Expand All @@ -64,18 +60,19 @@ function BIP32Factory(ecc) {
if (lowR === undefined)
lowR = this.lowR;
if (lowR === false) {
return Buffer.from(ecc.sign(hash, this.privateKey));
return ecc.sign(hash, this.privateKey);
}
else {
let sig = Buffer.from(ecc.sign(hash, this.privateKey));
const extraData = Buffer.alloc(32, 0);
let sig = ecc.sign(hash, this.privateKey);
const extraData = new Uint8Array(32);
const extraDataView = new DataView(extraData.buffer);
let counter = 0;
// if first try is lowR, skip the loop
// for second try and on, add extra entropy counting up
while (sig[0] > 0x7f) {
counter++;
extraData.writeUIntLE(counter, 0, 6);
sig = Buffer.from(ecc.sign(hash, this.privateKey, extraData));
extraDataView.setUint32(0, counter, true);
sig = ecc.sign(hash, this.privateKey, extraData);
}
return sig;
}
Expand All @@ -85,7 +82,7 @@ function BIP32Factory(ecc) {
throw new Error('Missing private key');
if (!ecc.signSchnorr)
throw new Error('signSchnorr not supported by ecc library');
return Buffer.from(ecc.signSchnorr(hash, this.privateKey));
return ecc.signSchnorr(hash, this.privateKey);
}
verify(hash, signature) {
return ecc.verify(hash, this.publicKey, signature);
Expand Down Expand Up @@ -119,7 +116,7 @@ function BIP32Factory(ecc) {
return crypto.hash160(this.publicKey);
}
get fingerprint() {
return this.identifier.slice(0, 4);
return this.identifier.subarray(0, 4);
}
get compressed() {
return true;
Expand All @@ -137,56 +134,53 @@ function BIP32Factory(ecc) {
const version = !this.isNeutered()
? network.bip32.private
: network.bip32.public;
const buffer = Buffer.allocUnsafe(78);
const buffer = new Uint8Array(78);
const bufferView = new DataView(buffer.buffer);
// 4 bytes: version bytes
buffer.writeUInt32BE(version, 0);
bufferView.setUint32(0, version, false);
// 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ....
buffer.writeUInt8(this.depth, 4);
bufferView.setUint8(4, this.depth);
// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
buffer.writeUInt32BE(this.parentFingerprint, 5);
bufferView.setUint32(5, this.parentFingerprint, false);
// 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
// This is encoded in big endian. (0x00000000 if master key)
buffer.writeUInt32BE(this.index, 9);
bufferView.setUint32(9, this.index, false);
// 32 bytes: the chain code
this.chainCode.copy(buffer, 13);
buffer.set(this.chainCode, 13);
// 33 bytes: the public key or private key data
if (!this.isNeutered()) {
// 0x00 + k for private keys
buffer.writeUInt8(0, 45);
this.privateKey.copy(buffer, 46);
bufferView.setUint8(45, 0);
buffer.set(this.privateKey, 46);
// 33 bytes: the public key
}
else {
// X9.62 encoding for public keys
this.publicKey.copy(buffer, 45);
buffer.set(this.publicKey, 45);
}
return bs58check.encode(buffer);
}
toWIF() {
if (!this.privateKey)
throw new TypeError('Missing private key');
return wif.encode(this.network.wif, this.privateKey, true);
}
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions
derive(index) {
typeforce(typeforce.UInt32, index);
const isHardened = index >= HIGHEST_BIT;
const data = Buffer.allocUnsafe(37);
const data = new Uint8Array(37);
const dataView = new DataView(data.buffer);
// Hardened child
if (isHardened) {
if (this.isNeutered())
throw new TypeError('Missing private key for hardened child key');
// data = 0x00 || ser256(kpar) || ser32(index)
data[0] = 0x00;
this.privateKey.copy(data, 1);
data.writeUInt32BE(index, 33);
data.set(this.privateKey, 1);
dataView.setUint32(33, index, false);
// Normal child
}
else {
// data = serP(point(kpar)) || ser32(index)
// = serP(Kpar) || ser32(index)
this.publicKey.copy(data, 0);
data.writeUInt32BE(index, 33);
data.set(this.publicKey, 0);
dataView.setUint32(33, index, false);
}
const I = crypto.hmacSHA512(this.chainCode, data);
const IL = I.slice(0, 32);
Expand All @@ -198,21 +192,21 @@ function BIP32Factory(ecc) {
let hd;
if (!this.isNeutered()) {
// ki = parse256(IL) + kpar (mod n)
const ki = Buffer.from(ecc.privateAdd(this.privateKey, IL));
const ki = ecc.privateAdd(this.privateKey, IL);
// In case ki == 0, proceed with the next value for i
if (ki == null)
return this.derive(index + 1);
hd = fromPrivateKeyLocal(ki, IR, this.network, this.depth + 1, index, this.fingerprint.readUInt32BE(0));
hd = fromPrivateKeyLocal(ki, IR, this.network, this.depth + 1, index, new DataView(this.fingerprint.buffer).getUint32(0, false));
// Public parent key -> public child key
}
else {
// Ki = point(parse256(IL)) + Kpar
// = G*IL + Kpar
const Ki = Buffer.from(ecc.pointAddScalar(this.publicKey, IL, true));
const Ki = ecc.pointAddScalar(this.publicKey, IL, true);
// In case Ki is the point at infinity, proceed with the next value for i
if (Ki === null)
return this.derive(index + 1);
hd = fromPublicKeyLocal(Ki, IR, this.network, this.depth + 1, index, this.fingerprint.readUInt32BE(0));
hd = fromPublicKeyLocal(Ki, IR, this.network, this.depth + 1, index, new DataView(this.fingerprint.buffer).getUint32(0, false));
}
return hd;
}
Expand Down Expand Up @@ -253,13 +247,12 @@ function BIP32Factory(ecc) {
const tweakedPublicKey = ecc.xOnlyPointAddTweak(xOnlyPubKey, t);
if (!tweakedPublicKey || tweakedPublicKey.xOnlyPubkey === null)
throw new Error('Cannot tweak public key!');
const parityByte = Buffer.from([
const parityByte = Uint8Array.from([
tweakedPublicKey.parity === 0 ? 0x02 : 0x03,
]);
const tweakedPublicKeyCompresed = Buffer.concat([
parityByte,
tweakedPublicKey.xOnlyPubkey,
]);
const tweakedPublicKeyCompresed = new Uint8Array(tweakedPublicKey.xOnlyPubkey.length + 1);
tweakedPublicKeyCompresed.set(parityByte);
tweakedPublicKeyCompresed.set(tweakedPublicKey.xOnlyPubkey, 1);
return new Bip32Signer(undefined, tweakedPublicKeyCompresed);
}
tweakFromPrivateKey(t) {
Expand All @@ -276,44 +269,45 @@ function BIP32Factory(ecc) {
const tweakedPrivateKey = ecc.privateAdd(privateKey, t);
if (!tweakedPrivateKey)
throw new Error('Invalid tweaked private key!');
return new Bip32Signer(Buffer.from(tweakedPrivateKey), undefined);
return new Bip32Signer(tweakedPrivateKey, undefined);
}
}
function fromBase58(inString, network) {
const buffer = bs58check.decode(inString);
const bufferView = new DataView(buffer.buffer);
if (buffer.length !== 78)
throw new TypeError('Invalid buffer length');
network = network || BITCOIN;
// 4 bytes: version bytes
const version = buffer.readUInt32BE(0);
const version = bufferView.getUint32(0, false);
if (version !== network.bip32.private && version !== network.bip32.public)
throw new TypeError('Invalid network version');
// 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ...
const depth = buffer[4];
// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
const parentFingerprint = buffer.readUInt32BE(5);
const parentFingerprint = bufferView.getUint32(5, false);
if (depth === 0) {
if (parentFingerprint !== 0x00000000)
throw new TypeError('Invalid parent fingerprint');
}
// 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
// This is encoded in MSB order. (0x00000000 if master key)
const index = buffer.readUInt32BE(9);
const index = bufferView.getUint32(9, false);
if (depth === 0 && index !== 0)
throw new TypeError('Invalid index');
// 32 bytes: the chain code
const chainCode = buffer.slice(13, 45);
const chainCode = buffer.subarray(13, 45);
let hd;
// 33 bytes: private key data (0x00 + k)
if (version === network.bip32.private) {
if (buffer.readUInt8(45) !== 0x00)
if (bufferView.getUint8(45) !== 0x00)
throw new TypeError('Invalid private key');
const k = buffer.slice(46, 78);
const k = buffer.subarray(46, 78);
hd = fromPrivateKeyLocal(k, chainCode, network, depth, index, parentFingerprint);
// 33 bytes: public key data (0x02 + X or 0x03 + X)
}
else {
const X = buffer.slice(45, 78);
const X = buffer.subarray(45, 78);
hd = fromPublicKeyLocal(X, chainCode, network, depth, index, parentFingerprint);
}
return hd;
Expand All @@ -336,7 +330,7 @@ function BIP32Factory(ecc) {
}
function fromPublicKeyLocal(publicKey, chainCode, network, depth, index, parentFingerprint) {
typeforce({
publicKey: typeforce.BufferN(33),
publicKey: (0, uint8array_utils_1.Uint8ArrayTypeN)(33),
chainCode: UINT256_TYPE,
}, { publicKey, chainCode });
network = network || BITCOIN;
Expand All @@ -346,13 +340,14 @@ function BIP32Factory(ecc) {
return new BIP32(undefined, publicKey, chainCode, network, depth, index, parentFingerprint);
}
function fromSeed(seed, network) {
typeforce(typeforce.Buffer, seed);
typeforce(uint8array_utils_1.Uint8ArrayType, seed);
if (seed.length < 16)
throw new TypeError('Seed should be at least 128 bits');
if (seed.length > 64)
throw new TypeError('Seed should be at most 512 bits');
network = network || BITCOIN;
const I = crypto.hmacSHA512(Buffer.from('Bitcoin seed', 'utf8'), seed);
const encoder = new TextEncoder();
const I = crypto.hmacSHA512(encoder.encode('Bitcoin seed'), seed);
const IL = I.slice(0, 32);
const IR = I.slice(32);
return fromPrivateKey(IL, IR, network);
Expand Down
5 changes: 2 additions & 3 deletions src/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ const ripemd160_1 = require("@noble/hashes/ripemd160");
const sha256_1 = require("@noble/hashes/sha256");
const sha512_1 = require("@noble/hashes/sha512");
function hash160(buffer) {
const sha256Hash = (0, sha256_1.sha256)(Uint8Array.from(buffer));
return Buffer.from((0, ripemd160_1.ripemd160)(sha256Hash));
return (0, ripemd160_1.ripemd160)((0, sha256_1.sha256)(buffer));
}
exports.hash160 = hash160;
function hmacSHA512(key, data) {
return Buffer.from((0, hmac_1.hmac)(sha512_1.sha512, key, data));
return (0, hmac_1.hmac)(sha512_1.sha512, key, data);
}
exports.hmacSHA512 = hmacSHA512;
Loading
Loading