Skip to content

Commit f2f4072

Browse files
committed
feat: add noble impl for fallback when BigInt is available
1 parent 358c039 commit f2f4072

File tree

8 files changed

+325
-3
lines changed

8 files changed

+325
-3
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# secp256k1-node
22

3-
This module provides native bindings to [bitcoin-core/secp256k1](https://github.com/bitcoin-core/secp256k1). In browser [elliptic](https://github.com/indutny/elliptic) will be used as fallback.
3+
This module provides native bindings to [bitcoin-core/secp256k1](https://github.com/bitcoin-core/secp256k1).
4+
In browser [noble](https://github.com/paulmillr/noble-secp256k1) or [elliptic](https://github.com/indutny/elliptic) will be used as fallback.
45

56
Works on node version 14.0.0 or greater, because use [N-API](https://nodejs.org/api/n-api.html).
67

benchmarks/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const util = require('../test/util')
55
const implementations = {
66
bindings: require('../bindings'),
77
elliptic: require('../elliptic'),
8+
noble: require('../noble'),
89
ecdsa: require('./ecdsa')
910
}
1011

browser.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
if (typeof BigInt !== 'undefined') {
2+
module.exports = require('./noble.js')
3+
} else {
4+
module.exports = require('./elliptic.js')
5+
}

index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
try {
22
module.exports = require('./bindings')
33
} catch (err) {
4-
module.exports = require('./elliptic')
4+
module.exports = require('./browser')
55
}

lib/noble.js

+311
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
const secp256k1 = require('@noble/secp256k1')
2+
const { sha256 } = require('@noble/hashes/sha2')
3+
const { hmac } = require('@noble/hashes/hmac')
4+
5+
if (!secp256k1.utils.hmacSha256Sync) {
6+
secp256k1.utils.hmacSha256Sync = (key, ...msgs) => hmac(sha256, key, secp256k1.utils.concatBytes(...msgs))
7+
}
8+
if (!secp256k1.utils.sha256Sync) {
9+
secp256k1.utils.sha256Sync = (...msgs) => sha256(secp256k1.utils.concatBytes(...msgs))
10+
}
11+
12+
function writePublicKey(output, point) {
13+
const buf = point.toRawBytes(output.length === 33)
14+
if (output.length !== buf.length) return 1
15+
output.set(buf)
16+
return 0
17+
}
18+
19+
function toBig(arr) {
20+
// args already typechecked in ./lib/index.js
21+
return BigInt('0x' + secp256k1.utils.bytesToHex(arr))
22+
}
23+
24+
const _0n = BigInt(0)
25+
const _1n = BigInt(1)
26+
27+
let elliptic // used for signing with nonce function and/or non-32 byte extra entropy data
28+
29+
module.exports = {
30+
contextRandomize () {
31+
return 0
32+
},
33+
34+
privateKeyVerify (seckey) {
35+
return secp256k1.utils.isValidPrivateKey(seckey) ? 0 : 1
36+
},
37+
38+
// Validation matches ./elliptic.js
39+
// Doesn't fail on out of bounds values, normalize them
40+
privateKeyNegate (seckey) {
41+
const res = secp256k1.utils.mod(secp256k1.CURVE.n - toBig(seckey), secp256k1.CURVE.n)
42+
43+
const buf = secp256k1.utils._bigintTo32Bytes(res)
44+
seckey.set(buf)
45+
46+
return 0
47+
},
48+
49+
// Validation matches ./elliptic.js
50+
privateKeyTweakAdd (seckey, tweak) {
51+
let res = toBig(tweak)
52+
if (res >= secp256k1.CURVE.n) return 1
53+
54+
res = secp256k1.utils.mod(res + toBig(seckey), secp256k1.CURVE.n)
55+
if (res === _0n) return 1
56+
57+
const buf = secp256k1.utils._bigintTo32Bytes(res)
58+
seckey.set(buf)
59+
60+
return 0
61+
},
62+
63+
// Validation matches ./elliptic.js
64+
privateKeyTweakMul (seckey, tweak) {
65+
let res = toBig(tweak)
66+
if (res >= secp256k1.CURVE.n || res === 0n) return 1
67+
68+
res = secp256k1.utils.mod(res * toBig(seckey), secp256k1.CURVE.n)
69+
70+
const buf = secp256k1.utils._bigintTo32Bytes(res)
71+
seckey.set(buf)
72+
73+
return 0
74+
},
75+
76+
publicKeyVerify (pubkey) {
77+
try {
78+
return secp256k1.Point.fromHex(pubkey) ? 0 : 1
79+
} catch (err) {
80+
return 1
81+
}
82+
},
83+
84+
publicKeyCreate (output, seckey) {
85+
try {
86+
const publicKey = secp256k1.getPublicKey(seckey, output.length === 33)
87+
if (output.length !== publicKey.length) return 1
88+
output.set(publicKey)
89+
return 0
90+
} catch (err) {
91+
return 1
92+
}
93+
},
94+
95+
publicKeyConvert (output, pubkey) {
96+
try {
97+
const publicKey = secp256k1.Point.fromHex(pubkey).toRawBytes(output.length === 33)
98+
if (output.length !== publicKey.length) return 1
99+
output.set(publicKey)
100+
return 0
101+
} catch (err) {
102+
return 1
103+
}
104+
},
105+
106+
publicKeyNegate (output, pubkey) {
107+
let P
108+
try {
109+
P = secp256k1.Point.fromHex(pubkey)
110+
} catch (err) {
111+
return 1
112+
}
113+
114+
const point = P.negate()
115+
return writePublicKey(output, point)
116+
},
117+
118+
publicKeyCombine (output, pubkeys) {
119+
const points = new Array(pubkeys.length)
120+
for (let i = 0; i < pubkeys.length; ++i) {
121+
try {
122+
points[i] = secp256k1.Point.fromHex(pubkeys[i])
123+
} catch (err) {
124+
return 1
125+
}
126+
}
127+
128+
let point = points[0]
129+
for (let i = 1; i < points.length; ++i) point = point.add(points[i])
130+
if (point.equals(secp256k1.Point.ZERO)) return 2
131+
return writePublicKey(output, point)
132+
},
133+
134+
publicKeyTweakAdd (output, pubkey, tweak) {
135+
let P
136+
try {
137+
P = secp256k1.Point.fromHex(pubkey)
138+
} catch (err) {
139+
return 1
140+
}
141+
142+
tweak = toBig(tweak)
143+
if (tweak >= secp256k1.CURVE.n) return 2
144+
145+
// returns a non-zero point or undefined
146+
const point = secp256k1.Point.BASE.multiplyAndAddUnsafe(P, tweak, _1n) // timing-unsafe, ok here
147+
if (!point) return 2 // returns undefined on ZERO
148+
return writePublicKey(output, point)
149+
},
150+
151+
publicKeyTweakMul (output, pubkey, tweak) {
152+
let P
153+
try {
154+
P = secp256k1.Point.fromHex(pubkey)
155+
} catch (err) {
156+
return 1
157+
}
158+
159+
tweak = toBig(tweak)
160+
if (tweak >= secp256k1.CURVE.n || tweak === _0n) return 2
161+
162+
const point = P.multiply(tweak)
163+
if (point.equals(secp256k1.Point.ZERO)) return 2
164+
return writePublicKey(output, point)
165+
},
166+
167+
signatureNormalize (sig) {
168+
try {
169+
const signature = secp256k1.Signature.fromCompact(sig)
170+
if (signature.hasHighS()) {
171+
const normal = signature.normalizeS().toCompactRawBytes()
172+
sig.set(normal.subarray(32), 32)
173+
}
174+
} catch (err) {
175+
return 1
176+
}
177+
178+
return 0
179+
},
180+
181+
signatureExport (obj, sig) {
182+
let der
183+
try {
184+
der = secp256k1.Signature.fromCompact(sig).toDERRawBytes()
185+
} catch (err) {
186+
return 1
187+
}
188+
189+
if (obj.output.length < der.length) return 1
190+
191+
obj.output.set(der)
192+
obj.outputlen = der.length
193+
return 0
194+
},
195+
196+
signatureImport (output, sig) {
197+
try {
198+
buf = secp256k1.Signature.fromDER(sig).toCompactRawBytes()
199+
} catch (err) {
200+
return 1
201+
}
202+
203+
if (output.length !== buf.length) return 1
204+
output.set(buf)
205+
return 0
206+
},
207+
208+
ecdsaSign (obj, message, seckey, data, noncefn) {
209+
if (noncefn || data && data.length !== 32) {
210+
// Can we deprecate noncefn & drop it in next major? Also non-32 byte data
211+
if (!elliptic) elliptic = require('./elliptic.js')
212+
return elliptic.ecdsaSign(obj, message, seckey, data, noncefn)
213+
}
214+
215+
let sig
216+
try {
217+
sig = secp256k1.signSync(message, seckey, { der: false, recovered: true, extraEntropy: data })
218+
} catch (err) {
219+
return 1
220+
}
221+
222+
if (obj.signature.length !== sig[0].length) return 1
223+
obj.signature.set(sig[0])
224+
obj.recid = sig[1]
225+
return 0
226+
},
227+
228+
// Complex logic to return correct error codes
229+
ecdsaVerify (sig, msg32, pubkey) {
230+
if (sig.subarray(0, 32).every((x) => x === 0)) return 3
231+
if (sig.subarray(32, 64).every((x) => x === 0)) return 3
232+
233+
let signature
234+
try {
235+
signature = secp256k1.Signature.fromCompact(sig)
236+
} catch (err) {
237+
return 1
238+
}
239+
if (signature.hasHighS()) return 3
240+
241+
let pub
242+
try {
243+
pub = secp256k1.Point.fromHex(pubkey)
244+
} catch (err) {
245+
return 2
246+
}
247+
248+
return secp256k1.verify(sig, msg32, pub) ? 0 : 3
249+
},
250+
251+
// Complex logic to return correct error codes
252+
ecdsaRecover (output, sig, recid, msg32) {
253+
if (sig.subarray(0, 32).every((x) => x === 0)) return 2
254+
if (sig.subarray(32, 64).every((x) => x === 0)) return 2
255+
256+
let signature
257+
try {
258+
signature = secp256k1.Signature.fromCompact(sig)
259+
} catch (err) {
260+
return 1
261+
}
262+
263+
let buf
264+
try {
265+
buf = secp256k1.recoverPublicKey(msg32, signature, recid, output.length === 33)
266+
} catch (err) {
267+
return 2
268+
}
269+
270+
if (output.length !== buf.length) return 1
271+
output.set(buf)
272+
return 0
273+
},
274+
275+
ecdh (output, pubkey, seckey, data, hashfn, xbuf, ybuf) {
276+
let pub
277+
try {
278+
pub = secp256k1.Point.fromHex(pubkey)
279+
} catch (err) {
280+
return 1
281+
}
282+
283+
const compressed = hashfn === undefined
284+
285+
let point
286+
try {
287+
point = secp256k1.getSharedSecret(seckey, pub, compressed)
288+
} catch (err) {
289+
return 2
290+
}
291+
292+
if (hashfn === undefined) {
293+
output.set(sha256(point))
294+
} else {
295+
if (!xbuf) xbuf = new Uint8Array(32)
296+
xbuf.set(point.subarray(1, 33))
297+
298+
if (!ybuf) ybuf = new Uint8Array(32)
299+
ybuf.set(point.subarray(33))
300+
301+
const hash = hashfn(xbuf, ybuf, data)
302+
const isValid = hash instanceof Uint8Array && hash.length === output.length
303+
if (!isValid) return 2
304+
305+
output.set(hash)
306+
return 0
307+
}
308+
309+
return 0
310+
}
311+
}

noble.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./lib')(require('./lib/noble'))

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@
2626
],
2727
"main": "./index.js",
2828
"browser": {
29-
"./index.js": "./elliptic.js"
29+
"./index.js": "./browser.js"
3030
},
3131
"scripts": {
3232
"install": "node-gyp-build || exit 0"
3333
},
3434
"dependencies": {
35+
"@noble/hashes": "^1.3.3",
36+
"@noble/secp256k1": "^1.7.1",
3537
"elliptic": "^6.5.7",
3638
"node-addon-api": "^5.0.0",
3739
"node-gyp-build": "^4.2.0"

test/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ function testAPI (secp256k1, description) {
1717
}
1818

1919
if (!process.browser) testAPI(require('../bindings'), 'secp256k1 bindings')
20+
testAPI(require('../noble'), 'noble')
2021
testAPI(require('../elliptic'), 'elliptic')

0 commit comments

Comments
 (0)