Skip to content

Commit

Permalink
Refactor code to use Uint8Array instead of Buffer
Browse files Browse the repository at this point in the history
  • Loading branch information
pajasevi committed Jan 6, 2024
1 parent 08a8a52 commit 4d61f3e
Show file tree
Hide file tree
Showing 12 changed files with 355 additions and 251 deletions.
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

0 comments on commit 4d61f3e

Please sign in to comment.