1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | 'use strict'
|
20 |
|
21 | import { Bn } from './bn'
|
22 | import { Br } from './br'
|
23 | import { Hash } from './hash'
|
24 | import { KeyPair } from './key-pair'
|
25 | import { Point } from './point'
|
26 | import { PubKey } from './pub-key'
|
27 | import { Random } from './random'
|
28 | import { Sig } from './sig'
|
29 | import { Struct } from './struct'
|
30 | import { Workers } from './workers'
|
31 |
|
32 | class Ecdsa extends Struct {
|
33 | constructor (sig, keyPair, hashBuf, k, endian, verified) {
|
34 | super({ sig, keyPair, hashBuf, k, endian, verified })
|
35 | }
|
36 |
|
37 | toJSON () {
|
38 | return {
|
39 | sig: this.sig ? this.sig.toString() : undefined,
|
40 | keyPair: this.keyPair
|
41 | ? this.keyPair.toBuffer().toString('hex')
|
42 | : undefined,
|
43 | hashBuf: this.hashBuf ? this.hashBuf.toString('hex') : undefined,
|
44 | k: this.k ? this.k.toString() : undefined,
|
45 | endian: this.endian,
|
46 | verified: this.verified
|
47 | }
|
48 | }
|
49 |
|
50 | fromJSON (json) {
|
51 | this.sig = json.sig ? new Sig().fromString(json.sig) : undefined
|
52 | this.keyPair = json.keyPair
|
53 | ? new KeyPair().fromBuffer(Buffer.from(json.keyPair, 'hex'))
|
54 | : undefined
|
55 | this.hashBuf = json.hashBuf ? Buffer.from(json.hashBuf, 'hex') : undefined
|
56 | this.k = json.k ? new Bn().fromString(json.k) : undefined
|
57 | this.endian = json.endian
|
58 | this.verified = json.verified
|
59 | return this
|
60 | }
|
61 |
|
62 | toBuffer () {
|
63 | const str = JSON.stringify(this.toJSON())
|
64 | return Buffer.from(str)
|
65 | }
|
66 |
|
67 | fromBuffer (buf) {
|
68 | const json = JSON.parse(buf.toString())
|
69 | return this.fromJSON(json)
|
70 | }
|
71 |
|
72 | calcrecovery () {
|
73 | for (let recovery = 0; recovery < 4; recovery++) {
|
74 | let Qprime
|
75 | this.sig.recovery = recovery
|
76 | try {
|
77 | Qprime = this.sig2PubKey()
|
78 | } catch (e) {
|
79 | continue
|
80 | }
|
81 |
|
82 | if (Qprime.point.eq(this.keyPair.pubKey.point)) {
|
83 | const compressed = this.keyPair.pubKey.compressed
|
84 | this.sig.compressed =
|
85 | this.keyPair.pubKey.compressed === undefined ? true : compressed
|
86 | return this
|
87 | }
|
88 | }
|
89 |
|
90 | this.sig.recovery = undefined
|
91 | throw new Error('Unable to find valid recovery factor')
|
92 | }
|
93 |
|
94 | async asyncCalcrecovery () {
|
95 | const workersResult = await Workers.asyncObjectMethod(
|
96 | this,
|
97 | 'calcrecovery',
|
98 | []
|
99 | )
|
100 | return this.fromFastBuffer(workersResult.resbuf)
|
101 | }
|
102 |
|
103 | |
104 |
|
105 |
|
106 |
|
107 |
|
108 | static calcrecovery (sig, pubKey, hashBuf) {
|
109 | const ecdsa = new Ecdsa().fromObject({
|
110 | sig: sig,
|
111 | keyPair: new KeyPair().fromObject({ pubKey: pubKey }),
|
112 | hashBuf: hashBuf
|
113 | })
|
114 | return ecdsa.calcrecovery().sig
|
115 | }
|
116 |
|
117 | static async asyncCalcrecovery (sig, pubKey, hashBuf) {
|
118 | const workersResult = await Workers.asyncClassMethod(
|
119 | Ecdsa,
|
120 | 'calcrecovery',
|
121 | [sig, pubKey, hashBuf]
|
122 | )
|
123 | return new Sig().fromFastBuffer(workersResult.resbuf)
|
124 | }
|
125 |
|
126 | fromString (str) {
|
127 | const obj = JSON.parse(str)
|
128 | if (obj.hashBuf) {
|
129 | this.hashBuf = Buffer.from(obj.hashBuf, 'hex')
|
130 | }
|
131 | if (obj.keyPair) {
|
132 | this.keyPair = new KeyPair().fromString(obj.keyPair)
|
133 | }
|
134 | if (obj.sig) {
|
135 | this.sig = new Sig().fromString(obj.sig)
|
136 | }
|
137 | if (obj.k) {
|
138 | this.k = new Bn(obj.k, 10)
|
139 | }
|
140 | return this
|
141 | }
|
142 |
|
143 | randomK () {
|
144 | const N = Point.getN()
|
145 | let k
|
146 | do {
|
147 | k = new Bn().fromBuffer(Random.getRandomBuffer(32))
|
148 | } while (!(k.lt(N) && k.gt(0)))
|
149 | this.k = k
|
150 | return this
|
151 | }
|
152 |
|
153 | |
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 | deterministicK (badrs) {
|
168 | let v = Buffer.alloc(32)
|
169 | v.fill(0x01)
|
170 | let k = Buffer.alloc(32)
|
171 | k.fill(0x00)
|
172 | const x = this.keyPair.privKey.bn.toBuffer({ size: 32 })
|
173 | k = Hash.sha256Hmac(
|
174 | Buffer.concat([v, Buffer.from([0x00]), x, this.hashBuf]),
|
175 | k
|
176 | )
|
177 | v = Hash.sha256Hmac(v, k)
|
178 | k = Hash.sha256Hmac(
|
179 | Buffer.concat([v, Buffer.from([0x01]), x, this.hashBuf]),
|
180 | k
|
181 | )
|
182 | v = Hash.sha256Hmac(v, k)
|
183 | v = Hash.sha256Hmac(v, k)
|
184 | let T = new Bn().fromBuffer(v)
|
185 | const N = Point.getN()
|
186 |
|
187 |
|
188 |
|
189 |
|
190 | if (badrs === undefined) {
|
191 | badrs = 0
|
192 | }
|
193 |
|
194 | for (let i = 0; i < badrs || !(T.lt(N) && T.gt(0)); i++) {
|
195 | k = Hash.sha256Hmac(Buffer.concat([v, Buffer.from([0x00])]), k)
|
196 | v = Hash.sha256Hmac(v, k)
|
197 | v = Hash.sha256Hmac(v, k)
|
198 | T = new Bn().fromBuffer(v)
|
199 | }
|
200 |
|
201 | this.k = T
|
202 | return this
|
203 | }
|
204 |
|
205 | |
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | sig2PubKey () {
|
212 | const recovery = this.sig.recovery
|
213 | if (
|
214 | !(recovery === 0 || recovery === 1 || recovery === 2 || recovery === 3)
|
215 | ) {
|
216 | throw new Error('i must be equal to 0, 1, 2, or 3')
|
217 | }
|
218 |
|
219 | const e = new Bn().fromBuffer(this.hashBuf)
|
220 | const r = this.sig.r
|
221 | const s = this.sig.s
|
222 |
|
223 |
|
224 | const isYOdd = recovery & 1
|
225 |
|
226 |
|
227 |
|
228 | const isSecondKey = recovery >> 1
|
229 |
|
230 | const n = Point.getN()
|
231 | const G = Point.getG()
|
232 |
|
233 |
|
234 | const x = isSecondKey ? r.add(n) : r
|
235 | const R = Point.fromX(isYOdd, x)
|
236 |
|
237 |
|
238 | let errm = ''
|
239 | try {
|
240 | R.mul(n)
|
241 | } catch (err) {
|
242 | errm = err.message
|
243 | }
|
244 | if (errm !== 'point mul out of range') {
|
245 | throw new Error('nR is not a valid curve point')
|
246 | }
|
247 |
|
248 |
|
249 | const eNeg = e.neg().umod(n)
|
250 |
|
251 |
|
252 |
|
253 | const rInv = r.invm(n)
|
254 |
|
255 |
|
256 | const Q = R.mul(s)
|
257 | .add(G.mul(eNeg))
|
258 | .mul(rInv)
|
259 |
|
260 | const pubKey = new PubKey(Q)
|
261 | pubKey.compressed = this.sig.compressed
|
262 | pubKey.validate()
|
263 |
|
264 | return pubKey
|
265 | }
|
266 |
|
267 | async asyncSig2PubKey () {
|
268 | const workersResult = await Workers.asyncObjectMethod(
|
269 | this,
|
270 | 'sig2PubKey',
|
271 | []
|
272 | )
|
273 | return PubKey.fromFastBuffer(workersResult.resbuf)
|
274 | }
|
275 |
|
276 | static sig2PubKey (sig, hashBuf) {
|
277 | const ecdsa = new Ecdsa().fromObject({
|
278 | sig: sig,
|
279 | hashBuf: hashBuf
|
280 | })
|
281 | return ecdsa.sig2PubKey()
|
282 | }
|
283 |
|
284 | static async asyncSig2PubKey (sig, hashBuf) {
|
285 | const ecdsa = new Ecdsa().fromObject({
|
286 | sig: sig,
|
287 | hashBuf: hashBuf
|
288 | })
|
289 | const pubKey = await ecdsa.asyncSig2PubKey()
|
290 | return pubKey
|
291 | }
|
292 |
|
293 | verifyStr (enforceLowS = true) {
|
294 | if (!Buffer.isBuffer(this.hashBuf) || this.hashBuf.length !== 32) {
|
295 | return 'hashBuf must be a 32 byte buffer'
|
296 | }
|
297 |
|
298 | try {
|
299 | this.keyPair.pubKey.validate()
|
300 | } catch (e) {
|
301 | return 'Invalid pubKey: ' + e
|
302 | }
|
303 |
|
304 | const r = this.sig.r
|
305 | const s = this.sig.s
|
306 | if (
|
307 | !(r.gt(0) && r.lt(Point.getN())) ||
|
308 | !(s.gt(0) && s.lt(Point.getN()))
|
309 | ) {
|
310 | return 'r and s not in range'
|
311 | }
|
312 |
|
313 | if (enforceLowS) {
|
314 | if (!this.sig.hasLowS()) {
|
315 | return 's is too high and does not satisfy low s contraint - see bip 62'
|
316 | }
|
317 | }
|
318 |
|
319 | const e = new Bn().fromBuffer(
|
320 | this.hashBuf,
|
321 | this.endian ? { endian: this.endian } : undefined
|
322 | )
|
323 | const n = Point.getN()
|
324 | const sinv = s.invm(n)
|
325 | const u1 = sinv.mul(e).mod(n)
|
326 | const u2 = sinv.mul(r).mod(n)
|
327 |
|
328 | const p = Point.getG().mulAdd(u1, this.keyPair.pubKey.point, u2)
|
329 |
|
330 | if (p.isInfinity()) {
|
331 | return 'p is infinity'
|
332 | }
|
333 |
|
334 | if (
|
335 | !(
|
336 | p
|
337 | .getX()
|
338 | .mod(n)
|
339 | .cmp(r) === 0
|
340 | )
|
341 | ) {
|
342 | return 'Invalid signature'
|
343 | } else {
|
344 | return false
|
345 | }
|
346 | }
|
347 |
|
348 | sign () {
|
349 | const hashBuf =
|
350 | this.endian === 'little'
|
351 | ? new Br(this.hashBuf).readReverse()
|
352 | : this.hashBuf
|
353 |
|
354 | const privKey = this.keyPair.privKey
|
355 |
|
356 | const d = privKey.bn
|
357 |
|
358 | if (!hashBuf || !privKey || !d) {
|
359 | throw new Error('invalid parameters')
|
360 | }
|
361 |
|
362 | if (!Buffer.isBuffer(hashBuf) || hashBuf.length !== 32) {
|
363 | throw new Error('hashBuf must be a 32 byte buffer')
|
364 | }
|
365 |
|
366 | const N = Point.getN()
|
367 | const G = Point.getG()
|
368 | const e = new Bn().fromBuffer(hashBuf)
|
369 |
|
370 |
|
371 | let badrs = 0
|
372 | let k, Q, r, s
|
373 | do {
|
374 | if (!this.k || badrs > 0) {
|
375 | this.deterministicK(badrs)
|
376 | }
|
377 | badrs++
|
378 | k = this.k
|
379 | Q = G.mul(k)
|
380 | r = Q.getX().mod(N)
|
381 | s = k
|
382 | .invm(N)
|
383 | .mul(e.add(d.mul(r)))
|
384 | .mod(N)
|
385 | } while (r.cmp(0) <= 0 || s.cmp(0) <= 0)
|
386 |
|
387 |
|
388 |
|
389 | if (
|
390 | s.gt(
|
391 | new Bn().fromBuffer(
|
392 | Buffer.from(
|
393 | '7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0',
|
394 | 'hex'
|
395 | )
|
396 | )
|
397 | )
|
398 | ) {
|
399 | s = Point.getN().sub(s)
|
400 | }
|
401 | this.sig = Sig.fromObject({
|
402 | r: r,
|
403 | s: s,
|
404 | compressed: this.keyPair.pubKey.compressed
|
405 | })
|
406 | return this
|
407 | }
|
408 |
|
409 | async asyncSign () {
|
410 | const workersResult = await Workers.asyncObjectMethod(this, 'sign', [])
|
411 | return this.fromFastBuffer(workersResult.resbuf)
|
412 | }
|
413 |
|
414 | signRandomK () {
|
415 | this.randomK()
|
416 | return this.sign()
|
417 | }
|
418 |
|
419 | toString () {
|
420 | const obj = {}
|
421 | if (this.hashBuf) {
|
422 | obj.hashBuf = this.hashBuf.toString('hex')
|
423 | }
|
424 | if (this.keyPair) {
|
425 | obj.keyPair = this.keyPair.toString()
|
426 | }
|
427 | if (this.sig) {
|
428 | obj.sig = this.sig.toString()
|
429 | }
|
430 | if (this.k) {
|
431 | obj.k = this.k.toString()
|
432 | }
|
433 | return JSON.stringify(obj)
|
434 | }
|
435 |
|
436 | verify (enforceLowS = true) {
|
437 | if (!this.verifyStr(enforceLowS)) {
|
438 | this.verified = true
|
439 | } else {
|
440 | this.verified = false
|
441 | }
|
442 | return this
|
443 | }
|
444 |
|
445 | async asyncVerify (enforceLowS = true) {
|
446 | const workersResult = await Workers.asyncObjectMethod(this, 'verify', [
|
447 | enforceLowS
|
448 | ])
|
449 | return this.fromFastBuffer(workersResult.resbuf)
|
450 | }
|
451 |
|
452 | static sign (hashBuf, keyPair, endian) {
|
453 | return new Ecdsa()
|
454 | .fromObject({
|
455 | hashBuf: hashBuf,
|
456 | endian: endian,
|
457 | keyPair: keyPair
|
458 | })
|
459 | .sign().sig
|
460 | }
|
461 |
|
462 | static async asyncSign (hashBuf, keyPair, endian) {
|
463 | const ecdsa = new Ecdsa().fromObject({
|
464 | hashBuf: hashBuf,
|
465 | endian: endian,
|
466 | keyPair: keyPair
|
467 | })
|
468 | await ecdsa.asyncSign()
|
469 | return ecdsa.sig
|
470 | }
|
471 |
|
472 | static verify (hashBuf, sig, pubKey, endian, enforceLowS = true) {
|
473 | return new Ecdsa()
|
474 | .fromObject({
|
475 | hashBuf: hashBuf,
|
476 | endian: endian,
|
477 | sig: sig,
|
478 | keyPair: new KeyPair().fromObject({ pubKey: pubKey })
|
479 | })
|
480 | .verify(enforceLowS).verified
|
481 | }
|
482 |
|
483 | static async asyncVerify (hashBuf, sig, pubKey, endian, enforceLowS = true) {
|
484 | const ecdsa = new Ecdsa().fromObject({
|
485 | hashBuf: hashBuf,
|
486 | endian: endian,
|
487 | sig: sig,
|
488 | keyPair: new KeyPair().fromObject({ pubKey: pubKey })
|
489 | })
|
490 | await ecdsa.asyncVerify(enforceLowS)
|
491 | return ecdsa.verified
|
492 | }
|
493 | }
|
494 |
|
495 | export { Ecdsa }
|