diff --git a/index.js b/index.js index 13e446a6d..4da2a9e40 100644 --- a/index.js +++ b/index.js @@ -46,6 +46,7 @@ bitcore.PaymentProtocol = require('./lib/paymentprotocol'); bitcore.PrivateKey = require('./lib/privatekey'); bitcore.PublicKey = require('./lib/publickey'); bitcore.Script = require('./lib/script'); +bitcore.ScriptInterpreter = require('./lib/script_interpreter'); bitcore.Transaction = require('./lib/transaction'); bitcore.URI = require('./lib/uri'); bitcore.Unit = require('./lib/unit'); diff --git a/lib/crypto/bn.js b/lib/crypto/bn.js index 1eb8c2aba..67881868b 100644 --- a/lib/crypto/bn.js +++ b/lib/crypto/bn.js @@ -140,7 +140,7 @@ BN.prototype.toSM = function(opts) { // 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) { +BN.prototype.fromScriptNumBuffer = function(buf, fRequireMinimal) { var nMaxNumSize = 4; if (buf.length > nMaxNumSize) throw new Error('script number overflow'); @@ -169,7 +169,7 @@ BN.prototype.fromCScriptNumBuffer = function(buf, fRequireMinimal) { // 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) { +BN.prototype.toScriptNumBuffer = function(buf) { return this.toSM({endian: 'little'}); }; diff --git a/lib/crypto/ecdsa.js b/lib/crypto/ecdsa.js index 78a62c19b..2b1081e8b 100644 --- a/lib/crypto/ecdsa.js +++ b/lib/crypto/ecdsa.js @@ -1,7 +1,6 @@ 'use strict'; var BN = require('./bn'); -var BufferReader = require('../encoding/bufferreader'); var Point = require('./point'); var Signature = require('./signature'); var PublicKey = require('../publickey'); @@ -66,7 +65,7 @@ ECDSA.prototype.fromString = function(str) { this.privkey = PrivateKey.fromString(obj.privkey); } if (obj.sig) { - this.sig = Signature().fromString(obj.sig); + this.sig = Signature.fromString(obj.sig); } if (obj.k) { this.k = BN(obj.k, 10); diff --git a/lib/crypto/hash.js b/lib/crypto/hash.js index 7ca569247..0661a46df 100644 --- a/lib/crypto/hash.js +++ b/lib/crypto/hash.js @@ -2,44 +2,44 @@ var hashjs = require('hash.js'); var sha512 = require('sha512'); +var crypto = require('crypto'); +var BufferUtil = require('../util/buffer'); +var $ = require('../util/preconditions'); var Hash = module.exports; +Hash.sha1 = function(buf) { + $.checkArgument(BufferUtil.isBuffer(buf)); + return crypto.createHash('sha1').update(buf).digest(); +}; + +Hash.sha1.blocksize = 512; + Hash.sha256 = function(buf) { - if (!Buffer.isBuffer(buf)) - throw new Error('sha256 hash must be of a buffer'); - var hash = (new hashjs.sha256()).update(buf).digest(); - return new Buffer(hash); + $.checkArgument(BufferUtil.isBuffer(buf)); + return crypto.createHash('sha256').update(buf).digest(); }; Hash.sha256.blocksize = 512; Hash.sha256sha256 = function(buf) { - try { - return Hash.sha256(Hash.sha256(buf)); - } catch (e) { - throw new Error('sha256sha256 hash must be of a buffer'); - } + $.checkArgument(BufferUtil.isBuffer(buf)); + return Hash.sha256(Hash.sha256(buf)); }; Hash.ripemd160 = function(buf) { - if (!Buffer.isBuffer(buf)) - throw new Error('ripemd160 hash must be of a buffer'); + $.checkArgument(BufferUtil.isBuffer(buf)); var hash = (new hashjs.ripemd160()).update(buf).digest(); return new Buffer(hash); }; Hash.sha256ripemd160 = function(buf) { - try { - return Hash.ripemd160(Hash.sha256(buf)); - } catch (e) { - throw new Error('sha256ripemd160 hash must be of a buffer'); - } + $.checkArgument(BufferUtil.isBuffer(buf)); + return Hash.ripemd160(Hash.sha256(buf)); }; Hash.sha512 = function(buf) { - if (!Buffer.isBuffer(buf)) - throw new Error('sha512 hash must be of a buffer'); + $.checkArgument(BufferUtil.isBuffer(buf)); var hash = sha512(buf); return new Buffer(hash); }; @@ -47,19 +47,17 @@ Hash.sha512 = function(buf) { Hash.sha512.blocksize = 1024; Hash.hmac = function(hashf, data, key) { - if (!Buffer.isBuffer(data) || !Buffer.isBuffer(key)) - throw new Error('data and key must be buffers'); - //http://en.wikipedia.org/wiki/Hash-based_message_authentication_code //http://tools.ietf.org/html/rfc4868#section-2 - if (!hashf.blocksize) - throw new Error('Blocksize for hash function unknown'); + $.checkArgument(BufferUtil.isBuffer(data)); + $.checkArgument(BufferUtil.isBuffer(key)); + $.checkArgument(hashf.blocksize); - var blocksize = hashf.blocksize/8; - - if (key.length > blocksize) + var blocksize = hashf.blocksize / 8; + + if (key.length > blocksize) { key = hashf(key); - else if (key < blocksize) { + } else if (key < blocksize) { var fill = new Buffer(blocksize); fill.fill(0); key.copy(fill); diff --git a/lib/crypto/signature.js b/lib/crypto/signature.js index e18267e8f..48cb923cc 100644 --- a/lib/crypto/signature.js +++ b/lib/crypto/signature.js @@ -10,8 +10,7 @@ var Signature = function Signature(r, s) { r: r, s: s }); - } - else if (r) { + } else if (r) { var obj = r; this.set(obj); } @@ -25,88 +24,114 @@ Signature.prototype.set = function(obj) { return this; }; -Signature.prototype.fromCompact = function(buf) { +Signature.fromCompact = function(buf) { + var sig = new Signature(); var compressed = true; var i = buf.slice(0, 1)[0] - 27 - 4; //TODO: handle uncompressed pubkeys - /* - if (i < 0) { - compressed = false; - i = i + 4; - } - */ var b2 = buf.slice(1, 33); var b3 = buf.slice(33, 65); - if (!(i === 0 || i === 1 || i === 2 || i === 3)) + if (!(i === 0 || i === 1 || i === 2 || i === 3)) { throw new Error('i must be 0, 1, 2, or 3'); - if (b2.length !== 32) + } + if (b2.length !== 32) { throw new Error('r must be 32 bytes'); - if (b3.length !== 32) + } + if (b3.length !== 32) { throw new Error('s must be 32 bytes'); + } - this.compressed = compressed; - this.i = i; - this.r = BN().fromBuffer(b2); - this.s = BN().fromBuffer(b3); + sig.compressed = compressed; + sig.i = i; + sig.r = BN().fromBuffer(b2); + sig.s = BN().fromBuffer(b3); - return this; + return sig; }; -Signature.prototype.fromDER = function(buf) { - var obj = Signature.parseDER(buf); - this.r = obj.r; - this.s = obj.s; +Signature.fromDER = function(buf, strict) { + var obj = Signature.parseDER(buf, strict); + var sig = new Signature(); - return this; + sig.r = obj.r; + sig.s = obj.s; + + return sig; }; -Signature.prototype.fromString = function(str) { +// The format used in a tx +Signature.fromTxFormat = function(buf) { + var nhashtype = buf.readUInt8(buf.length - 1); + var derbuf = buf.slice(0, buf.length - 1); + var sig = new Signature.fromDER(derbuf, false); + sig.nhashtype = nhashtype; + return sig; +}; + +Signature.fromString = function(str) { var buf = new Buffer(str, 'hex'); - this.fromDER(buf); - - return this; + return Signature.fromDER(buf); }; -Signature.parseDER = function(buf) { - if (!Buffer.isBuffer(buf)) + +/** + * In order to mimic the non-strict DER encoding of OpenSSL, set strict = false. + */ +Signature.parseDER = function(buf, strict) { + if (typeof strict === 'undefined') { + strict = true; + } + + if (!Buffer.isBuffer(buf)) { throw new Error('DER formatted signature should be a buffer'); + } var header = buf[0]; - if (header !== 0x30) + if (header !== 0x30) { throw new Error('Header byte should be 0x30'); + } var length = buf[1]; - if (length !== buf.slice(2).length) + var buflength = buf.slice(2).length; + if (strict && length !== buflength) { throw new Error('Length byte should length of what follows'); + } else { + length = length < buflength ? length : buflength; + } var rheader = buf[2 + 0]; - if (rheader !== 0x02) + if (rheader !== 0x02) { throw new Error('Integer byte for r should be 0x02'); + } var rlength = buf[2 + 1]; var rbuf = buf.slice(2 + 2, 2 + 2 + rlength); var r = BN().fromBuffer(rbuf); var rneg = buf[2 + 1 + 1] === 0x00 ? true : false; - if (rlength !== rbuf.length) + if (rlength !== rbuf.length) { throw new Error('Length of r incorrect'); + } var sheader = buf[2 + 2 + rlength + 0]; - if (sheader !== 0x02) + if (sheader !== 0x02) { throw new Error('Integer byte for s should be 0x02'); + } var slength = buf[2 + 2 + rlength + 1]; var sbuf = buf.slice(2 + 2 + rlength + 2, 2 + 2 + rlength + 2 + slength); var s = BN().fromBuffer(sbuf); var sneg = buf[2 + 2 + rlength + 2 + 2] === 0x00 ? true : false; - if (slength !== sbuf.length) + if (slength !== sbuf.length) { throw new Error('Length of s incorrect'); + } var sumlength = 2 + 2 + rlength + 2 + slength; - if (length !== sumlength - 2) + if (length !== sumlength - 2) { throw new Error('Length of signature incorrect'); + } var obj = { header: header, @@ -126,19 +151,25 @@ Signature.parseDER = function(buf) { 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)) + if (!(i === 0 || i === 1 || i === 2 || i === 3)) { throw new Error('i must be equal to 0, 1, 2, or 3'); - + } + var val = i + 27 + 4; if (compressed === false) val = val - 4; var b1 = new Buffer([val]); - var b2 = this.r.toBuffer({size: 32}); - var b3 = this.s.toBuffer({size: 32}); + var b2 = this.r.toBuffer({ + size: 32 + }); + var b3 = this.s.toBuffer({ + size: 32 + }); return Buffer.concat([b1, b2, b3]); }; @@ -168,6 +199,115 @@ Signature.prototype.toString = function() { 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; + } + var nLenR = buf[3]; + if (5 + nLenR >= buf.length) { + // Non-canonical signature: S length misplaced + return false; + } + var nLenS = buf[5 + nLenR]; + if ((nLenR + nLenS + 7) !== buf.length) { + // Non-canonical signature: R+S length mismatch + return false; + } + + var 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; + } + + var 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; +}; + +/** + * 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(1) || + this.s.gt(BN('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0'))) { + return false; + } + 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 (this.nhashtype < Signature.SIGHASH_ALL || this.nhashtype > Signature.SIGHASH_SINGLE) { + return false; + } + return true; +}; + +Signature.prototype.toTxFormat = function() { + var derbuf = this.toDER(); + var buf = new Buffer(1); + buf.writeUInt8(this.nhashtype, 0); + return Buffer.concat([derbuf, buf]); +}; + Signature.SIGHASH_ALL = 0x01; Signature.SIGHASH_NONE = 0x02; Signature.SIGHASH_SINGLE = 0x03; diff --git a/lib/paymentprotocol/common.js b/lib/paymentprotocol/common.js index e52a93ab2..d62966549 100644 --- a/lib/paymentprotocol/common.js +++ b/lib/paymentprotocol/common.js @@ -388,7 +388,7 @@ PaymentProtocol.prototype.sinVerify = function() { var buf = this.serializeForSig(); var hash = magicHash(buf); var publicKey = PublicKey.fromBuffer(pubkey); - var signature = new Signature().fromString(sig); + var signature = new Signature.fromString(sig); var verified = ECDSA.verify(hash, signature, publicKey); return verified; }; diff --git a/lib/publickey.js b/lib/publickey.js index 16821a4ba..25bc52d01 100644 --- a/lib/publickey.js +++ b/lib/publickey.js @@ -1,11 +1,12 @@ 'use strict'; -var _ = require('lodash'); var Address = require('./address'); var BN = require('./crypto/bn'); var Point = require('./crypto/point'); var JSUtil = require('./util/js'); var Network = require('./networks'); +var _ = require('lodash'); +var $ = require('./util/preconditions'); /** * Instantiate a PublicKey from a 'PrivateKey', 'Point', 'string', 'Buffer'. @@ -33,9 +34,9 @@ var PublicKey = function PublicKey(data, extra) { if (!(this instanceof PublicKey)) { return new PublicKey(data, extra); } - if (!data) { - throw new TypeError('First argument is required, please include public key data.'); - } + + $.checkArgument(data, new TypeError('First argument is required, please include public key data.')); + if (data instanceof PublicKey) { // Return copy, but as it's an immutable object, return same argument return data; @@ -92,8 +93,8 @@ var PublicKey = function PublicKey(data, extra) { * @private */ PublicKey._isPrivateKey = function(param) { - return param && param.constructor && param.constructor.name - && param.constructor.name === 'PrivateKey'; + var PrivateKey = require('./privatekey'); + return param instanceof PrivateKey; }; /** @@ -126,10 +127,9 @@ PublicKey._isJSON = function(json) { * @private */ PublicKey._transformPrivateKey = function(privkey) { + $.checkArgument(PublicKey._isPrivateKey(privkey), + new TypeError('Must be an instance of PrivateKey')); var info = {}; - if (!PublicKey._isPrivateKey(privkey)) { - throw new TypeError('Must be an instance of PrivateKey'); - } info.point = Point.getG().mul(privkey.bn); info.compressed = privkey.compressed; info.network = privkey.network; @@ -140,21 +140,22 @@ PublicKey._transformPrivateKey = function(privkey) { * Internal function to transform DER into a public key point * * @param {Buffer} buf - An hex encoded buffer + * @param {bool} [strict] - if set to false, will loosen some conditions * @returns {Object} An object with keys: point and compressed * @private */ -PublicKey._transformDER = function(buf){ +PublicKey._transformDER = function(buf, strict) { + $.checkArgument(PublicKey._isBuffer(buf), new TypeError('Must be a hex buffer of DER encoded public key')); var info = {}; - if (!PublicKey._isBuffer(buf)) { - throw new TypeError('Must be a hex buffer of DER encoded public key'); - } + + strict = _.isUndefined(strict) ? true : strict; var x; var y; var xbuf; var ybuf; - if (buf[0] === 0x04) { + if (buf[0] === 0x04 || (!strict && (buf[0] === 0x06 || buf[0] === 0x07))) { xbuf = buf.slice(1, 33); ybuf = buf.slice(33, 65); if (xbuf.length !== 32 || ybuf.length !== 32 || buf.length !== 65) { @@ -169,7 +170,7 @@ PublicKey._transformDER = function(buf){ x = BN(xbuf); info = PublicKey._transformX(true, x); info.compressed = true; - } else if (buf[0] == 0x02) { + } else if (buf[0] === 0x02) { xbuf = buf.slice(1); x = BN(xbuf); info = PublicKey._transformX(false, x); @@ -188,11 +189,10 @@ PublicKey._transformDER = function(buf){ * @returns {Object} An object with keys: point and compressed * @private */ -PublicKey._transformX = function(odd, x){ +PublicKey._transformX = function(odd, x) { + $.checkArgument(typeof odd === 'boolean', + new TypeError('Must specify whether y is odd or not (true or false)')); var info = {}; - if (typeof odd !== 'boolean') { - throw new TypeError('Must specify whether y is odd or not (true or false)'); - } info.point = Point.fromX(odd, x); return info; }; @@ -204,10 +204,8 @@ PublicKey._transformX = function(odd, x){ * @returns {PublicKey} A new valid instance of PublicKey */ PublicKey.fromJSON = function(json) { - if (!PublicKey._isJSON(json)) { - throw new TypeError('Must be a valid JSON string or plain object'); - } - + $.checkArgument(PublicKey._isJSON(json), + new TypeError('Must be a valid JSON string or plain object')); return new PublicKey(json); }; @@ -225,7 +223,9 @@ PublicKey._transformJSON = function(json) { var x = BN(json.x, 'hex'); var y = BN(json.y, 'hex'); var point = new Point(x, y); - return new PublicKey(point, {compressed: json.compressed}); + return new PublicKey(point, { + compressed: json.compressed + }); }; /** @@ -235,25 +235,27 @@ PublicKey._transformJSON = function(json) { * @returns {PublicKey} A new valid instance of PublicKey */ PublicKey.fromPrivateKey = function(privkey) { - if (!PublicKey._isPrivateKey(privkey)) { - throw new TypeError('Must be an instance of PrivateKey'); - } + $.checkArgument(PublicKey._isPrivateKey(privkey), new TypeError('Must be an instance of PrivateKey')); var info = PublicKey._transformPrivateKey(privkey); - return new PublicKey(info.point, {compressed: info.compressed, network: info.network}); + return new PublicKey(info.point, { + compressed: info.compressed, + network: info.network + }); }; /** * Instantiate a PublicKey from a Buffer - * - * @param {Buffer} buf - A DER buffer + * @param {Buffer} buf - A DER hex buffer + * @param {bool} [strict] - if set to false, will loosen some conditions * @returns {PublicKey} A new valid instance of PublicKey */ -PublicKey.fromDER = PublicKey.fromBuffer = function(buf) { - if (!PublicKey._isBuffer(buf)) { - throw new TypeError('Must be a hex buffer of DER encoded public key'); - } - var info = PublicKey._transformDER(buf); - return new PublicKey(info.point, {compressed: info.compressed}); +PublicKey.fromDER = PublicKey.fromBuffer = function(buf, strict) { + $.checkArgument(PublicKey._isBuffer(buf), + new TypeError('Must be a hex buffer of DER encoded public key')); + var info = PublicKey._transformDER(buf, strict); + return new PublicKey(info.point, { + compressed: info.compressed + }); }; /** @@ -263,11 +265,12 @@ PublicKey.fromDER = PublicKey.fromBuffer = function(buf) { * @param {boolean=true} compressed - whether to store this public key as compressed format * @returns {PublicKey} A new valid instance of PublicKey */ -PublicKey.fromPoint = function(point, compressed){ - if (!(point instanceof Point)) { - throw new TypeError('First argument must be an instance of Point.'); - } - return new PublicKey(point, {compressed: compressed}); +PublicKey.fromPoint = function(point, compressed) { + $.checkArgument(point instanceof Point, + new TypeError('First argument must be an instance of Point.')); + return new PublicKey(point, { + compressed: compressed + }); }; /** @@ -280,7 +283,9 @@ PublicKey.fromPoint = function(point, compressed){ PublicKey.fromString = function(str, encoding) { var buf = new Buffer(str, encoding || 'hex'); var info = PublicKey._transformDER(buf); - return new PublicKey(info.point, {compressed: info.compressed}); + return new PublicKey(info.point, { + compressed: info.compressed + }); }; /** @@ -292,7 +297,9 @@ PublicKey.fromString = function(str, encoding) { */ PublicKey.fromX = function(odd, x) { var info = PublicKey._transformX(odd, x); - return new PublicKey(info.point, {compressed: info.compressed}); + return new PublicKey(info.point, { + compressed: info.compressed + }); }; /** @@ -334,7 +341,7 @@ PublicKey.prototype.toObject = function toObject() { }; }; -PublicKey.prototype.toJSON = function toJSON(){ +PublicKey.prototype.toJSON = function toJSON() { return JSON.stringify(this.toObject()); }; @@ -347,8 +354,12 @@ PublicKey.prototype.toBuffer = PublicKey.prototype.toDER = function() { var x = this.point.getX(); var y = this.point.getY(); - var xbuf = x.toBuffer({size: 32}); - var ybuf = y.toBuffer({size: 32}); + var xbuf = x.toBuffer({ + size: 32 + }); + var ybuf = y.toBuffer({ + size: 32 + }); var prefix; if (!this.compressed) { @@ -395,4 +406,5 @@ PublicKey.prototype.inspect = function() { (this.network ? this.network.name : '') + '>'; }; + module.exports = PublicKey; diff --git a/lib/script.js b/lib/script.js index de9e57803..073be1d3e 100644 --- a/lib/script.js +++ b/lib/script.js @@ -127,7 +127,7 @@ Script.prototype.toBuffer = function() { }; Script.fromString = function(str) { - if (jsUtil.isHexa(str)) { + if (jsUtil.isHexa(str) || str.length === 0) { return new Script(new buffer.Buffer(str, 'hex')); } var script = new Script(); @@ -140,7 +140,7 @@ Script.fromString = function(str) { var opcode = Opcode(token); var opcodenum = opcode.toNumber(); - if (typeof opcodenum === 'undefined') { + if (_.isUndefined(opcodenum)) { opcodenum = parseInt(token); if (opcodenum > 0 && opcodenum < Opcode.OP_PUSHDATA1) { script.chunks.push({ @@ -184,7 +184,11 @@ Script.prototype.toString = function() { if (typeof Opcode.reverseMap[opcodenum] !== 'undefined') { str = str + ' ' + Opcode(opcodenum).toString(); } else { - str = str + ' ' + '0x' + opcodenum.toString(16); + var numstr = opcodenum.toString(16); + if (numstr.length % 2 !== 0) { + numstr = '0' + numstr; + } + str = str + ' ' + '0x' + numstr; } } else { if (opcodenum === Opcode.OP_PUSHDATA1 || @@ -193,7 +197,9 @@ Script.prototype.toString = function() { str = str + ' ' + Opcode(opcodenum).toString(); } str = str + ' ' + chunk.len; - str = str + ' ' + '0x' + chunk.buf.toString('hex'); + if (chunk.len > 0) { + str = str + ' ' + '0x' + chunk.buf.toString('hex'); + } } } @@ -258,11 +264,11 @@ Script.prototype.isPublicKeyIn = function() { * @returns true if this is a p2sh output script */ Script.prototype.isScriptHashOut = function() { - return this.chunks.length === 3 && - this.chunks[0].opcodenum === Opcode.OP_HASH160 && - this.chunks[1].buf && - this.chunks[1].buf.length === 20 && - this.chunks[2].opcodenum === Opcode.OP_EQUAL; + var buf = this.toBuffer(); + return (buf.length === 23 && + buf[0] === Opcode.OP_HASH160 && + buf[1] === 0x14 && + buf[buf.length - 1] === Opcode.OP_EQUAL); }; /** @@ -475,9 +481,7 @@ Script.prototype._addOpcode = function(opcode, prepend) { Script.prototype._addBuffer = function(buf, prepend) { var opcodenum; var len = buf.length; - if (len === 0) { - return; - } else if (len > 0 && len < Opcode.OP_PUSHDATA1) { + if (len >= 0 && len < Opcode.OP_PUSHDATA1) { opcodenum = len; } else if (len < Math.pow(2, 8)) { opcodenum = Opcode.OP_PUSHDATA1; @@ -522,7 +526,9 @@ Script.buildMultisigOut = function(pubkeys, m, opts) { opts = opts || {}; var s = new Script(); s.add(Opcode.smallInt(m)); - pubkeys = _.map(pubkeys, function(pubkey) { return PublicKey(pubkey); }); + pubkeys = _.map(pubkeys, function(pubkey) { + return PublicKey(pubkey); + }); var sorted = pubkeys; if (!opts.noSorting) { sorted = _.sortBy(pubkeys, function(pubkey) { @@ -548,7 +554,7 @@ Script.buildMultisigOut = function(pubkeys, m, opts) { * @param {boolean=false} opts.noSorting don't sort the given public keys before creating the script * @param {Script=} opts.cachedMultisig don't recalculate the redeemScript * - * @returns Script + * @returns Script */ Script.buildP2SHMultisigIn = function(pubkeys, threshold, signatures, opts) { opts = opts || {}; @@ -601,8 +607,10 @@ Script.buildDataOut = function(data) { data = new Buffer(data); } var s = new Script(); - s.add(Opcode.OP_RETURN) - .add(data); + s.add(Opcode.OP_RETURN); + if (!_.isUndefined(data)) { + s.add(data); + } return s; }; @@ -663,4 +671,61 @@ Script.fromAddress = function(address) { throw new errors.Script.UnrecognizedAddress(address); }; +/** + * Analagous to bitcoind's FindAndDelete. Find and delete equivalent chunks, + * typically used with push data chunks. Note that this will find and delete + * not just the same data, but the same data with the same push data op as + * produced by default. i.e., if a pushdata in a tx does not use the minimal + * pushdata op, then when you try to remove the data it is pushing, it will not + * be removed, because they do not use the same pushdata op. + */ +Script.prototype.findAndDelete = function(script) { + var buf = script.toBuffer(); + var hex = buf.toString('hex'); + for (var i = 0; i < this.chunks.length; i++) { + var script2 = Script({ + chunks: [this.chunks[i]] + }); + var buf2 = script2.toBuffer(); + var hex2 = buf2.toString('hex'); + if (hex === hex2) { + this.chunks.splice(i, 1); + } + } + return this; +}; + +/** + * @returns true if the chunk {i} is the smallest way to push that particular data. + * Comes from bitcoind's script interpreter CheckMinimalPush function + */ +Script.prototype.checkMinimalPush = function(i) { + var chunk = this.chunks[i]; + var buf = chunk.buf; + var opcodenum = chunk.opcodenum; + if (!buf) { + return true; + } + if (buf.length === 0) { + // Could have used OP_0. + return opcodenum === Opcode.OP_0; + } else if (buf.length === 1 && buf[0] >= 1 && buf[0] <= 16) { + // Could have used OP_1 .. OP_16. + return opcodenum === Opcode.OP_1 + (buf[0] - 1); + } else if (buf.length === 1 && buf[0] === 0x81) { + // Could have used OP_1NEGATE + return opcodenum === Opcode.OP_1NEGATE; + } else if (buf.length <= 75) { + // Could have used a direct push (opcode indicating number of bytes pushed + those bytes). + return opcodenum === buf.length; + } else if (buf.length <= 255) { + // Could have used OP_PUSHDATA. + return opcodenum === Opcode.OP_PUSHDATA1; + } else if (buf.length <= 65535) { + // Could have used OP_PUSHDATA2. + return opcodenum === Opcode.OP_PUSHDATA2; + } + return true; +}; + module.exports = Script; diff --git a/lib/script_interpreter.js b/lib/script_interpreter.js new file mode 100644 index 000000000..324477073 --- /dev/null +++ b/lib/script_interpreter.js @@ -0,0 +1,1148 @@ +'use strict'; + +var _ = require('lodash'); + +var Script = require('./script'); +var Opcode = require('./opcode'); +var BN = require('./crypto/bn'); +var Hash = require('./crypto/hash'); +var BufferReader = require('./encoding/bufferreader'); +var BufferWriter = require('./encoding/bufferwriter'); +var Signature = require('./crypto/signature'); +var PublicKey = require('./publickey'); +var Transaction = require('./transaction'); + +/** + * Bitcoin transactions contain scripts. Each input has a script called the + * scriptSig, and each output has a script called the scriptPubkey. To validate + * an input, the input's script is concatenated with the referenced output script, + * and the result is executed. If at the end of execution the stack contains a + * "true" value, then the transaction is valid. + * + * The primary way to use this class is via the verify function. + * e.g., ScriptInterpreter().verify( ... ); + */ +var ScriptInterpreter = function ScriptInterpreter(obj) { + if (!(this instanceof ScriptInterpreter)) { + return new ScriptInterpreter(obj); + } + if (obj) { + this.initialize(); + this.set(obj); + } else { + this.initialize(); + } +}; + +module.exports = ScriptInterpreter; + +ScriptInterpreter.prototype.initialize = function(obj) { + this.stack = []; + this.altstack = []; + this.pc = 0; + this.pbegincodehash = 0; + this.nOpCount = 0; + this.vfExec = []; + this.errstr = ''; + this.flags = 0; +}; + +ScriptInterpreter.prototype.set = function(obj) { + this.script = obj.script || this.script; + this.tx = obj.tx || this.tx; + this.nin = typeof obj.nin !== 'undefined' ? obj.nin : this.nin; + this.stack = obj.stack || this.stack; + this.altstack = obj.altack || this.altstack; + this.pc = typeof obj.pc !== 'undefined' ? obj.pc : this.pc; + this.pbegincodehash = typeof obj.pbegincodehash !== 'undefined' ? obj.pbegincodehash : this.pbegincodehash; + this.nOpCount = typeof obj.nOpCount !== 'undefined' ? obj.nOpCount : this.nOpCount; + this.vfExec = obj.vfExec || this.vfExec; + this.errstr = obj.errstr || this.errstr; + this.flags = typeof obj.flags !== 'undefined' ? obj.flags : this.flags; +}; + +ScriptInterpreter.true = new Buffer([1]); +ScriptInterpreter.false = new Buffer([]); + +ScriptInterpreter.MAX_SCRIPT_ELEMENT_SIZE = 520; + +// flags taken from bitcoind +// bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 +ScriptInterpreter.SCRIPT_VERIFY_NONE = 0; + +// Evaluate P2SH subscripts (softfork safe, BIP16). +ScriptInterpreter.SCRIPT_VERIFY_P2SH = (1 << 0); + +// Passing a non-strict-DER signature or one with undefined hashtype to a checksig operation causes script failure. +// Passing a pubkey that is not (0x04 + 64 bytes) or (0x02 or 0x03 + 32 bytes) to checksig causes that pubkey to be +// skipped (not softfork safe: this flag can widen the validity of OP_CHECKSIG OP_NOT). +ScriptInterpreter.SCRIPT_VERIFY_STRICTENC = (1 << 1); + +// Passing a non-strict-DER signature to a checksig operation causes script failure (softfork safe, BIP62 rule 1) +ScriptInterpreter.SCRIPT_VERIFY_DERSIG = (1 << 2); + +// Passing a non-strict-DER signature or one with S > order/2 to a checksig operation causes script failure +// (softfork safe, BIP62 rule 5). +ScriptInterpreter.SCRIPT_VERIFY_LOW_S = (1 << 3); + +// verify dummy stack item consumed by CHECKMULTISIG is of zero-length (softfork safe, BIP62 rule 7). +ScriptInterpreter.SCRIPT_VERIFY_NULLDUMMY = (1 << 4); + +// Using a non-push operator in the scriptSig causes script failure (softfork safe, BIP62 rule 2). +ScriptInterpreter.SCRIPT_VERIFY_SIGPUSHONLY = (1 << 5); + +// Require minimal encodings for all push operations (OP_0... OP_16, OP_1NEGATE where possible, direct +// pushes up to 75 bytes, OP_PUSHDATA up to 255 bytes, OP_PUSHDATA2 for anything larger). Evaluating +// any other push causes the script to fail (BIP62 rule 3). +// In addition, whenever a stack element is interpreted as a number, it must be of minimal length (BIP62 rule 4). +// (softfork safe) +ScriptInterpreter.SCRIPT_VERIFY_MINIMALDATA = (1 << 6); + +// Discourage use of NOPs reserved for upgrades (NOP1-10) +// +// Provided so that nodes can avoid accepting or mining transactions +// containing executed NOP's whose meaning may change after a soft-fork, +// thus rendering the script invalid; with this flag set executing +// discouraged NOPs fails the script. This verification flag will never be +// a mandatory flag applied to scripts in a block. NOPs that are not +// executed, e.g. within an unexecuted IF ENDIF block, are *not* rejected. +ScriptInterpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 7); + +ScriptInterpreter.castToBool = function(buf) { + for (var i = 0; i < buf.length; i++) { + if (buf[i] !== 0) { + // can be negative zero + if (i === buf.length - 1 && buf[i] === 0x80) { + return false; + } + return true; + } + } + return false; +}; + +/** + * Translated from bitcoind's CheckSignatureEncoding + */ +ScriptInterpreter.prototype.checkSignatureEncoding = function(buf) { + var sig; + if ((this.flags & (ScriptInterpreter.SCRIPT_VERIFY_DERSIG | ScriptInterpreter.SCRIPT_VERIFY_LOW_S | ScriptInterpreter.SCRIPT_VERIFY_STRICTENC)) !== 0 && !Signature.isTxDER(buf)) { + this.errstr = 'SCRIPT_ERR_SIG_DER'; + return false; + } else if ((this.flags & ScriptInterpreter.SCRIPT_VERIFY_LOW_S) !== 0) { + sig = Signature().fromTxFormat(buf); + if (!sig.hasLowS()) { + this.errstr = 'SCRIPT_ERR_SIG_DER'; + return false; + } + } else if ((this.flags & ScriptInterpreter.SCRIPT_VERIFY_STRICTENC) !== 0) { + sig = Signature.fromTxFormat(buf); + if (!sig.hasDefinedHashtype()) { + this.errstr = 'SCRIPT_ERR_SIG_HASHTYPE'; + return false; + } + } + return true; +}; + +/** + * Translated from bitcoind's CheckPubKeyEncoding + */ +ScriptInterpreter.prototype.checkPubkeyEncoding = function(buf) { + if ((this.flags & ScriptInterpreter.SCRIPT_VERIFY_STRICTENC) !== 0 && !PublicKey.isValid(buf)) { + this.errstr = 'SCRIPT_ERR_PUBKEYTYPE'; + return false; + } + return true; +}; + +/** + * Based on bitcoind's EvalScript function, with the inner loop moved to + * ScriptInterpreter.prototype.step() + * bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 + */ +ScriptInterpreter.prototype.evaluate = function() { + if (this.script.toBuffer().length > 10000) { + this.errstr = 'SCRIPT_ERR_SCRIPT_SIZE'; + return false; + } + + try { + while (this.pc < this.script.chunks.length) { + var fSuccess = this.step(); + if (!fSuccess) { + return false; + } + } + + // Size limits + if (this.stack.length + this.altstack.length > 1000) { + this.errstr = 'SCRIPT_ERR_STACK_SIZE'; + return false; + } + } catch (e) { + this.errstr = 'SCRIPT_ERR_UNKNOWN_ERROR: ' + e; + return false; + } + + if (this.vfExec.length > 0) { + this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; + return false; + } + + return true; +}; + +/** + * Based on the inner loop of bitcoind's EvalScript function + * bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 + */ +ScriptInterpreter.prototype.step = function() { + + var fRequireMinimal = (this.flags & ScriptInterpreter.SCRIPT_VERIFY_MINIMALDATA) !== 0; + + //bool fExec = !count(vfExec.begin(), vfExec.end(), false); + var fExec = (this.vfExec.indexOf(false) === -1); + + + // Read instruction + var chunk = this.script.chunks[this.pc]; + this.pc++; + var opcodenum = chunk.opcodenum; + if (_.isUndefined(opcodenum)) { + this.errstr = 'SCRIPT_ERR_UNDEFINED_OPCODE'; + return false; + } + if (chunk.buf && chunk.buf.length > ScriptInterpreter.MAX_SCRIPT_ELEMENT_SIZE) { + this.errstr = 'SCRIPT_ERR_PUSH_SIZE'; + return false; + } + + // Note how Opcode.OP_RESERVED does not count towards the opcode limit. + if (opcodenum > Opcode.OP_16 && ++(this.nOpCount) > 201) { + this.errstr = 'SCRIPT_ERR_OP_COUNT'; + return false; + } + + + if (opcodenum === Opcode.OP_CAT || + opcodenum === Opcode.OP_SUBSTR || + opcodenum === Opcode.OP_LEFT || + opcodenum === Opcode.OP_RIGHT || + opcodenum === Opcode.OP_INVERT || + opcodenum === Opcode.OP_AND || + opcodenum === Opcode.OP_OR || + opcodenum === Opcode.OP_XOR || + opcodenum === Opcode.OP_2MUL || + opcodenum === Opcode.OP_2DIV || + opcodenum === Opcode.OP_MUL || + opcodenum === Opcode.OP_DIV || + opcodenum === Opcode.OP_MOD || + opcodenum === Opcode.OP_LSHIFT || + opcodenum === Opcode.OP_RSHIFT) { + this.errstr = 'SCRIPT_ERR_DISABLED_OPCODE'; + return false; + } + + if (fExec && 0 <= opcodenum && opcodenum <= Opcode.OP_PUSHDATA4) { + if (fRequireMinimal && !this.script.checkMinimalPush(this.pc - 1)) { + this.errstr = 'SCRIPT_ERR_MINIMALDATA'; + return false; + } + if (!chunk.buf) { + this.stack.push(ScriptInterpreter.false); + } else if (chunk.len !== chunk.buf.length) { + throw new Error('Length of push value not equal to length of data'); + } else { + this.stack.push(chunk.buf); + } + } else if (fExec || (Opcode.OP_IF <= opcodenum && opcodenum <= Opcode.OP_ENDIF)) { + switch (opcodenum) { + // Push value + case Opcode.OP_1NEGATE: + case Opcode.OP_1: + case Opcode.OP_2: + case Opcode.OP_3: + case Opcode.OP_4: + case Opcode.OP_5: + case Opcode.OP_6: + case Opcode.OP_7: + case Opcode.OP_8: + case Opcode.OP_9: + case Opcode.OP_10: + case Opcode.OP_11: + case Opcode.OP_12: + case Opcode.OP_13: + case Opcode.OP_14: + case Opcode.OP_15: + case Opcode.OP_16: + { + // ( -- value) + // ScriptNum bn((int)opcode - (int)(Opcode.OP_1 - 1)); + var n = opcodenum - (Opcode.OP_1 - 1); + var buf = BN(n).toScriptNumBuffer(); + this.stack.push(buf); + // The result of these opcodes should always be the minimal way to push the data + // they push, so no need for a CheckMinimalPush here. + } + break; + + + // + // Control + // + case Opcode.OP_NOP: + break; + + case Opcode.OP_NOP1: + case Opcode.OP_NOP2: + case Opcode.OP_NOP3: + case Opcode.OP_NOP4: + case Opcode.OP_NOP5: + case Opcode.OP_NOP6: + case Opcode.OP_NOP7: + case Opcode.OP_NOP8: + case Opcode.OP_NOP9: + case Opcode.OP_NOP10: + { + if (this.flags & ScriptInterpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { + this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS'; + return false; + } + } + break; + + case Opcode.OP_IF: + case Opcode.OP_NOTIF: + { + // if [statements] [else [statements]] endif + // bool fValue = false; + var fValue = false; + if (fExec) { + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; + return false; + } + var buf = this.stack.pop(); + fValue = ScriptInterpreter.castToBool(buf); + if (opcodenum === Opcode.OP_NOTIF) + fValue = !fValue; + } + this.vfExec.push(fValue); + } + break; + + case Opcode.OP_ELSE: + { + if (this.vfExec.length === 0) { + this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; + return false; + } + this.vfExec[this.vfExec.length - 1] = !this.vfExec[this.vfExec.length - 1]; + } + break; + + case Opcode.OP_ENDIF: + { + if (this.vfExec.length === 0) { + this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; + return false; + } + this.vfExec.pop(); + } + break; + + case Opcode.OP_VERIFY: + { + // (true -- ) or + // (false -- false) and return + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var buf = this.stack[this.stack.length - 1]; + var fValue = ScriptInterpreter.castToBool(buf); + if (fValue) + this.stack.pop(); + else { + this.errstr = 'SCRIPT_ERR_VERIFY'; + return false; + } + } + break; + + case Opcode.OP_RETURN: + { + this.errstr = 'SCRIPT_ERR_OP_RETURN'; + return false; + } + break; + + + // + // Stack ops + // + case Opcode.OP_TOALTSTACK: + { + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + this.altstack.push(this.stack.pop()); + } + break; + + case Opcode.OP_FROMALTSTACK: + { + if (this.altstack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_ALTSTACK_OPERATION'; + return false; + } + this.stack.push(this.altstack.pop()); + } + break; + + case Opcode.OP_2DROP: + { + // (x1 x2 -- ) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + this.stack.pop(); + this.stack.pop(); + } + break; + + case Opcode.OP_2DUP: + { + // (x1 x2 -- x1 x2 x1 x2) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var buf1 = this.stack[this.stack.length - 2]; + var buf2 = this.stack[this.stack.length - 1]; + this.stack.push(buf1); + this.stack.push(buf2); + } + break; + + case Opcode.OP_3DUP: + { + // (x1 x2 x3 -- x1 x2 x3 x1 x2 x3) + if (this.stack.length < 3) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var buf1 = this.stack[this.stack.length - 3]; + var buf2 = this.stack[this.stack.length - 2]; + var buf3 = this.stack[this.stack.length - 1]; + this.stack.push(buf1); + this.stack.push(buf2); + this.stack.push(buf3); + } + break; + + case Opcode.OP_2OVER: + { + // (x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2) + if (this.stack.length < 4) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var buf1 = this.stack[this.stack.length - 4]; + var buf2 = this.stack[this.stack.length - 3]; + this.stack.push(buf1); + this.stack.push(buf2); + } + break; + + case Opcode.OP_2ROT: + { + // (x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2) + if (this.stack.length < 6) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var spliced = this.stack.splice(this.stack.length - 6, 2); + this.stack.push(spliced[0]); + this.stack.push(spliced[1]); + } + break; + + case Opcode.OP_2SWAP: + { + // (x1 x2 x3 x4 -- x3 x4 x1 x2) + if (this.stack.length < 4) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var spliced = this.stack.splice(this.stack.length - 4, 2); + this.stack.push(spliced[0]); + this.stack.push(spliced[1]); + } + break; + + case Opcode.OP_IFDUP: + { + // (x - 0 | x x) + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var buf = this.stack[this.stack.length - 1]; + var fValue = ScriptInterpreter.castToBool(buf); + if (fValue) + this.stack.push(buf); + } + break; + + case Opcode.OP_DEPTH: + { + // -- stacksize + var buf = BN(this.stack.length).toScriptNumBuffer(); + this.stack.push(buf); + } + break; + + case Opcode.OP_DROP: + { + // (x -- ) + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + this.stack.pop(); + } + break; + + case Opcode.OP_DUP: + { + // (x -- x x) + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + this.stack.push(this.stack[this.stack.length - 1]); + } + break; + + case Opcode.OP_NIP: + { + // (x1 x2 -- x2) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + this.stack.splice(this.stack.length - 2, 1); + } + break; + + case Opcode.OP_OVER: + { + // (x1 x2 -- x1 x2 x1) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + this.stack.push(this.stack[this.stack.length - 2]); + } + break; + + case Opcode.OP_PICK: + case Opcode.OP_ROLL: + { + // (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn) + // (xn ... x2 x1 x0 n - ... x2 x1 x0 xn) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var buf = this.stack[this.stack.length - 1]; + var bn = BN().fromScriptNumBuffer(buf, fRequireMinimal); + var n = bn.toNumber(); + this.stack.pop(); + if (n < 0 || n >= this.stack.length) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var buf = this.stack[this.stack.length - n - 1]; + if (opcodenum === Opcode.OP_ROLL) + this.stack.splice(this.stack.length - n - 1, 1); + this.stack.push(buf); + } + break; + + case Opcode.OP_ROT: + { + // (x1 x2 x3 -- x2 x3 x1) + // x2 x1 x3 after first swap + // x2 x3 x1 after second swap + if (this.stack.length < 3) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var x1 = this.stack[this.stack.length - 3]; + var x2 = this.stack[this.stack.length - 2]; + var x3 = this.stack[this.stack.length - 1]; + this.stack[this.stack.length - 3] = x2; + this.stack[this.stack.length - 2] = x3; + this.stack[this.stack.length - 1] = x1; + } + break; + + case Opcode.OP_SWAP: + { + // (x1 x2 -- x2 x1) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var x1 = this.stack[this.stack.length - 2]; + var x2 = this.stack[this.stack.length - 1]; + this.stack[this.stack.length - 2] = x2; + this.stack[this.stack.length - 1] = x1; + } + break; + + case Opcode.OP_TUCK: + { + // (x1 x2 -- x2 x1 x2) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + this.stack.splice(this.stack.length - 2, 0, this.stack[this.stack.length - 1]); + } + break; + + + case Opcode.OP_SIZE: + { + // (in -- in size) + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var bn = BN(this.stack[this.stack.length - 1].length); + this.stack.push(bn.toScriptNumBuffer()); + } + break; + + + // + // Bitwise logic + // + case Opcode.OP_EQUAL: + case Opcode.OP_EQUALVERIFY: + //case Opcode.OP_NOTEQUAL: // use Opcode.OP_NUMNOTEQUAL + { + // (x1 x2 - bool) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var buf1 = this.stack[this.stack.length - 2]; + var buf2 = this.stack[this.stack.length - 1]; + var fEqual = buf1.toString('hex') === buf2.toString('hex'); + // Opcode.OP_NOTEQUAL is disabled because it would be too easy to say + // something like n != 1 and have some wiseguy pass in 1 with extra + // zero bytes after it (numerically, 0x01 == 0x0001 == 0x000001) + //if (opcode == Opcode.OP_NOTEQUAL) + // fEqual = !fEqual; + this.stack.pop(); + this.stack.pop(); + this.stack.push(fEqual ? ScriptInterpreter.true : ScriptInterpreter.false); + if (opcodenum === Opcode.OP_EQUALVERIFY) { + if (fEqual) + this.stack.pop(); + else { + this.errstr = 'SCRIPT_ERR_EQUALVERIFY'; + return false; + } + } + } + break; + + + // + // Numeric + // + case Opcode.OP_1ADD: + case Opcode.OP_1SUB: + case Opcode.OP_NEGATE: + case Opcode.OP_ABS: + case Opcode.OP_NOT: + case Opcode.OP_0NOTEQUAL: + { + // (in -- out) + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var buf = this.stack[this.stack.length - 1]; + var bn = BN().fromScriptNumBuffer(buf, fRequireMinimal); + switch (opcodenum) { + case Opcode.OP_1ADD: + bn = bn.add(1); + break; + case Opcode.OP_1SUB: + bn = bn.sub(1); + break; + case Opcode.OP_NEGATE: + bn = bn.neg(); + break; + case Opcode.OP_ABS: + if (bn.cmp(0) < 0) bn = bn.neg(); + break; + case Opcode.OP_NOT: + bn = BN((bn.cmp(0) === 0) + 0); + break; + case Opcode.OP_0NOTEQUAL: + bn = BN((bn.cmp(0) !== 0) + 0); + break; + //default: assert(!'invalid opcode'); break; // TODO: does this ever occur? + } + this.stack.pop(); + this.stack.push(bn.toScriptNumBuffer()); + } + break; + + case Opcode.OP_ADD: + case Opcode.OP_SUB: + case Opcode.OP_BOOLAND: + case Opcode.OP_BOOLOR: + case Opcode.OP_NUMEQUAL: + case Opcode.OP_NUMEQUALVERIFY: + case Opcode.OP_NUMNOTEQUAL: + case Opcode.OP_LESSTHAN: + case Opcode.OP_GREATERTHAN: + case Opcode.OP_LESSTHANOREQUAL: + case Opcode.OP_GREATERTHANOREQUAL: + case Opcode.OP_MIN: + case Opcode.OP_MAX: + { + // (x1 x2 -- out) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var bn1 = BN().fromScriptNumBuffer(this.stack[this.stack.length - 2], fRequireMinimal); + var bn2 = BN().fromScriptNumBuffer(this.stack[this.stack.length - 1], fRequireMinimal); + var bn = BN(0); + + switch (opcodenum) { + case Opcode.OP_ADD: + bn = bn1.add(bn2); + break; + + case Opcode.OP_SUB: + bn = bn1.sub(bn2); + break; + + // case Opcode.OP_BOOLAND: bn = (bn1 != bnZero && bn2 != bnZero); break; + case Opcode.OP_BOOLAND: + bn = BN(((bn1.cmp(0) !== 0) && (bn2.cmp(0) !== 0)) + 0); + break; + // case Opcode.OP_BOOLOR: bn = (bn1 != bnZero || bn2 != bnZero); break; + case Opcode.OP_BOOLOR: + bn = BN(((bn1.cmp(0) !== 0) || (bn2.cmp(0) !== 0)) + 0); + break; + // case Opcode.OP_NUMEQUAL: bn = (bn1 == bn2); break; + case Opcode.OP_NUMEQUAL: + bn = BN((bn1.cmp(bn2) === 0) + 0); + break; + // case Opcode.OP_NUMEQUALVERIFY: bn = (bn1 == bn2); break; + case Opcode.OP_NUMEQUALVERIFY: + bn = BN((bn1.cmp(bn2) === 0) + 0); + break; + // case Opcode.OP_NUMNOTEQUAL: bn = (bn1 != bn2); break; + case Opcode.OP_NUMNOTEQUAL: + bn = BN((bn1.cmp(bn2) !== 0) + 0); + break; + // case Opcode.OP_LESSTHAN: bn = (bn1 < bn2); break; + case Opcode.OP_LESSTHAN: + bn = BN((bn1.cmp(bn2) < 0) + 0); + break; + // case Opcode.OP_GREATERTHAN: bn = (bn1 > bn2); break; + case Opcode.OP_GREATERTHAN: + bn = BN((bn1.cmp(bn2) > 0) + 0); + break; + // case Opcode.OP_LESSTHANOREQUAL: bn = (bn1 <= bn2); break; + case Opcode.OP_LESSTHANOREQUAL: + bn = BN((bn1.cmp(bn2) <= 0) + 0); + break; + // case Opcode.OP_GREATERTHANOREQUAL: bn = (bn1 >= bn2); break; + case Opcode.OP_GREATERTHANOREQUAL: + bn = BN((bn1.cmp(bn2) >= 0) + 0); + break; + case Opcode.OP_MIN: + bn = (bn1.cmp(bn2) < 0 ? bn1 : bn2); + break; + case Opcode.OP_MAX: + bn = (bn1.cmp(bn2) > 0 ? bn1 : bn2); + break; + // default: assert(!'invalid opcode'); break; //TODO: does this ever occur? + } + this.stack.pop(); + this.stack.pop(); + this.stack.push(bn.toScriptNumBuffer()); + + if (opcodenum === Opcode.OP_NUMEQUALVERIFY) { + // if (CastToBool(stacktop(-1))) + if (ScriptInterpreter.castToBool(this.stack[this.stack.length - 1])) + this.stack.pop(); + else { + this.errstr = 'SCRIPT_ERR_NUMEQUALVERIFY'; + return false; + } + } + } + break; + + case Opcode.OP_WITHIN: + { + // (x min max -- out) + if (this.stack.length < 3) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var bn1 = BN().fromScriptNumBuffer(this.stack[this.stack.length - 3], fRequireMinimal); + var bn2 = BN().fromScriptNumBuffer(this.stack[this.stack.length - 2], fRequireMinimal); + var bn3 = BN().fromScriptNumBuffer(this.stack[this.stack.length - 1], fRequireMinimal); + //bool fValue = (bn2 <= bn1 && bn1 < bn3); + var fValue = (bn2.cmp(bn1) <= 0) && (bn1.cmp(bn3) < 0); + this.stack.pop(); + this.stack.pop(); + this.stack.pop(); + this.stack.push(fValue ? ScriptInterpreter.true : ScriptInterpreter.false); + } + break; + + + // + // Crypto + // + case Opcode.OP_RIPEMD160: + case Opcode.OP_SHA1: + case Opcode.OP_SHA256: + case Opcode.OP_HASH160: + case Opcode.OP_HASH256: + { + // (in -- hash) + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var buf = this.stack[this.stack.length - 1]; + //valtype vchHash((opcode == Opcode.OP_RIPEMD160 || opcode == Opcode.OP_SHA1 || opcode == Opcode.OP_HASH160) ? 20 : 32); + var bufHash; + if (opcodenum === Opcode.OP_RIPEMD160) + bufHash = Hash.ripemd160(buf); + else if (opcodenum === Opcode.OP_SHA1) + bufHash = Hash.sha1(buf); + else if (opcodenum === Opcode.OP_SHA256) + bufHash = Hash.sha256(buf); + else if (opcodenum === Opcode.OP_HASH160) + bufHash = Hash.sha256ripemd160(buf); + else if (opcodenum === Opcode.OP_HASH256) + bufHash = Hash.sha256sha256(buf); + this.stack.pop(); + this.stack.push(bufHash); + } + break; + + case Opcode.OP_CODESEPARATOR: + { + // Hash starts after the code separator + this.pbegincodehash = this.pc; + } + break; + + case Opcode.OP_CHECKSIG: + case Opcode.OP_CHECKSIGVERIFY: + { + // (sig pubkey -- bool) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + + var bufSig = this.stack[this.stack.length - 2]; + var bufPubkey = this.stack[this.stack.length - 1]; + + // Subset of script starting at the most recent codeseparator + // CScript scriptCode(pbegincodehash, pend); + var subscript = Script().set({ + chunks: this.script.chunks.slice(this.pbegincodehash) + }); + + // Drop the signature, since there's no way for a signature to sign itself + var tmpScript = Script().add(bufSig); + subscript.findAndDelete(tmpScript); + + if (!this.checkSignatureEncoding(bufSig) || !this.checkPubkeyEncoding(bufPubkey)) { + // serror is set + return false; + } + + var fSuccess; + try { + var sig = Signature.fromTxFormat(bufSig); + var pubkey = PublicKey.fromBuffer(bufPubkey, false); + fSuccess = this.tx.verify(sig, pubkey, this.nin, subscript); + } catch (e) { + //invalid sig or pubkey + fSuccess = false; + } + + this.stack.pop(); + this.stack.pop(); + // stack.push_back(fSuccess ? vchTrue : vchFalse); + this.stack.push(fSuccess ? ScriptInterpreter.true : ScriptInterpreter.false); + if (opcodenum === Opcode.OP_CHECKSIGVERIFY) { + if (fSuccess) { + this.stack.pop(); + } else { + this.errstr = 'SCRIPT_ERR_CHECKSIGVERIFY'; + return false; + } + } + } + break; + + case Opcode.OP_CHECKMULTISIG: + case Opcode.OP_CHECKMULTISIGVERIFY: + { + // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) + + var i = 1; + if (this.stack.length < i) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + + var nKeysCount = BN().fromScriptNumBuffer(this.stack[this.stack.length - i], fRequireMinimal).toNumber(); + if (nKeysCount < 0 || nKeysCount > 20) { + this.errstr = 'SCRIPT_ERR_PUBKEY_COUNT'; + return false; + } + this.nOpCount += nKeysCount; + if (this.nOpCount > 201) { + this.errstr = 'SCRIPT_ERR_OP_COUNT'; + return false; + } + // int ikey = ++i; + var ikey = ++i; + i += nKeysCount; + if (this.stack.length < i) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + + var nSigsCount = BN().fromScriptNumBuffer(this.stack[this.stack.length - i], fRequireMinimal).toNumber(); + if (nSigsCount < 0 || nSigsCount > nKeysCount) { + this.errstr = 'SCRIPT_ERR_SIG_COUNT'; + return false; + } + // int isig = ++i; + var isig = ++i; + i += nSigsCount; + if (this.stack.length < i) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + + // Subset of script starting at the most recent codeseparator + var subscript = Script().set({ + chunks: this.script.chunks.slice(this.pbegincodehash) + }); + + // Drop the signatures, since there's no way for a signature to sign itself + for (var k = 0; k < nSigsCount; k++) { + var bufSig = this.stack[this.stack.length - isig - k]; + subscript.findAndDelete(Script().add(bufSig)); + } + + var fSuccess = true; + while (fSuccess && nSigsCount > 0) { + // valtype& vchSig = stacktop(-isig); + var bufSig = this.stack[this.stack.length - isig]; + // valtype& vchPubKey = stacktop(-ikey); + var bufPubkey = this.stack[this.stack.length - ikey]; + + if (!this.checkSignatureEncoding(bufSig) || !this.checkPubkeyEncoding(bufPubkey)) { + // serror is set + return false; + } + + var fOk; + try { + var sig = Signature.fromTxFormat(bufSig); + var pubkey = PublicKey.fromBuffer(bufPubkey, false); + fOk = this.tx.verify(sig, pubkey, this.nin, subscript); + } catch (e) { + //invalid sig or pubkey + fOk = false; + } + + if (fOk) { + isig++; + nSigsCount--; + } + ikey++; + nKeysCount--; + + // If there are more signatures left than keys left, + // then too many signatures have failed + if (nSigsCount > nKeysCount) { + fSuccess = false; + } + } + + // Clean up stack of actual arguments + while (i-- > 1) { + this.stack.pop(); + } + + // A bug causes CHECKMULTISIG to consume one extra argument + // whose contents were not checked in any way. + // + // Unfortunately this is a potential source of mutability, + // so optionally verify it is exactly equal to zero prior + // to removing it from the stack. + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + if ((this.flags & ScriptInterpreter.SCRIPT_VERIFY_NULLDUMMY) && this.stack[this.stack.length - 1].length) { + this.errstr = 'SCRIPT_ERR_SIG_NULLDUMMY'; + return false; + } + this.stack.pop(); + + this.stack.push(fSuccess ? ScriptInterpreter.true : ScriptInterpreter.false); + + if (opcodenum === Opcode.OP_CHECKMULTISIGVERIFY) { + if (fSuccess) + this.stack.pop(); + else { + this.errstr = 'SCRIPT_ERR_CHECKMULTISIGVERIFY'; + return false; + } + } + } + break; + + default: + this.errstr = 'SCRIPT_ERR_BAD_OPCODE'; + return false; + } + } + + return true; +} + +/** + * Translated from bitcoind's VerifyScript + */ +ScriptInterpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags) { + if (_.isUndefined(tx)) { + tx = new Transaction(); + } + if (_.isUndefined(nin)) { + nin = 0; + } + this.set({ + script: scriptSig, + tx: tx, + nin: nin, + flags: flags + }); + + if ((flags & ScriptInterpreter.SCRIPT_VERIFY_SIGPUSHONLY) != 0 && !scriptSig.isPushOnly()) { + this.errstr = 'SCRIPT_ERR_SIG_PUSHONLY'; + return false; + } + + // evaluate scriptSig + if (!this.evaluate()) { + return false; + } + + if (flags & ScriptInterpreter.SCRIPT_VERIFY_P2SH) + var stackCopy = this.stack.slice(); + + var stack = this.stack; + this.initialize(); + this.set({ + script: scriptPubkey, + stack: stack, + tx: tx, + nin: nin, + flags: flags + }); + + // evaluate scriptPubkey + if (!this.evaluate()) + return false; + + if (this.stack.length === 0) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE_NO_RESULT'; + return false; + } + + var buf = this.stack[this.stack.length - 1]; + if (!ScriptInterpreter.castToBool(buf)) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_STACK'; + return false; + } + + // Additional validation for spend-to-script-hash transactions: + if ((flags & ScriptInterpreter.SCRIPT_VERIFY_P2SH) && scriptPubkey.isScriptHashOut()) { + // scriptSig must be literals-only or validation fails + if (!scriptSig.isPushOnly()) { + this.errstr = 'SCRIPT_ERR_SIG_PUSHONLY'; + return false; + } + + // stackCopy cannot be empty here, because if it was the + // P2SH HASH <> EQUAL scriptPubKey would be evaluated with + // an empty stack and the EvalScript above would return false. + if (stackCopy.length === 0) + throw new Error('internal error - stack copy empty'); + + var redeemScriptSerialized = stackCopy[stackCopy.length - 1]; + var redeemScript = Script.fromBuffer(redeemScriptSerialized); + stackCopy.pop(); + + this.initialize(); + this.set({ + script: redeemScript, + stack: stackCopy, + tx: tx, + nin: nin, + flags: flags + }); + + // evaluate redeemScript + if (!this.evaluate()) + // serror is set + return false; + + if (stackCopy.length === 0) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE_NO_P2SH_STACK'; + return false; + } + + if (!ScriptInterpreter.castToBool(stackCopy[stackCopy.length - 1])) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_P2SH_STACK'; + return false; + } else { + return true; + } + } + + return true; +}; diff --git a/lib/transaction/input/input.js b/lib/transaction/input/input.js index 23b6ef9c9..b059edf2a 100644 --- a/lib/transaction/input/input.js +++ b/lib/transaction/input/input.js @@ -4,7 +4,7 @@ var _ = require('lodash'); var errors = require('../../errors'); var BufferWriter = require('../../encoding/bufferwriter'); var buffer = require('buffer'); -var bufferUtil = require('../../util/buffer'); +var BufferUtil = require('../../util/buffer'); var JSUtil = require('../../util/js'); var Script = require('../../script'); var Sighash = require('../sighash'); @@ -99,7 +99,7 @@ Input.prototype.setScript = function(script) { if (script instanceof Script) { this._script = script; this._scriptBuffer = script.toBuffer(); - } else if (bufferUtil.isBuffer(script)) { + } else if (BufferUtil.isBuffer(script)) { this._script = null; this._scriptBuffer = new buffer.Buffer(script); } else { diff --git a/lib/transaction/sighash.js b/lib/transaction/sighash.js index 3d8eca6ff..361a08bdd 100644 --- a/lib/transaction/sighash.js +++ b/lib/transaction/sighash.js @@ -10,6 +10,7 @@ var BufferWriter = require('../encoding/bufferwriter'); var BN = require('../crypto/bn'); var Hash = require('../crypto/hash'); var ECDSA = require('../crypto/ecdsa'); +var $ = require('../util/preconditions'); var SIGHASH_SINGLE_BUG = '0000000000000000000000000000000000000000000000000000000000000001'; var BITS_64_ON = 'ffffffffffffffff'; @@ -23,7 +24,7 @@ var BITS_64_ON = 'ffffffffffffffff'; * @param {number} inputNumber the input index for the signature * @param {Script} subscript the script that will be signed */ -function sighash(transaction, sighashType, inputNumber, subscript) { +var sighash = function sighash(transaction, sighashType, inputNumber, subscript) { var Transaction = require('./transaction'); var Input = require('./input'); @@ -39,11 +40,11 @@ function sighash(transaction, sighashType, inputNumber, subscript) { // Blank signatures for other inputs txcopy.inputs[i] = new Input(txcopy.inputs[i]).setScript(Script.empty()); } - + txcopy.inputs[inputNumber] = new Input(txcopy.inputs[inputNumber]).setScript(subscript); if ((sighashType & 31) === Signature.SIGHASH_NONE || - (sighashType & 31) === Signature.SIGHASH_SINGLE) { + (sighashType & 31) === Signature.SIGHASH_SINGLE) { // clear all sequenceNumbers for (i = 0; i < txcopy.inputs.length; i++) { @@ -84,21 +85,25 @@ function sighash(transaction, sighashType, inputNumber, subscript) { .write(txcopy.toBuffer()) .writeInt32LE(sighashType) .toBuffer(); - return BufferReader(Hash.sha256sha256(buf)).readReverse(); -} + var ret = Hash.sha256sha256(buf); + ret = new BufferReader(ret).readReverse(); + return ret; +}; -function sign(transaction, keypair, nhashtype, nin, subscript) { +var sign = function sign(transaction, keypair, nhashtype, nin, subscript) { var hashbuf = sighash(transaction, nhashtype, nin, subscript); - hashbuf = new BufferReader(hashbuf).readReverse(); - var sig = ECDSA.sign(hashbuf, keypair, 'little').set({nhashtype: nhashtype}); + var sig = ECDSA.sign(hashbuf, keypair, 'little').set({ + nhashtype: nhashtype + }); return sig; -} +}; -function verify(transaction, sig, pubkey, nin, subscript) { +var verify = function verify(transaction, sig, pubkey, nin, subscript) { + $.checkArgument(transaction); + $.checkArgument(sig && sig.nhashtype); var hashbuf = sighash(transaction, sig.nhashtype, nin, subscript); - hashbuf = new BufferReader(hashbuf).readReverse(); return ECDSA.verify(hashbuf, sig, pubkey, 'little'); -} +}; module.exports = { sighash: sighash, diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 8839ab7fe..26fa466a5 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -11,6 +11,7 @@ var BufferReader = require('../encoding/bufferreader'); var BufferWriter = require('../encoding/bufferwriter'); var Hash = require('../crypto/hash'); var Signature = require('../crypto/signature'); +var Sighash = require('./sighash'); var Address = require('../address'); var Unit = require('../unit'); @@ -97,7 +98,7 @@ Transaction.prototype.serialize = Transaction.prototype.toString = function() { return this.toBuffer().toString('hex'); }; -Transaction.prototype.inspect = function () { +Transaction.prototype.inspect = function() { return ''; }; @@ -231,12 +232,16 @@ Transaction.prototype._fromNonP2SH = function(utxo) { }; Transaction._isNewUtxo = function(utxo) { - var isDefined = function(param) { return !_.isUndefined(param); }; + var isDefined = function(param) { + return !_.isUndefined(param); + }; return _.all(_.map([utxo.txId, utxo.outputIndex, utxo.satoshis, utxo.script], isDefined)); }; Transaction._isOldUtxo = function(utxo) { - var isDefined = function(param) { return !_.isUndefined(param); }; + var isDefined = function(param) { + return !_.isUndefined(param); + }; return _.all(_.map([utxo.txid, utxo.vout, utxo.scriptPubKey, utxo.amount], isDefined)); }; @@ -383,4 +388,11 @@ Transaction.prototype.isValidSignature = function(signature) { return this.inputs[signature.inputIndex].isValidSignature(self, signature); }; +/** + * @returns {bool} whether the signature is valid for this transaction input + */ +Transaction.prototype.verify = function(sig, pubkey, nin, subscript) { + return Sighash.verify(this, sig, pubkey, nin, subscript); +}; + module.exports = Transaction; diff --git a/test/crypto/ecdsa.js b/test/crypto/ecdsa.js index e76431650..d30316a08 100644 --- a/test/crypto/ecdsa.js +++ b/test/crypto/ecdsa.js @@ -127,7 +127,7 @@ describe("ECDSA", function() { 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 = Signature.fromString('3045022100ec3cfe0e335791ad278b4ec8eac93d0347a97877bb1d54d35d189e225c15f6650220278cf15b05ce47fb37d2233802899d94c774d5480bba9f0f2d996baa13370c43'); ecdsa.sig.i = 0; var pubkey = ecdsa.sig2pubkey(); pubkey.point.eq(ecdsa.pubkey.point).should.equal(true); @@ -136,7 +136,7 @@ describe("ECDSA", function() { 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 = Signature.fromString('3046022100ec3cfe0e335791ad278b4ec8eac93d0347a97877bb1d54d35d189e225c15f665022100d8730ea4fa31b804c82ddcc7fd766269f33a079ea38e012c9238f2e2bcff34fe'); ecdsa.sig.i = 1; var pubkey = ecdsa.sig2pubkey(); pubkey.point.eq(ecdsa.pubkey.point).should.equal(true); @@ -169,8 +169,7 @@ describe("ECDSA", function() { }); it('should return an error if the signature is incorrect', function() { - ecdsa.sig = new Signature(); - ecdsa.sig.fromString('3046022100e9915e6236695f093a4128ac2a956c40ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827'); + ecdsa.sig = new Signature.fromString('3046022100e9915e6236695f093a4128ac2a956c40ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827'); ecdsa.sig.r = ecdsa.sig.r.add(BN(1)); ecdsa.sigError().should.equal("Invalid signature"); }); @@ -235,8 +234,7 @@ describe("ECDSA", function() { describe('#verify', function() { it('should verify a signature that was just signed', function() { - ecdsa.sig = new Signature(); - ecdsa.sig.fromString('3046022100e9915e6236695f093a4128ac2a956c40ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827'); + ecdsa.sig = new Signature.fromString('3046022100e9915e6236695f093a4128ac2a956c40ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827'); ecdsa.verify().verified.should.equal(true); }); diff --git a/test/crypto/hash.js b/test/crypto/hash.js index 3ea3ffd53..7509d924e 100644 --- a/test/crypto/hash.js +++ b/test/crypto/hash.js @@ -8,6 +8,23 @@ describe('Hash', function() { var buf = new Buffer([0, 1, 2, 3, 253, 254, 255]); var str = 'test string'; + describe('@sha1', function() { + + it('should calculate the hash of this buffer correctly', function() { + var hash = Hash.sha1(buf); + hash.toString('hex').should.equal('de69b8a4a5604d0486e6420db81e39eb464a17b2'); + hash = Hash.sha1(new Buffer(0)); + hash.toString('hex').should.equal('da39a3ee5e6b4b0d3255bfef95601890afd80709'); + }); + + it('should throw an error when the input is not a buffer', function() { + (function() { + Hash.sha1(str); + }).should.throw('Invalid Argument'); + }); + + }); + describe('#sha256', function() { it('should calculate the hash of this buffer correctly', function() { @@ -18,13 +35,13 @@ describe('Hash', function() { it('should throw an error when the input is not a buffer', function() { (function() { Hash.sha256(str); - }).should.throw('sha256 hash must be of a buffer'); + }).should.throw('Invalid Argument'); }); }); describe('#sha256hmac', function() { - + it('should compute this known empty test vector correctly', function() { var key = new Buffer(''); var data = new Buffer(''); @@ -49,7 +66,7 @@ describe('Hash', function() { it('should throw an error when the input is not a buffer', function() { (function() { Hash.sha256sha256(str); - }).should.throw('sha256sha256 hash must be of a buffer'); + }).should.throw('Invalid Argument'); }); }); @@ -64,7 +81,7 @@ describe('Hash', function() { it('should throw an error when the input is not a buffer', function() { (function() { Hash.sha256ripemd160(str); - }).should.throw('sha256ripemd160 hash must be of a buffer'); + }).should.throw('Invalid Argument'); }); }); @@ -79,7 +96,7 @@ describe('Hash', function() { it('should throw an error when the input is not a buffer', function() { (function() { Hash.ripemd160(str); - }).should.throw('ripemd160 hash must be of a buffer'); + }).should.throw('Invalid Argument'); }); }); @@ -94,7 +111,7 @@ describe('Hash', function() { it('should throw an error when the input is not a buffer', function() { (function() { Hash.sha512(str); - }).should.throw('sha512 hash must be of a buffer'); + }).should.throw('Invalid Argument'); }); }); diff --git a/test/crypto/signature.js b/test/crypto/signature.js index 477408280..77fdefd8b 100644 --- a/test/crypto/signature.js +++ b/test/crypto/signature.js @@ -4,6 +4,10 @@ var should = require('chai').should(); var bitcore = require('../..'); var BN = bitcore.crypto.BN; var Signature = bitcore.crypto.Signature; +var JSUtil = bitcore.util.js; + +var sig_canonical = require('../data/bitcoind/sig_canonical'); +var sig_noncanonical = require('../data/bitcoind/sig_noncanonical'); describe('Signature', function() { @@ -41,8 +45,7 @@ describe('Signature', function() { blank, blank ]); - var sig = new Signature(); - sig.fromCompact(compressed); + var sig = Signature.fromCompact(compressed); sig.r.cmp(0).should.equal(0); sig.s.cmp(0).should.equal(0); }); @@ -54,8 +57,7 @@ describe('Signature', function() { var buf = new Buffer('3044022075fc517e541bd54769c080b64397e32161c850f6c1b2b67a5c433affbb3e62770220729e85cc46ffab881065ec07694220e71d4df9b2b8c8fd12c3122cf3a5efbcf2', 'hex'); it('should parse this DER format signature', function() { - var sig = new Signature(); - sig.fromDER(buf); + var sig = Signature.fromDER(buf); sig.r.toBuffer({ size: 32 }).toString('hex').should.equal('75fc517e541bd54769c080b64397e32161c850f6c1b2b67a5c433affbb3e6277'); @@ -71,8 +73,7 @@ describe('Signature', function() { var buf = new Buffer('3044022075fc517e541bd54769c080b64397e32161c850f6c1b2b67a5c433affbb3e62770220729e85cc46ffab881065ec07694220e71d4df9b2b8c8fd12c3122cf3a5efbcf2', 'hex'); it('should parse this DER format signature in hex', function() { - var sig = new Signature(); - sig.fromString(buf.toString('hex')); + var sig = Signature.fromString(buf.toString('hex')); sig.r.toBuffer({ size: 32 }).toString('hex').should.equal('75fc517e541bd54769c080b64397e32161c850f6c1b2b67a5c433affbb3e6277'); @@ -83,6 +84,25 @@ describe('Signature', function() { }); + describe('#fromTxFormat', function() { + + it('should convert from this known tx-format buffer', function() { + var buf = new Buffer('30450221008bab1f0a2ff2f9cb8992173d8ad73c229d31ea8e10b0f4d4ae1a0d8ed76021fa02200993a6ec81755b9111762fc2cf8e3ede73047515622792110867d12654275e7201', 'hex'); + var sig = Signature.fromTxFormat(buf); + sig.r.toString().should.equal('63173831029936981022572627018246571655303050627048489594159321588908385378810'); + sig.s.toString().should.equal('4331694221846364448463828256391194279133231453999942381442030409253074198130'); + sig.nhashtype.should.equal(Signature.SIGHASH_ALL); + }); + + it('should parse this known signature and rebuild it', function() { + var hex = "3044022007415aa37ce7eaa6146001ac8bdefca0ddcba0e37c5dc08c4ac99392124ebac802207d382307fd53f65778b07b9c63b6e196edeadf0be719130c5db21ff1e700d67501"; + var buf = new Buffer(hex, 'hex'); + var sig = Signature.fromTxFormat(buf); + sig.toTxFormat().toString('hex').should.equal(hex); + }); + + }); + describe('#parseDER', function() { it('should parse this signature generated in node', function() { @@ -133,6 +153,13 @@ describe('Signature', function() { parsed.s.toString().should.equal('44212963026209759051804639008236126356702363229859210154760104982946304432721'); }); + it('should parse this signature from script_valid.json', function() { + var sighex = '304502203e4516da7253cf068effec6b95c41221c0cf3a8e6ccb8cbf1725b562e9afde2c022100ab1e3da73d67e32045a20e0b999e049978ea8d6ee5480d485fcf2ce0d03b2ef051'; + var sig = Buffer(sighex, 'hex'); + var parsed = Signature.parseDER(sig, false); + should.exist(parsed); + }); + }); describe('#toDER', function() { @@ -151,7 +178,6 @@ describe('Signature', function() { }); describe('#toString', function() { - it('should convert this signature in to hex DER', function() { var r = BN('63173831029936981022572627018246571655303050627048489594159321588908385378810'); var s = BN('4331694221846364448463828256391194279133231453999942381442030409253074198130'); @@ -162,7 +188,62 @@ describe('Signature', function() { var hex = sig.toString(); hex.should.equal('30450221008bab1f0a2ff2f9cb8992173d8ad73c229d31ea8e10b0f4d4ae1a0d8ed76021fa02200993a6ec81755b9111762fc2cf8e3ede73047515622792110867d12654275e72'); }); + }); + + describe('@isTxDER', function() { + it('should know this is a DER signature', function() { + var sighex = '3042021e17cfe77536c3fb0526bd1a72d7a8e0973f463add210be14063c8a9c37632022061bfa677f825ded82ba0863fb0c46ca1388dd3e647f6a93c038168b59d131a5101'; + var sigbuf = new Buffer(sighex, 'hex'); + Signature.isTxDER(sigbuf).should.equal(true); + }); + + it('should know this is not a DER signature', function() { + //for more extensive tests, see the script interpreter + var sighex = '3042021e17cfe77536c3fb0526bd1a72d7a8e0973f463add210be14063c8a9c37632022061bfa677f825ded82ba0863fb0c46ca1388dd3e647f6a93c038168b59d131a5101'; + var sigbuf = new Buffer(sighex, 'hex'); + sigbuf[0] = 0x31; + Signature.isTxDER(sigbuf).should.equal(false); + }); + + + describe('bitcoind fixtures', function() { + var test_sigs = function(set, expected) { + var i = 0; + set.forEach(function(vector) { + if (!JSUtil.isHexa(vector)) { + // non-hex strings are ignored + return; + } + it('should be ' + (expected ? '' : 'in') + 'valid for fixture #' + i, function() { + var sighex = vector; + Signature.isTxDER(new Buffer(sighex, 'hex')).should.equal(expected); + }); + i++; + }); + }; + test_sigs(sig_canonical, true); + //test_sigs(sig_noncanonical, false); + }); + + }); + describe('#hasLowS', function() { + it('should detect high and low S', function() { + var r = BN('63173831029936981022572627018246571655303050627048489594159321588908385378810'); + var s = BN('4331694221846364448463828256391194279133231453999942381442030409253074198130'); + var s2 = BN('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B2000'); + var sig = new Signature({ + r: r, + s: s + }); + var sig2 = new Signature({ + r: r, + s: s2 + }); + sig2.hasLowS().should.equal(true); + sig.hasLowS().should.equal(false); + + }); }); }); diff --git a/test/data/bitcoind/sig_canonical.json b/test/data/bitcoind/sig_canonical.json new file mode 100644 index 000000000..e43a08629 --- /dev/null +++ b/test/data/bitcoind/sig_canonical.json @@ -0,0 +1,7 @@ +[ + "300602010002010001", + "3008020200ff020200ff01", + "304402203932c892e2e550f3af8ee4ce9c215a87f9bb831dcac87b2838e2c2eaa891df0c022030b61dd36543125d56b9f9f3a1f9353189e5af33cdda8d77a5209aec03978fa001", + "30450220076045be6f9eca28ff1ec606b833d0b87e70b2a630f5e3a496b110967a40f90a0221008fffd599910eefe00bc803c688c2eca1d2ba7f6b180620eaa03488e6585db6ba01", + "3046022100876045be6f9eca28ff1ec606b833d0b87e70b2a630f5e3a496b110967a40f90a0221008fffd599910eefe00bc803c688c2eca1d2ba7f6b180620eaa03488e6585db6ba01" +] diff --git a/test/data/bitcoind/sig_noncanonical.json b/test/data/bitcoind/sig_noncanonical.json new file mode 100644 index 000000000..d9a6c1cdd --- /dev/null +++ b/test/data/bitcoind/sig_noncanonical.json @@ -0,0 +1,22 @@ +[ + "non-hex strings are ignored", + + "too short:", "30050201FF020001", + "too long:", "30470221005990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba6105022200002d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01", + "hashtype:", "304402205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610502202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed11", + "type:", "314402205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610502202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01", + "total length:", "304502205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610502202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01", + "S len oob:", "301F01205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb101", + "R+S:", "304502205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610502202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed0001", + + "R type:", "304401205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610502202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01", + "R len = 0:", "3024020002202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01", + "R<0:", "304402208990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610502202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01", + "R padded:", "30450221005990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610502202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01", + + + "S type:", "304402205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610501202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01", + "S len = 0:", "302402205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba6105020001", + "S<0:", "304402205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba61050220fd5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01", + "S padded:", "304502205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba61050221002d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01" +] diff --git a/test/script.js b/test/script.js index 96264aee3..0de98b989 100644 --- a/test/script.js +++ b/test/script.js @@ -7,7 +7,6 @@ var Opcode = bitcore.Opcode; var PublicKey = bitcore.PublicKey; var Address = bitcore.Address; - describe('Script', function() { it('should make a new script', function() { @@ -19,7 +18,7 @@ describe('Script', function() { it('should parse this buffer containing an OP code', function() { var buf = new Buffer(1); - buf[0] = Opcode('OP_0').toNumber(); + buf[0] = Opcode.OP_0; var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].opcodenum.should.equal(buf[0]); @@ -27,7 +26,7 @@ describe('Script', function() { it('should parse this buffer containing another OP code', function() { var buf = new Buffer(1); - buf[0] = Opcode('OP_CHECKMULTISIG').toNumber(); + buf[0] = Opcode.OP_CHECKMULTISIG; var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].opcodenum.should.equal(buf[0]); @@ -42,7 +41,7 @@ describe('Script', function() { it('should parse this buffer containing OP_PUSHDATA1 and three bytes of data', function() { var buf = new Buffer([0, 0, 1, 2, 3]); - buf[0] = Opcode('OP_PUSHDATA1').toNumber(); + buf[0] = Opcode.OP_PUSHDATA1; buf.writeUInt8(3, 1); var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); @@ -51,7 +50,7 @@ describe('Script', function() { it('should parse this buffer containing OP_PUSHDATA2 and three bytes of data', function() { var buf = new Buffer([0, 0, 0, 1, 2, 3]); - buf[0] = Opcode('OP_PUSHDATA2').toNumber(); + buf[0] = Opcode.OP_PUSHDATA2; buf.writeUInt16LE(3, 1); var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); @@ -60,7 +59,7 @@ describe('Script', function() { it('should parse this buffer containing OP_PUSHDATA4 and three bytes of data', function() { var buf = new Buffer([0, 0, 0, 0, 0, 1, 2, 3]); - buf[0] = Opcode('OP_PUSHDATA4').toNumber(); + buf[0] = Opcode.OP_PUSHDATA4; buf.writeUInt16LE(3, 1); var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); @@ -69,10 +68,10 @@ describe('Script', function() { it('should parse this buffer an OP code, data, and another OP code', function() { var buf = new Buffer([0, 0, 0, 0, 0, 0, 1, 2, 3, 0]); - buf[0] = Opcode('OP_0').toNumber(); - buf[1] = Opcode('OP_PUSHDATA4').toNumber(); + buf[0] = Opcode.OP_0; + buf[1] = Opcode.OP_PUSHDATA4; buf.writeUInt16LE(3, 2); - buf[buf.length - 1] = Opcode('OP_0').toNumber(); + buf[buf.length - 1] = Opcode.OP_0; var script = Script.fromBuffer(buf); script.chunks.length.should.equal(3); script.chunks[0].opcodenum.should.equal(buf[0]); @@ -86,7 +85,7 @@ describe('Script', function() { it('should output this buffer containing an OP code', function() { var buf = new Buffer(1); - buf[0] = Opcode('OP_0').toNumber(); + buf[0] = Opcode.OP_0; var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].opcodenum.should.equal(buf[0]); @@ -95,7 +94,7 @@ describe('Script', function() { it('should output this buffer containing another OP code', function() { var buf = new Buffer(1); - buf[0] = Opcode('OP_CHECKMULTISIG').toNumber(); + buf[0] = Opcode.OP_CHECKMULTISIG; var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].opcodenum.should.equal(buf[0]); @@ -112,7 +111,7 @@ describe('Script', function() { it('should output this buffer containing OP_PUSHDATA1 and three bytes of data', function() { var buf = new Buffer([0, 0, 1, 2, 3]); - buf[0] = Opcode('OP_PUSHDATA1').toNumber(); + buf[0] = Opcode.OP_PUSHDATA1; buf.writeUInt8(3, 1); var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); @@ -122,7 +121,7 @@ describe('Script', function() { it('should output this buffer containing OP_PUSHDATA2 and three bytes of data', function() { var buf = new Buffer([0, 0, 0, 1, 2, 3]); - buf[0] = Opcode('OP_PUSHDATA2').toNumber(); + buf[0] = Opcode.OP_PUSHDATA2; buf.writeUInt16LE(3, 1); var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); @@ -132,7 +131,7 @@ describe('Script', function() { it('should output this buffer containing OP_PUSHDATA4 and three bytes of data', function() { var buf = new Buffer([0, 0, 0, 0, 0, 1, 2, 3]); - buf[0] = Opcode('OP_PUSHDATA4').toNumber(); + buf[0] = Opcode.OP_PUSHDATA4; buf.writeUInt16LE(3, 1); var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); @@ -142,10 +141,10 @@ describe('Script', function() { it('should output this buffer an OP code, data, and another OP code', function() { var buf = new Buffer([0, 0, 0, 0, 0, 0, 1, 2, 3, 0]); - buf[0] = Opcode('OP_0').toNumber(); - buf[1] = Opcode('OP_PUSHDATA4').toNumber(); + buf[0] = Opcode.OP_0; + buf[1] = Opcode.OP_PUSHDATA4; buf.writeUInt16LE(3, 2); - buf[buf.length - 1] = Opcode('OP_0').toNumber(); + buf[buf.length - 1] = Opcode.OP_0; var script = Script.fromBuffer(buf); script.chunks.length.should.equal(3); script.chunks[0].opcodenum.should.equal(buf[0]); @@ -176,10 +175,10 @@ describe('Script', function() { it('should output this buffer an OP code, data, and another OP code', function() { var buf = new Buffer([0, 0, 0, 0, 0, 0, 1, 2, 3, 0]); - buf[0] = Opcode('OP_0').toNumber(); - buf[1] = Opcode('OP_PUSHDATA4').toNumber(); + buf[0] = Opcode.OP_0; + buf[1] = Opcode.OP_PUSHDATA4; buf.writeUInt16LE(3, 2); - buf[buf.length - 1] = Opcode('OP_0').toNumber(); + buf[buf.length - 1] = Opcode.OP_0; var script = Script.fromBuffer(buf); script.chunks.length.should.equal(3); script.chunks[0].opcodenum.should.equal(buf[0]); @@ -349,12 +348,11 @@ describe('Script', function() { describe('#add and #prepend', function() { it('should add these ops', function() { - Script().add(Opcode('OP_RETURN')).add(new Buffer('')).toString().should.equal('OP_RETURN'); - }); - it('should add these ops', function() { + Script().add(1).add(10).add(186).toString().should.equal('0x01 0x0a 0xba'); + Script().add(1000).toString().should.equal('0x03e8'); Script().add('OP_CHECKMULTISIG').toString().should.equal('OP_CHECKMULTISIG'); Script().add('OP_1').add('OP_2').toString().should.equal('OP_1 OP_2'); - Script().add(new Opcode('OP_CHECKMULTISIG')).toString().should.equal('OP_CHECKMULTISIG'); + Script().add(Opcode.OP_CHECKMULTISIG).toString().should.equal('OP_CHECKMULTISIG'); Script().add(Opcode.map.OP_CHECKMULTISIG).toString().should.equal('OP_CHECKMULTISIG'); }); @@ -390,6 +388,10 @@ describe('Script', function() { buf.fill(0); Script().add(buf).toString().should.equal('1 0x00'); }); + + it('should work for no data OP_RETURN', function() { + Script().add(Opcode.OP_RETURN).add(new Buffer('')).toString().should.equal('OP_RETURN 0'); + }); }); describe('#isStandard', function() { @@ -469,11 +471,17 @@ describe('Script', function() { }); }); describe('#buildDataOut', function() { + it('should create script from no data', function() { + var s = Script.buildDataOut(); + should.exist(s); + s.toString().should.equal('OP_RETURN'); + s.isDataOut().should.equal(true); + }); it('should create script from empty data', function() { var data = new Buffer(''); var s = Script.buildDataOut(data); should.exist(s); - s.toString().should.equal('OP_RETURN'); + s.toString().should.equal('OP_RETURN 0'); s.isDataOut().should.equal(true); }); it('should create script from some data', function() { @@ -515,4 +523,47 @@ describe('Script', function() { }); }); + + describe('#findAndDelete', function() { + it('should find and delete this buffer', function() { + Script('OP_RETURN 2 0xf0f0') + .findAndDelete(Script('2 0xf0f0')) + .toString() + .should.equal('OP_RETURN'); + }); + it('should do nothing', function() { + Script('OP_RETURN 2 0xf0f0') + .findAndDelete(Script('2 0xffff')) + .toString() + .should.equal('OP_RETURN 2 0xf0f0'); + }); + }); + + + describe('#checkMinimalPush', function() { + + it('should check these minimal pushes', function() { + Script().add(1).checkMinimalPush(0).should.equal(true); + Script().add(0).checkMinimalPush(0).should.equal(true); + Script().add(-1).checkMinimalPush(0).should.equal(true); + Script().add(1000).checkMinimalPush(0).should.equal(true); + Script().add(0xffffffff).checkMinimalPush(0).should.equal(true); + Script().add(0xffffffffffffffff).checkMinimalPush(0).should.equal(true); + Script().add(new Buffer([0])).checkMinimalPush(0).should.equal(true); + + var buf = new Buffer(75); + buf.fill(1); + Script().add(buf).checkMinimalPush(0).should.equal(true); + + buf = new Buffer(76); + buf.fill(1); + Script().add(buf).checkMinimalPush(0).should.equal(true); + + buf = new Buffer(256); + buf.fill(1); + Script().add(buf).checkMinimalPush(0).should.equal(true); + }); + + }); + }); diff --git a/test/script_interpreter.js b/test/script_interpreter.js new file mode 100644 index 000000000..91220b1cc --- /dev/null +++ b/test/script_interpreter.js @@ -0,0 +1,315 @@ +'use strict'; + +var should = require('chai').should(); +var bitcore = require('..'); +var ScriptInterpreter = bitcore.ScriptInterpreter; +var Transaction = bitcore.Transaction; +var Script = bitcore.Script; +var BN = bitcore.crypto.BN; +var BufferReader = bitcore.encoding.BufferReader; +var BufferWriter = bitcore.encoding.BufferWriter; +var Opcode = bitcore.Opcode; + +var script_valid = require('./data/bitcoind/script_valid'); +var script_invalid = require('./data/bitcoind/script_invalid'); +var tx_valid = require('./transaction/tx_valid'); +var tx_invalid = require('./transaction/tx_invalid'); + +//the script string format used in bitcoind data tests +Script.fromBitcoindString = function(str) { + var bw = new BufferWriter(); + var tokens = str.split(' '); + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + if (token === '') { + continue; + } + + var opstr; + var opcodenum; + var tbuf; + if (token[0] === '0' && token[1] === 'x') { + var hex = token.slice(2); + bw.write(new Buffer(hex, 'hex')); + } else if (token[0] === '\'') { + var tstr = token.slice(1, token.length - 1); + var cbuf = new Buffer(tstr); + tbuf = Script().add(cbuf).toBuffer(); + bw.write(tbuf); + } else if (typeof Opcode['OP_' + token] !== 'undefined') { + opstr = 'OP_' + token; + opcodenum = Opcode[opstr]; + bw.writeUInt8(opcodenum); + } else if (typeof Opcode[token] === 'number') { + opstr = token; + opcodenum = Opcode[opstr]; + bw.writeUInt8(opcodenum); + } else if (!isNaN(parseInt(token))) { + var script = Script().add(BN(token).toScriptNumBuffer()); + tbuf = script.toBuffer(); + bw.write(tbuf); + } else { + throw new Error('Could not determine type of script value'); + } + } + var buf = bw.concat(); + return this.fromBuffer(buf); +}; + + + +describe('ScriptInterpreter', function() { + + it('should make a new interp', function() { + var interp = new ScriptInterpreter(); + (interp instanceof ScriptInterpreter).should.equal(true); + interp.stack.length.should.equal(0); + interp.altstack.length.should.equal(0); + interp.pc.should.equal(0); + interp.pbegincodehash.should.equal(0); + interp.nOpCount.should.equal(0); + interp.vfExec.length.should.equal(0); + interp.errstr.should.equal(''); + interp.flags.should.equal(0); + }); + + describe('@castToBool', function() { + + it('should cast these bufs to bool correctly', function() { + ScriptInterpreter.castToBool(BN(0).toSM({ + endian: 'little' + })).should.equal(false); + ScriptInterpreter.castToBool(new Buffer('0080', 'hex')).should.equal(false); //negative 0 + ScriptInterpreter.castToBool(BN(1).toSM({ + endian: 'little' + })).should.equal(true); + ScriptInterpreter.castToBool(BN(-1).toSM({ + endian: 'little' + })).should.equal(true); + + var buf = new Buffer('00', 'hex'); + var bool = BN().fromSM(buf, { + endian: 'little' + }).cmp(0) !== 0; + ScriptInterpreter.castToBool(buf).should.equal(bool); + }); + + }); + + describe('#verify', function() { + + it('should verify these trivial scripts', function() { + var verified; + var si = ScriptInterpreter(); + verified = si.verify(Script('OP_1'), Script('OP_1')); + verified.should.equal(true); + verified = ScriptInterpreter().verify(Script('OP_1'), Script('OP_0')); + verified.should.equal(false); + verified = ScriptInterpreter().verify(Script('OP_0'), Script('OP_1')); + verified.should.equal(true); + verified = ScriptInterpreter().verify(Script('OP_CODESEPARATOR'), Script('OP_1')); + verified.should.equal(true); + verified = ScriptInterpreter().verify(Script(''), Script('OP_DEPTH OP_0 OP_EQUAL')); + verified.should.equal(true); + verified = ScriptInterpreter().verify(Script('OP_1 OP_2'), Script('OP_2 OP_EQUALVERIFY OP_1 OP_EQUAL')); + verified.should.equal(true); + verified = ScriptInterpreter().verify(Script('9 0x000000000000000010'), Script('')); + verified.should.equal(true); + verified = ScriptInterpreter().verify(Script('OP_1'), Script('OP_15 OP_ADD OP_16 OP_EQUAL')); + verified.should.equal(true); + verified = ScriptInterpreter().verify(Script('OP_0'), Script('OP_IF OP_VER OP_ELSE OP_1 OP_ENDIF')); + verified.should.equal(true); + }); + + }); + + + var getFlags = function getFlags(flagstr) { + var flags = 0; + if (flagstr.indexOf('NONE') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_NONE; + } + if (flagstr.indexOf('P2SH') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_P2SH; + } + if (flagstr.indexOf('STRICTENC') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_STRICTENC; + } + if (flagstr.indexOf('DERSIG') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_DERSIG; + } + if (flagstr.indexOf('LOW_S') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_LOW_S; + } + if (flagstr.indexOf('NULLDUMMY') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_NULLDUMMY; + } + if (flagstr.indexOf('SIGPUSHONLY') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_SIGPUSHONLY; + } + if (flagstr.indexOf('MINIMALDATA') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_MINIMALDATA; + } + if (flagstr.indexOf('DISCOURAGE_UPGRADABLE_NOPS') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS; + } + return flags; + }; + + + var testToFromString = function(script) { + var s = script.toString(); + Script.fromString(s).toString().should.equal(s); + }; + + var testFixture = function(vector, expected) { + var scriptSig = Script.fromBitcoindString(vector[0]); + var scriptPubkey = Script.fromBitcoindString(vector[1]); + var flags = getFlags(vector[2]); + + + //testToFromString(scriptSig); + //testToFromString(scriptPubkey); + + var hashbuf = new Buffer(32); + hashbuf.fill(0); + var credtx = Transaction(); + credtx.inputs.push(new Transaction.Input({ + prevTxId: '0000000000000000000000000000000000000000000000000000000000000000', + outputIndex: 0xffffffff, + sequenceNumber: 0xffffffff, + script: Script('OP_0 OP_0') + })); + credtx._addOutput(new Transaction.Output({ + script: scriptPubkey, + satoshis: 0 + })); + var idbuf = credtx.id; + + var spendtx = Transaction(); + spendtx.inputs.push(new Transaction.Input({ + prevTxId: idbuf.toString('hex'), + outputIndex: 0, + sequenceNumber: 0xffffffff, + script: scriptSig + })); + spendtx._addOutput(new Transaction.Output({ + script: Script(), + satoshis: 0 + })); + + var interp = ScriptInterpreter(); + var verified = interp.verify(scriptSig, scriptPubkey, spendtx, 0, flags); + verified.should.equal(expected); + }; + describe('bitcoind fixtures', function() { + var testAllFixtures = function(set, expected) { + var c = 0; + set.forEach(function(vector) { + if (vector.length === 1) { + return; + } + c++; + var descstr = vector[3]; + var fullScriptString = vector[0] + ' ' + vector[1]; + var comment = descstr ? (' (' + descstr + ')') : ''; + it('should pass script_' + (expected ? '' : 'in') + 'valid vector #' + c + ': ' + fullScriptString + comment, function() { + testFixture(vector, expected); + }); + }); + }; + testAllFixtures(script_valid, true); + testAllFixtures(script_invalid, false); + + var c = 0; + tx_valid.forEach(function(vector) { + if (vector.length === 1) { + return; + } + c++; + it.skip('should pass tx_valid vector ' + c, function() { + var inputs = vector[0]; + var txhex = vector[1]; + var flags = getFlags(vector[2]); + + var map = {}; + inputs.forEach(function(input) { + var txid = input[0]; + var txoutnum = input[1]; + var scriptPubKeyStr = input[2]; + if (txoutnum === -1) { + txoutnum = 0xffffffff; //bitcoind casts -1 to an unsigned int + } + var txkey = txid + ':' + txoutnum; + map[txkey] = Script.fromBitcoindString(scriptPubKeyStr); + }); + + var tx = Transaction(txhex); + tx.inputs.forEach(function(txin, j) { + var scriptSig = txin.script; + var txidhex = txin.prevTxId.toString('hex'); + var txoutnum = txin.outputIndex; + var txkey = txidhex + ':' + txoutnum; + var scriptPubkey = map[txkey]; + should.exist(scriptPubkey); + var interp = ScriptInterpreter(); + var verified = interp.verify(scriptSig, scriptPubkey, tx, j, flags); + verified.should.equal(true); + }); + }); + }); + + c = 0; + tx_invalid.forEach(function(vector) { + if (vector.length === 1) { + return; + } + c++; + + // tests intentionally not performed by the script interpreter: + // TODO: check this? + /* + if (c === 7 || // tests if valuebn is negative + c === 8 || // tests if valuebn is greater than MAX_MONEY + c === 10 || // tests if two inputs are equal + c === 11 || // coinbase + c === 12 || // coinbase + c === 13 // null input + ) { + return; + } + */ + + it.skip('should pass tx_invalid vector ' + c, function() { + var inputs = vector[0]; + var txhex = vector[1]; + var flags = getFlags(vector[2]); + + var map = {}; + inputs.forEach(function(input) { + var txoutnum = input[1]; + if (txoutnum === -1) { + txoutnum = 0xffffffff; //bitcoind casts -1 to an unsigned int + } + map[input[0] + ':' + txoutnum] = Script.fromBitcoindString(input[2]); + }); + + var tx = Transaction().fromBuffer(new Buffer(txhex, 'hex')); + if (tx.txins.length > 0) { + tx.txins.some(function(txin, j) { + var scriptSig = txin.script; + var txidhex = BufferReader(txin.txidbuf).readReverse().toString('hex'); + var txoutnum = txin.txoutnum; + var scriptPubkey = map[txidhex + ':' + txoutnum]; + should.exist(scriptPubkey); + var interp = ScriptInterpreter(); + var verified = interp.verify(scriptSig, scriptPubkey, tx, j, flags); + return verified === false; + }).should.equal(true); + } + }); + }); + + }); + +}); diff --git a/test/transaction/creation.js b/test/transaction/creation.js index 33657c683..2b15052e3 100644 --- a/test/transaction/creation.js +++ b/test/transaction/creation.js @@ -14,7 +14,7 @@ module.exports = [ }], 'to', ['mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', 1010000], 'sign', ['cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY'], - 'serialize', '01000000015884e5db9de218238671572340b207ee85b628074e7e467096c267266baf77a4000000006b4830450221009972100061da4a17a471ac1906c18bb5445c03da2a0be52c59aca6c58f1e342302205eac5ba43830a397f613f40addea4a2eeaa485a1f9a6efa61344c3560762fe3d01210223078d2942df62c45621d209fab84ea9a7a23346201b7727b9b45a29c4e76f5effffffff0150690f00000000001976a9147821c0a3768aa9d1a37e16cf76002aef5373f1a888ac00000000' + 'serialize', '01000000015884e5db9de218238671572340b207ee85b628074e7e467096c267266baf77a4000000006a473044022013fa3089327b50263029265572ae1b022a91d10ac80eb4f32f291c914533670b02200d8a5ed5f62634a7e1a0dc9188a3cc460a986267ae4d58faf50c79105431327501210223078d2942df62c45621d209fab84ea9a7a23346201b7727b9b45a29c4e76f5effffffff0150690f00000000001976a9147821c0a3768aa9d1a37e16cf76002aef5373f1a888ac00000000' ], [ 'from', [{ @@ -27,7 +27,7 @@ module.exports = [ 'to', ['mn9new5vPYWuVN5m3gUBujfKh1uPQvR9mf', 500000], 'to', ['mw5ctwgEaNRbxkM4JhXH3rp5AyGvTWDZCD', 570000], 'sign', ['cSQUuwwJBAg6tYQhzqqLWW115D1s5KFZDyhCF2ffrnukZxMK6rNZ'], - 'serialize', '0100000001863957ca797bf847eae50f6999e4c3616dc64b1e6661b16d9da2b57d184724e4010000006b483045022100855691c90510edf83ab632f0a0b17f5202d2cf7071050dcf0c2778325ed403cd02207270a2f0b30c13dc3c1dee74b5ccabcc2632b402c4f38adabcd07357df1442270121039dd446bbc85db6917f39c0b4c295b0f8cce76d1926fa76d7b84e3f7ff1c5eec5ffffffff0220a10700000000001976a91448c819246ae5645ceecd41fbe1aa6202a0a9b5ca88ac90b20800000000001976a914aab76ba4877d696590d94ea3e02948b55294815188ac00000000' + 'serialize', '0100000001863957ca797bf847eae50f6999e4c3616dc64b1e6661b16d9da2b57d184724e4010000006b4830450221009d23f7c1e790ecf839e0e53248dacfa559194735e477aa3ee5897fd74fe3ec0402205eff578518e7c59beeb03ee85e5c4b5bc2730addca2f0321d80aadfbcc1976de0121039dd446bbc85db6917f39c0b4c295b0f8cce76d1926fa76d7b84e3f7ff1c5eec5ffffffff0220a10700000000001976a91448c819246ae5645ceecd41fbe1aa6202a0a9b5ca88ac90b20800000000001976a914aab76ba4877d696590d94ea3e02948b55294815188ac00000000' ], [ 'from', [[{ @@ -49,7 +49,7 @@ module.exports = [ }]], 'to', ['mtymcCX5KixPjT1zxtg59qewBGWptj9etH', 1060000], 'sign', [['cPGbA2C54ZZ1sw4dc2ckBE1WqkdrNSbEV8Tkjhi2p1J15oErdgP2', 'cSpyve5bXAuyHrNeV9MjTdFz3HLw739yUjjUAUSMe3ppf2qzj2hw']], - 'serialize', '0100000002b3028cf4ae5b4b6d8f79ab6fc0dd251b28ab28287d33861e35c90f6e5684dba9000000006a4730440220635e95e1981bbb360feaf4c232f626a0af8eb5c043a99749a21b0e37fd0048fd02207889f6974f0cad39ce8c2a6dff05c8ca402da9ff6fc41e06c12d86853c91a9d80121030253c73236acf5ea9085d408220141197f6094de07426bd0d32c7a543614fdd7ffffffffb3028cf4ae5b4b6d8f79ab6fc0dd251b28ab28287d33861e35c90f6e5684dba9010000006a4730440220319a0b5ee9c67ccb7de4222234f31059354be4f239c99ca24bff30adfec8e8ec022056e6e99e50f7ceaa062958b8424cde1d504019f95c1dc0a0f0778848d0fb9f4b012102977a001a0a7bbfd1f8a647c7d46e13e8f6920635b328390b43b3303977101149ffffffff01a02c1000000000001976a91493abf1e9e4a20c125b93f93ee39efc16b6e4bc4688ac00000000' + 'serialize', '0100000002b3028cf4ae5b4b6d8f79ab6fc0dd251b28ab28287d33861e35c90f6e5684dba9000000006a47304402205d591b93871b63205ea80f6976b4d76ce23ca95c825d0c74b44e9816c9488ae8022012476dd8a2780028ed3e72ac1f2620580e82a25c22d1c31afca6fb14b125a35c0121030253c73236acf5ea9085d408220141197f6094de07426bd0d32c7a543614fdd7ffffffffb3028cf4ae5b4b6d8f79ab6fc0dd251b28ab28287d33861e35c90f6e5684dba9010000006a4730440220320367535c9bc525c581939b36ebe70dd0845851e68fa9e3cf2d90642bf551b3022055d6fcaef32d9b584eea757fa633c31449f43272d131588ddd87a22d16dd7129012102977a001a0a7bbfd1f8a647c7d46e13e8f6920635b328390b43b3303977101149ffffffff01a02c1000000000001976a91493abf1e9e4a20c125b93f93ee39efc16b6e4bc4688ac00000000' ], [ 'from', [{ @@ -62,7 +62,7 @@ module.exports = [ }], 'to', ['2NEQb8rtiUgxqQ9eif4XVeMUEW2LSZ64s58', 1050000], 'sign', ['cMh7xdJ5EZVg6kvFsBybwK1EYGJw3G1DHhe5sNPAwbDts94ohKyK'], - 'serialize', '01000000019c58233f15d582a2e969d37bd05f2e0cb52994015defc65eaec7de64b9cebec8000000006a473044022050442862e892b1d12bcaa03857746f0ed168122e093d799861f4e081756bb8aa0220081d4eaf9281ae8f954efaeb47500d9a02e5a74b3ada51b6a258ac83c1f4f6420121039dbeac2610d53eb7107b14c0fa9be4006a731fa5bcef392d4e1a25ec0e58f0d3ffffffff01900510000000000017a91490edc43da6b052c4a23fc178979ce358a8caad5e8700000000' + 'serialize', '01000000019c58233f15d582a2e969d37bd05f2e0cb52994015defc65eaec7de64b9cebec8000000006a47304402205db94e075d4cf740c69e878fa0079e004bbc323be71b1f8944de702b362ca6880220616e2791168e0a2ffc36dc1847f46b79d7ffb5314ae20ee4066feea2b5a32cdc0121039dbeac2610d53eb7107b14c0fa9be4006a731fa5bcef392d4e1a25ec0e58f0d3ffffffff01900510000000000017a91490edc43da6b052c4a23fc178979ce358a8caad5e8700000000' ], [ 'from', [{ @@ -74,6 +74,18 @@ module.exports = [ }, ['03fd45c8cd28c4c6a9a89b4515173edebc66a2418353976eccf01c73a7da9bbb12', '0349e0138b2c2f496121258e0426e1dbd698b2c6038e70fd17e3563aa87b4384f9'], 2], 'to', ['mssMdcEm6PiJEr4XZtjk6kkai84EjBbi91', 1040000], 'sign', [['L3wRFe9XHLnkLquf41F56ac77uRXwJ97HZPQ9tppqyMANBKXpoc5', 'KzkfNSL1gvdyU3CGLaP1Cs3pW167M8r9uE8yMtWQrAzz5vCv59CM']], - 'serialize', '010000000140c1ae9d6933e4a08594f814ba73a4e94d19c8a83f45784b1684b3a3f84ee66600000000da004730440220366678972728684a94f35635b855583603b28065d430949c08be89412a4ee45d02201aa62e3129c8819ecf2048230e8c77e244d6a496f296954a5bb4a0d0185f8c0201483045022100d06f348b4ef793f2bf749b288f1df165c0946779391c50ddc050e5b1608b2dda02200fcc8c6874b9a313374020253c5de346fe3517c97b18bfa769cea1089ad97144014752210349e0138b2c2f496121258e0426e1dbd698b2c6038e70fd17e3563aa87b4384f92103fd45c8cd28c4c6a9a89b4515173edebc66a2418353976eccf01c73a7da9bbb1252aeffffffff0180de0f00000000001976a914877d4f3be444448f868b345153bc4fc7a11a7c6388ac00000000' + 'serialize', '010000000140c1ae9d6933e4a08594f814ba73a4e94d19c8a83f45784b1684b3a3f84ee66600000000d900473044022057c6961adc330ad231f7e1e58f46987637118f85f5f621425a401c609f36abca022017a21edf778d115bab05c70d3c8c4ac16fff52a2686cbb6b60a08b192a5e4e8a01473044022049c8bc0137c49ff87c1c6ef6ef9a7162a64e4519022bd7d68ae523dd6b14c4b2022012f28917b1602d0311ab6c43fa901bf3e5414524252ac85bc9ef8a52d9094210014752210349e0138b2c2f496121258e0426e1dbd698b2c6038e70fd17e3563aa87b4384f92103fd45c8cd28c4c6a9a89b4515173edebc66a2418353976eccf01c73a7da9bbb1252aeffffffff0180de0f00000000001976a914877d4f3be444448f868b345153bc4fc7a11a7c6388ac00000000' + ], + [ + 'from', [{ + "address": "mgJT8iegL4f9NCgQFeFyfvnSw1Yj4M5Woi", + "txid": "f50e13cecda9a438ebd7df213a2899e42b2461a18d4630ee773d26b4f2688bdc", + "vout": 1, + "scriptPubKey": "76a914089acaba6af8b2b4fb4bed3b747ab1e4e60b496588ac", + "amount": 0.01 + }], + 'to', ['n3riXZowrjGnY74rx7Hdi9wCyvgyJC28zZ', 990000], + 'sign', ['cPwWtDztEgRCMCU8pMQp4HgphvyadrAsYBrCjXUZuDSmnZkyoyNF'], + 'serialize', '0100000001dc8b68f2b4263d77ee30468da161242be499283a21dfd7eb38a4a9cdce130ef5010000006a4730440220337e09c2729423302abe5e386d5e0f060ae8c006693f87342322bb1fe50065ff0220217a12de44139c57f01d35e988ffe3b0f86005d0cefcecf877b54c67473211d2012103e26b47e7c0d8946954bf9dd4bc7f9e415437eb98271d05f69e78cef8fc6c9a54ffffffff01301b0f00000000001976a914f50f9826ef186074c6fe206cca6b71472ff07ba888ac00000000' ] ]; diff --git a/test/transaction/sighash.js b/test/transaction/sighash.js index a514badea..6da0b3320 100644 --- a/test/transaction/sighash.js +++ b/test/transaction/sighash.js @@ -1,10 +1,8 @@ 'use strict'; var buffer = require('buffer'); -var bufferUtil = require('../../lib/util/buffer'); var Script = require('../../lib/script'); -var Signature = require('../../lib/crypto/signature'); var Transaction = require('../../lib/transaction'); var sighash = require('../../lib/transaction/sighash'); @@ -12,12 +10,12 @@ var vectors_sighash = require('./sighash.json'); describe('sighash', function() { - it('test vector from bitcoind', function() { - vectors_sighash.forEach(function(vector, i) { - if (i === 0) { - // First element is just a row describing the next ones - return; - } + vectors_sighash.forEach(function(vector, i) { + if (i === 0) { + // First element is just a row describing the next ones + return; + } + it('test vector from bitcoind #' + i + ' (' + vector[4].substring(0, 16) + ')', function() { var txbuf = new buffer.Buffer(vector[0], 'hex'); var scriptbuf = new buffer.Buffer(vector[1], 'hex'); var subscript = Script(scriptbuf); diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index c7612e8b0..1d534fa9e 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -50,7 +50,7 @@ describe('Transaction', function() { var transaction = new Transaction(); while (i < vector.length) { var command = vector[i]; - var args = vector[i+1]; + var args = vector[i + 1]; if (command === 'serialize') { transaction.serialize().should.equal(args); } else {