Skip to content

Commit 99a398c

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

File tree

8 files changed

+327
-3
lines changed

8 files changed

+327
-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

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

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)