Skip to content

Commit

Permalink
ref!: replace node Buffer with Uint8Array
Browse files Browse the repository at this point in the history
  • Loading branch information
AJ ONeal committed Feb 3, 2023
1 parent 625ed07 commit a655cce
Showing 1 changed file with 93 additions and 81 deletions.
174 changes: 93 additions & 81 deletions lib/hdkey.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@

/**
* @typedef hdkey
* @prop {Buffer} chainCode - extra 32-bytes of shared entropy for xkeys
* @prop {Uint8Array} chainCode - extra 32-bytes of shared entropy for xkeys
* @prop {Number} depth - of hd path - typically 0 is seed, 1-3 hardened, 4-5 are not
* @prop {Buffer} identifier - same bytes as pubKeyHash, but used for id
* @prop {Uint8Array} identifier - same bytes as pubKeyHash, but used for id
* @prop {Number} index - the final segment of an HD Path, the index of the wif/addr
* @prop {Number} parentFingerprint - 32-bit int, slice of id, stored in child xkeys
* @prop {Buffer} publicKey
* @prop {Uint8Array} publicKey
* @prop {HDVersions} versions - magic bytes for base58 prefix
* @prop {HDDerivePath} derive - derive a full hd path from the given root
* @prop {HDDeriveChild} deriveChild - get the next child xkey (in a path segment)
Expand All @@ -43,22 +43,31 @@ var HDKey = ("object" === typeof module && exports) || {};
(function (window, HDKey) {
"use strict";

//const BUFFER_LE = true;
const BUFFER_BE = false;

let crypto = require("crypto");
let bs58check = require("bs58check");
let RIPEMD160 = require("ripemd160");
let secp256k1 = require("secp256k1");

let MASTER_SECRET = Buffer.from("Bitcoin seed", "utf8");
// "Bitcoin seed"
let MASTER_SECRET = Uint8Array.from([
// B i t c o i n " " s e e d
0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x20, 0x73, 0x65, 0x65, 0x64,
]);
let HARDENED_OFFSET = 0x80000000;
let LEN = 78;
let KEY_SIZE = 33;
let INDEXED_KEY_SIZE = 4 + KEY_SIZE;
let XKEY_SIZE = 78;

// Bitcoin hardcoded by default, can use package `coininfo` for others
let BITCOIN_VERSIONS = { private: 0x0488ade4, public: 0x0488b21e };

HDKey.create = function (versions) {
/** @type {hdkey} */
let hdkey = {};
/** @type {Buffer?} */
/** @type {Uint8Array?} */
let _privateKey = null;

hdkey.versions = versions || BITCOIN_VERSIONS;
Expand All @@ -73,9 +82,22 @@ var HDKey = ("object" === typeof module && exports) || {};
if (!hdkey.identifier) {
throw new Error("Public key has not been set");
}
return hdkey.identifier.slice(0, 4).readUInt32BE(0);
let i32be = readUInt32BE(hdkey.identifier, 0);
return i32be;
};

/**
* @param {Uint8Array} u8 - a "web" JS buffer
* @param {Number} offset - where to start reading
* @returns {Number} - a 0-shifted (uint) JS Number
*/
function readUInt32BE(u8, offset) {
let dv = new DataView(u8.buffer);
// will read offset + 4 bytes (32-bit uint)
let n = dv.getUint32(offset, BUFFER_BE);
return n;
}

hdkey.getPrivateKey = function () {
return _privateKey;
};
Expand All @@ -84,7 +106,7 @@ var HDKey = ("object" === typeof module && exports) || {};
assert(secp256k1.privateKeyVerify(value) === true, "Invalid private key");

_privateKey = value;
hdkey.publicKey = Buffer.from(secp256k1.publicKeyCreate(value, true));
hdkey.publicKey = secp256k1.publicKeyCreate(value, true);
hdkey.identifier = hash160(hdkey.publicKey);
};

Expand All @@ -101,10 +123,10 @@ var HDKey = ("object" === typeof module && exports) || {};
};

/**
* @param {Buffer} publicKey
* @param {Uint8Array} publicKey
*/
hdkey._setPublicKey = function (publicKey) {
hdkey.publicKey = Buffer.from(publicKey);
hdkey.publicKey = publicKey;
hdkey.identifier = hash160(publicKey);
_privateKey = null;
};
Expand All @@ -114,13 +136,10 @@ var HDKey = ("object" === typeof module && exports) || {};
return null;
}

return bs58check.encode(
serialize(
hdkey,
hdkey.versions.private,
Buffer.concat([Buffer.alloc(1, 0), _privateKey]),
),
);
let key = new Uint8Array(KEY_SIZE);
key.set([0], 0);
key.set(_privateKey, 1);
return bs58check.encode(serialize(hdkey, hdkey.versions.private, key));
};

