Skip to content

Commit

Permalink
migrate to proper crypto, part 1
Browse files Browse the repository at this point in the history
  • Loading branch information
ChALkeR committed Nov 4, 2024
1 parent a7915ca commit 8f55642
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 346 deletions.
53 changes: 53 additions & 0 deletions lib/crypto/ecdsa-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';

const { sign, verify } = require('./ecdsa')
const { randomBytes } = require('@exodus/crypto/randomBytes')

var ECDSA = function ECDSA(obj) {
if (!(this instanceof ECDSA)) {
return new ECDSA(obj);
}
if (obj) {
this.set(obj);
}
};

/* jshint maxcomplexity: 9 */
ECDSA.prototype.set = function(obj) {
this.hashbuf = obj.hashbuf || this.hashbuf;
this.endian = obj.endian || this.endian; //the endianness of hashbuf
this.privkey = obj.privkey || this.privkey;
this.pubkey = obj.pubkey || (this.privkey ? this.privkey.publicKey : this.pubkey);
this.sig = obj.sig || this.sig;
this.verified = obj.verified || this.verified;
return this;
};

ECDSA.prototype.privkey2pubkey = function() {
this.pubkey = this.privkey.toPublicKey();
};

ECDSA.fromString = function(str) {
var obj = JSON.parse(str);
return new ECDSA(obj);
};

ECDSA.prototype.sign = function() {
this.sig = ECDSA.sign(this.hashbuf, this.privkey, this.endian)
return this
};

ECDSA.prototype.signRandomK = function() {
this.sig = ECDSA.sign(this.hashbuf, this.privkey, this.endian, randomBytes(32))
return this
};

ECDSA.prototype.verify = function() {
this.verified = ECDSA.verify(this.hashbuf, this.sig, this.pubkey, this.endian)
return this
};

ECDSA.sign = sign
ECDSA.verify = verify

module.exports = ECDSA;
306 changes: 23 additions & 283 deletions lib/crypto/ecdsa.js
Original file line number Diff line number Diff line change
@@ -1,296 +1,36 @@
'use strict';

var BN = require('./bn');
var Point = require('./point');
const { hmacSync } = require('@exodus/crypto/hmac')
const secp256k1 = require('@noble/secp256k1')
var Signature = require('./signature');
var PublicKey = require('../publickey');
var Random = require('./random');
var Hash = require('./hash');
var BufferUtil = require('../util/buffer');
var _ = require('lodash');
var $ = require('../util/preconditions');

var ECDSA = function ECDSA(obj) {
if (!(this instanceof ECDSA)) {
return new ECDSA(obj);
}
if (obj) {
this.set(obj);
}
};

/* jshint maxcomplexity: 9 */
ECDSA.prototype.set = function(obj) {
this.hashbuf = obj.hashbuf || this.hashbuf;
this.endian = obj.endian || this.endian; //the endianness of hashbuf
this.privkey = obj.privkey || this.privkey;
this.pubkey = obj.pubkey || (this.privkey ? this.privkey.publicKey : this.pubkey);
this.sig = obj.sig || this.sig;
this.k = obj.k || this.k;
this.verified = obj.verified || this.verified;
return this;
};

ECDSA.prototype.privkey2pubkey = function() {
this.pubkey = this.privkey.toPublicKey();
};

ECDSA.prototype.calci = function() {
for (var i = 0; i < 4; i++) {
this.sig.i = i;
var Qprime;
try {
Qprime = this.toPublicKey();
} catch (e) {
console.error(e);
continue;
}

if (Qprime.point.eq(this.pubkey.point)) {
this.sig.compressed = this.pubkey.compressed;
return this;
}
}

this.sig.i = undefined;
throw new Error('Unable to find valid recovery factor');
};

ECDSA.fromString = function(str) {
var obj = JSON.parse(str);
return new ECDSA(obj);
};

ECDSA.prototype.randomK = function() {
var N = Point.getN();
var k;
do {
k = BN.fromBuffer(Random.getRandomBuffer(32));
} while (!(k.lt(N) && k.gt(BN.Zero)));
this.k = k;
return this;
};


