Skip to content

Commit

Permalink
Merge pull request #549 from synonymdev/replace-tiny-secp256k1
Browse files Browse the repository at this point in the history
fix(wallet): Implement noble-secp256k1 Wrapper
  • Loading branch information
Jasonvdb authored Oct 20, 2022
2 parents d6cddeb + 3f8a83a commit a38141f
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 6 deletions.
4 changes: 3 additions & 1 deletion nodejs-assets/nodejs-project/bitcoin-actions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const networks = require("./networks");
const bip39 = require("bip39");
const bip32 = require("bip32");
const ecc = require('./nobleSecp256k1Wrapper');
const BIP32Factory = require('bip32').BIP32Factory;
const bip32 = BIP32Factory(ecc);
const bitcoin = require("bitcoinjs-lib");
const {
sha256,
Expand Down
147 changes: 147 additions & 0 deletions nodejs-assets/nodejs-project/nobleSecp256k1Wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Version 1.7.0 of noble-secp256k1 removed privateAdd, privateNegate,
* pointAddScalar, pointMultiply
* https://github.com/paulmillr/noble-secp256k1/releases/tag/1.7.0
*
* We add them back here.
*
* Read these notes to understand some of the changes on the functions above:
* https://github.com/bitcoinjs/ecpair/issues/13
*
* Initial version based on BitGo/BitGoJS:
* https://github.com/BitGo/BitGoJS/blob/bitcoinjs_lib_6_sync/modules/utxo-lib/src/noble_ecc.ts
*
*/

const { crypto: bcrypto } = require('bitcoinjs-lib');
const createHmac = require('create-hmac');
const necc = require('@noble/secp256k1');

necc.utils.sha256Sync = (...messages) => {
return bcrypto.sha256(Buffer.concat(messages));
};
necc.utils.hmacSha256Sync = (key, ...messages) => {
const hash = createHmac('sha256', Buffer.from(key));
messages.forEach(m => hash.update(m));
return Uint8Array.from(hash.digest());
};

const normalizePrivateKey = necc.utils._normalizePrivateKey;
function hexToNumber(hex) {
if (typeof hex !== 'string') {
throw new TypeError('hexToNumber: expected string, got ' + typeof hex);
}
return BigInt(`0x${hex}`);
}
function bytesToNumber(bytes) {
return hexToNumber(necc.utils.bytesToHex(bytes));
}
function normalizeScalar(scalar) {
let num;
if (typeof scalar === 'bigint') {
num = scalar;
} else if (
typeof scalar === 'number' &&
Number.isSafeInteger(scalar) &&
scalar >= 0
) {
num = BigInt(scalar);
} else if (typeof scalar === 'string') {
if (scalar.length !== 64)
throw new Error('Expected 32 bytes of private scalar');
num = hexToNumber(scalar);
} else if (scalar instanceof Uint8Array) {
if (scalar.length !== 32)
throw new Error('Expected 32 bytes of private scalar');
num = bytesToNumber(scalar);
} else {
throw new TypeError('Expected valid private scalar');
}
if (num < 0) throw new Error('Expected private scalar >= 0');
return num;
}
const privateAdd = (privateKey, tweak) => {
const p = normalizePrivateKey(privateKey);
const t = normalizeScalar(tweak);
const add = necc.utils._bigintTo32Bytes(necc.utils.mod(p + t, necc.CURVE.n));
if (necc.utils.isValidPrivateKey(add)) return add;
else return null;
};
const privateNegate = privateKey => {
const p = normalizePrivateKey(privateKey);
const not = necc.utils._bigintTo32Bytes(necc.CURVE.n - p);
if (necc.utils.isValidPrivateKey(not)) return not;
else return null;
};
const pointAddScalar = (p, tweak, isCompressed) => {
const P = necc.Point.fromHex(p);
const t = normalizeScalar(tweak);
const Q = necc.Point.BASE.multiplyAndAddUnsafe(P, t, 1n);
if (!Q) throw new Error('Tweaked point at infinity');
return Q.toRawBytes(isCompressed);
};
const pointMultiply = (p, tweak, isCompressed) => {
const P = necc.Point.fromHex(p);
const h = typeof tweak === 'string' ? tweak : necc.utils.bytesToHex(tweak);
const t = BigInt(`0x${h}`);
return P.multiply(t).toRawBytes(isCompressed);
};

const defaultTrue = param => param !== false;
function throwToNull(fn) {
try {
return fn();
} catch (e) {
return null;
}
}
function _isPoint(p, xOnly) {
if ((p.length === 32) !== xOnly) return false;
try {
return !!necc.Point.fromHex(p);
} catch (e) {
return false;
}
}
const ecc = {
isPoint: p => _isPoint(p, false),
isPrivate: d => necc.utils.isValidPrivateKey(d),
isXOnlyPoint: p => _isPoint(p, true),
xOnlyPointAddTweak: (p, tweak) =>
throwToNull(() => {
const P = pointAddScalar(p, tweak, true);
const parity = P[0] % 2 === 1 ? 1 : 0;
return { parity, xOnlyPubkey: P.slice(1) };
}),
pointFromScalar: (sk, compressed) =>
throwToNull(() => necc.getPublicKey(sk, defaultTrue(compressed))),
pointCompress: (p, compressed) => {
return necc.Point.fromHex(p).toRawBytes(defaultTrue(compressed));
},
pointMultiply: (a, tweak, compressed) =>
throwToNull(() => pointMultiply(a, tweak, defaultTrue(compressed))),
pointAdd: (a, b, compressed) =>
throwToNull(() => {
const A = necc.Point.fromHex(a);
const B = necc.Point.fromHex(b);
return A.add(B).toRawBytes(defaultTrue(compressed));
}),
pointAddScalar: (p, tweak, compressed) =>
throwToNull(() => pointAddScalar(p, tweak, defaultTrue(compressed))),
privateAdd: (d, tweak) => throwToNull(() => privateAdd(d, tweak)),
privateNegate: d => privateNegate(d),
sign: (h, d, e) => {
return necc.signSync(h, d, { der: false, extraEntropy: e });
},
signSchnorr: (h, d, e = Buffer.alloc(32, 0x00)) => {
return necc.schnorr.signSync(h, d, e);
},
verify: (h, Q, signature, strict) => {
return necc.verify(signature, h, Q, { strict });
},
verifySchnorr: (h, Q, signature) => {
return necc.schnorr.verifySync(signature, h, Q);
}
};

module.exports = ecc;
6 changes: 4 additions & 2 deletions nodejs-assets/nodejs-project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
"author": "Synonym",
"license": "MIT",
"dependencies": {
"bip32": "https://github.com/synonymdev/bip32",
"@noble/secp256k1": "^1.7.0",
"bip32": "git+ssh://[email protected]/synonymdev/bip32",
"bip39": "^3.0.4",
"bitcoinjs-lib": "^6.0.1"
"bitcoinjs-lib": "^6.0.1",
"create-hmac": "^1.1.7"
}
}
9 changes: 7 additions & 2 deletions nodejs-assets/nodejs-project/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
# yarn lockfile v1