hdkey.getPublicExtendedKey = function () {
Expand Down Expand Up @@ -160,36 +179,42 @@ var HDKey = ("object" === typeof module && exports) || {};
return _hdkey;
};

// IMPORTANT: never allow `await` (or other async) between writing to
// and accessing these! (otherwise the data will be corrupted)
// (stored here for performance - no allocations or garbage collection)
let _indexBuffer = new Uint8Array(4);
let _indexDv = new DataView(_indexBuffer.buffer);

hdkey.deriveChild = function (index) {
let isHardened = index >= HARDENED_OFFSET;
let indexBuffer = Buffer.allocUnsafe(4);
indexBuffer.writeUInt32BE(index, 0);
let offset = 0;
_indexDv.setUint32(offset, index, BUFFER_BE);

let data;
let data = new Uint8Array(INDEXED_KEY_SIZE);

if (isHardened) {
// Hardened child
if (!_privateKey) {
throw new Error("Could not derive hardened child key");
}

let pk = _privateKey;
let zb = Buffer.alloc(1, 0);
pk = Buffer.concat([zb, pk]);

// data = 0x00 || ser256(kpar) || ser32(index)
data = Buffer.concat([pk, indexBuffer]);
data.set([0], 0); // 1
data.set(_privateKey, 1); // 32
data.set(_indexBuffer, KEY_SIZE);
} else {
// Normal child
// data = serP(point(kpar)) || ser32(index)
// = serP(Kpar) || ser32(index)
data = Buffer.concat([hdkey.publicKey, indexBuffer]);
data.set(hdkey.publicKey, 0);
data.set(_indexBuffer, KEY_SIZE);
}

let I = crypto
let IBuf = crypto
.createHmac("sha512", hdkey.chainCode)
.update(data)
.digest();
let I = new Uint8Array(IBuf);
let IL = I.slice(0, 32);
let IR = I.slice(32);

Expand All @@ -199,11 +224,8 @@ var HDKey = ("object" === typeof module && exports) || {};
if (_privateKey) {
// ki = parse256(IL) + kpar (mod n)
try {
hd.setPrivateKey(
Buffer.from(
secp256k1.privateKeyTweakAdd(Buffer.from(_privateKey), IL),
),
);
let privateKeyCopy = new Uint8Array(_privateKey);
hd.setPrivateKey(secp256k1.privateKeyTweakAdd(privateKeyCopy, IL));
// throw if IL >= n || (privateKey + IL) === 0
} catch (err) {
// In case parse256(IL) >= n or ki == 0, one should proceed with the next value for i
Expand All @@ -214,15 +236,8 @@ var HDKey = ("object" === typeof module && exports) || {};
// Ki = point(parse256(IL)) + Kpar
// = G*IL + Kpar
try {
hd.setPublicKey(
Buffer.from(
secp256k1.publicKeyTweakAdd(
Buffer.from(hdkey.publicKey),
IL,
true,
),
),
);
let publicKeyCopy = new Uint8Array(hdkey.publicKey);
hd.setPublicKey(secp256k1.publicKeyTweakAdd(publicKeyCopy, IL, true));
// throw if IL >= n || (g**IL + publicKey) is infinity
} catch (err) {
// In case parse256(IL) >= n or Ki is the point at infinity, one should proceed with the next value for i
Expand All @@ -243,18 +258,11 @@ var HDKey = ("object" === typeof module && exports) || {};
throw new Error("Private Key must be set");
}

return Buffer.from(
secp256k1.ecdsaSign(Uint8Array.from(hash), Uint8Array.from(_privateKey))
.signature,
);
return secp256k1.ecdsaSign(hash, _privateKey).signature;
};

hdkey.verify = function (hash, signature) {
return secp256k1.ecdsaVerify(
Uint8Array.from(signature),
Uint8Array.from(hash),
Uint8Array.from(hdkey.publicKey),
);
return secp256k1.ecdsaVerify(signature, hash, hdkey.publicKey);
};

hdkey.wipePrivateData = function () {
Expand All @@ -276,12 +284,13 @@ var HDKey = ("object" === typeof module && exports) || {};
};

HDKey.fromMasterSeed = function (seedBuffer, versions) {
let I = crypto
let IBuf = crypto
.createHmac("sha512", MASTER_SECRET)
.update(seedBuffer)
.digest();
let IL = I.slice(0, 32);
let IR = I.slice(32);
let I = new Uint8Array(IBuf);
let IL = I.subarray(0, 32);
let IR = I.subarray(32);

let hdkey = HDKey.create(versions);
hdkey.chainCode = IR;
Expand All @@ -297,26 +306,28 @@ var HDKey = ("object" === typeof module && exports) || {};
let hdkey = HDKey.create(versions);

let keyBuffer = bs58check.decode(base58key);
let keyU8 = new Uint8Array(keyBuffer);
let keyDv = new DataView(keyU8.buffer, 0, keyU8.byteLength);

let version = keyBuffer.readUInt32BE(0);
let version = keyDv.getUint32(0, BUFFER_BE);
assert(
version === versions.private || version === versions.public,
"Version mismatch: does not match private or public",
);

hdkey.depth = keyBuffer.readUInt8(4);
hdkey.parentFingerprint = keyBuffer.readUInt32BE(5);
hdkey.index = keyBuffer.readUInt32BE(9);
hdkey.chainCode = keyBuffer.slice(13, 45);
hdkey.depth = keyDv.getUint8(4);
hdkey.parentFingerprint = keyDv.getUint32(5, BUFFER_BE);
hdkey.index = keyDv.getUint32(9, BUFFER_BE);
hdkey.chainCode = keyU8.subarray(13, 45);

let key = keyBuffer.slice(45);
if (key.readUInt8(0) === 0) {
let key = keyU8.subarray(45);
if (keyDv.getUint8(45) === 0) {
// private
assert(
version === versions.private,
"Version mismatch: version does not match private",
);
hdkey.setPrivateKey(key.slice(1)); // cut off first 0x0 byte
hdkey.setPrivateKey(key.subarray(1)); // cut off first 0x0 byte
} else {
assert(
version === versions.public,
Expand Down Expand Up @@ -349,28 +360,29 @@ var HDKey = ("object" === typeof module && exports) || {};
/**
* @param {hdkey} hdkey - TODO attach to hdkey
* @param {Number} version
* @param {Buffer} key
* @param {Uint8Array} key
*/
function serialize(hdkey, version, key) {
// => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33)
let buffer = Buffer.allocUnsafe(LEN);
let xkey = new Uint8Array(XKEY_SIZE);
let xkeyDv = new DataView(xkey.buffer);

buffer.writeUInt32BE(version, 0);
buffer.writeUInt8(hdkey.depth, 4);
xkeyDv.setUint32(0, version, BUFFER_BE);
xkeyDv.setUint8(4, hdkey.depth);

let fingerprint = hdkey.depth ? hdkey.parentFingerprint : 0x00000000;
buffer.writeUInt32BE(fingerprint, 5);
buffer.writeUInt32BE(hdkey.index, 9);
let fingerprint = (hdkey.depth && hdkey.parentFingerprint) || 0x00000000;
xkeyDv.setUint32(5, fingerprint, BUFFER_BE);
xkeyDv.setUint32(9, hdkey.index, BUFFER_BE);

hdkey.chainCode.copy(buffer, 13);
key.copy(buffer, 45);
xkey.set(hdkey.chainCode, 13);
xkey.set(key, 45);

return buffer;
return xkey;
}

/**
* @param {Buffer} buf
* @returns {Buffer}
* @param {Uint8Array} buf
* @returns {Uint8Array}
*/
function hash160(buf) {
let sha = crypto.createHash("sha256").update(buf).digest();
Expand Down Expand Up @@ -433,13 +445,13 @@ if ("object" === typeof module) {

/**
* @callback HDFromSeed
* @param {Buffer} seedBuffer
* @param {Uint8Array} seedBuffer
* @param {HDVersions} [versions]
*/

/**
* @callback HDGetBuffer
* @returns {Buffer}
* @returns {Uint8Array}
*/

/**
Expand All @@ -449,7 +461,7 @@ if ("object" === typeof module) {

/**
* @callback HDMaybeGetBuffer
* @returns {Buffer?}
* @returns {Uint8Array?}
*/

/**
Expand All @@ -459,13 +471,13 @@ if ("object" === typeof module) {

/**
* @callback HDSetBuffer
* @param {Buffer} buf
* @param {Uint8Array} buf
*/

/**
* @callback HDSign
* @param {Buffer} hash
* @returns {Buffer} - signature
* @param {Uint8Array} hash
* @returns {Uint8Array} - signature
*/

/**
Expand All @@ -475,8 +487,8 @@ if ("object" === typeof module) {

/**
* @callback HDVerify
* @param {Buffer} hash
* @param {Buffer} signature
* @param {Uint8Array} hash
* @param {Uint8Array} signature
* @returns {Boolean}
*/

Expand Down

0 comments on commit a655cce

Please sign in to comment.