-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #549 from synonymdev/replace-tiny-secp256k1
fix(wallet): Implement noble-secp256k1 Wrapper
- Loading branch information
Showing
5 changed files
with
161 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -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" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters