Browse Source

Backport changes to ecdsa from fullnode

patch-2
Esteban Ordano 10 years ago
parent
commit
736bcd6bd4
  1. 145
      lib/crypto/bn.js
  2. 132
      lib/crypto/ecdsa.js
  3. 13
      lib/crypto/signature.js
  4. 167
      test/crypto/ecdsa.js
  5. 158
      test/crypto/vectors/ecdsa.json

145
lib/crypto/bn.js

@ -1,23 +1,30 @@
/**
* @file ecdsa.js
* @license MIT
*
* Copyright (c) 2014 reddit, Inc.
* Copyright (c) 2014 Ryan X. Charles
* Copyright (c) 2014 BitPay, Inc.
*/
'use strict'; 'use strict';
var _BN = require('bn.js'); var _BN = require('bn.js');
var BN = function BN_extended(n) { var BN = function BN(n, base) {
if (!(this instanceof BN_extended)) { if (!(this instanceof BN)) {
return new BN(n); return new BN(n, base);
} }
arguments[0] = n; _BN.apply(this, arguments);
return _BN.apply(this, arguments);
}; };
module.exports = BN;
BN.prototype = _BN.prototype; BN.prototype = _BN.prototype;
var reversebuf = function(buf, nbuf) { var reversebuf = function(buf) {
var buf2 = new Buffer(buf.length);
for (var i = 0; i < buf.length; i++) { for (var i = 0; i < buf.length; i++) {
nbuf[i] = buf[buf.length-1-i]; buf2[i] = buf[buf.length-1-i];
} }
return buf2;
}; };
BN.prototype.toJSON = function() { BN.prototype.toJSON = function() {
@ -30,6 +37,16 @@ BN.prototype.fromJSON = function(str) {
return this; return this;
}; };
BN.prototype.fromNumber = function(n) {
var bn = BN(n);
bn.copy(this);
return this;
};
BN.prototype.toNumber = function() {
return parseInt(this['toString'](10), 10);
};
BN.prototype.fromString = function(str) { BN.prototype.fromString = function(str) {
var bn = BN(str); var bn = BN(str);
bn.copy(this); bn.copy(this);
@ -38,9 +55,7 @@ BN.prototype.fromString = function(str) {
BN.fromBuffer = function(buf, opts) { BN.fromBuffer = function(buf, opts) {
if (typeof opts !== 'undefined' && opts.endian === 'little') { if (typeof opts !== 'undefined' && opts.endian === 'little') {
var nbuf = new Buffer(buf.length); buf = reversebuf(buf);
reversebuf(buf, nbuf);
buf = nbuf;
} }
var hex = buf.toString('hex'); var hex = buf.toString('hex');
var bn = new BN(hex, 16); var bn = new BN(hex, 16);
@ -70,7 +85,6 @@ BN.prototype.toBuffer = function(opts) {
else if (natlen < opts.size) { else if (natlen < opts.size) {
var rbuf = new Buffer(opts.size); var rbuf = new Buffer(opts.size);
//rbuf.fill(0);
for (var i = 0; i < buf.length; i++) for (var i = 0; i < buf.length; i++)
rbuf[rbuf.length-1-i] = buf[buf.length-1-i]; rbuf[rbuf.length-1-i] = buf[buf.length-1-i];
for (var i = 0; i < opts.size - natlen; i++) for (var i = 0; i < opts.size - natlen; i++)
@ -84,31 +98,116 @@ BN.prototype.toBuffer = function(opts) {
} }
if (typeof opts !== 'undefined' && opts.endian === 'little') { if (typeof opts !== 'undefined' && opts.endian === 'little') {
var nbuf = new Buffer(buf.length); buf = reversebuf(buf);
reversebuf(buf, nbuf);
buf = nbuf;
} }
return buf; return buf;
}; };
// signed magnitude buffer
// most significant bit represents sign (0 = positive, -1 = negative)
BN.prototype.fromSM = function(buf, opts) {
if (buf.length === 0)
this.fromBuffer(new Buffer([0]));
var endian = 'big';
if (opts)
endian = opts.endian;
if (endian == 'little')
buf = reversebuf(buf);
if (buf[0] & 0x80) {
buf[0] = buf[0] & 0x7f;
this.fromBuffer(buf);
this.neg().copy(this);
} else {
this.fromBuffer(buf);
}
return this;
};
BN.prototype.toSM = function(opts) {
var endian = 'big';
if (opts)
endian = opts.endian;
var buf;
if (this.cmp(0) == -1) {
buf = this.neg().toBuffer();
if (buf[0] & 0x80)
buf = Buffer.concat([new Buffer([0x80]), buf]);
else
buf[0] = buf[0] | 0x80;
} else {
buf = this.toBuffer();
if (buf[0] & 0x80)
buf = Buffer.concat([new Buffer([0x00]), buf]);
}
if (buf.length === 1 & buf[0] === 0)
buf = new Buffer([]);
if (endian == 'little')
buf = reversebuf(buf);
return buf;
};
// This is analogous to the constructor for CScriptNum in bitcoind. Many ops in
// bitcoind's script interpreter use CScriptNum, which is not really a proper
// bignum. Instead, an error is thrown if trying to input a number bigger than
// 4 bytes. We copy that behavior here.
BN.prototype.fromCScriptNumBuffer = function(buf, fRequireMinimal) {
var nMaxNumSize = 4;
if (buf.length > nMaxNumSize)
throw new Error('script number overflow');
if (fRequireMinimal && buf.length > 0) {
// Check that the number is encoded with the minimum possible
// number of bytes.
//
// If the most-significant-byte - excluding the sign bit - is zero
// then we're not minimal. Note how this test also rejects the
// negative-zero encoding, 0x80.
if ((buf[buf.length - 1] & 0x7f) === 0) {
// One exception: if there's more than one byte and the most
// significant bit of the second-most-significant-byte is set
// it would conflict with the sign bit. An example of this case
// is +-255, which encode to 0xff00 and 0xff80 respectively.
// (big-endian).
if (buf.length <= 1 || (buf[buf.length - 2] & 0x80) === 0) {
throw new Error("non-minimally encoded script number");
}
}
}
return this.fromSM(buf, {endian: 'little'});
};
// The corollary to the above, with the notable exception that we do not throw
// an error if the output is larger than four bytes. (Which can happen if
// performing a numerical operation that results in an overflow to more than 4
// bytes).
BN.prototype.toCScriptNumBuffer = function(buf) {
return this.toSM({endian: 'little'});
};
function decorate(name) { function decorate(name) {
BN.prototype['_' + name] = _BN.prototype[name]; BN.prototype['_' + name] = BN.prototype[name];
var f = function(b) { var f = function(b) {
if (typeof b === 'string') if (typeof b === 'string')
b = new _BN(b); b = new BN(b);
else if (typeof b === 'number') else if (typeof b === 'number')
b = new _BN(b.toString()); b = new BN(b.toString());
return this['_' + name](b); return this['_' + name](b);
}; };
BN.prototype[name] = f; BN.prototype[name] = f;
}; };
_BN.prototype.gt = function(b) { BN.prototype.gt = function(b) {
return this.cmp(b) > 0; return this.cmp(b) > 0;
}; };
_BN.prototype.lt = function(b) { BN.prototype.lt = function(b) {
return this.cmp(b) < 0; return this.cmp(b) < 0;
}; };
@ -121,8 +220,4 @@ decorate('cmp');
decorate('gt'); decorate('gt');
decorate('lt'); decorate('lt');
BN.prototype.toNumber = function() {
return parseInt(this['toString'](10), 10);
};
module.exports = BN; module.exports = BN;

132
lib/crypto/ecdsa.js

@ -1,11 +1,20 @@
/**
* @file ecdsa.js
* @license MIT
*
* Copyright (c) 2014 reddit, Inc.
* Copyright (c) 2014 Ryan X. Charles
* Copyright (c) 2014 BitPay, Inc.
*/
'use strict'; 'use strict';
var BN = require('./bn'); var BN = require('./bn');
var Point = require('./point'); var Point = require('./point');
var Signature = require('./signature'); var Signature = require('./signature');
var Random = require('./random');
var PublicKey = require('../publickey'); var PublicKey = require('../publickey');
var PrivateKey = require('../privatekey'); var PrivateKey = require('../privatekey');
var Random = require('./random');
var Hash = require('./hash');
var ECDSA = function ECDSA(obj) { var ECDSA = function ECDSA(obj) {
if (!(this instanceof ECDSA)) { if (!(this instanceof ECDSA)) {
@ -18,16 +27,17 @@ var ECDSA = function ECDSA(obj) {
ECDSA.prototype.set = function(obj) { ECDSA.prototype.set = function(obj) {
this.hashbuf = obj.hashbuf || this.hashbuf; this.hashbuf = obj.hashbuf || this.hashbuf;
this.endian = obj.endian || this.endian; //the endianness of hashbuf
this.privkey = obj.privkey || this.privkey; this.privkey = obj.privkey || this.privkey;
this.pubkey = obj.pubkey || this.pubkey; this.pubkey = obj.pubkey || (this.privkey ? this.privkey.publicKey : this.pubkey);
this.sig = obj.sig || this.sig; this.sig = obj.sig || this.sig;
this.k = obj.k || this.k; this.k = obj.k || this.k;
this.verified = obj.verified || this.verified; this.verified = obj.verified || this.verified;
return this; return this;
}; };
ECDSA.prototype.privkey2pubkey = function(){ ECDSA.prototype.privkey2pubkey = function() {
this.pubkey = PublicKey.fromPrivateKey(this.privkey); this.pubkey = this.privkey.toPublicKey();
}; };
ECDSA.prototype.calci = function() { ECDSA.prototype.calci = function() {
@ -37,9 +47,10 @@ ECDSA.prototype.calci = function() {
try { try {
Qprime = this.sig2pubkey(); Qprime = this.sig2pubkey();
} catch (e) { } catch (e) {
console.log(e); console.error(e);
continue; continue;
} }
if (Qprime.point.eq(this.pubkey.point)) { if (Qprime.point.eq(this.pubkey.point)) {
this.sig.compressed = this.pubkey.compressed; this.sig.compressed = this.pubkey.compressed;
return this; return this;
@ -52,16 +63,21 @@ ECDSA.prototype.calci = function() {
ECDSA.prototype.fromString = function(str) { ECDSA.prototype.fromString = function(str) {
var obj = JSON.parse(str); var obj = JSON.parse(str);
if (obj.hashbuf) if (obj.hashbuf) {
this.hashbuf = new Buffer(obj.hashbuf, 'hex'); this.hashbuf = new Buffer(obj.hashbuf, 'hex');
if (obj.pubkey) }
if (obj.pubkey) {
this.pubkey = PublicKey.fromString(obj.pubkey); this.pubkey = PublicKey.fromString(obj.pubkey);
if (obj.privkey) }
if (obj.privkey) {
this.privkey = PrivateKey.fromString(obj.privkey); this.privkey = PrivateKey.fromString(obj.privkey);
if (obj.sig) }
if (obj.sig) {
this.sig = Signature().fromString(obj.sig); this.sig = Signature().fromString(obj.sig);
if (obj.k) }
if (obj.k) {
this.k = BN(obj.k, 10); this.k = BN(obj.k, 10);
}
return this; return this;
}; };
@ -75,6 +91,37 @@ ECDSA.prototype.randomK = function() {
return this; return this;
}; };
// https://tools.ietf.org/html/rfc6979#section-3.2
ECDSA.prototype.deterministicK = function(badrs) {
var v = new Buffer(32);
v.fill(0x01);
var k = new Buffer(32);
k.fill(0x00);
var x = this.privkey.bn.toBuffer({size: 32});
k = Hash.sha256hmac(Buffer.concat([v, new Buffer([0x00]), x, this.hashbuf]), k);
v = Hash.sha256hmac(v, k);
k = Hash.sha256hmac(Buffer.concat([v, new Buffer([0x01]), x, this.hashbuf]), k);
v = Hash.sha256hmac(v, k);
v = Hash.sha256hmac(v, k);
var T = BN().fromBuffer(v);
var N = Point.getN();
// 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 (typeof badrs === 'undefined')
badrs = 0;
// 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(0)); i++) {
k = Hash.sha256hmac(Buffer.concat([v, new Buffer([0x00])]), k);
v = Hash.sha256hmac(v, k);
T = BN().fromBuffer(v);
}
this.k = T;
return this;
};
// Information about public key recovery: // Information about public key recovery:
// https://bitcointalk.org/index.php?topic=6430.0 // 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 // http://stackoverflow.com/questions/19665491/how-do-i-get-an-ecdsa-public-key-from-just-a-bitcoin-signature-sec1-4-1-6-k
@ -123,8 +170,9 @@ ECDSA.prototype.sig2pubkey = function() {
}; };
ECDSA.prototype.sigError = function() { ECDSA.prototype.sigError = function() {
if (!Buffer.isBuffer(this.hashbuf) || this.hashbuf.length !== 32) if (!Buffer.isBuffer(this.hashbuf) || this.hashbuf.length !== 32) {
return 'hashbuf must be a 32 byte buffer'; return 'hashbuf must be a 32 byte buffer';
}
var r = this.sig.r; var r = this.sig.r;
var s = this.sig.s; var s = this.sig.s;
@ -132,7 +180,7 @@ ECDSA.prototype.sigError = function() {
|| !(s.gt(0) && s.lt(Point.getN()))) || !(s.gt(0) && s.lt(Point.getN())))
return 'r and s not in range'; return 'r and s not in range';
var e = BN().fromBuffer(this.hashbuf); var e = BN().fromBuffer(this.hashbuf, this.endian ? {endian: this.endian} : undefined);
var n = Point.getN(); var n = Point.getN();
var sinv = s.invm(n); var sinv = s.invm(n);
var u1 = sinv.mul(e).mod(n); var u1 = sinv.mul(e).mod(n);
@ -151,11 +199,8 @@ ECDSA.prototype.sigError = function() {
ECDSA.prototype.sign = function() { ECDSA.prototype.sign = function() {
var hashbuf = this.hashbuf; var hashbuf = this.hashbuf;
var privkey = this.privkey; var privkey = this.privkey;
var k = this.k;
var d = privkey.bn;
if (!k) var d = privkey.bn;
throw new Error('You must specify k - perhaps you should run signRandomK instead');
if (!hashbuf || !privkey || !d) if (!hashbuf || !privkey || !d)
throw new Error('invalid parameters'); throw new Error('invalid parameters');
@ -165,60 +210,79 @@ ECDSA.prototype.sign = function() {
var N = Point.getN(); var N = Point.getN();
var G = Point.getG(); var G = Point.getG();
var e = BN().fromBuffer(hashbuf); var e = BN().fromBuffer(hashbuf, this.endian ? {endian: this.endian} : undefined);
// try different values of k until r, s are valid
var badrs = 0;
do { do {
if (!this.k || badrs > 0)
this.deterministicK(badrs);
badrs++;
var k = this.k;
var Q = G.mul(k); var Q = G.mul(k);
var r = Q.x.mod(N); var r = Q.x.mod(N);
var s = k.invm(N).mul(e.add(d.mul(r))).mod(N); var s = k.invm(N).mul(e.add(d.mul(r))).mod(N);
} while (r.cmp(0) <= 0 || s.cmp(0) <= 0); } while (r.cmp(0) <= 0 || s.cmp(0) <= 0);
this.sig = new Signature({r: r, s: s, compressed: this.privkey.compressed}); //enforce low s
return this.sig; //see BIP 62, "low S values in signatures"
if (s.gt(BN().fromBuffer(new Buffer('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex')))) {
s = Point.getN().sub(s);
}
this.sig = new Signature({r: r, s: s, compressed: this.pubkey.compressed});
return this;
}; };
ECDSA.prototype.signRandomK = function() { ECDSA.prototype.signRandomK = function() {
var k = this.randomK(); this.randomK();
return this.sign(); return this.sign();
}; };
ECDSA.prototype.toString = function() { ECDSA.prototype.toString = function() {
var obj = {}; var obj = {};
if (this.hashbuf) if (this.hashbuf) {
obj.hashbuf = this.hashbuf.toString('hex'); obj.hashbuf = this.hashbuf.toString('hex');
if (this.pubkey) }
obj.pubkey = this.pubkey.toString(); if (this.privkey) {
if (this.privkey)
obj.privkey = this.privkey.toString(); obj.privkey = this.privkey.toString();
if (this.keypair) }
obj.keypair = this.keypair.toString(); if (this.pubkey) {
if (this.sig) obj.pubkey = this.pubkey.toString();
}
if (this.sig) {
obj.sig = this.sig.toString(); obj.sig = this.sig.toString();
if (this.k) }
if (this.k) {
obj.k = this.k.toString(); obj.k = this.k.toString();
}
return JSON.stringify(obj); return JSON.stringify(obj);
}; };
ECDSA.prototype.verify = function() { ECDSA.prototype.verify = function() {
if (!this.sigError()) if (!this.sigError())
return true; this.verified = true;
else else
return false; this.verified = false;
return this;
}; };
ECDSA.sign = function(hashbuf, privkey) { ECDSA.sign = function(hashbuf, privkey, endian) {
console.log('Signing', hashbuf, 'with key', privkey);
return ECDSA().set({ return ECDSA().set({
hashbuf: hashbuf, hashbuf: hashbuf,
endian: endian,
privkey: privkey privkey: privkey
}).signRandomK(); }).sign().sig;
}; };
ECDSA.verify = function(hashbuf, sig, pubkey) { ECDSA.verify = function(hashbuf, sig, pubkey, endian) {
return ECDSA().set({ return ECDSA().set({
hashbuf: hashbuf, hashbuf: hashbuf,
endian: endian,
sig: sig, sig: sig,
pubkey: pubkey pubkey: pubkey
}).verify(); }).verify().verified;
}; };
module.exports = ECDSA; module.exports = ECDSA;

13
lib/crypto/signature.js

@ -1,3 +1,11 @@
/**
* @file ecdsa.js
* @license MIT
*
* Copyright (c) 2014 reddit, Inc.
* Copyright (c) 2014 Ryan X. Charles
* Copyright (c) 2014 BitPay, Inc.
*/
'use strict'; 'use strict';
var BN = require('./bn'); var BN = require('./bn');
@ -168,4 +176,9 @@ Signature.prototype.toString = function() {
return buf.toString('hex'); return buf.toString('hex');
}; };
Signature.SIGHASH_ALL = 0x01;
Signature.SIGHASH_NONE = 0x02;
Signature.SIGHASH_SINGLE = 0x03;
Signature.SIGHASH_ANYONECANPAY = 0x80;
module.exports = Signature; module.exports = Signature;

167
test/crypto/ecdsa.js

@ -1,24 +1,23 @@
'use strict'; var ECDSA = require('../../lib/crypto/ecdsa');
var Hash = require('../../lib/crypto/hash');
var Privkey = require('../../lib/privatekey');
var Pubkey = require('../../lib/publickey');
var Signature = require('../../lib/crypto/signature');
var Point = require('../../lib/crypto/point');
var BN = require('../../lib/crypto/bn');
var point = require('../../lib/crypto/point');
var should = require('chai').should(); var should = require('chai').should();
var bitcore = require('../..'); var vectors = require('./vectors/ecdsa');
var ECDSA = bitcore.crypto.ECDSA;
var Hash = bitcore.crypto.Hash;
var Signature = bitcore.crypto.Signature;
var PrivateKey = bitcore.PrivateKey;
var PublicKey = bitcore.PublicKey;
var BN = bitcore.crypto.BN;
describe('ECDSA', function() { describe("ECDSA", function() {
it('should create a blank ecdsa', function() { it('should create a blank ecdsa', function() {
var ecdsa = new ECDSA(); var ecdsa = new ECDSA();
should.exist(ecdsa);
}); });
var ecdsa = new ECDSA(); var ecdsa = new ECDSA();
ecdsa.hashbuf = Hash.sha256(new Buffer('test data')); ecdsa.hashbuf = Hash.sha256(new Buffer('test data'));
ecdsa.privkey = new PrivateKey(BN().fromBuffer(new Buffer('fee0a1f7afebf9d2a5a80c0c98a31c709681cce195cbcd06342b517970c0be1e', 'hex'))); ecdsa.privkey = new Privkey(BN().fromBuffer(new Buffer('fee0a1f7afebf9d2a5a80c0c98a31c709681cce195cbcd06342b517970c0be1e', 'hex')));
ecdsa.privkey2pubkey(); ecdsa.privkey2pubkey();
describe('#set', function() { describe('#set', function() {
@ -42,11 +41,12 @@ describe('ECDSA', function() {
var hashbuf = Hash.sha256(new Buffer('some data')); var hashbuf = Hash.sha256(new Buffer('some data'));
var r = BN('71706645040721865894779025947914615666559616020894583599959600180037551395766', 10); var r = BN('71706645040721865894779025947914615666559616020894583599959600180037551395766', 10);
var s = BN('109412465507152403114191008482955798903072313614214706891149785278625167723646', 10); var s = BN('109412465507152403114191008482955798903072313614214706891149785278625167723646', 10);
var ecdsa = new ECDSA(); var ecdsa = new ECDSA({
ecdsa.privkey = PrivateKey(BN().fromBuffer(Hash.sha256(new Buffer('test')))); privkey: Privkey(BN().fromBuffer(Hash.sha256(new Buffer('test')))),
ecdsa.privkey2pubkey(); hashbuf: hashbuf,
ecdsa.hashbuf = hashbuf; sig: new Signature({r: r, s: s})
ecdsa.sig = new Signature({r: r, s: s}); });
ecdsa.calci(); ecdsa.calci();
ecdsa.sig.i.should.equal(1); ecdsa.sig.i.should.equal(1);
}); });
@ -60,7 +60,6 @@ describe('ECDSA', function() {
var ecdsa2 = new ECDSA(); var ecdsa2 = new ECDSA();
ecdsa2.fromString(str); ecdsa2.fromString(str);
should.exist(ecdsa.hashbuf); should.exist(ecdsa.hashbuf);
should.exist(ecdsa.pubkey);
should.exist(ecdsa.privkey); should.exist(ecdsa.privkey);
}); });
@ -85,11 +84,59 @@ describe('ECDSA', function() {
}); });
describe('#deterministicK', function() {
it('should generate the same deterministic k', function() {
ecdsa.deterministicK();
ecdsa.k.toBuffer().toString('hex').should.equal('fcce1de7a9bcd6b2d3defade6afa1913fb9229e3b7ddf4749b55c4848b2a196e');
});
it('should generate the same deterministic k if badrs is set', function() {
ecdsa.deterministicK(0);
ecdsa.k.toBuffer().toString('hex').should.equal('fcce1de7a9bcd6b2d3defade6afa1913fb9229e3b7ddf4749b55c4848b2a196e');
ecdsa.deterministicK(1);
ecdsa.k.toBuffer().toString('hex').should.not.equal('fcce1de7a9bcd6b2d3defade6afa1913fb9229e3b7ddf4749b55c4848b2a196e');
ecdsa.k.toBuffer().toString('hex').should.equal('6f4dcca6fa7a137ae9d110311905013b3c053c732ad18611ec2752bb3dcef9d8');
});
it('should compute this test vector correctly', function() {
// test fixture from bitcoinjs
// https://github.com/bitcoinjs/bitcoinjs-lib/blob/10630873ebaa42381c5871e20336fbfb46564ac8/test/fixtures/ecdsa.json#L6
var ecdsa = new ECDSA();
ecdsa.hashbuf = Hash.sha256(new Buffer('Everything should be made as simple as possible, but not simpler.'));
ecdsa.privkey = Privkey(BN(1));
ecdsa.privkey2pubkey();
ecdsa.deterministicK();
ecdsa.k.toBuffer().toString('hex').should.equal('ec633bd56a5774a0940cb97e27a9e4e51dc94af737596a0c5cbb3d30332d92a5');
ecdsa.sign();
ecdsa.sig.r.toString().should.equal('23362334225185207751494092901091441011938859014081160902781146257181456271561');
ecdsa.sig.s.toString().should.equal('50433721247292933944369538617440297985091596895097604618403996029256432099938');
});
});
describe('#sig2pubkey', function() { describe('#sig2pubkey', function() {
it('should calculate the correct public key', function() { it('should calculate the correct public key', function() {
ecdsa.k = BN('114860389168127852803919605627759231199925249596762615988727970217268189974335', 10); ecdsa.k = BN('114860389168127852803919605627759231199925249596762615988727970217268189974335', 10);
ecdsa.sign(); ecdsa.sign();
ecdsa.sig.i = 0;
var pubkey = ecdsa.sig2pubkey();
pubkey.point.eq(ecdsa.pubkey.point).should.equal(true);
});
it('should calculate the correct public key for this signature with low s', function() {
ecdsa.k = BN('114860389168127852803919605627759231199925249596762615988727970217268189974335', 10);
ecdsa.sig = Signature().fromString('3045022100ec3cfe0e335791ad278b4ec8eac93d0347a97877bb1d54d35d189e225c15f6650220278cf15b05ce47fb37d2233802899d94c774d5480bba9f0f2d996baa13370c43');
ecdsa.sig.i = 0;
var pubkey = ecdsa.sig2pubkey();
pubkey.point.eq(ecdsa.pubkey.point).should.equal(true);
});
it('should calculate the correct public key for this signature with high s', function() {
ecdsa.k = BN('114860389168127852803919605627759231199925249596762615988727970217268189974335', 10);
ecdsa.sign();
ecdsa.sig = Signature().fromString('3046022100ec3cfe0e335791ad278b4ec8eac93d0347a97877bb1d54d35d189e225c15f665022100d8730ea4fa31b804c82ddcc7fd766269f33a079ea38e012c9238f2e2bcff34fe');
ecdsa.sig.i = 1; ecdsa.sig.i = 1;
var pubkey = ecdsa.sig2pubkey(); var pubkey = ecdsa.sig2pubkey();
pubkey.point.eq(ecdsa.pubkey.point).should.equal(true); pubkey.point.eq(ecdsa.pubkey.point).should.equal(true);
@ -104,22 +151,28 @@ describe('ECDSA', function() {
ecdsa.sigError().should.equal('hashbuf must be a 32 byte buffer'); ecdsa.sigError().should.equal('hashbuf must be a 32 byte buffer');
}); });
it.skip('should return an error if the pubkey is invalid', function() {
var ecdsa = new ECDSA();
ecdsa.hashbuf = Hash.sha256(new Buffer('test'));
ecdsa.sigError().indexOf("Invalid pubkey").should.equal(0);
});
it('should return an error if r, s are invalid', function() { it('should return an error if r, s are invalid', function() {
var ecdsa = new ECDSA(); var ecdsa = new ECDSA();
ecdsa.hashbuf = Hash.sha256(new Buffer('test')); ecdsa.hashbuf = Hash.sha256(new Buffer('test'));
var pk = PublicKey.fromDER(new Buffer('041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341', 'hex')); var pk = Pubkey.fromDER(new Buffer('041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341', 'hex'));
ecdsa.pubkey = pk; ecdsa.pubkey = pk;
ecdsa.sig = new Signature(); ecdsa.sig = new Signature();
ecdsa.sig.r = BN(0); ecdsa.sig.r = BN(0);
ecdsa.sig.s = BN(0); ecdsa.sig.s = BN(0);
ecdsa.sigError().should.equal('r and s not in range'); ecdsa.sigError().should.equal("r and s not in range");
}); });
it('should return an error if the signature is incorrect', function() { it('should return an error if the signature is incorrect', function() {
ecdsa.sig = new Signature(); ecdsa.sig = new Signature();
ecdsa.sig.fromString('3046022100e9915e6236695f093a4128ac2a956c40ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827'); ecdsa.sig.fromString('3046022100e9915e6236695f093a4128ac2a956c40ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827');
ecdsa.sig.r = ecdsa.sig.r.add(BN(1)); ecdsa.sig.r = ecdsa.sig.r.add(BN(1));
ecdsa.sigError().should.equal('Invalid signature'); ecdsa.sigError().should.equal("Invalid signature");
}); });
}); });
@ -129,13 +182,12 @@ describe('ECDSA', function() {
it('should create a valid signature', function() { it('should create a valid signature', function() {
ecdsa.randomK(); ecdsa.randomK();
ecdsa.sign(); ecdsa.sign();
ecdsa.verify().should.equal(true); ecdsa.verify().verified.should.equal(true);
}); });
it('should should throw an error if hashbuf is not 32 bytes', function() { it('should should throw an error if hashbuf is not 32 bytes', function() {
var ecdsa2 = ECDSA().set({ var ecdsa2 = ECDSA().set({
hashbuf: ecdsa.hashbuf.slice(0, 31), hashbuf: ecdsa.hashbuf.slice(0, 31),
pubkey: ecdsa.pubkey,
privkey: ecdsa.privkey privkey: ecdsa.privkey
}); });
ecdsa2.randomK(); ecdsa2.randomK();
@ -144,13 +196,29 @@ describe('ECDSA', function() {
}).should.throw('hashbuf must be a 32 byte buffer'); }).should.throw('hashbuf must be a 32 byte buffer');
}); });
it('should default to deterministicK', function() {
var ecdsa2 = new ECDSA(ecdsa);
ecdsa2.k = undefined;
var called = 0;
var deterministicK = ecdsa2.deterministicK.bind(ecdsa2);
ecdsa2.deterministicK = function() {
deterministicK();
called++;
};
ecdsa2.sign();
called.should.equal(1);
});
}); });
describe('#signRandomK', function() { describe('#signRandomK', function() {
it('should produce a signature', function() { it('should produce a signature, and be different when called twice', function() {
ecdsa.signRandomK(); ecdsa.signRandomK();
should.exist(ecdsa.sig); should.exist(ecdsa.sig);
var ecdsa2 = ECDSA(ecdsa);
ecdsa2.signRandomK();
ecdsa.sig.toString().should.not.equal(ecdsa2.sig.toString());
}); });
}); });
@ -169,12 +237,12 @@ describe('ECDSA', function() {
it('should verify a signature that was just signed', function() { it('should verify a signature that was just signed', function() {
ecdsa.sig = new Signature(); ecdsa.sig = new Signature();
ecdsa.sig.fromString('3046022100e9915e6236695f093a4128ac2a956c40ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827'); ecdsa.sig.fromString('3046022100e9915e6236695f093a4128ac2a956c40ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827');
ecdsa.verify().should.equal(true); ecdsa.verify().verified.should.equal(true);
}); });
it('should verify this known good signature', function() { it('should verify this known good signature', function() {
ecdsa.signRandomK(); ecdsa.signRandomK();
ecdsa.verify().should.equal(true); ecdsa.verify().verified.should.equal(true);
}); });
}); });
@ -197,6 +265,53 @@ describe('ECDSA', function() {
ECDSA.verify(ecdsa.hashbuf, fakesig, ecdsa.pubkey).should.equal(false); ECDSA.verify(ecdsa.hashbuf, fakesig, ecdsa.pubkey).should.equal(false);
}); });
it('should work with big and little endian', function() {
var sig = ECDSA.sign(ecdsa.hashbuf, ecdsa.privkey, 'big');
ECDSA.verify(ecdsa.hashbuf, sig, ecdsa.pubkey, 'big').should.equal(true);
ECDSA.verify(ecdsa.hashbuf, sig, ecdsa.pubkey, 'little').should.equal(false);
sig = ECDSA.sign(ecdsa.hashbuf, ecdsa.privkey, 'little');
ECDSA.verify(ecdsa.hashbuf, sig, ecdsa.pubkey, 'big').should.equal(false);
ECDSA.verify(ecdsa.hashbuf, sig, ecdsa.pubkey, 'little').should.equal(true);
});
});
describe('vectors', function() {
vectors.valid.forEach(function(obj, i) {
it('should validate valid vector ' + i, function() {
var ecdsa = ECDSA().set({
privkey: Privkey(BN().fromBuffer(new Buffer(obj.d, 'hex'))),
k: BN().fromBuffer(new Buffer(obj.k, 'hex')),
hashbuf: Hash.sha256(new Buffer(obj.message)),
sig: Signature().set({
r: BN(obj.signature.r),
s: BN(obj.signature.s),
i: obj.i
})
});
var ecdsa2 = ECDSA(ecdsa);
ecdsa2.k = undefined;
ecdsa2.sign();
ecdsa2.calci();
ecdsa2.k.toString().should.equal(ecdsa.k.toString());
ecdsa2.sig.toString().should.equal(ecdsa.sig.toString());
ecdsa2.sig.i.should.equal(ecdsa.sig.i);
ecdsa.verify().verified.should.equal(true);
});
});
vectors.invalid.sigError.forEach(function(obj, i) {
it('should validate invalid.sigError vector ' + i + ': ' + obj.description, function() {
var ecdsa = ECDSA().set({
pubkey: Pubkey.fromPoint(point.fromX(true, 1)),
sig: Signature(BN(obj.signature.r), BN(obj.signature.s)),
hashbuf: Hash.sha256(new Buffer(obj.message))
});
ecdsa.sigError().should.equal(obj.exception);
});
});
}); });
}); });

158
test/crypto/vectors/ecdsa.json

@ -0,0 +1,158 @@
{
"valid": [
{
"d": "01",
"k": "ec633bd56a5774a0940cb97e27a9e4e51dc94af737596a0c5cbb3d30332d92a5",
"message": "Everything should be made as simple as possible, but not simpler.",
"i": 0,
"signature": {
"r": "23362334225185207751494092901091441011938859014081160902781146257181456271561",
"s": "50433721247292933944369538617440297985091596895097604618403996029256432099938"
}
},
{
"d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
"k": "9dc74cbfd383980fb4ae5d2680acddac9dac956dca65a28c80ac9c847c2374e4",
"message": "Equations are more important to me, because politics is for the present, but an equation is something for eternity.",
"i": 0,
"signature": {
"r": "38341707918488238920692284707283974715538935465589664377561695343399725051885",
"s": "3180566392414476763164587487324397066658063772201694230600609996154610926757"
}
},
{
"d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
"k": "fd27071f01648ebbdd3e1cfbae48facc9fa97edc43bbbc9a7fdc28eae13296f5",
"message": "Not only is the Universe stranger than we think, it is stranger than we can think.",
"i": 0,
"signature": {
"r": "115464191557905790016094131873849783294273568009648050793030031933291767741904",
"s": "50562520307781850052192542766631199590053690478900449960232079510155113443971"
}
},
{
"d": "0000000000000000000000000000000000000000000000000000000000000001",
"k": "f0cd2ba5fc7c183de589f6416220a36775a146740798756d8d949f7166dcc87f",
"message": "How wonderful that we have met with a paradox. Now we have some hope of making progress.",
"i": 1,
"signature": {
"r": "87230998027579607140680851455601772643840468630989315269459846730712163783123",
"s": "53231320085894623106179381504478252331065330583563809963303318469380290929875"
}
},
{
"d": "69ec59eaa1f4f2e36b639716b7c30ca86d9a5375c7b38d8918bd9c0ebc80ba64",
"k": "6bb4a594ad57c1aa22dbe991a9d8501daf4688bf50a4892ef21bd7c711afda97",
"message": "Computer science is no more about computers than astronomy is about telescopes.",
"i": 0,
"signature": {
"r": "51348483531757779992459563033975330355971795607481991320287437101831125115997",
"s": "6277080015686056199074771961940657638578000617958603212944619747099038735862"
}
},
{
"d": "00000000000000000000000000007246174ab1e92e9149c6e446fe194d072637",
"k": "097b5c8ee22c3ea78a4d3635e0ff6fe85a1eb92ce317ded90b9e71aab2b861cb",
"message": "...if you aren't, at any given time, scandalized by code you wrote five or even three years ago, you're not learning anywhere near enough",
"i": 1,
"signature": {
"r": "113979859486826658566290715281614250298918272782414232881639314569529560769671",
"s": "6517071009538626957379450615706485096874328019806177698938278220732027419959"
}
},
{
"d": "000000000000000000000000000000000000000000056916d0f9b31dc9b637f3",
"k": "19355c36c8cbcdfb2382e23b194b79f8c97bf650040fc7728dfbf6b39a97c25b",
"message": "The question of whether computers can think is like the question of whether submarines can swim.",
"i": 1,
"signature": {
"r": "93122007060065279508564838030979550535085999589142852106617159184757394422777",
"s": "3078539468410661027472930027406594684630312677495124015420811882501887769839"
}
}
],
"invalid": {
"sigError": [
{
"description": "The wrong signature",
"exception": "Invalid signature",
"d": "01",
"message": "foo",
"signature": {
"r": "38341707918488238920692284707283974715538935465589664377561695343399725051885",
"s": "3180566392414476763164587487324397066658063772201694230600609996154610926757"
}
},
{
"description": "Invalid r value (< 0)",
"exception": "r and s not in range",
"d": "01",
"message": "foo",
"signature": {
"r": "-1",
"s": "2"
}
},
{
"description": "Invalid r value (== 0)",
"exception": "r and s not in range",
"d": "01",
"message": "foo",
"signature": {
"r": "0",
"s": "2"
}
},
{
"description": "Invalid r value (>= n)",
"exception": "r and s not in range",
"d": "01",
"message": "foo",
"signature": {
"r": "115792089237316195423570985008687907852837564279074904382605163141518161494337",
"s": "2"
}
},
{
"description": "Invalid s value (< 0)",
"exception": "r and s not in range",
"d": "01",
"message": "foo",
"signature": {
"r": "2",
"s": "-1"
}
},
{
"description": "Invalid s value (== 0)",
"exception": "r and s not in range",
"d": "01",
"message": "foo",
"signature": {
"r": "2",
"s": "0"
}
},
{
"description": "Invalid s value (>= n)",
"exception": "r and s not in range",
"d": "01",
"message": "foo",
"signature": {
"r": "2",
"s": "115792089237316195423570985008687907852837564279074904382605163141518161494337"
}
},
{
"description": "Invalid r, s values (r = s = -n)",
"exception": "r and s not in range",
"d": "01",
"message": "foo",
"signature": {
"r": "-115792089237316195423570985008687907852837564279074904382605163141518161494337",
"s": "-115792089237316195423570985008687907852837564279074904382605163141518161494337"
}
}
]
}
}
Loading…
Cancel
Save