"@noble/secp256k1@^1.7.0":
version "1.7.0"
resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.0.tgz#d15357f7c227e751d90aa06b05a0e5cf993ba8c1"
integrity sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw==

"@types/[email protected]":
version "11.11.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a"
Expand All @@ -24,9 +29,9 @@ bip174@^2.0.1:
resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.0.tgz#cd3402581feaa5116f0f00a0eaee87a5843a2d30"
integrity sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA==

"bip32@https://github.com/synonymdev/bip32":
"bip32@git+ssh://git@github.com/synonymdev/bip32":
version "3.1.0"
resolved "https://github.com/synonymdev/bip32#58f0dd7d253d70fa5ff442997464892c46874348"
resolved "git+ssh://git@github.com/synonymdev/bip32#58f0dd7d253d70fa5ff442997464892c46874348"
dependencies:
bs58check "^2.1.1"
create-hash "^1.2.0"
Expand Down
1 change: 0 additions & 1 deletion src/utils/wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2025,7 +2025,6 @@ export const createDefaultWallet = async ({
changeAddressAmount,
keyDerivationPath: pathObject.value,
addressType: type,
seed, // Skip calculating the seed again (bip39.mnemonicToSeed takes 2-5s).
});
if (generatedAddresses.isErr()) {
return err(generatedAddresses.error);
Expand Down

0 comments on commit a38141f

Please sign in to comment.