diff --git a/README.md b/README.md index d09c5f0f..410b1add 100644 --- a/README.md +++ b/README.md @@ -85,11 +85,11 @@ $ npm test | block/blockheader.js | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | | block/index.js | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | | block/merkleblock.js | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | -| crypto/bn.js | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | -| crypto/ecdsa.js | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | -| crypto/hash.js | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | +| crypto/bn.js | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | +| crypto/ecdsa.js | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | +| crypto/hash.js | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | | crypto/point.js | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | -| crypto/random.js | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | +| crypto/random.js | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | | crypto/signature.js | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | | encoding/base58.js | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | | encoding/base58check.js | ![done](https://i.imgur.com/RXSkZTD.png "Done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | ![not done](https://i.imgur.com/MleS2Jt.png "Not done") | diff --git a/src/crypto/bn.js b/src/crypto/bn.js index fdb0750e..23c3fcaf 100644 --- a/src/crypto/bn.js +++ b/src/crypto/bn.js @@ -1,10 +1,8 @@ - - const BN = require('bn.js'); const _ = require('lodash'); const $ = require('../util/preconditions'); -const reversebuf = function (buf) { +const reversebuf = buf => { const buf2 = Buffer.alloc(buf.length); for (let i = 0; i < buf.length; i += 1) { buf2[i] = buf[buf.length - 1 - i]; @@ -16,17 +14,17 @@ BN.Zero = new BN(0); BN.One = new BN(1); BN.Minus1 = new BN(-1); -BN.fromNumber = function (n) { +BN.fromNumber = function(n) { $.checkArgument(_.isNumber(n)); return new BN(n); }; -BN.fromString = function (str, base) { +BN.fromString = function(str, base) { $.checkArgument(_.isString(str)); return new BN(str, base); }; -BN.fromBuffer = function (buf, opts) { +BN.fromBuffer = function(buf, opts) { if (typeof opts !== 'undefined' && opts.endian === 'little') { buf = reversebuf(buf); } @@ -39,7 +37,7 @@ BN.fromBuffer = function (buf, opts) { * Instantiate a BigNumber from a "signed magnitude buffer" * (a buffer where the most significant bit represents the sign (0 = positive, -1 = negative)) */ -BN.fromSM = function (buf, opts) { +BN.fromSM = function(buf, opts) { let ret; if (buf.length === 0) { return BN.fromBuffer(Buffer.from([0])); @@ -63,14 +61,13 @@ BN.fromSM = function (buf, opts) { return ret; }; - -BN.prototype.toNumber = function () { +BN.prototype.toNumber = function() { return parseInt(this.toString(10), 10); }; -BN.prototype.toBuffer = function (opts) { - let buf; let - hex; +BN.prototype.toBuffer = function(opts) { + let buf; + let hex; if (opts && opts.size) { hex = this.toString(16, 2); const natlen = hex.length / 2; @@ -93,7 +90,7 @@ BN.prototype.toBuffer = function (opts) { return buf; }; -BN.prototype.toSMBigEndian = function () { +BN.prototype.toSMBigEndian = function() { let buf; if (this.cmp(BN.Zero) === -1) { buf = this.neg().toBuffer(); @@ -109,13 +106,13 @@ BN.prototype.toSMBigEndian = function () { } } - if (buf.length === 1 & buf[0] === 0) { + if ((buf.length === 1) & (buf[0] === 0)) { buf = Buffer.from([]); } return buf; }; -BN.prototype.toSM = function (opts) { +BN.prototype.toSM = function(opts) { const endian = opts ? opts.endian : 'big'; let buf = this.toSMBigEndian(); @@ -133,7 +130,7 @@ BN.prototype.toSM = function (opts) { * 4 bytes. We copy that behavior here. A third argument, `size`, is provided to * extend the hard limit of 4 bytes, as some usages require more than 4 bytes. */ -BN.fromScriptNumBuffer = function (buf, fRequireMinimal, size) { +BN.fromScriptNumBuffer = function(buf, fRequireMinimal, size) { const nMaxNumSize = size || 4; $.checkArgument(buf.length <= nMaxNumSize, new Error('script number overflow')); if (fRequireMinimal && buf.length > 0) { @@ -165,29 +162,29 @@ BN.fromScriptNumBuffer = function (buf, fRequireMinimal, size) { * performing a numerical operation that results in an overflow to more than 4 * bytes). */ -BN.prototype.toScriptNumBuffer = function () { +BN.prototype.toScriptNumBuffer = function() { return this.toSM({ endian: 'little', }); }; -BN.prototype.gt = function (b) { +BN.prototype.gt = function(b) { return this.cmp(b) > 0; }; -BN.prototype.gte = function (b) { +BN.prototype.gte = function(b) { return this.cmp(b) >= 0; }; -BN.prototype.lt = function (b) { +BN.prototype.lt = function(b) { return this.cmp(b) < 0; }; -BN.trim = function (buf, natlen) { +BN.trim = function(buf, natlen) { return buf.slice(natlen - buf.length, buf.length); }; -BN.pad = function (buf, natlen, size) { +BN.pad = function(buf, natlen, size) { const rbuf = Buffer.alloc(size); for (let i = 0; i < buf.length; i += 1) { rbuf[rbuf.length - 1 - i] = buf[buf.length - 1 - i]; diff --git a/src/crypto/ecdsa.js b/src/crypto/ecdsa.js index 405a0490..ea9f4ba2 100644 --- a/src/crypto/ecdsa.js +++ b/src/crypto/ecdsa.js @@ -8,284 +8,311 @@ const Hash = require('./hash'); const BufferUtil = require('../util/buffer'); const $ = require('../util/preconditions'); -const ECDSA = function ECDSA(obj) { - if (!(this instanceof ECDSA)) { - return new ECDSA(obj); +class ECDSA { + /* jshint maxcomplexity: 9 */ + constructor(obj) { + if (!(this instanceof ECDSA)) { + return new ECDSA(obj); + } + if (obj) { + this.set(obj); + } } - if (obj) { - this.set(obj); + + set(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; + } + + privkey2pubkey() { + this.pubkey = this.privkey.toPublicKey(); } -}; - -/* 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 (let i = 0; i < 4; i += 1) { - this.sig.i = i; - let Qprime; - try { - Qprime = this.toPublicKey(); - - if (Qprime.point.eq(this.pubkey.point)) { - this.sig.compressed = this.pubkey.compressed; - return this; + + calci() { + for (let i = 0; i < 4; i += 1) { + this.sig.i = i; + let Qprime; + try { + Qprime = this.toPublicKey(); + + if (Qprime.point.eq(this.pubkey.point)) { + this.sig.compressed = this.pubkey.compressed; + return this; + } + } catch (e) { + console.error(e); // eslint-disable-line no-console } - } catch (e) { - console.error(e); // eslint-disable-line no-console } + + this.sig.i = undefined; + throw new Error('Unable to find valid recovery factor'); + } + + static fromString(str) { + const obj = JSON.parse(str); + return new ECDSA(obj); } - this.sig.i = undefined; - throw new Error('Unable to find valid recovery factor'); -}; - -ECDSA.fromString = function (str) { - const obj = JSON.parse(str); - return new ECDSA(obj); -}; - -ECDSA.prototype.randomK = function () { - const N = Point.getN(); - let 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; + randomK() { + const N = Point.getN(); + let k; + do { + k = BN.fromBuffer(Random.getRandomBuffer(32)); + } while (!(k.lt(N) && k.gt(BN.Zero))); + this.k = k; + return this; } - let v = Buffer.alloc(32); - v.fill(0x01); - let k = Buffer.alloc(32); - k.fill(0x00); - const x = this.privkey.bn.toBuffer({ - size: 32, - }); - const 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); - let T = BN.fromBuffer(v); - const N = Point.getN(); - - // also explained in 3.2, we must ensure T is in the proper range (0, N) - for (let i = 0; i < badrs || !(T.lt(N) && T.gt(BN.Zero)); i += 1) { - k = Hash.sha256hmac(Buffer.concat([v, Buffer.from([0x00])]), k); + + // https://tools.ietf.org/html/rfc6979#section-3.2 + deterministicK(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; + } + let v = Buffer.alloc(32); + v.fill(0x01); + let k = Buffer.alloc(32); + k.fill(0x00); + const x = this.privkey.bn.toBuffer({ + size: 32, + }); + const 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); - T = BN.fromBuffer(v); + let T = BN.fromBuffer(v); + const N = Point.getN(); + + // also explained in 3.2, we must ensure T is in the proper range (0, N) + for (let i = 0; i < badrs || !(T.lt(N) && T.gt(BN.Zero)); i += 1) { + k = Hash.sha256hmac(Buffer.concat([v, Buffer.from([0x00])]), k); + v = Hash.sha256hmac(v, k); + v = Hash.sha256hmac(v, k); + T = BN.fromBuffer(v); + } + + this.k = T; + return this; } - 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 + toPublicKey() { + /* jshint maxstatements: 25 */ + const { i } = this.sig; + $.checkArgument(i === 0 || i === 1 || i === 2 || i === 3, new Error('i must be equal to 0, 1, 2, or 3')); -// 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 */ - const { i } = this.sig; - $.checkArgument(i === 0 || i === 1 || i === 2 || i === 3, new Error('i must be equal to 0, 1, 2, or 3')); + const e = BN.fromBuffer(this.hashbuf); + const { r } = this.sig; + const { s } = this.sig; - const e = BN.fromBuffer(this.hashbuf); - const { r } = this.sig; - const { s } = this.sig; + // A set LSB signifies that the y-coordinate is odd + const isYOdd = i & 1; - // A set LSB signifies that the y-coordinate is odd - const isYOdd = i & 1; + // The more significant bit specifies whether we should use the + // first or second candidate key. + const isSecondKey = i >> 1; - // The more significant bit specifies whether we should use the - // first or second candidate key. - const isSecondKey = i >> 1; + const n = Point.getN(); + const G = Point.getG(); - const n = Point.getN(); - const G = Point.getG(); + // 1.1 Let x = r + jn + const x = isSecondKey ? r.add(n) : r; + const R = Point.fromX(isYOdd, x); - // 1.1 Let x = r + jn - const x = isSecondKey ? r.add(n) : r; - const R = Point.fromX(isYOdd, x); + // 1.4 Check that nR is at infinity + const nR = R.mul(n); - // 1.4 Check that nR is at infinity - const nR = R.mul(n); + if (!nR.isInfinity()) { + throw new Error('nR is not a valid curve point'); + } - if (!nR.isInfinity()) { - throw new Error('nR is not a valid curve point'); - } + // Compute -e from e + const eNeg = e.neg().mod(n); - // Compute -e from e - const eNeg = e.neg().mod(n); + // 1.6.1 Compute Q = r^-1 (sR - eG) + // Q = r^-1 (sR + -eG) + const rInv = r.invm(n); - // 1.6.1 Compute Q = r^-1 (sR - eG) - // Q = r^-1 (sR + -eG) - const rInv = r.invm(n); + // var Q = R.multiplyTwo(s, G, eNeg).mul(rInv); + const Q = R.mul(s) + .add(G.mul(eNeg)) + .mul(rInv); - // var Q = R.multiplyTwo(s, G, eNeg).mul(rInv); - const Q = R.mul(s).add(G.mul(eNeg)).mul(rInv); + const pubkey = PublicKey.fromPoint(Q, this.sig.compressed); - const pubkey = PublicKey.fromPoint(Q, this.sig.compressed); + return pubkey; + } - return pubkey; -}; + sigError() { + /* jshint maxstatements: 25 */ + if (!BufferUtil.isBuffer(this.hashbuf) || this.hashbuf.length !== 32) { + return 'hashbuf must be a 32 byte buffer'; + } + + const { r } = this.sig; + const { s } = this.sig; + if (!(r.gt(BN.Zero) && r.lt(Point.getN())) || !(s.gt(BN.Zero) && s.lt(Point.getN()))) { + return 'r and s not in range'; + } -ECDSA.prototype.sigError = function () { - /* jshint maxstatements: 25 */ - if (!BufferUtil.isBuffer(this.hashbuf) || this.hashbuf.length !== 32) { - return 'hashbuf must be a 32 byte buffer'; + const e = BN.fromBuffer( + this.hashbuf, + this.endian + ? { + endian: this.endian, + } + : undefined, + ); + const n = Point.getN(); + const sinv = s.invm(n); + const u1 = sinv.mul(e).mod(n); + const u2 = sinv.mul(r).mod(n); + + const 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'; + } + return false; } - const { r } = this.sig; - const { s } = this.sig; - if (!(r.gt(BN.Zero) && r.lt(Point.getN())) || !(s.gt(BN.Zero) && s.lt(Point.getN()))) { - return 'r and s not in range'; + static toLowS(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; } - const e = BN.fromBuffer(this.hashbuf, this.endian ? { - endian: this.endian, - } : undefined); - const n = Point.getN(); - const sinv = s.invm(n); - const u1 = sinv.mul(e).mod(n); - const u2 = sinv.mul(r).mod(n); - - const p = Point.getG().mulAdd(u1, this.pubkey.point, u2); - if (p.isInfinity()) { - return 'p is infinity'; + _findSignature(d, e) { + const N = Point.getN(); + const G = Point.getG(); + // try different values of k until r, s are valid + let badrs = 0; + let Q; + let r; + let s; + do { + if (!this.k || badrs > 0) { + this.deterministicK(badrs); + } + badrs += 1; + const { k } = this; + 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, + r, + }; } - if (p.getX().mod(n).cmp(r) !== 0) { - return 'Invalid signature'; + sign() { + const { hashbuf } = this; + const { privkey } = this; + const d = privkey.bn; + + $.checkState(hashbuf && privkey && d, new Error('invalid parameters')); + $.checkState(BufferUtil.isBuffer(hashbuf) && hashbuf.length === 32, new Error('hashbuf must be a 32 byte buffer')); + + const e = BN.fromBuffer( + hashbuf, + this.endian + ? { + endian: this.endian, + } + : undefined, + ); + + const obj = this._findSignature(d, e); + obj.compressed = this.pubkey.compressed; + + this.sig = new Signature(obj); + return this; } - 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); + + signRandomK() { + this.randomK(); + return this.sign(); } - return s; -}; - -ECDSA.prototype._findSignature = function (d, e) { - const N = Point.getN(); - const G = Point.getG(); - // try different values of k until r, s are valid - let badrs = 0; - let Q; let r; let s; - do { - if (!this.k || badrs > 0) { - this.deterministicK(badrs); + + toString() { + const obj = {}; + if (this.hashbuf) { + obj.hashbuf = this.hashbuf.toString('hex'); } - badrs += 1; - const { k } = this; - 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, - r, - }; -}; - -ECDSA.prototype.sign = function () { - const { hashbuf } = this; - const { privkey } = this; - const d = privkey.bn; - - $.checkState(hashbuf && privkey && d, new Error('invalid parameters')); - $.checkState(BufferUtil.isBuffer(hashbuf) && hashbuf.length === 32, new Error('hashbuf must be a 32 byte buffer')); - - const e = BN.fromBuffer(hashbuf, this.endian ? { - endian: this.endian, - } : undefined); - - const 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 () { - const 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.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 (this.sig) { - obj.sig = this.sig.toString(); + + verify() { + if (!this.sigError()) { + this.verified = true; + } else { + this.verified = false; + } + return this; } - if (this.k) { - obj.k = this.k.toString(); + + static sign(hashbuf, privkey, endian) { + return new ECDSA() + .set({ + hashbuf, + endian, + privkey, + }) + .sign().sig; } - return JSON.stringify(obj); -}; - -ECDSA.prototype.verify = function () { - if (!this.sigError()) { - this.verified = true; - } else { - this.verified = false; + + static verify(hashbuf, sig, pubkey, endian) { + return new ECDSA() + .set({ + hashbuf, + endian, + sig, + pubkey, + }) + .verify().verified; } - return this; -}; - -ECDSA.sign = function (hashbuf, privkey, endian) { - return ECDSA().set({ - hashbuf, - endian, - privkey, - }).sign().sig; -}; - -ECDSA.verify = function (hashbuf, sig, pubkey, endian) { - return ECDSA().set({ - hashbuf, - endian, - sig, - pubkey, - }).verify().verified; -}; +} module.exports = ECDSA; diff --git a/src/crypto/hash.js b/src/crypto/hash.js index 83c244b7..49e20375 100644 --- a/src/crypto/hash.js +++ b/src/crypto/hash.js @@ -1,48 +1,58 @@ - - const crypto = require('crypto'); const BufferUtil = require('../util/buffer'); const $ = require('../util/preconditions'); const Hash = module.exports; -Hash.sha1 = function (buf) { +Hash.sha1 = buf => { $.checkArgument(BufferUtil.isBuffer(buf)); - return crypto.createHash('sha1').update(buf).digest(); + return crypto + .createHash('sha1') + .update(buf) + .digest(); }; Hash.sha1.blocksize = 512; -Hash.sha256 = function (buf) { +Hash.sha256 = buf => { $.checkArgument(BufferUtil.isBuffer(buf)); - return crypto.createHash('sha256').update(buf).digest(); + return crypto + .createHash('sha256') + .update(buf) + .digest(); }; Hash.sha256.blocksize = 512; -Hash.sha256sha256 = function (buf) { +Hash.sha256sha256 = buf => { $.checkArgument(BufferUtil.isBuffer(buf)); return Hash.sha256(Hash.sha256(buf)); }; -Hash.ripemd160 = function (buf) { +Hash.ripemd160 = buf => { $.checkArgument(BufferUtil.isBuffer(buf)); - return crypto.createHash('ripemd160').update(buf).digest(); + return crypto + .createHash('ripemd160') + .update(buf) + .digest(); }; -Hash.sha256ripemd160 = function (buf) { +Hash.sha256ripemd160 = buf => { $.checkArgument(BufferUtil.isBuffer(buf)); return Hash.ripemd160(Hash.sha256(buf)); }; -Hash.sha512 = function (buf) { +Hash.sha512 = buf => { $.checkArgument(BufferUtil.isBuffer(buf)); - return crypto.createHash('sha512').update(buf).digest(); + return crypto + .createHash('sha512') + .update(buf) + .digest(); }; Hash.sha512.blocksize = 1024; -Hash.hmac = function (hashf, data, key) { +Hash.hmac = (hashf, data, key) => { // http://en.wikipedia.org/wiki/Hash-based_message_authentication_code // http://tools.ietf.org/html/rfc4868#section-2 $.checkArgument(BufferUtil.isBuffer(data)); @@ -76,10 +86,5 @@ Hash.hmac = function (hashf, data, key) { return hashf(Buffer.concat([oKeyPad, hashf(Buffer.concat([iKeyPad, data]))])); }; -Hash.sha256hmac = function (data, key) { - return Hash.hmac(Hash.sha256, data, key); -}; - -Hash.sha512hmac = function (data, key) { - return Hash.hmac(Hash.sha512, data, key); -}; +Hash.sha256hmac = (data, key) => Hash.hmac(Hash.sha256, data, key); +Hash.sha512hmac = (data, key) => Hash.hmac(Hash.sha512, data, key); diff --git a/src/crypto/random.js b/src/crypto/random.js index 4301bb5a..6ae4cc7c 100644 --- a/src/crypto/random.js +++ b/src/crypto/random.js @@ -1,60 +1,61 @@ - const crypto = require('crypto'); -function Random() { -} +class Random { -/* secure random bytes that sometimes throws an error due to lack of entropy */ -Random.getRandomBuffer = function (size) { - if (process.browser) { - return Random.getRandomBufferBrowser(size); + /* secure random bytes that sometimes throws an error due to lack of entropy */ + static getRandomBuffer(size) { + if (process.browser) { + return Random.getRandomBufferBrowser(size); + } + return Random.getRandomBufferNode(size); } - return Random.getRandomBufferNode(size); -}; - -Random.getRandomBufferNode = function (size) { - return crypto.randomBytes(size); -}; -Random.getRandomBufferBrowser = function (size) { - let windowCrypto; - if (!window.crypto && !window.msCrypto) { - throw new Error('window.crypto not available'); + static getRandomBufferNode(size) { + return crypto.randomBytes(size); } - if (window.crypto && window.crypto.getRandomValues) { - windowCrypto = window.crypto; - } else if (window.msCrypto && window.msCrypto.getRandomValues) { // internet explorer - windowCrypto = window.msCrypto; - } else { - throw new Error('window crypto.getRandomValues not available'); - } + static getRandomBufferBrowser(size) { + let windowCrypto; + if (!window.crypto && !window.msCrypto) { + throw new Error('window.crypto not available'); + } - const bbuf = new Uint8Array(size); - windowCrypto.getRandomValues(bbuf); - const buf = Buffer.from(bbuf); - - return buf; -}; - -/* insecure random bytes, but it never fails */ -Random.getPseudoRandomBuffer = function (size) { - const b32 = 0x100000000; - const b = Buffer.alloc(size); - let r; - - for (let i = 0; i <= size; i += 1) { - const j = Math.floor(i / 4); - const k = i - j * 4; - if (k === 0) { - r = Math.random() * b32; - b[i] = r & 0xff; + if (window.crypto && window.crypto.getRandomValues) { + windowCrypto = window.crypto; + } else if (window.msCrypto && window.msCrypto.getRandomValues) { + // internet explorer + windowCrypto = window.msCrypto; } else { - b[i] = (r >>>= 8) & 0xff; + throw new Error('window crypto.getRandomValues not available'); } + + const bbuf = new Uint8Array(size); + windowCrypto.getRandomValues(bbuf); + const buf = Buffer.from(bbuf); + + return buf; + } + + /* insecure random bytes, but it never fails */ + static getPseudoRandomBuffer(size) { + const b32 = 0x100000000; + const b = Buffer.alloc(size); + let r; + + for (let i = 0; i <= size; i += 1) { + const j = Math.floor(i / 4); + const k = i - j * 4; + if (k === 0) { + r = Math.random() * b32; + b[i] = r & 0xff; + } else { + b[i] = (r >>>= 8) & 0xff; + } + } + + return b; } - return b; -}; +} module.exports = Random; diff --git a/src/crypto/signature.js b/src/crypto/signature.js index 7b6cf747..ee911a16 100644 --- a/src/crypto/signature.js +++ b/src/crypto/signature.js @@ -4,312 +4,319 @@ const $ = require('../util/preconditions'); const BufferUtil = require('../util/buffer'); const JSUtil = require('../util/js'); -const Signature = function Signature(r, s) { - if (!(this instanceof Signature)) { - return new Signature(r, s); +class Signature { + /* jshint maxcomplexity: 7 */ + constructor(r, s) { + if (!(this instanceof Signature)) { + return new Signature(r, s); + } + if (r instanceof BN) { + this.set({ + r, + s, + }); + } else if (r) { + const obj = r; + this.set(obj); + } } - if (r instanceof BN) { - this.set({ - r, - s, - }); - } else if (r) { - const obj = r; - this.set(obj); - } -}; - -/* jshint maxcomplexity: 7 */ -Signature.prototype.set = function (obj) { - this.r = obj.r || this.r || undefined; - this.s = obj.s || this.s || undefined; - this.i = typeof obj.i !== 'undefined' ? obj.i : this.i; // public key recovery parameter in range [0, 3] - this.compressed = typeof obj.compressed !== 'undefined' - ? obj.compressed : this.compressed; // whether the recovered pubkey is compressed - this.nhashtype = obj.nhashtype || this.nhashtype || undefined; - return this; -}; - -Signature.fromCompact = function (buf) { - $.checkArgument(BufferUtil.isBuffer(buf), 'Argument is expected to be a Buffer'); - - const sig = new Signature(); - - let compressed = true; - let i = buf.slice(0, 1)[0] - 27 - 4; - if (i < 0) { - compressed = false; - i += 4; - } - - const b2 = buf.slice(1, 33); - const b3 = buf.slice(33, 65); - $.checkArgument(i === 0 || i === 1 || i === 2 || i === 3, new Error('i must be 0, 1, 2, or 3')); - $.checkArgument(b2.length === 32, new Error('r must be 32 bytes')); - $.checkArgument(b3.length === 32, new Error('s must be 32 bytes')); + set(obj) { + this.r = obj.r || this.r || undefined; + this.s = obj.s || this.s || undefined; + this.i = typeof obj.i !== 'undefined' ? obj.i : this.i; // public key recovery parameter in range [0, 3] + this.compressed = typeof obj.compressed !== 'undefined' ? obj.compressed : this.compressed; // whether the recovered pubkey is compressed + this.nhashtype = obj.nhashtype || this.nhashtype || undefined; + return this; + } - sig.compressed = compressed; - sig.i = i; - sig.r = BN.fromBuffer(b2); - sig.s = BN.fromBuffer(b3); + static fromCompact(buf) { + $.checkArgument(BufferUtil.isBuffer(buf), 'Argument is expected to be a Buffer'); - return sig; -}; + const sig = new Signature(); -Signature.fromDER = function (buf, strict) { - const obj = Signature.parseDER(buf, strict); - const sig = new Signature(); + let compressed = true; + let i = buf.slice(0, 1)[0] - 27 - 4; + if (i < 0) { + compressed = false; + i += 4; + } - sig.r = obj.r; - sig.s = obj.s; + const b2 = buf.slice(1, 33); + const b3 = buf.slice(33, 65); - return sig; -}; + $.checkArgument(i === 0 || i === 1 || i === 2 || i === 3, new Error('i must be 0, 1, 2, or 3')); + $.checkArgument(b2.length === 32, new Error('r must be 32 bytes')); + $.checkArgument(b3.length === 32, new Error('s must be 32 bytes')); -Signature.fromBuffer = Signature.fromDER; + sig.compressed = compressed; + sig.i = i; + sig.r = BN.fromBuffer(b2); + sig.s = BN.fromBuffer(b3); -// The format used in a tx -Signature.fromTxFormat = function (buf) { - const nhashtype = buf.readUInt8(buf.length - 1); - const derbuf = buf.slice(0, buf.length - 1); - const sig = Signature.fromDER(derbuf, false); - sig.nhashtype = nhashtype; - return sig; -}; + return sig; + } -Signature.fromString = function (str) { - const buf = Buffer.from(str, 'hex'); - return Signature.fromDER(buf); -}; + static fromDER(buf, strict) { + const obj = Signature.parseDER(buf, strict); + const sig = new Signature(); + sig.r = obj.r; + sig.s = obj.s; -/** - * In order to mimic the non-strict DER encoding of OpenSSL, set strict = false. - */ -Signature.parseDER = function (buf, strict) { - $.checkArgument(BufferUtil.isBuffer(buf), new Error('DER formatted signature should be a buffer')); - if (_.isUndefined(strict)) { - strict = true; + return sig; } - const header = buf[0]; - $.checkArgument(header === 0x30, new Error('Header byte should be 0x30')); - - let length = buf[1]; - const buflength = buf.slice(2).length; - $.checkArgument(!strict || length === buflength, new Error('Length byte should length of what follows')); - - length = length < buflength ? length : buflength; - - const rheader = buf[2 + 0]; - $.checkArgument(rheader === 0x02, new Error('Integer byte for r should be 0x02')); - - const rlength = buf[2 + 1]; - const rbuf = buf.slice(2 + 2, 2 + 2 + rlength); - const r = BN.fromBuffer(rbuf); - const rneg = buf[2 + 1 + 1] === 0x00; - $.checkArgument(rlength === rbuf.length, new Error('Length of r incorrect')); - - const sheader = buf[2 + 2 + rlength + 0]; - $.checkArgument(sheader === 0x02, new Error('Integer byte for s should be 0x02')); - - const slength = buf[2 + 2 + rlength + 1]; - const sbuf = buf.slice(2 + 2 + rlength + 2, 2 + 2 + rlength + 2 + slength); - const s = BN.fromBuffer(sbuf); - const sneg = buf[2 + 2 + rlength + 2 + 2] === 0x00; - $.checkArgument(slength === sbuf.length, new Error('Length of s incorrect')); - - const sumlength = 2 + 2 + rlength + 2 + slength; - $.checkArgument(length === sumlength - 2, new Error('Length of signature incorrect')); - - const obj = { - header, - length, - rheader, - rlength, - rneg, - rbuf, - r, - sheader, - slength, - sneg, - sbuf, - s, - }; - - return obj; -}; - - -Signature.prototype.toCompact = function (i, compressed) { - i = typeof i === 'number' ? i : this.i; - compressed = typeof compressed === 'boolean' ? compressed : this.compressed; - - if (!(i === 0 || i === 1 || i === 2 || i === 3)) { - throw new Error('i must be equal to 0, 1, 2, or 3'); + fromBuffer() { + return this.fromDER(); } - let val = i + 27 + 4; - if (compressed === false) { - val -= 4; - } - const b1 = Buffer.from([val]); - const b2 = this.r.toBuffer({ - size: 32, - }); - const b3 = this.s.toBuffer({ - size: 32, - }); - return Buffer.concat([b1, b2, b3]); -}; - -Signature.prototype.toBuffer = function () { - const rnbuf = this.r.toBuffer(); - const snbuf = this.s.toBuffer(); - - const rneg = !!(rnbuf[0] & 0x80); - const sneg = !!(snbuf[0] & 0x80); - - const rbuf = rneg ? Buffer.concat([Buffer.from([0x00]), rnbuf]) : rnbuf; - const sbuf = sneg ? Buffer.concat([Buffer.from([0x00]), snbuf]) : snbuf; - - const rlength = rbuf.length; - const slength = sbuf.length; - const length = 2 + rlength + 2 + slength; - const rheader = 0x02; - const sheader = 0x02; - const header = 0x30; - - const der = Buffer.concat([ - Buffer.from([header, length, rheader, rlength]), - rbuf, - Buffer.from([sheader, slength]), - sbuf, - ]); - return der; -}; - -Signature.prototype.toDER = Signature.prototype.toBuffer; - -Signature.prototype.toString = function () { - const buf = this.toDER(); - return buf.toString('hex'); -}; - -/** - * This function is translated from bitcoind's IsDERSignature and is used in - * the script interpreter. This "DER" format actually includes an extra byte, - * the nhashtype, at the end. It is really the tx format, not DER format. - * - * A canonical signature exists of: [30] [total len] [02] [len R] [R] [02] [len S] [S] [hashtype] - * Where R and S are not negative (their first byte has its highest bit not set), and not - * excessively padded (do not start with a 0 byte, unless an otherwise negative number follows, - * in which case a single 0 byte is necessary and even required). - * - * See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623 - */ -Signature.isTxDER = function (buf) { - if (buf.length < 9) { - // Non-canonical signature: too short - return false; - } - if (buf.length > 73) { - // Non-canonical signature: too long - return false; - } - if (buf[0] !== 0x30) { - // Non-canonical signature: wrong type - return false; - } - if (buf[1] !== buf.length - 3) { - // Non-canonical signature: wrong length marker - return false; - } - const nLenR = buf[3]; - if (5 + nLenR >= buf.length) { - // Non-canonical signature: S length misplaced - return false; - } - const nLenS = buf[5 + nLenR]; - if ((nLenR + nLenS + 7) !== buf.length) { - // Non-canonical signature: R+S length mismatch - return false; + // The format used in a tx + fromTxFormat(buf) { + const nhashtype = buf.readUInt8(buf.length - 1); + const derbuf = buf.slice(0, buf.length - 1); + const sig = Signature.fromDER(derbuf, false); + sig.nhashtype = nhashtype; + return sig; } - const R = buf.slice(4); - if (buf[4 - 2] !== 0x02) { - // Non-canonical signature: R value type mismatch - return false; + static fromString(str) { + const buf = Buffer.from(str, 'hex'); + return Signature.fromDER(buf); } - if (nLenR === 0) { - // Non-canonical signature: R length is zero - return false; - } - if (R[0] & 0x80) { - // Non-canonical signature: R value negative - return false; + + /** + * In order to mimic the non-strict DER encoding of OpenSSL, set strict = false. + */ + + static parseDER(buf, strict) { + $.checkArgument(BufferUtil.isBuffer(buf), new Error('DER formatted signature should be a buffer')); + if (_.isUndefined(strict)) { + strict = true; + } + + const header = buf[0]; + $.checkArgument(header === 0x30, new Error('Header byte should be 0x30')); + + let length = buf[1]; + const buflength = buf.slice(2).length; + $.checkArgument(!strict || length === buflength, new Error('Length byte should length of what follows')); + + length = length < buflength ? length : buflength; + + const rheader = buf[2 + 0]; + $.checkArgument(rheader === 0x02, new Error('Integer byte for r should be 0x02')); + + const rlength = buf[2 + 1]; + const rbuf = buf.slice(2 + 2, 2 + 2 + rlength); + const r = BN.fromBuffer(rbuf); + const rneg = buf[2 + 1 + 1] === 0x00; + $.checkArgument(rlength === rbuf.length, new Error('Length of r incorrect')); + + const sheader = buf[2 + 2 + rlength + 0]; + $.checkArgument(sheader === 0x02, new Error('Integer byte for s should be 0x02')); + + const slength = buf[2 + 2 + rlength + 1]; + const sbuf = buf.slice(2 + 2 + rlength + 2, 2 + 2 + rlength + 2 + slength); + const s = BN.fromBuffer(sbuf); + const sneg = buf[2 + 2 + rlength + 2 + 2] === 0x00; + $.checkArgument(slength === sbuf.length, new Error('Length of s incorrect')); + + const sumlength = 2 + 2 + rlength + 2 + slength; + $.checkArgument(length === sumlength - 2, new Error('Length of signature incorrect')); + + const obj = { + header, + length, + rheader, + rlength, + rneg, + rbuf, + r, + sheader, + slength, + sneg, + sbuf, + s, + }; + + return obj; } - if (nLenR > 1 && (R[0] === 0x00) && !(R[1] & 0x80)) { - // Non-canonical signature: R value excessively padded - return false; + + toCompact(i, compressed) { + i = typeof i === 'number' ? i : this.i; + compressed = typeof compressed === 'boolean' ? compressed : this.compressed; + + if (!(i === 0 || i === 1 || i === 2 || i === 3)) { + throw new Error('i must be equal to 0, 1, 2, or 3'); + } + + let val = i + 27 + 4; + if (compressed === false) { + val -= 4; + } + const b1 = Buffer.from([val]); + const b2 = this.r.toBuffer({ + size: 32, + }); + const b3 = this.s.toBuffer({ + size: 32, + }); + return Buffer.concat([b1, b2, b3]); } - const S = buf.slice(6 + nLenR); - if (buf[6 + nLenR - 2] !== 0x02) { - // Non-canonical signature: S value type mismatch - return false; + static toBuffer() { + const rnbuf = this.r.toBuffer(); + const snbuf = this.s.toBuffer(); + + const rneg = !!(rnbuf[0] & 0x80); + const sneg = !!(snbuf[0] & 0x80); + + const rbuf = rneg ? Buffer.concat([Buffer.from([0x00]), rnbuf]) : rnbuf; + const sbuf = sneg ? Buffer.concat([Buffer.from([0x00]), snbuf]) : snbuf; + + const rlength = rbuf.length; + const slength = sbuf.length; + const length = 2 + rlength + 2 + slength; + const rheader = 0x02; + const sheader = 0x02; + const header = 0x30; + + const der = Buffer.concat([ + Buffer.from([header, length, rheader, rlength]), + rbuf, + Buffer.from([sheader, slength]), + sbuf, + ]); + return der; } - if (nLenS === 0) { - // Non-canonical signature: S length is zero - return false; + + static toDER() { + return this.toBuffer(); } - if (S[0] & 0x80) { - // Non-canonical signature: S value negative - return false; + + static toString() { + const buf = this.toDER(); + return buf.toString('hex'); } - if (nLenS > 1 && (S[0] === 0x00) && !(S[1] & 0x80)) { - // Non-canonical signature: S value excessively padded - return false; + + /** + * This function is translated from bitcoind's IsDERSignature and is used in + * the script interpreter. This "DER" format actually includes an extra byte, + * the nhashtype, at the end. It is really the tx format, not DER format. + * + * A canonical signature exists of: [30] [total len] [02] [len R] [R] [02] [len S] [S] [hashtype] + * Where R and S are not negative (their first byte has its highest bit not set), and not + * excessively padded (do not start with a 0 byte, unless an otherwise negative number follows, + * in which case a single 0 byte is necessary and even required). + * + * See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623 + */ + + static isTxDER(buf) { + if (buf.length < 9) { + // Non-canonical signature: too short + return false; + } + if (buf.length > 73) { + // Non-canonical signature: too long + return false; + } + if (buf[0] !== 0x30) { + // Non-canonical signature: wrong type + return false; + } + if (buf[1] !== buf.length - 3) { + // Non-canonical signature: wrong length marker + return false; + } + const nLenR = buf[3]; + if (5 + nLenR >= buf.length) { + // Non-canonical signature: S length misplaced + return false; + } + const nLenS = buf[5 + nLenR]; + if (nLenR + nLenS + 7 !== buf.length) { + // Non-canonical signature: R+S length mismatch + return false; + } + + const R = buf.slice(4); + if (buf[4 - 2] !== 0x02) { + // Non-canonical signature: R value type mismatch + return false; + } + if (nLenR === 0) { + // Non-canonical signature: R length is zero + return false; + } + if (R[0] & 0x80) { + // Non-canonical signature: R value negative + return false; + } + if (nLenR > 1 && R[0] === 0x00 && !(R[1] & 0x80)) { + // Non-canonical signature: R value excessively padded + return false; + } + + const S = buf.slice(6 + nLenR); + if (buf[6 + nLenR - 2] !== 0x02) { + // Non-canonical signature: S value type mismatch + return false; + } + if (nLenS === 0) { + // Non-canonical signature: S length is zero + return false; + } + if (S[0] & 0x80) { + // Non-canonical signature: S value negative + return false; + } + if (nLenS > 1 && S[0] === 0x00 && !(S[1] & 0x80)) { + // Non-canonical signature: S value excessively padded + return false; + } + return true; } - return true; -}; - -/** - * Compares to bitcoind's IsLowDERSignature - * See also ECDSA signature algorithm which enforces this. - * See also BIP 62, "low S values in signatures" - */ -Signature.prototype.hasLowS = function () { - if (this.s.lt(new BN(1)) - || this.s.gt(new BN('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex'))) { - return false; + + /** + * Compares to bitcoind's IsLowDERSignature + * See also ECDSA signature algorithm which enforces this. + * See also BIP 62, "low S values in signatures" + */ + hasLowS() { + if ( + this.s.lt(new BN(1)) || + this.s.gt(new BN('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex')) + ) { + return false; + } + return true; } - return true; -}; - -/** - * @returns true if the nhashtype is exactly equal to one of the standard options - * or combinations thereof. Translated from bitcoind's IsDefinedHashtypeSignature - */ -Signature.prototype.hasDefinedHashtype = function () { - if (!JSUtil.isNaturalNumber(this.nhashtype)) { - return false; + + /** + * @returns true if the nhashtype is exactly equal to one of the standard options + * or combinations thereof. Translated from bitcoind's IsDefinedHashtypeSignature + */ + hasDefinedHashtype() { + if (!JSUtil.isNaturalNumber(this.nhashtype)) { + return false; + } + // accept with or without Signature.SIGHASH_ANYONECANPAY by ignoring the bit + const temp = this.nhashtype & ~Signature.SIGHASH_ANYONECANPAY; + if (temp < Signature.SIGHASH_ALL || temp > Signature.SIGHASH_SINGLE) { + return false; + } + return true; } - // accept with or without Signature.SIGHASH_ANYONECANPAY by ignoring the bit - const temp = this.nhashtype & ~Signature.SIGHASH_ANYONECANPAY; - if (temp < Signature.SIGHASH_ALL || temp > Signature.SIGHASH_SINGLE) { - return false; + + static toTxFormat() { + const derbuf = Signature.toDER(); + const buf = Buffer.alloc(1); + buf.writeUInt8(this.nhashtype, 0); + return Buffer.concat([derbuf, buf]); } - return true; -}; - -Signature.prototype.toTxFormat = function () { - const derbuf = this.toDER(); - const buf = Buffer.alloc(1); - buf.writeUInt8(this.nhashtype, 0); - return Buffer.concat([derbuf, buf]); -}; +} Signature.SIGHASH_ALL = 0x01; Signature.SIGHASH_NONE = 0x02; diff --git a/test/crypto/ecdsa.js b/test/crypto/ecdsa.js index 7be18f73..40ef8eb0 100644 --- a/test/crypto/ecdsa.js +++ b/test/crypto/ecdsa.js @@ -12,7 +12,6 @@ var should = require('chai').should(); var vectors = require('../data/ecdsa'); describe('ECDSA', function() { - it('instantiation', function() { var ecdsa = new ECDSA(); should.exist(ecdsa); @@ -20,16 +19,18 @@ describe('ECDSA', function() { var ecdsa = new ECDSA(); ecdsa.hashbuf = Hash.sha256(new Buffer('test data')); - ecdsa.privkey = new Privkey(BN.fromBuffer( - new Buffer('fee0a1f7afebf9d2a5a80c0c98a31c709681cce195cbcd06342b517970c0be1e', 'hex') - )); + ecdsa.privkey = new Privkey( + BN.fromBuffer(new Buffer('fee0a1f7afebf9d2a5a80c0c98a31c709681cce195cbcd06342b517970c0be1e', 'hex')), + ); ecdsa.privkey2pubkey(); describe('#set', function() { it('sets hashbuf', function() { - should.exist(ECDSA().set({ - hashbuf: ecdsa.hashbuf - }).hashbuf); + should.exist( + new ECDSA().set({ + hashbuf: ecdsa.hashbuf, + }).hashbuf, + ); }); }); @@ -50,29 +51,25 @@ describe('ECDSA', function() { hashbuf: hashbuf, sig: new Signature({ r: r, - s: s - }) + s: s, + }), }); ecdsa.calci(); ecdsa.sig.i.should.equal(1); }); - }); describe('#fromString', function() { - it('round trip with fromString', function() { var str = ecdsa.toString(); - var ecdsa2 = new ECDSA.fromString(str); + var ecdsa2 = ECDSA.fromString(str); should.exist(ecdsa2.hashbuf); should.exist(ecdsa2.privkey); }); - }); describe('#randomK', function() { - it('should generate a new random k when called twice in a row', function() { ecdsa.randomK(); var k1 = ecdsa.k; @@ -87,23 +84,30 @@ describe('ECDSA', function() { var k2 = new BN(Math.pow(2, 32)).mul(new BN(Math.pow(2, 32))).mul(new BN(Math.pow(2, 32))); k2.gt(k1).should.equal(false); }); - }); describe('#deterministicK', function() { it('should generate the same deterministic k', function() { ecdsa.deterministicK(); - ecdsa.k.toBuffer().toString('hex') + 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') + ecdsa.k + .toBuffer() + .toString('hex') .should.equal('fcce1de7a9bcd6b2d3defade6afa1913fb9229e3b7ddf4749b55c4848b2a196e'); ecdsa.deterministicK(1); - ecdsa.k.toBuffer().toString('hex') + ecdsa.k + .toBuffer() + .toString('hex') .should.not.equal('fcce1de7a9bcd6b2d3defade6afa1913fb9229e3b7ddf4749b55c4848b2a196e'); - ecdsa.k.toBuffer().toString('hex') + ecdsa.k + .toBuffer() + .toString('hex') .should.equal('727fbcb59eb48b1d7d46f95a04991fc512eb9dbf9105628e3aec87428df28fd8'); }); it('should compute this test vector correctly', function() { @@ -114,12 +118,16 @@ describe('ECDSA', function() { ecdsa.privkey = new Privkey(new BN(1)); ecdsa.privkey2pubkey(); ecdsa.deterministicK(); - ecdsa.k.toBuffer().toString('hex') + ecdsa.k + .toBuffer() + .toString('hex') .should.equal('ec633bd56a5774a0940cb97e27a9e4e51dc94af737596a0c5cbb3d30332d92a5'); ecdsa.sign(); - ecdsa.sig.r.toString() + ecdsa.sig.r + .toString() .should.equal('23362334225185207751494092901091441011938859014081160902781146257181456271561'); - ecdsa.sig.s.toString() + ecdsa.sig.s + .toString() .should.equal('50433721247292933944369538617440297985091596895097604618403996029256432099938'); }); }); @@ -135,8 +143,10 @@ describe('ECDSA', function() { it('should calculate the correct public key for this signature with low s', function() { ecdsa.k = new BN('114860389168127852803919605627759231199925249596762615988727970217268189974335', 10); - ecdsa.sig = Signature.fromString('3045022100ec3cfe0e335791ad278b4ec8eac93d0347' + - 'a97877bb1d54d35d189e225c15f6650220278cf15b05ce47fb37d2233802899d94c774d5480bba9f0f2d996baa13370c43'); + ecdsa.sig = Signature.fromString( + '3045022100ec3cfe0e335791ad278b4ec8eac93d0347' + + 'a97877bb1d54d35d189e225c15f6650220278cf15b05ce47fb37d2233802899d94c774d5480bba9f0f2d996baa13370c43', + ); ecdsa.sig.i = 0; var pubkey = ecdsa.toPublicKey(); pubkey.point.eq(ecdsa.pubkey.point).should.equal(true); @@ -145,17 +155,17 @@ describe('ECDSA', function() { it('should calculate the correct public key for this signature with high s', function() { ecdsa.k = new BN('114860389168127852803919605627759231199925249596762615988727970217268189974335', 10); ecdsa.sign(); - ecdsa.sig = Signature.fromString('3046022100ec3cfe0e335791ad278b4ec8eac93d0347' + - 'a97877bb1d54d35d189e225c15f665022100d8730ea4fa31b804c82ddcc7fd766269f33a079ea38e012c9238f2e2bcff34fe'); + ecdsa.sig = Signature.fromString( + '3046022100ec3cfe0e335791ad278b4ec8eac93d0347' + + 'a97877bb1d54d35d189e225c15f665022100d8730ea4fa31b804c82ddcc7fd766269f33a079ea38e012c9238f2e2bcff34fe', + ); ecdsa.sig.i = 1; var pubkey = ecdsa.toPublicKey(); pubkey.point.eq(ecdsa.pubkey.point).should.equal(true); }); - }); describe('#sigError', function() { - it('should return an error if the hash is invalid', function() { var ecdsa = new ECDSA(); ecdsa.sigError().should.equal('hashbuf must be a 32 byte buffer'); @@ -164,8 +174,13 @@ describe('ECDSA', function() { it('should return an error if r, s are invalid', function() { var ecdsa = new ECDSA(); ecdsa.hashbuf = Hash.sha256(new Buffer('test')); - var pk = Pubkey.fromDER(new Buffer('041ff0fe0f7b15ffaa85ff9f4744d539139c252a49' + - '710fb053bb9f2b933173ff9a7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341', 'hex')); + var pk = Pubkey.fromDER( + new Buffer( + '041ff0fe0f7b15ffaa85ff9f4744d539139c252a49' + + '710fb053bb9f2b933173ff9a7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341', + 'hex', + ), + ); ecdsa.pubkey = pk; ecdsa.sig = new Signature(); ecdsa.sig.r = new BN(0); @@ -174,16 +189,16 @@ describe('ECDSA', function() { }); it('should return an error if the signature is incorrect', function() { - ecdsa.sig = Signature.fromString('3046022100e9915e6236695f093a4128ac2a956c40' + - 'ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827'); + ecdsa.sig = Signature.fromString( + '3046022100e9915e6236695f093a4128ac2a956c40' + + 'ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827', + ); ecdsa.sig.r = ecdsa.sig.r.add(new BN(1)); ecdsa.sigError().should.equal('Invalid signature'); }); - }); describe('#sign', function() { - it('should create a valid signature', function() { ecdsa.randomK(); ecdsa.sign(); @@ -191,9 +206,9 @@ describe('ECDSA', function() { }); it('should should throw an error if hashbuf is not 32 bytes', function() { - var ecdsa2 = ECDSA().set({ + var ecdsa2 = new ECDSA().set({ hashbuf: ecdsa.hashbuf.slice(0, 31), - privkey: ecdsa.privkey + privkey: ecdsa.privkey, }); ecdsa2.randomK(); ecdsa2.sign.bind(ecdsa2).should.throw('hashbuf must be a 32 byte buffer'); @@ -214,13 +229,16 @@ describe('ECDSA', function() { it('should generate right K', function() { var msg1 = new Buffer('52204d20fd0131ae1afd173fd80a3a746d2dcc0cddced8c9dc3d61cc7ab6e966', 'hex'); - var msg2 = [].reverse.call(new Buffer(msg1)) + var msg2 = [].reverse.call(new Buffer(msg1)); var pk = new Buffer('16f243e962c59e71e54189e67e66cf2440a1334514c09c00ddcc21632bac9808', 'hex'); - var signature1 = ECDSA.sign(msg1, Privkey.fromBuffer(pk)).toBuffer().toString('hex'); - var signature2 = ECDSA.sign(msg2, Privkey.fromBuffer(pk), 'little').toBuffer().toString('hex'); + var signature1 = ECDSA.sign(msg1, Privkey.fromBuffer(pk)) + .toBuffer() + .toString('hex'); + var signature2 = ECDSA.sign(msg2, Privkey.fromBuffer(pk), 'little') + .toBuffer() + .toString('hex'); signature1.should.equal(signature2); }); - }); describe('#toString', function() { @@ -239,7 +257,7 @@ describe('ECDSA', function() { it('should produce a signature, and be different when called twice', function() { ecdsa.signRandomK(); should.exist(ecdsa.sig); - var ecdsa2 = ECDSA(ecdsa); + var ecdsa2 = new ECDSA(ecdsa); ecdsa2.signRandomK(); ecdsa.sig.toString().should.not.equal(ecdsa2.sig.toString()); }); @@ -247,8 +265,10 @@ describe('ECDSA', function() { describe('#verify', function() { it('should verify a signature that was just signed', function() { - ecdsa.sig = Signature.fromString('3046022100e9915e6236695f093a4128ac2a956c' + - '40ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827'); + ecdsa.sig = Signature.fromString( + '3046022100e9915e6236695f093a4128ac2a956c' + + '40ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827', + ); ecdsa.verify().verified.should.equal(true); }); it('should verify this known good signature', function() { @@ -272,20 +292,19 @@ describe('ECDSA', function() { }); describe('vectors', function() { - vectors.valid.forEach(function(obj, i) { it('should validate valid vector ' + i, function() { - var ecdsa = ECDSA().set({ + var ecdsa = new ECDSA().set({ privkey: new Privkey(BN.fromBuffer(new Buffer(obj.d, 'hex'))), k: BN.fromBuffer(new Buffer(obj.k, 'hex')), hashbuf: Hash.sha256(new Buffer(obj.message)), sig: new Signature().set({ r: new BN(obj.signature.r), s: new BN(obj.signature.s), - i: obj.i - }) + i: obj.i, + }), }); - var ecdsa2 = ECDSA(ecdsa); + var ecdsa2 = new ECDSA(ecdsa); ecdsa2.k = undefined; ecdsa2.sign(); ecdsa2.calci(); @@ -298,10 +317,10 @@ describe('ECDSA', function() { vectors.invalid.sigError.forEach(function(obj, i) { it('should validate invalid.sigError vector ' + i + ': ' + obj.description, function() { - var ecdsa = ECDSA().set({ + var ecdsa = new ECDSA().set({ pubkey: Pubkey.fromPoint(point.fromX(true, 1)), sig: new Signature(new BN(obj.signature.r), new BN(obj.signature.s)), - hashbuf: Hash.sha256(new Buffer(obj.message)) + hashbuf: Hash.sha256(new Buffer(obj.message)), }); ecdsa.sigError().should.equal(obj.exception); }); @@ -311,16 +330,24 @@ describe('ECDSA', function() { it('should validate deterministicK vector ' + i, function() { var hashbuf = Hash.sha256(new Buffer(obj.message)); var privkey = Privkey(BN.fromBuffer(new Buffer(obj.privkey, 'hex')), 'mainnet'); - var ecdsa = ECDSA({ + var ecdsa = new ECDSA({ privkey: privkey, - hashbuf: hashbuf + hashbuf: hashbuf, }); - ecdsa.deterministicK(0).k.toString('hex').should.equal(obj.k_bad00); - ecdsa.deterministicK(1).k.toString('hex').should.equal(obj.k_bad01); - ecdsa.deterministicK(15).k.toString('hex').should.equal(obj.k_bad15); + ecdsa + .deterministicK(0) + .k.toString('hex') + .should.equal(obj.k_bad00); + ecdsa + .deterministicK(1) + .k.toString('hex') + .should.equal(obj.k_bad01); + ecdsa + .deterministicK(15) + .k.toString('hex') + .should.equal(obj.k_bad15); }); }); - }); }); });