Skip to content

Commit a38141f

Browse files
authored
Merge pull request #549 from synonymdev/replace-tiny-secp256k1
fix(wallet): Implement noble-secp256k1 Wrapper
2 parents d6cddeb + 3f8a83a commit a38141f

File tree

5 files changed

+161
-6
lines changed

5 files changed

+161
-6
lines changed

nodejs-assets/nodejs-project/bitcoin-actions.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
const networks = require("./networks");
22
const bip39 = require("bip39");
3-
const bip32 = require("bip32");
3+
const ecc = require('./nobleSecp256k1Wrapper');
4+
const BIP32Factory = require('bip32').BIP32Factory;
5+
const bip32 = BIP32Factory(ecc);
46
const bitcoin = require("bitcoinjs-lib");
57
const {
68
sha256,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Version 1.7.0 of noble-secp256k1 removed privateAdd, privateNegate,
3+
* pointAddScalar, pointMultiply
4+
* https://github.com/paulmillr/noble-secp256k1/releases/tag/1.7.0
5+
*
6+
* We add them back here.
7+
*
8+
* Read these notes to understand some of the changes on the functions above:
9+
* https://github.com/bitcoinjs/ecpair/issues/13
10+
*
11+
* Initial version based on BitGo/BitGoJS:
12+
* https://github.com/BitGo/BitGoJS/blob/bitcoinjs_lib_6_sync/modules/utxo-lib/src/noble_ecc.ts
13+
*
14+
*/
15+
16+
const { crypto: bcrypto } = require('bitcoinjs-lib');
17+
const createHmac = require('create-hmac');
18+
const necc = require('@noble/secp256k1');
19+
20+
necc.utils.sha256Sync = (...messages) => {
21+
return bcrypto.sha256(Buffer.concat(messages));
22+
};
23+
necc.utils.hmacSha256Sync = (key, ...messages) => {
24+
const hash = createHmac('sha256', Buffer.from(key));
25+
messages.forEach(m => hash.update(m));
26+
return Uint8Array.from(hash.digest());
27+
};
28+
29+
const normalizePrivateKey = necc.utils._normalizePrivateKey;
30+
function hexToNumber(hex) {
31+
if (typeof hex !== 'string') {
32+
throw new TypeError('hexToNumber: expected string, got ' + typeof hex);
33+
}
34+
return BigInt(`0x${hex}`);
35+
}
36+
function bytesToNumber(bytes) {
37+
return hexToNumber(necc.utils.bytesToHex(bytes));
38+
}
39+
function normalizeScalar(scalar) {
40+
let num;
41+
if (typeof scalar === 'bigint') {
42+
num = scalar;
43+
} else if (
44+
typeof scalar === 'number' &&
45+
Number.isSafeInteger(scalar) &&
46+
scalar >= 0
47+
) {
48+
num = BigInt(scalar);
49+
} else if (typeof scalar === 'string') {
50+
if (scalar.length !== 64)
51+
throw new Error('Expected 32 bytes of private scalar');
52+
num = hexToNumber(scalar);
53+
} else if (scalar instanceof Uint8Array) {
54+
if (scalar.length !== 32)
55+
throw new Error('Expected 32 bytes of private scalar');
56+
num = bytesToNumber(scalar);
57+
} else {
58+
throw new TypeError('Expected valid private scalar');
59+
}
60+
if (num < 0) throw new Error('Expected private scalar >= 0');
61+
return num;
62+
}
63+
const privateAdd = (privateKey, tweak) => {
64+
const p = normalizePrivateKey(privateKey);
65+
const t = normalizeScalar(tweak);
66+
const add = necc.utils._bigintTo32Bytes(necc.utils.mod(p + t, necc.CURVE.n));
67+
if (necc.utils.isValidPrivateKey(add)) return add;
68+
else return null;
69+
};
70+
const privateNegate = privateKey => {
71+
const p = normalizePrivateKey(privateKey);
72+
const not = necc.utils._bigintTo32Bytes(necc.CURVE.n - p);
73+
if (necc.utils.isValidPrivateKey(not)) return not;
74+
else return null;
75+
};
76+
const pointAddScalar = (p, tweak, isCompressed) => {
77+
const P = necc.Point.fromHex(p);
78+
const t = normalizeScalar(tweak);
79+
const Q = necc.Point.BASE.multiplyAndAddUnsafe(P, t, 1n);
80+
if (!Q) throw new Error('Tweaked point at infinity');
81+
return Q.toRawBytes(isCompressed);
82+
};
83+
const pointMultiply = (p, tweak, isCompressed) => {
84+
const P = necc.Point.fromHex(p);
85+
const h = typeof tweak === 'string' ? tweak : necc.utils.bytesToHex(tweak);
86+
const t = BigInt(`0x${h}`);
87+
return P.multiply(t).toRawBytes(isCompressed);
88+
};
89+
90+
const defaultTrue = param => param !== false;
91+
function throwToNull(fn) {
92+
try {
93+
return fn();
94+
} catch (e) {
95+
return null;
96+
}
97+
}
98+
function _isPoint(p, xOnly) {
99+
if ((p.length === 32) !== xOnly) return false;
100+
try {
101+
return !!necc.Point.fromHex(p);
102+
} catch (e) {
103+
return false;
104+
}
105+
}
106+
const ecc = {
107+
isPoint: p => _isPoint(p, false),
108+
isPrivate: d => necc.utils.isValidPrivateKey(d),
109+
isXOnlyPoint: p => _isPoint(p, true),
110+
xOnlyPointAddTweak: (p, tweak) =>
111+
throwToNull(() => {
112+
const P = pointAddScalar(p, tweak, true);
113+
const parity = P[0] % 2 === 1 ? 1 : 0;
114+
return { parity, xOnlyPubkey: P.slice(1) };
115+
}),
116+
pointFromScalar: (sk, compressed) =>
117+
throwToNull(() => necc.getPublicKey(sk, defaultTrue(compressed))),
118+
pointCompress: (p, compressed) => {
119+
return necc.Point.fromHex(p).toRawBytes(defaultTrue(compressed));
120+
},
121+
pointMultiply: (a, tweak, compressed) =>
122+
throwToNull(() => pointMultiply(a, tweak, defaultTrue(compressed))),
123+
pointAdd: (a, b, compressed) =>
124+
throwToNull(() => {
125+
const A = necc.Point.fromHex(a);
126+
const B = necc.Point.fromHex(b);
127+
return A.add(B).toRawBytes(defaultTrue(compressed));
128+
}),
129+
pointAddScalar: (p, tweak, compressed) =>
130+
throwToNull(() => pointAddScalar(p, tweak, defaultTrue(compressed))),
131+
privateAdd: (d, tweak) => throwToNull(() => privateAdd(d, tweak)),
132+
privateNegate: d => privateNegate(d),
133+
sign: (h, d, e) => {
134+
return necc.signSync(h, d, { der: false, extraEntropy: e });
135+
},
136+
signSchnorr: (h, d, e = Buffer.alloc(32, 0x00)) => {
137+
return necc.schnorr.signSync(h, d, e);
138+
},
139+
verify: (h, Q, signature, strict) => {
140+
return necc.verify(signature, h, Q, { strict });
141+
},
142+
verifySchnorr: (h, Q, signature) => {
143+
return necc.schnorr.verifySync(signature, h, Q);
144+
}
145+
};
146+
147+
module.exports = ecc;

nodejs-assets/nodejs-project/package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
"author": "Synonym",
1010
"license": "MIT",
1111
"dependencies": {
12-
"bip32": "https://github.com/synonymdev/bip32",
12+
"@noble/secp256k1": "^1.7.0",
13+
"bip32": "git+ssh://[email protected]/synonymdev/bip32",
1314
"bip39": "^3.0.4",
14-
"bitcoinjs-lib": "^6.0.1"
15+
"bitcoinjs-lib": "^6.0.1",
16+
"create-hmac": "^1.1.7"
1517
}
1618
}

