UNPKG

7.44 kBJavaScriptView Raw
1'use strict';
2
3var BN = require('./bn');
4var Point = require('./point');
5var Signature = require('./signature');
6var PublicKey = require('../publickey');
7var Random = require('./random');
8var Hash = require('./hash');
9var BufferUtil = require('../util/buffer');
10var _ = require('lodash');
11var $ = require('../util/preconditions');
12
13var 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 */
23ECDSA.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
34ECDSA.prototype.privkey2pubkey = function() {
35 this.pubkey = this.privkey.toPublicKey();
36};
37
38ECDSA.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
59ECDSA.fromString = function(str) {
60 var obj = JSON.parse(str);
61 return new ECDSA(obj);
62};
63
64ECDSA.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
76ECDSA.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();
99
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);
106 }
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
115ECDSA.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().umod(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
160ECDSA.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).umod(n);
178 var u2 = sinv.mul(r).umod(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().umod(n).cmp(r) !== 0) {
186 return 'Invalid signature';
187 } else {
188 return false;
189 }
190};
191
192ECDSA.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
201ECDSA.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.umod(N);
215 s = k.invm(N).mul(e.add(d.mul(r))).umod(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
226ECDSA.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'));
232 $.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
245ECDSA.prototype.signRandomK = function() {
246 this.randomK();
247 return this.sign();
248};
249
250ECDSA.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);
268};
269
270ECDSA.prototype.verify = function() {
271 if (!this.sigError()) {
272 this.verified = true;
273 } else {
274 this.verified = false;
275 }
276 return this;
277};
278
279ECDSA.sign = function(hashbuf, privkey, endian) {
280 return ECDSA().set({
281 hashbuf: hashbuf,
282 endian: endian,
283 privkey: privkey
284 }).sign().sig;
285};
286
287ECDSA.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
296module.exports = ECDSA;