|
1 | 1 | 'use strict';
|
2 | 2 |
|
3 |
| -var BN = require('./bn'); |
4 |
| -var Point = require('./point'); |
| 3 | +const { hmacSync } = require('@exodus/crypto/hmac') |
| 4 | +const secp256k1 = require('@noble/secp256k1') |
5 | 5 | var Signature = require('./signature');
|
6 |
| -var PublicKey = require('../publickey'); |
7 |
| -var Random = require('./random'); |
8 |
| -var Hash = require('./hash'); |
9 | 6 | var BufferUtil = require('../util/buffer');
|
10 | 7 | var _ = require('lodash');
|
11 | 8 | var $ = require('../util/preconditions');
|
12 | 9 |
|
13 |
| -var ECDSA = function ECDSA(obj) { |
14 |
| - if (!(this instanceof ECDSA)) { |
15 |
| - return new ECDSA(obj); |
16 |
| - } |
17 |
| - if (obj) { |
18 |
| - this.set(obj); |
19 |
| - } |
20 |
| -}; |
21 |
| - |
22 |
| -/* jshint maxcomplexity: 9 */ |
23 |
| -ECDSA.prototype.set = function(obj) { |
24 |
| - this.hashbuf = obj.hashbuf || this.hashbuf; |
25 |
| - this.endian = obj.endian || this.endian; //the endianness of hashbuf |
26 |
| - this.privkey = obj.privkey || this.privkey; |
27 |
| - this.pubkey = obj.pubkey || (this.privkey ? this.privkey.publicKey : this.pubkey); |
28 |
| - this.sig = obj.sig || this.sig; |
29 |
| - this.k = obj.k || this.k; |
30 |
| - this.verified = obj.verified || this.verified; |
31 |
| - return this; |
32 |
| -}; |
33 |
| - |
34 |
| -ECDSA.prototype.privkey2pubkey = function() { |
35 |
| - this.pubkey = this.privkey.toPublicKey(); |
36 |
| -}; |
37 |
| - |
38 |
| -ECDSA.prototype.calci = function() { |
39 |
| - for (var i = 0; i < 4; i++) { |
40 |
| - this.sig.i = i; |
41 |
| - var Qprime; |
42 |
| - try { |
43 |
| - Qprime = this.toPublicKey(); |
44 |
| - } catch (e) { |
45 |
| - console.error(e); |
46 |
| - continue; |
47 |
| - } |
48 |
| - |
49 |
| - if (Qprime.point.eq(this.pubkey.point)) { |
50 |
| - this.sig.compressed = this.pubkey.compressed; |
51 |
| - return this; |
52 |
| - } |
53 |
| - } |
54 |
| - |
55 |
| - this.sig.i = undefined; |
56 |
| - throw new Error('Unable to find valid recovery factor'); |
57 |
| -}; |
58 |
| - |
59 |
| -ECDSA.fromString = function(str) { |
60 |
| - var obj = JSON.parse(str); |
61 |
| - return new ECDSA(obj); |
62 |
| -}; |
63 |
| - |
64 |
| -ECDSA.prototype.randomK = function() { |
65 |
| - var N = Point.getN(); |
66 |
| - var k; |
67 |
| - do { |
68 |
| - k = BN.fromBuffer(Random.getRandomBuffer(32)); |
69 |
| - } while (!(k.lt(N) && k.gt(BN.Zero))); |
70 |
| - this.k = k; |
71 |
| - return this; |
72 |
| -}; |
73 |
| - |
74 |
| - |
75 |
| -// https://tools.ietf.org/html/rfc6979#section-3.2 |
76 |
| -ECDSA.prototype.deterministicK = function(badrs) { |
77 |
| - /* jshint maxstatements: 25 */ |
78 |
| - // if r or s were invalid when this function was used in signing, |
79 |
| - // we do not want to actually compute r, s here for efficiency, so, |
80 |
| - // we can increment badrs. explained at end of RFC 6979 section 3.2 |
81 |
| - if (_.isUndefined(badrs)) { |
82 |
| - badrs = 0; |
83 |
| - } |
84 |
| - var v = Buffer.alloc(32); |
85 |
| - v.fill(0x01); |
86 |
| - var k = Buffer.alloc(32); |
87 |
| - k.fill(0x00); |
88 |
| - var x = this.privkey.bn.toBuffer({ |
89 |
| - size: 32 |
90 |
| - }); |
91 |
| - var hashbuf = this.endian === 'little' ? BufferUtil.reverse(this.hashbuf) : this.hashbuf |
92 |
| - k = Hash.sha256hmac(Buffer.concat([v, Buffer.from([0x00]), x, hashbuf]), k); |
93 |
| - v = Hash.sha256hmac(v, k); |
94 |
| - k = Hash.sha256hmac(Buffer.concat([v, Buffer.from([0x01]), x, hashbuf]), k); |
95 |
| - v = Hash.sha256hmac(v, k); |
96 |
| - v = Hash.sha256hmac(v, k); |
97 |
| - var T = BN.fromBuffer(v); |
98 |
| - var N = Point.getN(); |
| 10 | +if (!secp256k1.utils.hmacSha256Sync) { |
| 11 | + secp256k1.utils.hmacSha256Sync = (key, ...msgs) => hmacSync('sha256', key, msgs, 'uint8') |
| 12 | +} |
99 | 13 |
|
100 |
| - // also explained in 3.2, we must ensure T is in the proper range (0, N) |
101 |
| - for (var i = 0; i < badrs || !(T.lt(N) && T.gt(BN.Zero)); i++) { |
102 |
| - k = Hash.sha256hmac(Buffer.concat([v, Buffer.from([0x00])]), k); |
103 |
| - v = Hash.sha256hmac(v, k); |
104 |
| - v = Hash.sha256hmac(v, k); |
105 |
| - T = BN.fromBuffer(v); |
| 14 | +exports.sign = function(hashbuf, privkey, endian, extraEntropy) { |
| 15 | + if (extraEntropy !== undefined) { |
| 16 | + if (!(extraEntropy instanceof Uint8Array)) throw new Error('Expected extraEntropy Uint8Array') |
| 17 | + if (extraEntropy.length !== 32) throw new Error('Expected extraEntropy to be of length 32') |
106 | 18 | }
|
107 |
| - |
108 |
| - this.k = T; |
109 |
| - return this; |
110 |
| -}; |
111 |
| - |
112 |
| -// Information about public key recovery: |
113 |
| -// https://bitcointalk.org/index.php?topic=6430.0 |
114 |
| -// http://stackoverflow.com/questions/19665491/how-do-i-get-an-ecdsa-public-key-from-just-a-bitcoin-signature-sec1-4-1-6-k |
115 |
| -ECDSA.prototype.toPublicKey = function() { |
116 |
| - /* jshint maxstatements: 25 */ |
117 |
| - var i = this.sig.i; |
118 |
| - $.checkArgument(i === 0 || i === 1 || i === 2 || i === 3, new Error('i must be equal to 0, 1, 2, or 3')); |
119 |
| - |
120 |
| - var e = BN.fromBuffer(this.hashbuf); |
121 |
| - var r = this.sig.r; |
122 |
| - var s = this.sig.s; |
123 |
| - |
124 |
| - // A set LSB signifies that the y-coordinate is odd |
125 |
| - var isYOdd = i & 1; |
126 |
| - |
127 |
| - // The more significant bit specifies whether we should use the |
128 |
| - // first or second candidate key. |
129 |
| - var isSecondKey = i >> 1; |
130 |
| - |
131 |
| - var n = Point.getN(); |
132 |
| - var G = Point.getG(); |
133 |
| - |
134 |
| - // 1.1 Let x = r + jn |
135 |
| - var x = isSecondKey ? r.add(n) : r; |
136 |
| - var R = Point.fromX(isYOdd, x); |
137 |
| - |
138 |
| - // 1.4 Check that nR is at infinity |
139 |
| - var nR = R.mul(n); |
140 |
| - |
141 |
| - if (!nR.isInfinity()) { |
142 |
| - throw new Error('nR is not a valid curve point'); |
143 |
| - } |
144 |
| - |
145 |
| - // Compute -e from e |
146 |
| - var eNeg = e.neg().mod(n); |
147 |
| - |
148 |
| - // 1.6.1 Compute Q = r^-1 (sR - eG) |
149 |
| - // Q = r^-1 (sR + -eG) |
150 |
| - var rInv = r.invm(n); |
151 |
| - |
152 |
| - //var Q = R.multiplyTwo(s, G, eNeg).mul(rInv); |
153 |
| - var Q = R.mul(s).add(G.mul(eNeg)).mul(rInv); |
154 |
| - |
155 |
| - var pubkey = PublicKey.fromPoint(Q, this.sig.compressed); |
156 |
| - |
157 |
| - return pubkey; |
158 |
| -}; |
159 |
| - |
160 |
| -ECDSA.prototype.sigError = function() { |
161 |
| - /* jshint maxstatements: 25 */ |
162 |
| - if (!BufferUtil.isBuffer(this.hashbuf) || this.hashbuf.length !== 32) { |
163 |
| - return 'hashbuf must be a 32 byte buffer'; |
164 |
| - } |
165 |
| - |
166 |
| - var r = this.sig.r; |
167 |
| - var s = this.sig.s; |
168 |
| - if (!(r.gt(BN.Zero) && r.lt(Point.getN())) || !(s.gt(BN.Zero) && s.lt(Point.getN()))) { |
169 |
| - return 'r and s not in range'; |
170 |
| - } |
171 |
| - |
172 |
| - var e = BN.fromBuffer(this.hashbuf, this.endian ? { |
173 |
| - endian: this.endian |
174 |
| - } : undefined); |
175 |
| - var n = Point.getN(); |
176 |
| - var sinv = s.invm(n); |
177 |
| - var u1 = sinv.mul(e).mod(n); |
178 |
| - var u2 = sinv.mul(r).mod(n); |
179 |
| - |
180 |
| - var p = Point.getG().mulAdd(u1, this.pubkey.point, u2); |
181 |
| - if (p.isInfinity()) { |
182 |
| - return 'p is infinity'; |
183 |
| - } |
184 |
| - |
185 |
| - if (p.getX().mod(n).cmp(r) !== 0) { |
186 |
| - return 'Invalid signature'; |
187 |
| - } else { |
188 |
| - return false; |
189 |
| - } |
190 |
| -}; |
191 |
| - |
192 |
| -ECDSA.toLowS = function(s) { |
193 |
| - //enforce low s |
194 |
| - //see BIP 62, "low S values in signatures" |
195 |
| - if (s.gt(BN.fromBuffer(Buffer.from('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex')))) { |
196 |
| - s = Point.getN().sub(s); |
197 |
| - } |
198 |
| - return s; |
199 |
| -}; |
200 |
| - |
201 |
| -ECDSA.prototype._findSignature = function(d, e) { |
202 |
| - var N = Point.getN(); |
203 |
| - var G = Point.getG(); |
204 |
| - // try different values of k until r, s are valid |
205 |
| - var badrs = 0; |
206 |
| - var k, Q, r, s; |
207 |
| - do { |
208 |
| - if (!this.k || badrs > 0) { |
209 |
| - this.deterministicK(badrs); |
210 |
| - } |
211 |
| - badrs++; |
212 |
| - k = this.k; |
213 |
| - Q = G.mul(k); |
214 |
| - r = Q.x.mod(N); |
215 |
| - s = k.invm(N).mul(e.add(d.mul(r))).mod(N); |
216 |
| - } while (r.cmp(BN.Zero) <= 0 || s.cmp(BN.Zero) <= 0); |
217 |
| - |
218 |
| - s = ECDSA.toLowS(s); |
219 |
| - return { |
220 |
| - s: s, |
221 |
| - r: r |
222 |
| - }; |
223 |
| - |
224 |
| -}; |
225 |
| - |
226 |
| -ECDSA.prototype.sign = function() { |
227 |
| - var hashbuf = this.hashbuf; |
228 |
| - var privkey = this.privkey; |
229 |
| - var d = privkey.bn; |
230 |
| - |
231 |
| - $.checkState(hashbuf && privkey && d, new Error('invalid parameters')); |
| 19 | + if (!(hashbuf instanceof Uint8Array)) throw new Error('Expected Uint8Array') |
| 20 | + $.checkState(hashbuf && privkey && privkey.bn, new Error('invalid parameters')); |
232 | 21 | $.checkState(BufferUtil.isBuffer(hashbuf) && hashbuf.length === 32, new Error('hashbuf must be a 32 byte buffer'));
|
233 |
| - |
234 |
| - var e = BN.fromBuffer(hashbuf, this.endian ? { |
235 |
| - endian: this.endian |
236 |
| - } : undefined); |
237 |
| - |
238 |
| - var obj = this._findSignature(d, e); |
239 |
| - obj.compressed = this.pubkey.compressed; |
240 |
| - |
241 |
| - this.sig = new Signature(obj); |
242 |
| - return this; |
243 |
| -}; |
244 |
| - |
245 |
| -ECDSA.prototype.signRandomK = function() { |
246 |
| - this.randomK(); |
247 |
| - return this.sign(); |
248 |
| -}; |
249 |
| - |
250 |
| -ECDSA.prototype.toString = function() { |
251 |
| - var obj = {}; |
252 |
| - if (this.hashbuf) { |
253 |
| - obj.hashbuf = this.hashbuf.toString('hex'); |
254 |
| - } |
255 |
| - if (this.privkey) { |
256 |
| - obj.privkey = this.privkey.toString(); |
257 |
| - } |
258 |
| - if (this.pubkey) { |
259 |
| - obj.pubkey = this.pubkey.toString(); |
260 |
| - } |
261 |
| - if (this.sig) { |
262 |
| - obj.sig = this.sig.toString(); |
263 |
| - } |
264 |
| - if (this.k) { |
265 |
| - obj.k = this.k.toString(); |
266 |
| - } |
267 |
| - return JSON.stringify(obj); |
| 22 | + if (endian === 'little') hashbuf = (Buffer.from(hashbuf)).reverse() |
| 23 | + const privbuf = privkey.bn.toBuffer({ size: 32 }) |
| 24 | + const der = secp256k1.signSync(hashbuf, privbuf) |
| 25 | + const sig = Signature.fromDER(Buffer.from(der)) |
| 26 | + sig.compressed = privkey.publicKey.compressed |
| 27 | + return sig |
268 | 28 | };
|
269 | 29 |
|
270 |
| -ECDSA.prototype.verify = function() { |
271 |
| - if (!this.sigError()) { |
272 |
| - this.verified = true; |
273 |
| - } else { |
274 |
| - this.verified = false; |
275 |
| - } |
276 |
| - return this; |
| 30 | +exports.verify = function(hashbuf, sig, pubkey, endian) { |
| 31 | + if (!(hashbuf instanceof Uint8Array)) throw new Error('Expected Uint8Array') |
| 32 | + if (endian === 'little') hashbuf = (Buffer.from(hashbuf)).reverse() |
| 33 | + const pubbuf = pubkey.toDER() |
| 34 | + const der = sig.toDER() |
| 35 | + return secp256k1.verify(der, hashbuf, pubbuf, { strict: false }) // allows highS per specific test |
277 | 36 | };
|
278 |
| - |
279 |
| -ECDSA.sign = function(hashbuf, privkey, endian) { |
280 |
| - return ECDSA().set({ |
281 |
| - hashbuf: hashbuf, |
282 |
| - endian: endian, |
283 |
| - privkey: privkey |
284 |
| - }).sign().sig; |
285 |
| -}; |
286 |
| - |
287 |
| -ECDSA.verify = function(hashbuf, sig, pubkey, endian) { |
288 |
| - return ECDSA().set({ |
289 |
| - hashbuf: hashbuf, |
290 |
| - endian: endian, |
291 |
| - sig: sig, |
292 |
| - pubkey: pubkey |
293 |
| - }).verify().verified; |
294 |
| -}; |
295 |
| - |
296 |
| -module.exports = ECDSA; |
0 commit comments