Skip to content

Commit 9b2f94a

Browse files
committed
Implement Bitcoin's method for arbitrary message signatures.
1 parent 6bf363b commit 9b2f94a

File tree

4 files changed

+364
-14
lines changed

4 files changed

+364
-14
lines changed

src/ecdsa.js

Lines changed: 255 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,118 @@ function integerToBytes(i, len) {
1010
return bytes;
1111
};
1212

13+
/**
14+
* Find a quadratic residue (mod p) of this number. p must be an odd prime.
15+
*
16+
* For a given number a, this function solves the congruence of the form
17+
*
18+
* x^2 = a (mod p)
19+
*
20+
* And returns x. Note that p - x is also a root.
21+
*
22+
* 0 is returned if no square root exists for these a and p.
23+
*
24+
* The Tonelli-Shanks algorithm is used (except for some simple cases
25+
* in which the solution is known from an identity). This algorithm
26+
* runs in polynomial time (unless the generalized Riemann hypothesis
27+
* is false).
28+
*
29+
* Originally implemented in Python by Eli Bendersky:
30+
* http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/
31+
*
32+
* Ported to JavaScript by Stefan Thomas.
33+
*/
34+
BigInteger.prototype.modSqrt = function (p) {
35+
var ONE = BigInteger.ONE,
36+
TWO = BigInteger.valueOf(2);
37+
38+
// Simple cases
39+
if (this.legendre(p) != 1) {
40+
return BigInteger.ZERO;
41+
} else if (this.equals(BigInteger.ZERO)) {
42+
return BigInteger.ZERO;
43+
} else if (p.equals(TWO)) {
44+
return p;
45+
} else if (p.mod(BigInteger.valueOf(4)).equals(BigInteger.valueOf(3))) {
46+
return this.modPow(p.add(ONE).divide(BigInteger.valueOf(4)), p);
47+
}
48+
49+
// Partition p-1 to s * 2^e for an odd s (i.e. reduce all the powers
50+
// of 2 from p-1)
51+
var s = p.subtract(ONE);
52+
var e = 0;
53+
while (s.isEven()) {
54+
s = s.divide(TWO);
55+
++e;
56+
}
57+
58+
// Find some 'n' with a legendre symbol n|p = -1.
59+
// Shouldn't take long.
60+
var n = TWO;
61+
while (n.legendre(p) != -1) {
62+
n = n.add(ONE);
63+
}
64+
65+
// Here be dragons!
66+
// Read the paper "Square roots from 1; 24, 51, 10 to Dan Shanks" by
67+
// Ezra Brown for more information
68+
69+
// x is a guess of the square root that gets better with each
70+
// iteration.
71+
//
72+
// b is the "fudge factor" - by how much we're off with the guess.
73+
// The invariant x^2 = ab (mod p) is maintained throughout the loop.
74+
//
75+
// g is used for successive powers of n to update both a and b
76+
//
77+
// r is the exponent - decreases with each update
78+
79+
var x = this.modPow(s.add(ONE).divide(TWO), p);
80+
var b = this.modPow(s, p);
81+
var g = n.modPow(s, p);
82+
var r = e;
83+
84+
for (;;) {
85+
var t = b;
86+
87+
var m;
88+
for (m = 0; m < r; m++) {
89+
if (t.equals(ONE)) break;
90+
91+
t = t.modPowInt(2, p);
92+
}
93+
94+
if (m == 0) {
95+
return x;
96+
}
97+
98+
var gs = g.modPow(TWO.pow(BigInteger.valueOf(r - m - 1)), p);
99+
g = gs.multiply(gs).mod(p);
100+
x = x.multiply(gs).mod(p);
101+
b = b.multiply(g).mod(p);
102+
r = m;
103+
}
104+
};
105+
106+
/**
107+
* Compute the Legendre symbol a|p using Euler's criterion.
108+
*
109+
* p is a prime, a is relatively prime to p
110+
* (if p divides a, then a | p = 0)
111+
*
112+
* Returns 1 if a has a square root modulo p, -1 otherwise.
113+
*/
114+
BigInteger.prototype.legendre = function (p) {
115+
var ls = this.modPow(p.subtract(BigInteger.ONE).shiftRight(1), p);
116+
if (ls.equals(p.subtract(BigInteger.ONE))) {
117+
return -1;
118+
} else if (ls.equals(BigInteger.ZERO)) {
119+
return 0;
120+
} else {
121+
return 1;
122+
}
123+
};
124+
13125
ECFieldElementFp.prototype.getByteLength = function () {
14126
return Math.floor((this.toBigInteger().bitLength() + 7) / 8);
15127
};
@@ -139,6 +251,11 @@ ECPointFp.prototype.isOnCurve = function () {
139251
return lhs.equals(rhs);
140252
};
141253

