From 3f8a83a73a6705f9d330ea3c774ffe7c5bbc3443 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Thu, 20 Oct 2022 16:10:45 +0200 Subject: [PATCH] fix(wallet): Implement noble-secp256k1 Wrapper Replace tiny-secp256k1 with noble-secp256k1 wrapper. --- .../nodejs-project/bitcoin-actions.js | 4 +- .../nodejs-project/nobleSecp256k1Wrapper.js | 147 ++++++++++++++++++ nodejs-assets/nodejs-project/package.json | 6 +- nodejs-assets/nodejs-project/yarn.lock | 9 +- src/utils/wallet/index.ts | 1 - 5 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 nodejs-assets/nodejs-project/nobleSecp256k1Wrapper.js diff --git a/nodejs-assets/nodejs-project/bitcoin-actions.js b/nodejs-assets/nodejs-project/bitcoin-actions.js index 8c022937e..94e95f941 100644 --- a/nodejs-assets/nodejs-project/bitcoin-actions.js +++ b/nodejs-assets/nodejs-project/bitcoin-actions.js @@ -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, diff --git a/nodejs-assets/nodejs-project/nobleSecp256k1Wrapper.js b/nodejs-assets/nodejs-project/nobleSecp256k1Wrapper.js new file mode 100644 index 000000000..3a016a8f4 --- /dev/null +++ b/nodejs-assets/nodejs-project/nobleSecp256k1Wrapper.js @@ -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; diff --git a/nodejs-assets/nodejs-project/package.json b/nodejs-assets/nodejs-project/package.json index 2c7eb4b31..f3fa7660b 100644 --- a/nodejs-assets/nodejs-project/package.json +++ b/nodejs-assets/nodejs-project/package.json @@ -9,8 +9,10 @@ "author": "Synonym", "license": "MIT", "dependencies": { - "bip32": "https://github.com/synonymdev/bip32", + "@noble/secp256k1": "^1.7.0", + "bip32": "git+ssh://git@github.com/synonymdev/bip32", "bip39": "^3.0.4", - "bitcoinjs-lib": "^6.0.1" + "bitcoinjs-lib": "^6.0.1", + "create-hmac": "^1.1.7" } } diff --git a/nodejs-assets/nodejs-project/yarn.lock b/nodejs-assets/nodejs-project/yarn.lock index 644c95998..bd2c8475a 100644 --- a/nodejs-assets/nodejs-project/yarn.lock +++ b/nodejs-assets/nodejs-project/yarn.lock @@ -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/node@11.11.6": version "11.11.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a" @@ -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" diff --git a/src/utils/wallet/index.ts b/src/utils/wallet/index.ts index 02d73f0b9..8726ea8a0 100644 --- a/src/utils/wallet/index.ts +++ b/src/utils/wallet/index.ts @@ -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);