From c7fe710c4ddb65e7197d5f4d579f3ecf7c8a9dc5 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 11 Aug 2015 17:01:06 +1000 Subject: [PATCH 01/14] package: use typeforce 1.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f09265a..d74bd03 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "create-hmac": "^1.1.3", "ecurve": "^1.0.0", "randombytes": "^2.0.1", - "typeforce": "^1.0.0" + "typeforce": "^1.3.0" }, "devDependencies": { "async": "^0.9.0", From 254b670427411375284cc580fb98efa66d56c7bb Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 11 Aug 2015 17:01:47 +1000 Subject: [PATCH 02/14] add types --- src/address.js | 8 ++-- src/ecdsa.js | 57 +++++++++++++-------------- src/ecpair.js | 29 +++++++------- src/ecsignature.js | 7 ++-- src/message.js | 1 + src/script.js | 8 ++-- src/scripts.js | 18 ++++----- src/transaction.js | 31 +++++++-------- src/types.js | 71 ++++++++++++++++++++++++++++++++++ test/fixtures/ecdsa.json | 2 +- test/fixtures/transaction.json | 4 +- 11 files changed, 151 insertions(+), 85 deletions(-) create mode 100644 src/types.js diff --git a/src/address.js b/src/address.js index 5624f19..0def957 100644 --- a/src/address.js +++ b/src/address.js @@ -1,7 +1,8 @@ var base58check = require('bs58check') -var typeForce = require('typeforce') +var typeforce = require('typeforce') var networks = require('./networks') var scripts = require('./scripts') +var types = require('./types') function fromBase58Check (string) { var payload = base58check.decode(string) @@ -23,10 +24,7 @@ function fromOutputScript (script, network) { } function toBase58Check (hash, version) { - typeForce('Buffer', hash) - - if (hash.length !== 20) throw new TypeError('Invalid hash length') - if (version & ~0xff) throw new TypeError('Invalid version byte') + typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments) var payload = new Buffer(21) payload.writeUInt8(version, 0) diff --git a/src/ecdsa.js b/src/ecdsa.js index 0a07412..701285f 100644 --- a/src/ecdsa.js +++ b/src/ecdsa.js @@ -1,6 +1,6 @@ -var assert = require('assert') var createHmac = require('create-hmac') -var typeForce = require('typeforce') +var typeforce = require('typeforce') +var types = require('./types') var BigInteger = require('bigi') var ECSignature = require('./ecsignature') @@ -10,12 +10,12 @@ var ONE = new Buffer([1]) // https://tools.ietf.org/html/rfc6979#section-3.2 function deterministicGenerateK (curve, hash, d, checkSig) { - typeForce('Buffer', hash) - typeForce('BigInteger', d) - typeForce('Function', checkSig) - - // sanity check - assert.equal(hash.length, 32, 'Hash must be 256 bit') + typeforce(types.tuple( + types.ECCurve, + types.Hash256bit, + types.BigInt, + types.Function + ), arguments) var x = d.toBuffer(32) var k = new Buffer(32) @@ -75,9 +75,7 @@ function deterministicGenerateK (curve, hash, d, checkSig) { } function sign (curve, hash, d) { - typeForce('Curve', curve) - typeForce('Buffer', hash) - typeForce('BigInteger', d) + typeforce(types.tuple(types.ECCurve, types.Hash256bit, types.BigInt), arguments) var e = BigInteger.fromBuffer(hash) var n = curve.n @@ -109,10 +107,11 @@ function sign (curve, hash, d) { } function verify (curve, hash, signature, Q) { - typeForce('Curve', curve) - typeForce('Buffer', hash) - typeForce('ECSignature', signature) - typeForce('Point', Q) + typeforce(types.tuple(types.ECCurve, + types.Hash256bit, + types.ECSignature, + types.ECPoint + ), arguments) var n = curve.n var G = curve.G @@ -162,20 +161,20 @@ function verify (curve, hash, signature, Q) { * http://www.secg.org/download/aid-780/sec1-v2.pdf */ function recoverPubKey (curve, e, signature, i) { - typeForce('Curve', curve) - typeForce('BigInteger', e) - typeForce('ECSignature', signature) - typeForce('Number', i) - assert.strictEqual(i & 3, i, 'Recovery param is more than two bits') + typeforce(types.tuple( + types.ECCurve, + types.BigInt, + types.ECSignature, + types.UInt2 + ), arguments) var n = curve.n var G = curve.G - var r = signature.r var s = signature.s - assert(r.signum() > 0 && r.compareTo(n) < 0, 'Invalid r value') - assert(s.signum() > 0 && s.compareTo(n) < 0, 'Invalid s value') + if (r.signum() <= 0 || r.compareTo(n) >= 0) throw new Error('Invalid r value') + if (s.signum() <= 0 || s.compareTo(n) >= 0) throw new Error('Invalid s value') // A set LSB signifies that the y-coordinate is odd var isYOdd = i & 1 @@ -190,7 +189,7 @@ function recoverPubKey (curve, e, signature, i) { // 1.4 Check that nR is at infinity var nR = R.multiply(n) - assert(curve.isInfinity(nR), 'nR is not a valid curve point') + if (!curve.isInfinity(nR)) throw new Error('nR is not a valid curve point') // Compute r^-1 var rInv = r.modInverse(n) @@ -219,10 +218,12 @@ function recoverPubKey (curve, e, signature, i) { * that resulted in a successful pubkey recovery. */ function calcPubKeyRecoveryParam (curve, e, signature, Q) { - typeForce('Curve', curve) - typeForce('BigInteger', e) - typeForce('ECSignature', signature) - typeForce('Point', Q) + typeforce(types.tuple( + types.ECCurve, + types.BigInt, + types.ECSignature, + types.ECPoint + ), arguments) for (var i = 0; i < 4; i++) { var Qprime = recoverPubKey(curve, e, signature, i) diff --git a/src/ecpair.js b/src/ecpair.js index 8279428..f9c55b5 100644 --- a/src/ecpair.js +++ b/src/ecpair.js @@ -5,34 +5,34 @@ var ecdsa = require('./ecdsa') var ecurve = require('ecurve') var NETWORKS = require('./networks') var randomBytes = require('randombytes') -var typeForce = require('typeforce') +var typeforce = require('typeforce') +var types = require('./types') var BigInteger = require('bigi') function ECPair (d, Q, options) { options = options || {} - var compressed = options.compressed === undefined ? true : options.compressed - var network = options.network === undefined ? NETWORKS.bitcoin : options.network - - typeForce('Boolean', compressed) - assert('pubKeyHash' in network, 'Unknown pubKeyHash constants for network') + typeforce({ + compressed: types.maybe(types.Boolean), + network: types.maybe(types.Network) + }, options) if (d) { - assert(d.signum() > 0, 'Private key must be greater than 0') - assert(d.compareTo(ECPair.curve.n) < 0, 'Private key must be less than the curve order') + if (d.signum() <= 0) throw new Error('Private key must be greater than 0') + if (d.compareTo(ECPair.curve.n) >= 0) throw new Error('Private key must be less than the curve order') + if (Q) throw new TypeError('Unexpected publicKey parameter') - assert(!Q, 'Unexpected publicKey parameter') this.d = d - // enforce Q is a public key if no private key given } else { - typeForce('Point', Q) + typeforce(types.ECPoint, Q) + this.__Q = Q } - this.compressed = compressed - this.network = network + this.compressed = options.compressed === undefined ? true : options.compressed + this.network = options.network || NETWORKS.bitcoin } Object.defineProperty(ECPair.prototype, 'Q', { @@ -106,8 +106,7 @@ ECPair.makeRandom = function (options) { var rng = options.rng || randomBytes var buffer = rng(32) - typeForce('Buffer', buffer) - assert.equal(buffer.length, 32, 'Expected 256-bit Buffer from RNG') + typeforce(types.Buffer256bit, buffer) var d = BigInteger.fromBuffer(buffer) d = d.mod(ECPair.curve.n) diff --git a/src/ecsignature.js b/src/ecsignature.js index 151e962..b17ed8b 100644 --- a/src/ecsignature.js +++ b/src/ecsignature.js @@ -1,11 +1,12 @@ var assert = require('assert') -var typeForce = require('typeforce') +var typeforce = require('typeforce') +var types = require('./types') var BigInteger = require('bigi') function ECSignature (r, s) { - typeForce('BigInteger', r) - typeForce('BigInteger', s) + typeforce(types.BigInt, r) + typeforce(types.BigInt, s) this.r = r this.s = s diff --git a/src/message.js b/src/message.js index 75a3e2e..bced6dc 100644 --- a/src/message.js +++ b/src/message.js @@ -41,6 +41,7 @@ function verify (address, signature, message, network) { var parsed = ECSignature.parseCompact(signature) var e = BigInteger.fromBuffer(hash) var Q = ecdsa.recoverPubKey(ecparams, e, parsed.signature, parsed.i) + var keyPair = new ECPair(null, Q, { compressed: parsed.compressed, network: network diff --git a/src/script.js b/src/script.js index 141fb1f..12d21e4 100644 --- a/src/script.js +++ b/src/script.js @@ -1,12 +1,12 @@ var assert = require('assert') var bufferutils = require('./bufferutils') var crypto = require('./crypto') -var typeForce = require('typeforce') +var typeforce = require('typeforce') +var types = require('./types') var opcodes = require('./opcodes') function Script (buffer, chunks) { - typeForce('Buffer', buffer) - typeForce('Array', chunks) + typeforce(types.tuple(types.Buffer, types.Array), arguments) this.buffer = buffer this.chunks = chunks @@ -63,7 +63,7 @@ Script.fromBuffer = function (buffer) { } Script.fromChunks = function (chunks) { - typeForce('Array', chunks) + typeforce(types.Array, chunks) var bufferSize = chunks.reduce(function (accum, chunk) { // data chunk diff --git a/src/scripts.js b/src/scripts.js index c9e4978..8b57f08 100644 --- a/src/scripts.js +++ b/src/scripts.js @@ -1,6 +1,7 @@ var assert = require('assert') var ops = require('./opcodes') -var typeForce = require('typeforce') +var typeforce = require('typeforce') +var types = require('./types') var ecurve = require('ecurve') var curve = ecurve.getCurveByName('secp256k1') @@ -134,7 +135,7 @@ function isNullDataOutput (script) { } function classifyOutput (script) { - typeForce('Script', script) + typeforce(types.Script, script) if (isPubKeyHashOutput(script)) { return 'pubkeyhash' @@ -152,7 +153,7 @@ function classifyOutput (script) { } function classifyInput (script, allowIncomplete) { - typeForce('Script', script) + typeforce(types.Script, script) if (isPubKeyHashInput(script)) { return 'pubkeyhash' @@ -178,7 +179,7 @@ function pubKeyOutput (pubKey) { // OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG function pubKeyHashOutput (hash) { - typeForce('Buffer', hash) + typeforce(types.Hash160bit, hash) return Script.fromChunks([ ops.OP_DUP, @@ -191,7 +192,7 @@ function pubKeyHashOutput (hash) { // OP_HASH160 {scriptHash} OP_EQUAL function scriptHashOutput (hash) { - typeForce('Buffer', hash) + typeforce(types.Hash160bit, hash) return Script.fromChunks([ ops.OP_HASH160, @@ -202,7 +203,7 @@ function scriptHashOutput (hash) { // m [pubKeys ...] n OP_CHECKMULTISIG function multisigOutput (m, pubKeys) { - typeForce(['Buffer'], pubKeys) + typeforce(types.tuple(types.Number, [types.Buffer]), arguments) var n = pubKeys.length assert(n >= m, 'Not enough pubKeys provided') @@ -217,15 +218,14 @@ function multisigOutput (m, pubKeys) { // {signature} function pubKeyInput (signature) { - typeForce('Buffer', signature) + typeforce(types.Buffer, signature) return Script.fromChunks([signature]) } // {signature} {pubKey} function pubKeyHashInput (signature, pubKey) { - typeForce('Buffer', signature) - typeForce('Buffer', pubKey) + typeforce(types.tuple(types.Buffer, types.Buffer), arguments) return Script.fromChunks([signature, pubKey]) } diff --git a/src/transaction.js b/src/transaction.js index 95cd2be..3a83193 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -1,7 +1,8 @@ var assert = require('assert') var bufferutils = require('./bufferutils') var crypto = require('./crypto') -var typeForce = require('typeforce') +var typeforce = require('typeforce') +var types = require('./types') var opcodes = require('./opcodes') var Script = require('./script') @@ -104,19 +105,19 @@ Transaction.isCoinbaseHash = function (buffer) { } Transaction.prototype.addInput = function (hash, index, sequence, script) { - if (sequence === undefined || sequence === null) { + typeforce(types.tuple( + types.Hash256bit, + types.UInt32, + types.maybe(types.UInt32), + types.maybe(types.Script) + ), arguments) + + if (types.Null(sequence)) { sequence = Transaction.DEFAULT_SEQUENCE } script = script || Script.EMPTY - typeForce('Buffer', hash) - typeForce('Number', index) - typeForce('Number', sequence) - typeForce('Script', script) - - assert.equal(hash.length, 32, 'Expected hash length of 32, got ' + hash.length) - // Add the input and return the input's index return (this.ins.push({ hash: hash, @@ -127,8 +128,7 @@ Transaction.prototype.addInput = function (hash, index, sequence, script) { } Transaction.prototype.addOutput = function (scriptPubKey, value) { - typeForce('Script', scriptPubKey) - typeForce('Number', value) + typeforce(types.tuple(types.Script, types.UInt53), arguments) // Add the output and return the output's index return (this.outs.push({ @@ -188,11 +188,7 @@ var ONE = new Buffer('0000000000000000000000000000000000000000000000000000000000 * This hash can then be used to sign the provided transaction input. */ Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashType) { - typeForce('Number', inIndex) - typeForce('Script', prevOutScript) - typeForce('Number', hashType) - - assert(inIndex >= 0, 'Invalid vin index') + typeforce(types.tuple(types.UInt32, types.Script, /* types.UInt8 */ types.Number), arguments) // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29 if (inIndex >= this.ins.length) return ONE @@ -327,8 +323,7 @@ Transaction.prototype.toHex = function () { } Transaction.prototype.setInputScript = function (index, script) { - typeForce('Number', index) - typeForce('Script', script) + typeforce(types.tuple(types.Number, types.Script), arguments) this.ins[index].script = script } diff --git a/src/types.js b/src/types.js new file mode 100644 index 0000000..ad8d62c --- /dev/null +++ b/src/types.js @@ -0,0 +1,71 @@ +var bigi = require('bigi') +var ecurve = require('ecurve') +var typeforce = require('typeforce') + +function nBuffer (value, n) { + if (!Buffer.isBuffer(value)) return false + if (value.length !== n) throw new Error('Expected ' + (n * 8) + '-bit Buffer, got ' + (value.length * 8) + '-bit') + return true +} + +function Hash160bit (value) { return nBuffer(value, 20) } +function Hash256bit (value) { return nBuffer(value, 32) } +function Buffer256bit (value) { return nBuffer(value, 32) } + +var UINT53_MAX = Math.pow(2, 53) - 1 +function UInt2 (value) { return (value & 3) === value } +function UInt8 (value) { return (value & 0xff) === value } +function UInt32 (value) { return (value >>> 0) === value } +function UInt53 (value) { + return typeforce.Number(value) && + value >= 0 && + value <= UINT53_MAX && + Math.floor(value) === value +} + +// external dependent types +function BigInt (value) { return !typeforce.Null(value) && value.constructor === bigi } +function ECCurve (value) { return !typeforce.Null(value) && value.constructor === ecurve.Curve } +function ECPoint (value) { return !typeforce.Null(value) && value.constructor === ecurve.Point } + +// exposed, external API +var ECSignature = typeforce.compile({ r: BigInt, s: BigInt }) +var Network = typeforce.compile({ + messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), + bip32: { + public: UInt32, + private: UInt32 + }, + pubKeyHash: UInt8, + scriptHash: UInt8, + wif: UInt8, + dustThreshold: typeforce.Number +}) + +var Script = typeforce.compile({ + buffer: typeforce.Buffer, + chunks: typeforce.arrayOf(typeforce.oneOf(typeforce.Number, typeforce.Buffer)) +}) + +// extend typeforce types with ours +var types = { + BigInt: BigInt, + Buffer256bit: Buffer256bit, + ECCurve: ECCurve, + ECPoint: ECPoint, + ECSignature: ECSignature, + Hash160bit: Hash160bit, + Hash256bit: Hash256bit, + Network: Network, + Script: Script, + UInt2: UInt2, + UInt8: UInt8, + UInt32: UInt32, + UInt53: UInt53 +} + +for (var typeName in typeforce) { + types[typeName] = typeforce[typeName] +} + +module.exports = types diff --git a/test/fixtures/ecdsa.json b/test/fixtures/ecdsa.json index 0205d54..ffd6cba 100644 --- a/test/fixtures/ecdsa.json +++ b/test/fixtures/ecdsa.json @@ -188,7 +188,7 @@ }, { "description": "Invalid i value (> 3)", - "exception": "Recovery param is more than two bits", + "exception": "Expected UInt2, got Number 4", "e": "01", "signatureRaw": { "r": "00", diff --git a/test/fixtures/transaction.json b/test/fixtures/transaction.json index 9710818..a47cf3a 100644 --- a/test/fixtures/transaction.json +++ b/test/fixtures/transaction.json @@ -229,12 +229,12 @@ "invalid": { "addInput": [ { - "exception": "Expected hash length of 32, got 30", + "exception": "Expected 256-bit Buffer, got 240-bit", "hash": "0aed1366a73b6057ee7800d737bff1bdf8c448e98d86bc0998f2b009816d", "index": 0 }, { - "exception": "Expected hash length of 32, got 34", + "exception": "Expected 256-bit Buffer, got 272-bit", "hash": "0aed1366a73b6057ee7800d737bff1bdf8c448e98d86bc0998f2b009816da9b0ffff", "index": 0 } From 8f821f47686320e188a3af9a1c0ca193d4ab4c0a Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 11 Aug 2015 17:03:10 +1000 Subject: [PATCH 03/14] ECPair: remove use of assert --- src/ecpair.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/ecpair.js b/src/ecpair.js index f9c55b5..bafc311 100644 --- a/src/ecpair.js +++ b/src/ecpair.js @@ -1,4 +1,3 @@ -var assert = require('assert') var bs58check = require('bs58check') var bcrypto = require('./crypto') var ecdsa = require('./ecdsa') @@ -63,7 +62,7 @@ ECPair.fromWIF = function (string, networks) { var compressed if (payload.length === 34) { - assert.strictEqual(payload[33], 0x01, 'Invalid compression flag') + if (payload[33] !== 0x01) throw new Error('Invalid compression flag') // truncate the version/compression bytes payload = payload.slice(1, -1) @@ -71,7 +70,7 @@ ECPair.fromWIF = function (string, networks) { // no compression flag } else { - assert.equal(payload.length, 33, 'Invalid WIF payload length') + if (payload.length !== 33) throw new Error('Invalid WIF payload length') // Truncate the version byte payload = payload.slice(1) @@ -115,7 +114,7 @@ ECPair.makeRandom = function (options) { } ECPair.prototype.toWIF = function () { - assert(this.d, 'Missing private key') + if (!this.d) throw new Error('Missing private key') var bufferLen = this.compressed ? 34 : 33 var buffer = new Buffer(bufferLen) @@ -146,7 +145,7 @@ ECPair.prototype.getPublicKeyBuffer = function () { } ECPair.prototype.sign = function (hash) { - assert(this.d, 'Missing private key') + if (!this.d) throw new Error('Missing private key') return ecdsa.sign(ECPair.curve, hash, this.d) } From 395c43ad74c582606409dd14df0cc6d3e5cfbe1e Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 11 Aug 2015 17:03:46 +1000 Subject: [PATCH 04/14] block: remove use of assert --- src/block.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/block.js b/src/block.js index 5d4dc1a..8a36f65 100644 --- a/src/block.js +++ b/src/block.js @@ -1,4 +1,3 @@ -var assert = require('assert') var bufferutils = require('./bufferutils') var crypto = require('./crypto') @@ -14,7 +13,7 @@ function Block () { } Block.fromBuffer = function (buffer) { - assert(buffer.length >= 80, 'Buffer too small (< 80 bytes)') + if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)') var offset = 0 function readSlice (n) { From 1d4a71852fa623c5153f7d053b314694e55d32ca Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 11 Aug 2015 17:05:27 +1000 Subject: [PATCH 05/14] Script: remove use of assert --- src/script.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/script.js b/src/script.js index 12d21e4..a1398b8 100644 --- a/src/script.js +++ b/src/script.js @@ -1,4 +1,3 @@ -var assert = require('assert') var bufferutils = require('./bufferutils') var crypto = require('./crypto') var typeforce = require('typeforce') @@ -93,7 +92,7 @@ Script.fromChunks = function (chunks) { } }) - assert.equal(offset, buffer.length, 'Could not decode chunks') + if (offset !== buffer.length) throw new Error('Could not decode chunks') return new Script(buffer, chunks) } From e4b697a261a59af17d276a14e4471446bd1e9148 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 11 Aug 2015 17:07:13 +1000 Subject: [PATCH 06/14] scripts: remove use of assert --- src/scripts.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/scripts.js b/src/scripts.js index 8b57f08..d6add2f 100644 --- a/src/scripts.js +++ b/src/scripts.js @@ -1,4 +1,3 @@ -var assert = require('assert') var ops = require('./opcodes') var typeforce = require('typeforce') var types = require('./types') @@ -206,7 +205,7 @@ function multisigOutput (m, pubKeys) { typeforce(types.tuple(types.Number, [types.Buffer]), arguments) var n = pubKeys.length - assert(n >= m, 'Not enough pubKeys provided') + if (n < m) throw new Error('Not enough pubKeys provided') return Script.fromChunks([].concat( (ops.OP_1 - 1) + m, @@ -241,15 +240,15 @@ function scriptHashInput (scriptSig, scriptPubKey) { // OP_0 [signatures ...] function multisigInput (signatures, scriptPubKey) { if (scriptPubKey) { - assert(isMultisigOutput(scriptPubKey)) + if (!isMultisigOutput(scriptPubKey)) throw new Error('Expected multisig scriptPubKey') var mOp = scriptPubKey.chunks[0] var nOp = scriptPubKey.chunks[scriptPubKey.chunks.length - 2] var m = mOp - (ops.OP_1 - 1) var n = nOp - (ops.OP_1 - 1) - assert(signatures.length >= m, 'Not enough signatures provided') - assert(signatures.length <= n, 'Too many signatures provided') + if (signatures.length < m) throw new Error('Not enough signatures provided') + if (signatures.length > n) throw new Error('Too many signatures provided') } return Script.fromChunks([].concat(ops.OP_0, signatures)) From f2f07471878253396ed2e9c292558618c5ed57ee Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 11 Aug 2015 17:09:34 +1000 Subject: [PATCH 07/14] Transaction: remove use of assert --- src/transaction.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index 3a83193..5085f91 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -1,4 +1,3 @@ -var assert = require('assert') var bufferutils = require('./bufferutils') var crypto = require('./crypto') var typeforce = require('typeforce') @@ -20,7 +19,7 @@ Transaction.SIGHASH_NONE = 0x02 Transaction.SIGHASH_SINGLE = 0x03 Transaction.SIGHASH_ANYONECANPAY = 0x80 -Transaction.fromBuffer = function (buffer, __disableAssert) { +Transaction.fromBuffer = function (buffer, __disableExcess) { var offset = 0 function readSlice (n) { offset += n @@ -87,8 +86,8 @@ Transaction.fromBuffer = function (buffer, __disableAssert) { tx.locktime = readUInt32() - if (!__disableAssert) { - assert.equal(offset, buffer.length, 'Transaction has unexpected data') + if (!__disableExcess) { + if (offset !== buffer.length) throw new Error('Transaction has unexpected data') } return tx From c1a965d09697c6ebdaf8c2a7d12e17e9b4be2874 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 11 Aug 2015 17:21:04 +1000 Subject: [PATCH 08/14] ECSignature: remove use of assert --- src/ecsignature.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/ecsignature.js b/src/ecsignature.js index b17ed8b..ecb4489 100644 --- a/src/ecsignature.js +++ b/src/ecsignature.js @@ -1,4 +1,3 @@ -var assert = require('assert') var typeforce = require('typeforce') var types = require('./types') @@ -13,22 +12,20 @@ function ECSignature (r, s) { } ECSignature.parseCompact = function (buffer) { - assert.equal(buffer.length, 65, 'Invalid signature length') - var i = buffer.readUInt8(0) - 27 + if (buffer.length !== 65) throw new Error('Invalid signature length') - // At most 3 bits - assert.equal(i, i & 7, 'Invalid signature parameter') - var compressed = !!(i & 4) + var flagByte = buffer.readUInt8(0) - 27 + if (flagByte !== (flagByte & 7)) throw new Error('Invalid signature parameter') - // Recovery param only - i = i & 3 + var compressed = !!(flagByte & 4) + var recoveryParam = flagByte & 3 var r = BigInteger.fromBuffer(buffer.slice(1, 33)) var s = BigInteger.fromBuffer(buffer.slice(33)) return { compressed: compressed, - i: i, + i: recoveryParam, signature: new ECSignature(r, s) } } @@ -118,7 +115,7 @@ ECSignature.prototype.toDER = function () { ECSignature.prototype.toScriptSignature = function (hashType) { var hashTypeMod = hashType & ~0x80 - assert(hashTypeMod > 0x00 && hashTypeMod < 0x04, 'Invalid hashType ' + hashType) + if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new Error('Invalid hashType ' + hashType) var hashTypeBuffer = new Buffer(1) hashTypeBuffer.writeUInt8(hashType, 0) From d85df4a6d61e6c4ff33c9bcc8fa7956839c5baf0 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 11 Aug 2015 18:39:59 +1000 Subject: [PATCH 09/14] TransactionBuilder: remove use of assert --- src/script.js | 4 ++ src/transaction_builder.js | 54 ++++++++++++++------------ test/fixtures/transaction_builder.json | 2 +- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/script.js b/src/script.js index a1398b8..211c74d 100644 --- a/src/script.js +++ b/src/script.js @@ -102,6 +102,10 @@ Script.fromHex = function (hex) { Script.EMPTY = Script.fromChunks([]) +Script.prototype.equals = function (script) { + return bufferutils.equal(this.buffer, script.buffer) +} + Script.prototype.getHash = function () { return crypto.hash160(this.buffer) } diff --git a/src/transaction_builder.js b/src/transaction_builder.js index 9914713..421bb9b 100644 --- a/src/transaction_builder.js +++ b/src/transaction_builder.js @@ -1,4 +1,3 @@ -var assert = require('assert') var bcrypto = require('./crypto') var bufferutils = require('./bufferutils') var networks = require('./networks') @@ -160,14 +159,16 @@ TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOu input.prevOutType = prevOutType } - assert(this.inputs.every(function (input2) { + var valid = this.inputs.every(function (input2) { if (input2.hashType === undefined) return true return input2.hashType & Transaction.SIGHASH_ANYONECANPAY - }), 'No, this would invalidate signatures') + }) + + if (!valid) throw new Error('No, this would invalidate signatures') var prevOut = txHash.toString('hex') + ':' + vout - assert(!(prevOut in this.prevTxMap), 'Transaction is already an input') + if (this.prevTxMap[prevOut]) throw new Error('Transaction is already an input') var vin = this.tx.addInput(txHash, vout, sequence) this.inputs[vin] = input @@ -177,11 +178,13 @@ TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOu } TransactionBuilder.prototype.addOutput = function (scriptPubKey, value) { - assert(this.inputs.every(function (input) { + var valid = this.inputs.every(function (input) { if (input.hashType === undefined) return true return (input.hashType & 0x1f) === Transaction.SIGHASH_SINGLE - }), 'No, this would invalidate signatures') + }) + + if (!valid) throw new Error('No, this would invalidate signatures') // Attempt to get a script if it's a base58 address string if (typeof scriptPubKey === 'string') { @@ -206,8 +209,8 @@ var canSignTypes = { TransactionBuilder.prototype.__build = function (allowIncomplete) { if (!allowIncomplete) { - assert(this.tx.ins.length > 0, 'Transaction has no inputs') - assert(this.tx.outs.length > 0, 'Transaction has no outputs') + if (!this.tx.ins.length) throw new Error('Transaction has no inputs') + if (!this.tx.outs.length) throw new Error('Transaction has no outputs') } var tx = this.tx.clone() @@ -218,9 +221,9 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) { var scriptSig if (!allowIncomplete) { - assert(!!scriptType, 'Transaction is not complete') - assert(scriptType in canSignTypes, scriptType + ' not supported') - assert(input.signatures, 'Transaction is missing signatures') + if (!scriptType) throw new Error('Transaction is not complete') + if (!canSignTypes[scriptType]) throw new Error(scriptType + ' not supported') + if (!input.signatures) throw new Error('Transaction is missing signatures') } if (input.signatures) { @@ -274,8 +277,8 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) { } TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hashType) { - assert.equal(keyPair.network, this.network, 'Inconsistent network') - assert(index in this.inputs, 'No input at index: ' + index) + if (keyPair.network !== this.network) throw new Error('Inconsistent network') + if (!this.inputs[index]) throw new Error('No input at index: ' + index) hashType = hashType || Transaction.SIGHASH_ALL var input = this.inputs[index] @@ -292,10 +295,10 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash if (canSign) { // if redeemScript was provided, enforce consistency if (redeemScript) { - assert.deepEqual(input.redeemScript, redeemScript, 'Inconsistent redeemScript') + if (!input.redeemScript.equals(redeemScript)) throw new Error('Inconsistent redeemScript') } - assert.equal(input.hashType, hashType, 'Inconsistent hashType') + if (input.hashType !== hashType) throw new Error('Inconsistent hashType') // no? prepare } else { @@ -303,14 +306,14 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash if (redeemScript) { // if we have a prevOutScript, enforce scriptHash equality to the redeemScript if (input.prevOutScript) { - assert.equal(input.prevOutType, 'scripthash', 'PrevOutScript must be P2SH') + if (input.prevOutType !== 'scripthash') throw new Error('PrevOutScript must be P2SH') var scriptHash = input.prevOutScript.chunks[1] - assert.deepEqual(scriptHash, redeemScript.getHash(), 'RedeemScript does not match ' + scriptHash.toString('hex')) + if (!bufferutils.equal(scriptHash, redeemScript.getHash())) throw new Error('RedeemScript does not match ' + scriptHash.toString('hex')) } var scriptType = scripts.classifyOutput(redeemScript) - assert(scriptType in canSignTypes, 'RedeemScript not supported (' + scriptType + ')') + if (!canSignTypes[scriptType]) throw new Error('RedeemScript not supported (' + scriptType + ')') var pubKeys = [] switch (scriptType) { @@ -322,7 +325,7 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash var pkh1 = redeemScript.chunks[2] var pkh2 = bcrypto.hash160(keyPair.getPublicKeyBuffer()) - assert.deepEqual(pkh1, pkh2, 'privateKey cannot sign for this input') + if (!bufferutils.equal(pkh1, pkh2)) throw new Error('privateKey cannot sign for this input') pubKeys = [kpPubKey] break @@ -342,11 +345,11 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash // cannot be pay-to-scriptHash } else { - assert.notEqual(input.prevOutType, 'scripthash', 'PrevOutScript is P2SH, missing redeemScript') + if (input.prevOutType === 'scripthash') throw new Error('PrevOutScript is P2SH, missing redeemScript') // can we otherwise sign this? if (input.scriptType) { - assert(input.pubKeys, input.scriptType + ' not supported') + if (!input.pubKeys) throw new Error(input.scriptType + ' not supported') // we know nothin' Jon Snow, assume pubKeyHash } else { @@ -389,16 +392,17 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash } // enforce in order signing of public keys - assert(input.pubKeys.some(function (pubKey, i) { + var valid = input.pubKeys.some(function (pubKey, i) { if (!bufferutils.equal(kpPubKey, pubKey)) return false - - assert(!input.signatures[i], 'Signature already exists') + if (input.signatures[i]) throw new Error('Signature already exists') var signature = keyPair.sign(signatureHash) input.signatures[i] = signature return true - }), 'key pair cannot sign for this input') + }) + + if (!valid) throw new Error('Key pair cannot sign for this input') } module.exports = TransactionBuilder diff --git a/test/fixtures/transaction_builder.json b/test/fixtures/transaction_builder.json index abc850d..e530f54 100644 --- a/test/fixtures/transaction_builder.json +++ b/test/fixtures/transaction_builder.json @@ -780,7 +780,7 @@ }, { "description": "Wrong key pair for multisig redeemScript", - "exception": "key pair cannot sign for this input", + "exception": "Key pair cannot sign for this input", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", From 70c874e3cc2460839f43446465ef52a20a5b6317 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 11 Aug 2015 18:41:24 +1000 Subject: [PATCH 10/14] bufferutils: remove use of assert --- src/bufferutils.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/bufferutils.js b/src/bufferutils.js index 773e502..a6f7664 100644 --- a/src/bufferutils.js +++ b/src/bufferutils.js @@ -1,12 +1,11 @@ -var assert = require('assert') var opcodes = require('./opcodes') // https://github.com/feross/buffer/blob/master/index.js#L1127 function verifuint (value, max) { - assert(typeof value === 'number', 'cannot write a non-number as a number') - assert(value >= 0, 'specified a negative value for writing an unsigned value') - assert(value <= max, 'value is larger than maximum value for type') - assert(Math.floor(value) === value, 'value has a fractional component') + if (typeof value !== 'number') throw new Error('cannot write a non-number as a number') + if (value < 0) throw new Error('specified a negative value for writing an unsigned value') + if (value > max) throw new Error('value is larger than maximum value for type') + if (Math.floor(value) !== value) throw new Error('value has a fractional component') } function pushDataSize (i) { @@ -40,7 +39,7 @@ function readPushDataInt (buffer, offset) { // 32 bit } else { if (offset + 5 > buffer.length) return null - assert.equal(opcode, opcodes.OP_PUSHDATA4, 'Unexpected opcode') + if (opcode !== opcodes.OP_PUSHDATA4) throw new Error('Unexpected opcode') number = buffer.readUInt32LE(offset + 1) size = 5 From 33a9dc4cce90b455a14d206f5217117ffcf098b4 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 12 Aug 2015 13:09:53 +1000 Subject: [PATCH 11/14] dustThreshold is a UInt53 --- src/types.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.js b/src/types.js index ad8d62c..bd7c149 100644 --- a/src/types.js +++ b/src/types.js @@ -39,7 +39,7 @@ var Network = typeforce.compile({ pubKeyHash: UInt8, scriptHash: UInt8, wif: UInt8, - dustThreshold: typeforce.Number + dustThreshold: UInt53 }) var Script = typeforce.compile({ From 92d16142d1e7c7713da0937a460d5769a584f8ec Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 12 Aug 2015 13:17:24 +1000 Subject: [PATCH 12/14] ECSignature: use types.tuple for arguments --- src/ecsignature.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ecsignature.js b/src/ecsignature.js index ecb4489..940387b 100644 --- a/src/ecsignature.js +++ b/src/ecsignature.js @@ -4,8 +4,7 @@ var types = require('./types') var BigInteger = require('bigi') function ECSignature (r, s) { - typeforce(types.BigInt, r) - typeforce(types.BigInt, s) + typeforce(types.tuple(types.BigInt, types.BigInt), arguments) this.r = r this.s = s @@ -40,7 +39,7 @@ ECSignature.fromDER = function (buffer) { if (buffer[1] !== buffer.length - 2) throw new Error('Invalid sequence length') if (buffer[2] !== 0x02) throw new Error('Expected a DER integer') - var lenR = buffer.readUInt8(3) + var lenR = buffer[3] if (lenR === 0) throw new Error('R length is zero') if (5 + lenR >= buffer.length) throw new Error('Invalid DER encoding') if (buffer[4 + lenR] !== 0x02) throw new Error('Expected a DER integer (2)') From 94e16fbe37486c8320c81c7997ae1a461d9737e1 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Fri, 14 Aug 2015 08:27:27 +1000 Subject: [PATCH 13/14] rename __disableExcess to __noStrict --- src/transaction.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index 5085f91..1267b5e 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -19,7 +19,7 @@ Transaction.SIGHASH_NONE = 0x02 Transaction.SIGHASH_SINGLE = 0x03 Transaction.SIGHASH_ANYONECANPAY = 0x80 -Transaction.fromBuffer = function (buffer, __disableExcess) { +Transaction.fromBuffer = function (buffer, __noStrict) { var offset = 0 function readSlice (n) { offset += n @@ -86,9 +86,8 @@ Transaction.fromBuffer = function (buffer, __disableExcess) { tx.locktime = readUInt32() - if (!__disableExcess) { - if (offset !== buffer.length) throw new Error('Transaction has unexpected data') - } + if (__noStrict) return tx + if (offset !== buffer.length) throw new Error('Transaction has unexpected data') return tx } From 6002d9abd4458e8d53292306a2bd6045ed6f2bef Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Fri, 14 Aug 2015 08:27:57 +1000 Subject: [PATCH 14/14] ecdsa: fix up tuple indentation --- src/ecdsa.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ecdsa.js b/src/ecdsa.js index 701285f..a5190b7 100644 --- a/src/ecdsa.js +++ b/src/ecdsa.js @@ -107,7 +107,8 @@ function sign (curve, hash, d) { } function verify (curve, hash, signature, Q) { - typeforce(types.tuple(types.ECCurve, + typeforce(types.tuple( + types.ECCurve, types.Hash256bit, types.ECSignature, types.ECPoint