254+
ECPointFp.prototype.toString = function () {
255+
return '('+this.getX().toBigInteger().toString()+','+
256+
this.getY().toBigInteger().toString()+')';
257+
};
258+
142259
/**
143260
* Validate an elliptic curve point.
144261
*
@@ -239,13 +356,35 @@ Bitcoin.ECDSA = (function () {
239356
},
240357

241358
verify: function (hash, sig, pubkey) {
242-
var obj = ECDSA.parseSig(sig);
243-
var r = obj.r;
244-
var s = obj.s;
359+
var r,s;
360+
if (Bitcoin.Util.isArray(sig)) {
361+
var obj = stringECDSA.parseSig(sig);
362+
r = obj.r;
363+
s = obj.s;
364+
} else if ("object" === typeof sig && sig.r && sig.s) {
365+
r = sig.r;
366+
s = sig.s;
367+
} else {
368+
throw "Invalid value for signature";
369+
}
245370

246-
var n = ecparams.getN();
371+
var Q;
372+
if (pubkey instanceof ECPointFp) {
373+
Q = pubkey;
374+
} else if (Bitcoin.Util.isArray(pubkey)) {
375+
Q = ECPointFp.decodeFrom(ecparams.getCurve(), pubkey);
376+
} else {
377+
throw "Invalid format for pubkey value, must be byte array or ECPointFp";
378+
}
247379
var e = BigInteger.fromByteArrayUnsigned(hash);
248380

381+
return ECDSA.verifyRaw(e, r, s, Q);
382+
},
383+
384+
verifyRaw: function (e, r, s, Q) {
385+
var n = ecparams.getN();
386+
var G = ecparams.getG();
387+
249388
if (r.compareTo(BigInteger.ONE) < 0 ||
250389
r.compareTo(n) >= 0)
251390
return false;
@@ -259,19 +398,20 @@ Bitcoin.ECDSA = (function () {
259398
var u1 = e.multiply(c).mod(n);
260399
var u2 = r.multiply(c).mod(n);
261400

262-
var G = ecparams.getG();
263-
var Q = ECPointFp.decodeFrom(ecparams.getCurve(), pubkey);
264-
265-
var point = implShamirsTrick(G, u1, Q, u2);
401+
// TODO(!!!): For some reason Shamir's trick isn't working with
402+
// signed message verification!? Probably an implementation
403+
// error!
404+
//var point = implShamirsTrick(G, u1, Q, u2);
405+
var point = G.multiply(u1).add(Q.multiply(u2));
266406

267-
var v = point.x.toBigInteger().mod(n);
407+
var v = point.getX().toBigInteger().mod(n);
268408

269409
return v.equals(r);
270410
},
271411

272412
/**
273413
* Serialize a signature into DER format.
274-
*
414+
*
275415
* Takes two BigIntegers representing r and s and returns a byte array.
276416
*/
277417
serializeSig: function (r, s) {
@@ -327,6 +467,111 @@ Bitcoin.ECDSA = (function () {
327467
var s = BigInteger.fromByteArrayUnsigned(sBa);
328468

329469
return {r: r, s: s};
470+
},
471+
472+
parseSigCompact: function (sig) {
473+
if (sig.length !== 65) {
474+
throw "Signature has the wrong length";
475+
}
476+
477+
// Signature is prefixed with a type byte storing three bits of
478+
// information.
479+
var i = sig[0] - 27;
480+
if (i < 0 || i > 7) {
481+
throw "Invalid signature type";
482+
}
483+
484+
var n = ecparams.getN();
485+
var r = BigInteger.fromByteArrayUnsigned(sig.slice(1, 33)).mod(n);
486+
var s = BigInteger.fromByteArrayUnsigned(sig.slice(33, 65)).mod(n);
487+
488+
return {r: r, s: s, i: i};
489+
},
490+
491+
/**
492+
* Recover a public key from a signature.
493+
*
494+
* See SEC 1: Elliptic Curve Cryptography, section 4.1.6, "Public
495+
* Key Recovery Operation".
496+
*
497+
* http://www.secg.org/download/aid-780/sec1-v2.pdf
498+
*/
499+
recoverPubKey: function (r, s, hash, i) {
500+
// The recovery parameter i has two bits.
501+
i = i & 3;
502+
503+
// The less significant bit specifies whether the y coordinate
504+
// of the compressed point is even or not.
505+
var isYEven = i & 1;
506+
507+
// The more significant bit specifies whether we should use the
508+
// first or second candidate key.
509+
var isSecondKey = i >> 1;
510+
511+
var n = ecparams.getN();
512+
var G = ecparams.getG();
513+
var curve = ecparams.getCurve();
514+
var p = curve.getQ();
515+
var a = curve.getA().toBigInteger();
516+
var b = curve.getB().toBigInteger();
517+
518+
// 1.1 Compute x
519+
var x = isSecondKey ? r.add(n) : r;
520+
521+
// 1.3 Convert x to point
522+
var alpha = x.multiply(x).multiply(x).add(a.multiply(x)).add(b).mod(p);
523+
var beta = alpha.modSqrt(p);
524+
525+
var xorOdd = beta.isEven() ? (i % 2) : ((i+1) % 2);
526+
// If beta is even, but y isn't or vice versa, then convert it,
527+
// otherwise we're done and y == beta.
528+
var y = (beta.isEven() ? !isYEven : isYEven) ? beta : p.subtract(beta);
529+
530+
// 1.4 Check that nR is at infinity
531+
var R = new ECPointFp(curve,
532+
curve.fromBigInteger(x),
533+
curve.fromBigInteger(y));
534+
R.validate();
535+
536+
// 1.5 Compute e from M
537+
var e = BigInteger.fromByteArrayUnsigned(hash);
538+
var eNeg = BigInteger.ZERO.subtract(e).mod(n);
539+
540+
// 1.6 Compute Q = r^-1 (sR - eG)
541+
var rInv = r.modInverse(n);
542+
var Q = implShamirsTrick(R, s, G, eNeg).multiply(rInv);
543+
544+
Q.validate();
545+
if (!ECDSA.verifyRaw(e, r, s, Q)) {
546+
throw "Pubkey recovery unsuccessful";
547+
}
548+
549+
var pubKey = new Bitcoin.ECKey();
550+
pubKey.pub = Q;
551+
return pubKey;
552+
},
553+
554+
/**
555+
* Calculate pubkey extraction parameter.
556+
*
557+
* When extracting a pubkey from a signature, we have to
558+
* distinguish four different cases. Rather than putting this
559+
* burden on the verifier, Bitcoin includes a 2-bit value with the
560+
* signature.
561+
*
562+
* This function simply tries all four cases and returns the value
563+
* that resulted in a successful pubkey recovery.
564+
*/
565+
calcPubkeyRecoveryParam: function (r, s, hash)
566+
{
567+
for (var i = 0; i < 4; i++) {
568+
try {
569+
if (Bitcoin.ECDSA.recoverPubKey(r, s, hash, i)) {
570+
return i;
571+
}
572+
} catch (e) {}
573+
}
574+
throw "Unable to find valid recovery factor";
330575
}
331576
};
332577

src/eckey.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,35 @@ Bitcoin.ECKey = (function () {
2323
this.priv = BigInteger.fromByteArrayUnsigned(Crypto.util.base64ToBytes(input));
2424
}
2525
}
26+
this.compressed = !!ECKey.compressByDefault;
2627
};
2728

