From 254b670427411375284cc580fb98efa66d56c7bb Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 11 Aug 2015 17:01:47 +1000 Subject: [PATCH] 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 }