Skip to content

Commit ac74937

Browse files
committed
migrate to proper crypto, part 1
1 parent a7915ca commit ac74937

File tree

9 files changed

+127
-343
lines changed

9 files changed

+127
-343
lines changed

lib/crypto/ecdsa-test.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
3+
const { sign, verify } = require('./ecdsa')
4+
const { randomBytes } = require('@exodus/crypto/randomBytes')
5+
6+
var ECDSA = function ECDSA(obj) {
7+
if (!(this instanceof ECDSA)) {
8+
return new ECDSA(obj);
9+
}
10+
if (obj) {
11+
this.set(obj);
12+
}
13+
};
14+
15+
/* jshint maxcomplexity: 9 */
16+
ECDSA.prototype.set = function(obj) {
17+
this.hashbuf = obj.hashbuf || this.hashbuf;
18+
this.endian = obj.endian || this.endian; //the endianness of hashbuf
19+
this.privkey = obj.privkey || this.privkey;
20+
this.pubkey = obj.pubkey || (this.privkey ? this.privkey.publicKey : this.pubkey);
21+
this.sig = obj.sig || this.sig;
22+
this.verified = obj.verified || this.verified;
23+
return this;
24+
};
25+
26+
ECDSA.prototype.privkey2pubkey = function() {
27+
this.pubkey = this.privkey.toPublicKey();
28+
};
29+
30+
ECDSA.fromString = function(str) {
31+
var obj = JSON.parse(str);
32+
return new ECDSA(obj);
33+
};
34+
35+
ECDSA.prototype.sign = function() {
36+
this.sig = ECDSA.sign(this.hashbuf, this.privkey, this.endian)
37+
return this
38+
};
39+
40+
ECDSA.prototype.signRandomK = function() {
41+
this.sig = ECDSA.sign(this.hashbuf, this.privkey, this.endian, randomBytes(32))
42+
return this
43+
};
44+
45+
ECDSA.prototype.verify = function() {
46+
this.verified = ECDSA.verify(this.hashbuf, this.sig, this.pubkey, this.endian)
47+
return this
48+
};
49+
50+
ECDSA.sign = sign
51+
ECDSA.verify = verify
52+
53+
module.exports = ECDSA;

lib/crypto/ecdsa.js

Lines changed: 23 additions & 283 deletions
Original file line numberDiff line numberDiff line change
@@ -1,296 +1,36 @@
11
'use strict';
22

3-
var BN = require('./bn');
4-
var Point = require('./point');
3+
const { hmacSync } = require('@exodus/crypto/hmac')
4+
const secp256k1 = require('@noble/secp256k1')
55
var Signature = require('./signature');
6-
var PublicKey = require('../publickey');
7-
var Random = require('./random');
8-
var Hash = require('./hash');
96
var BufferUtil = require('../util/buffer');
107
var _ = require('lodash');
118
var $ = require('../util/preconditions');
129

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+
}
9913

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')
10618
}
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'));
23221
$.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
26828
};
26929

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
27736
};
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

Comments
 (0)