29+
/**
30+
* Whether public keys should be returned compressed by default.
31+
*/
32+
ECKey.compressByDefault = false;
33+
34+
/**
35+
* Set whether the public key should be returned compressed or not.
36+
*/
37+
ECKey.prototype.setCompressed = function (v) {
38+
this.compressed = !!v;
39+
};
40+
41+
/**
42+
* Return public key in DER encoding.
43+
*/
2844
ECKey.prototype.getPub = function () {
29-
if (this.pub) return this.pub;
45+
return this.getPubPoint().getEncoded(this.compressed);
46+
};
47+
48+
/**
49+
* Return public point as ECPoint object.
50+
*/
51+
ECKey.prototype.getPubPoint = function () {
52+
if (!this.pub) this.pub = ecparams.getG().multiply(this.priv);
3053

31-
return this.pub = ecparams.getG().multiply(this.priv).getEncoded();
54+
return this.pub;
3255
};
3356

3457
/**
@@ -58,7 +81,7 @@ Bitcoin.ECKey = (function () {
5881
};
5982

6083
ECKey.prototype.setPub = function (pub) {
61-
this.pub = pub;
84+
this.pub = ECPointFp.decodeFrom(ecparams.getCurve(), pub);
6285
};
6386

6487
ECKey.prototype.toString = function (format) {

0 commit comments

Comments
 (0)