// https://tools.ietf.org/html/rfc6979#section-3.2
ECDSA.prototype.deterministicK = function(badrs) {
/* jshint maxstatements: 25 */
// if r or s were invalid when this function was used in signing,
// we do not want to actually compute r, s here for efficiency, so,
// we can increment badrs. explained at end of RFC 6979 section 3.2
if (_.isUndefined(badrs)) {
badrs = 0;
}
var v = Buffer.alloc(32);
v.fill(0x01);
var k = Buffer.alloc(32);
k.fill(0x00);
var x = this.privkey.bn.toBuffer({
size: 32
});
var hashbuf = this.endian === 'little' ? BufferUtil.reverse(this.hashbuf) : this.hashbuf
k = Hash.sha256hmac(Buffer.concat([v, Buffer.from([0x00]), x, hashbuf]), k);
v = Hash.sha256hmac(v, k);
k = Hash.sha256hmac(Buffer.concat([v, Buffer.from([0x01]), x, hashbuf]), k);
v = Hash.sha256hmac(v, k);
v = Hash.sha256hmac(v, k);
var T = BN.fromBuffer(v);
var N = Point.getN();
if (!secp256k1.utils.hmacSha256Sync) {
secp256k1.utils.hmacSha256Sync = (key, ...msgs) => hmacSync('sha256', key, msgs, 'uint8')
}

// also explained in 3.2, we must ensure T is in the proper range (0, N)
for (var i = 0; i < badrs || !(T.lt(N) && T.gt(BN.Zero)); i++) {
k = Hash.sha256hmac(Buffer.concat([v, Buffer.from([0x00])]), k);
v = Hash.sha256hmac(v, k);
v = Hash.sha256hmac(v, k);
T = BN.fromBuffer(v);
exports.sign = function(hashbuf, privkey, endian, extraEntropy) {
if (extraEntropy !== undefined) {
if (!(extraEntropy instanceof Uint8Array)) throw new Error('Expected extraEntropy Uint8Array')
if (extraEntropy.length !== 32) throw new Error('Expected extraEntropy to be of length 32')
}

this.k = T;
return this;
};

// Information about public key recovery:
// https://bitcointalk.org/index.php?topic=6430.0
// http://stackoverflow.com/questions/19665491/how-do-i-get-an-ecdsa-public-key-from-just-a-bitcoin-signature-sec1-4-1-6-k
ECDSA.prototype.toPublicKey = function() {
/* jshint maxstatements: 25 */
var i = this.sig.i;
$.checkArgument(i === 0 || i === 1 || i === 2 || i === 3, new Error('i must be equal to 0, 1, 2, or 3'));

var e = BN.fromBuffer(this.hashbuf);
var r = this.sig.r;
var s = this.sig.s;

// A set LSB signifies that the y-coordinate is odd
var isYOdd = i & 1;

// The more significant bit specifies whether we should use the
// first or second candidate key.
var isSecondKey = i >> 1;

var n = Point.getN();
var G = Point.getG();

// 1.1 Let x = r + jn
var x = isSecondKey ? r.add(n) : r;
var R = Point.fromX(isYOdd, x);

// 1.4 Check that nR is at infinity
var nR = R.mul(n);

if (!nR.isInfinity()) {
throw new Error('nR is not a valid curve point');
}

// Compute -e from e
var eNeg = e.neg().mod(n);

// 1.6.1 Compute Q = r^-1 (sR - eG)
// Q = r^-1 (sR + -eG)
var rInv = r.invm(n);

//var Q = R.multiplyTwo(s, G, eNeg).mul(rInv);
var Q = R.mul(s).add(G.mul(eNeg)).mul(rInv);

var pubkey = PublicKey.fromPoint(Q, this.sig.compressed);

return pubkey;
};

ECDSA.prototype.sigError = function() {
/* jshint maxstatements: 25 */
if (!BufferUtil.isBuffer(this.hashbuf) || this.hashbuf.length !== 32) {
return 'hashbuf must be a 32 byte buffer';
}

var r = this.sig.r;
var s = this.sig.s;
if (!(r.gt(BN.Zero) && r.lt(Point.getN())) || !(s.gt(BN.Zero) && s.lt(Point.getN()))) {
return 'r and s not in range';
}

var e = BN.fromBuffer(this.hashbuf, this.endian ? {
endian: this.endian
} : undefined);
var n = Point.getN();
var sinv = s.invm(n);
var u1 = sinv.mul(e).mod(n);
var u2 = sinv.mul(r).mod(n);

var p = Point.getG().mulAdd(u1, this.pubkey.point, u2);
if (p.isInfinity()) {
return 'p is infinity';
}

if (p.getX().mod(n).cmp(r) !== 0) {
return 'Invalid signature';
} else {
return false;
}
};

ECDSA.toLowS = function(s) {
//enforce low s
//see BIP 62, "low S values in signatures"
if (s.gt(BN.fromBuffer(Buffer.from('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex')))) {
s = Point.getN().sub(s);
}
return s;
};

ECDSA.prototype._findSignature = function(d, e) {
var N = Point.getN();
var G = Point.getG();
// try different values of k until r, s are valid
var badrs = 0;
var k, Q, r, s;
do {
if (!this.k || badrs > 0) {
this.deterministicK(badrs);
}
badrs++;
k = this.k;
Q = G.mul(k);
r = Q.x.mod(N);
s = k.invm(N).mul(e.add(d.mul(r))).mod(N);
} while (r.cmp(BN.Zero) <= 0 || s.cmp(BN.Zero) <= 0);

s = ECDSA.toLowS(s);
return {
s: s,
r: r
};

};

ECDSA.prototype.sign = function() {
var hashbuf = this.hashbuf;
var privkey = this.privkey;
var d = privkey.bn;

$.checkState(hashbuf && privkey && d, new Error('invalid parameters'));
if (!(hashbuf instanceof Uint8Array)) throw new Error('Expected Uint8Array')
$.checkState(hashbuf && privkey && privkey.bn, new Error('invalid parameters'));
$.checkState(BufferUtil.isBuffer(hashbuf) && hashbuf.length === 32, new Error('hashbuf must be a 32 byte buffer'));

var e = BN.fromBuffer(hashbuf, this.endian ? {
endian: this.endian
} : undefined);

var obj = this._findSignature(d, e);
obj.compressed = this.pubkey.compressed;

this.sig = new Signature(obj);
return this;
};

ECDSA.prototype.signRandomK = function() {
this.randomK();
return this.sign();
};

ECDSA.prototype.toString = function() {
var obj = {};
if (this.hashbuf) {
obj.hashbuf = this.hashbuf.toString('hex');
}
if (this.privkey) {
obj.privkey = this.privkey.toString();
}
if (this.pubkey) {
obj.pubkey = this.pubkey.toString();
}
if (this.sig) {
obj.sig = this.sig.toString();
}
if (this.k) {
obj.k = this.k.toString();
}
return JSON.stringify(obj);
if (endian === 'little') hashbuf = (Buffer.from(hashbuf)).reverse()
const privbuf = privkey.bn.toBuffer({ size: 32 })
const der = secp256k1.signSync(hashbuf, privbuf)
const sig = Signature.fromDER(Buffer.from(der))
sig.compressed = privkey.publicKey.compressed
return sig
};

ECDSA.prototype.verify = function() {
if (!this.sigError()) {
this.verified = true;
} else {
this.verified = false;
}
return this;
exports.verify = function(hashbuf, sig, pubkey, endian) {
if (!(hashbuf instanceof Uint8Array)) throw new Error('Expected Uint8Array')
if (endian === 'little') hashbuf = (Buffer.from(hashbuf)).reverse()
const pubbuf = pubkey.toDER()
const der = sig.toDER()
return secp256k1.verify(der, hashbuf, pubbuf, { strict: false }) // allows highS per specific test
};

ECDSA.sign = function(hashbuf, privkey, endian) {
return ECDSA().set({
hashbuf: hashbuf,
endian: endian,
privkey: privkey
}).sign().sig;
};

ECDSA.verify = function(hashbuf, sig, pubkey, endian) {
return ECDSA().set({
hashbuf: hashbuf,
endian: endian,
sig: sig,
pubkey: pubkey
}).verify().verified;
};

module.exports = ECDSA;
Loading

0 comments on commit 8f55642

Please sign in to comment.