nodejs-assets/nodejs-project/yarn.lock

+7-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
# yarn lockfile v1
33

44

5+
"@noble/secp256k1@^1.7.0":
6+
version "1.7.0"
7+
resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.0.tgz#d15357f7c227e751d90aa06b05a0e5cf993ba8c1"
8+
integrity sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw==
9+
510
611
version "11.11.6"
712
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a"
@@ -24,9 +29,9 @@ bip174@^2.0.1:
2429
resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.0.tgz#cd3402581feaa5116f0f00a0eaee87a5843a2d30"
2530
integrity sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA==
2631

27-
"bip32@https://github.com/synonymdev/bip32":
32+
"bip32@git+ssh://git@github.com/synonymdev/bip32":
2833
version "3.1.0"
29-
resolved "https://github.com/synonymdev/bip32#58f0dd7d253d70fa5ff442997464892c46874348"
34+
resolved "git+ssh://git@github.com/synonymdev/bip32#58f0dd7d253d70fa5ff442997464892c46874348"
3035
dependencies:
3136
bs58check "^2.1.1"
3237
create-hash "^1.2.0"

src/utils/wallet/index.ts

-1
Original file line numberDiff line numberDiff line change
@@ -2025,7 +2025,6 @@ export const createDefaultWallet = async ({
20252025
changeAddressAmount,
20262026
keyDerivationPath: pathObject.value,
20272027
addressType: type,
2028-
seed, // Skip calculating the seed again (bip39.mnemonicToSeed takes 2-5s).
20292028
});
20302029
if (generatedAddresses.isErr()) {
20312030
return err(generatedAddresses.error);

0 commit comments

Comments
 (0)