Repository: bitcoinjs/bitcoinjs-lib
Pull request #434 (2015-08-12 03:03:06)

Remove assert, use typeforce typing
Will wait on ACK before merge.

ping @weilu, @jprichardson 

Curious on your opinions about this...

dcousens: I'm happy to break out the `typeforce` typing changes if theres any issues,  as the move away from `assert` is a much simpler decision and it does have highly tangible benefits.
jprichardson: > as the move away from assert is a much simpler decision and it does have highly tangible benefits.

I think for posterity, these benefits should be explicitly stated.
weilu: I don't get why you are getting rid of asserts. IMHO in most of the places, assert is still more direct and semantically straightforward. It's not like we are throwing any custom Error types for better error handling. 
dcousens: > I think for posterity, these benefits should be explicitly stated.

* In a production application, with over 30 high level dependencies,  `bitcoinjs-lib`, `bs58`, `bip39`, `bs58check`,  are the only dependencies using `assert` for error handling.   All other conclusions aside,  this is a specific pattern we have adopted for our ecosystem.

* `assert` explicitly makes code coverage difficult,  because the branch is deeper down in the application.   `typeforce` changes do not help this,  however,  the majority of `typeforce` changes were `type` checking.  The majority of logic based changes that now exist as `throw` conditions are now visible in code coverage.

* `assert` only accepts value arguments (like any function), therefore error messages are evaluated/built on every call.   This has caused major performance problems for us in the past where `JSON.stringify` or `toString` has been more complex then expected and excessively slowed down applications.

* `assert.equal` is more often used over `assert.strictEqual`,  most likely because it is shorter to write.  We actually have our linter fail our tests if we wrote this code normally, and yet, we have no qualms with this.  Granted,  that could just be fixed by enforcing `strictEqual`,  it is more verbose and often longer code than just writing out the explicit branch anyway. 

Patch:
diff --git a/package.json b/package.json
index f09265af..d74bd03e 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",
diff --git a/src/address.js b/src/address.js
index 5624f195..0def957a 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/block.js b/src/block.js
index 5d4dc1a8..8a36f651 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) {
diff --git a/src/bufferutils.js b/src/bufferutils.js
index 773e5021..a6f7664b 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
diff --git a/src/ecdsa.js b/src/ecdsa.js
index 0a074128..a5190b76 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,12 @@ 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 +162,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 +190,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 +219,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 8279428a..bafc311b 100644
--- a/src/ecpair.js
+++ b/src/ecpair.js
@@ -1,38 +1,37 @@
-var assert = require('assert')
 var bs58check = require('bs58check')
 var bcrypto = require('./crypto')
 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', {
@@ -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)
@@ -106,8 +105,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)
@@ -116,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)
@@ -147,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)
 }
diff --git a/src/ecsignature.js b/src/ecsignature.js
index 151e962d..940387b8 100644
--- a/src/ecsignature.js
+++ b/src/ecsignature.js
@@ -1,33 +1,30 @@
-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.tuple(types.BigInt, types.BigInt), arguments)
 
   this.r = r
   this.s = 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)
   }
 }
@@ -42,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)')
@@ -117,7 +114,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)
diff --git a/src/message.js b/src/message.js
index 75a3e2ef..bced6dc0 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 141fb1fc..211c74d4 100644
--- a/src/script.js
+++ b/src/script.js
@@ -1,12 +1,11 @@
-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 +62,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
@@ -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)
 }
 
@@ -103,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/scripts.js b/src/scripts.js
index c9e49780..d6add2f1 100644
--- a/src/scripts.js
+++ b/src/scripts.js
@@ -1,6 +1,6 @@
-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 +134,7 @@ function isNullDataOutput (script) {
 }
 
 function classifyOutput (script) {
-  typeForce('Script', script)
+  typeforce(types.Script, script)
 
   if (isPubKeyHashOutput(script)) {
     return 'pubkeyhash'
@@ -152,7 +152,7 @@ function classifyOutput (script) {
 }
 
 function classifyInput (script, allowIncomplete) {
-  typeForce('Script', script)
+  typeforce(types.Script, script)
 
   if (isPubKeyHashInput(script)) {
     return 'pubkeyhash'
@@ -178,7 +178,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 +191,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,10 +202,10 @@ 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')
+  if (n < m) throw new Error('Not enough pubKeys provided')
 
   return Script.fromChunks([].concat(
     (ops.OP_1 - 1) + m,
@@ -217,15 +217,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])
 }
@@ -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))
diff --git a/src/transaction.js b/src/transaction.js
index 95cd2be5..1267b5e0 100644
--- a/src/transaction.js
+++ b/src/transaction.js
@@ -1,7 +1,7 @@
-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')
@@ -19,7 +19,7 @@ Transaction.SIGHASH_NONE = 0x02
 Transaction.SIGHASH_SINGLE = 0x03
 Transaction.SIGHASH_ANYONECANPAY = 0x80
 
-Transaction.fromBuffer = function (buffer, __disableAssert) {
+Transaction.fromBuffer = function (buffer, __noStrict) {
   var offset = 0
   function readSlice (n) {
     offset += n
@@ -86,9 +86,8 @@ Transaction.fromBuffer = function (buffer, __disableAssert) {
 
   tx.locktime = readUInt32()
 
-  if (!__disableAssert) {
-    assert.equal(offset, buffer.length, 'Transaction has unexpected data')
-  }
+  if (__noStrict) return tx
+  if (offset !== buffer.length) throw new Error('Transaction has unexpected data')
 
   return tx
 }
@@ -104,19 +103,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 +126,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 +186,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 +321,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/transaction_builder.js b/src/transaction_builder.js
index 9914713c..421bb9b9 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/src/types.js b/src/types.js
new file mode 100644
index 00000000..bd7c149c
--- /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: UInt53
+})
+
+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


Test patch:
diff --git a/test/fixtures/ecdsa.json b/test/fixtures/ecdsa.json
index 0205d549..ffd6cba7 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 97108181..a47cf3a1 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
       }
diff --git a/test/fixtures/transaction_builder.json b/test/fixtures/transaction_builder.json
index abc850d0..e530f543 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",