UNPKG

13.1 kBJavaScriptView Raw
1/**
2 * Ecdsa
3 * =====
4 *
5 * Ecdsa is the signature algorithm used by bitcoin. The way you probably want
6 * to use this is with the static Ecdsa.sign( ... ) and Ecdsa.verify( ... )
7 * functions. Note that in bitcoin, the hashBuf is little endian, so if you are
8 * signing or verifying something that has to do with a transaction, you should
9 * explicitly plug in that it is little endian as an option to the sign and
10 * verify functions.
11 *
12 * This implementation of Ecdsa uses deterministic signatures as defined in RFC
13 * 6979 as the default, which has become a defacto standard in bitcoin wallets
14 * due to recurring security issues around using a value of k pulled from a
15 * possibly faulty entropy pool. If you use the same value of k twice, someone
16 * can derive your private key. Deterministic k prevents this without needing
17 * an entropy pool.
18 */
19'use strict'
20
21import { Bn } from './bn'
22import { Br } from './br'
23import { Hash } from './hash'
24import { KeyPair } from './key-pair'
25import { Point } from './point'
26import { PubKey } from './pub-key'
27import { Random } from './random'
28import { Sig } from './sig'
29import { Struct } from './struct'
30import { Workers } from './workers'
31
32class 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 * Calculates the recovery factor, and mutates sig so that it now contains
105 * the recovery factor and the "compressed" variable. Throws an exception on
106 * failure.
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 * The traditional Ecdsa algorithm uses a purely random value of k. This has
155 * the negative that when signing, your entropy must be good, or the private
156 * key can be recovered if two signatures use the same value of k. It turns out
157 * that k does not have to be purely random. It can be deterministic, so long
158 * as an attacker can't guess it. RFC 6979 specifies how to do this using a
159 * combination of the private key and the hash of the thing to be signed. It is
160 * best practice to use this value, which can be tested for byte-for-byte
161 * accuracy, and is resistant to a broken RNG. Note that it is actually the
162 * case that bitcoin private keys have been compromised through that attack.
163 * Deterministic k is a best practice.
164 *
165 * https://tools.ietf.org/html/rfc6979#section-3.2
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 // if r or s were invalid when this function was used in signing,
188 // we do not want to actually compute r, s here for efficiency, so,
189 // we can increment badrs. explained at end of RFC 6979 section 3.2
190 if (badrs === undefined) {
191 badrs = 0
192 }
193 // also explained in 3.2, we must ensure T is in the proper range (0, N)
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 * Information about public key recovery:
207 * https://bitcointalk.org/index.php?topic=6430.0
208 * http://stackoverflow.com/questions/19665491/how-do-i-get-an-ecdsa-public-key-from-just-a-bitcoin-signature-sec1-4-1-6-k
209 * This code was originally taken from BitcoinJS
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 // A set LSB signifies that the y-coordinate is odd
224 const isYOdd = recovery & 1
225
226 // The more significant bit specifies whether we should use the
227 // first or second candidate key.
228 const isSecondKey = recovery >> 1
229
230 const n = Point.getN()
231 const G = Point.getG()
232
233 // 1.1 LEt x = r + jn
234 const x = isSecondKey ? r.add(n) : r
235 const R = Point.fromX(isYOdd, x)
236
237 // 1.4 Check that nR is at infinity
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 // Compute -e from e
249 const eNeg = e.neg().umod(n)
250
251 // 1.6.1 Compute Q = r^-1 (sR - eG)
252 // Q = r^-1 (sR + -eG)
253 const rInv = r.invm(n)
254
255 // const Q = R.multiplyTwo(s, G, eNeg).mul(rInv)
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 // const p = Point.getG().mulAdd(u1, this.keyPair.pubKey.point, u2)
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 // try different values of k until r, s are valid
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 // enforce low s
388 // see Bip 62, "low S values in signatures"
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
495export { Ecdsa }