Repository: bitcoinjs/bitcoinjs-lib
Pull request #915 (2017-10-19 00:27:39)

BIP32/secp256k1/keyPair preparation
As we move towards moving `HDNode` to [`bip32`](https://github.com/bitcoinjs/bip32/),  and supporting `secp256k1`,  I'm putting this PR up so we can move forward on those API's by liberating the type signature of `TransactionBuilder.prototype.sign` a little bit.

This would simultaneously allow [`HDKey`](https://github.com/cryptocoinjs/hdkey/pull/12) to inter-operate with `TransactionBuilder`,   supporting `secp256k1` signing operations near-immediately for those who use that module.
And soon-thereafter for our own [new `HDNode` module](https://github.com/bitcoinjs/bip32/).

`toRSBuffer` and `fromRSBuffer` are conceptually part of https://github.com/bitcoinjs/bitcoinjs-lib/pull/459,  for the [`4.0.0`](https://github.com/bitcoinjs/bitcoinjs-lib/issues/508) API.
That is,  with `ECSignature` removed in `4.0.0`, we will only interop on "RSBuffer"s,  a-kin to `secp256k1`,  and only need to transcode to a script signature when encoding the `Transaction` itself.

For now,  requesting feedback,


## TODO
- [x] ~~Add `TransactionBuilder.prototype.sign` tests for new functionality~~

junderw: SGTM for now.

Patch:
diff --git a/src/ecsignature.js b/src/ecsignature.js
index 897d7e81..ebdcc364 100644
--- a/src/ecsignature.js
+++ b/src/ecsignature.js
@@ -12,24 +12,30 @@ function ECSignature (r, s) {
 }
 
 ECSignature.parseCompact = function (buffer) {
-  if (buffer.length !== 65) throw new Error('Invalid signature length')
+  typeforce(types.BufferN(65), buffer)
 
   var flagByte = buffer.readUInt8(0) - 27
   if (flagByte !== (flagByte & 7)) throw new Error('Invalid signature parameter')
 
   var compressed = !!(flagByte & 4)
   var recoveryParam = flagByte & 3
-
-  var r = BigInteger.fromBuffer(buffer.slice(1, 33))
-  var s = BigInteger.fromBuffer(buffer.slice(33))
+  var signature = ECSignature.fromRSBuffer(buffer.slice(1))
 
   return {
     compressed: compressed,
     i: recoveryParam,
-    signature: new ECSignature(r, s)
+    signature: signature
   }
 }
 
+ECSignature.fromRSBuffer = function (buffer) {
+  typeforce(types.BufferN(64), buffer)
+
+  var r = BigInteger.fromBuffer(buffer.slice(0, 32))
+  var s = BigInteger.fromBuffer(buffer.slice(32, 64))
+  return new ECSignature(r, s)
+}
+
 ECSignature.fromDER = function (buffer) {
   var decode = bip66.decode(buffer)
   var r = BigInteger.fromDERInteger(decode.r)
@@ -60,9 +66,7 @@ ECSignature.prototype.toCompact = function (i, compressed) {
 
   var buffer = Buffer.alloc(65)
   buffer.writeUInt8(i, 0)
-  this.r.toBuffer(32).copy(buffer, 1)
-  this.s.toBuffer(32).copy(buffer, 33)
-
+  this.toRSBuffer(buffer, 1)
   return buffer
 }
 
@@ -73,6 +77,13 @@ ECSignature.prototype.toDER = function () {
   return bip66.encode(r, s)
 }
 
+ECSignature.prototype.toRSBuffer = function (buffer, offset) {
+  buffer = buffer || Buffer.alloc(64)
+  this.r.toBuffer(32).copy(buffer, offset)
+  this.s.toBuffer(32).copy(buffer, offset + 32)
+  return buffer
+}
+
 ECSignature.prototype.toScriptSignature = function (hashType) {
   var hashTypeMod = hashType & ~0x80
   if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new Error('Invalid hashType ' + hashType)
diff --git a/src/transaction_builder.js b/src/transaction_builder.js
index e2469e43..4892e06b 100644
--- a/src/transaction_builder.js
+++ b/src/transaction_builder.js
@@ -667,7 +667,8 @@ function canSign (input) {
 }
 
 TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashType, witnessValue, witnessScript) {
-  if (keyPair.network !== this.network) throw new Error('Inconsistent network')
+  // TODO: remove keyPair.network matching in 4.0.0
+  if (keyPair.network && keyPair.network !== this.network) throw new TypeError('Inconsistent network')
   if (!this.inputs[vin]) throw new Error('No input at index: ' + vin)
   hashType = hashType || Transaction.SIGHASH_ALL
 
@@ -680,7 +681,7 @@ TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashTy
     throw new Error('Inconsistent redeemScript')
   }
 
-  var kpPubKey = keyPair.getPublicKeyBuffer()
+  var kpPubKey = keyPair.publicKey || keyPair.getPublicKeyBuffer()
   if (!canSign(input)) {
     if (witnessValue !== undefined) {
       if (input.value !== undefined && input.value !== witnessValue) throw new Error('Input didn\'t match witnessValue')
@@ -699,14 +700,18 @@ TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashTy
   } else {
     signatureHash = this.tx.hashForSignature(vin, input.signScript, hashType)
   }
+
   // enforce in order signing of public keys
   var signed = input.pubKeys.some(function (pubKey, i) {
     if (!kpPubKey.equals(pubKey)) return false
     if (input.signatures[i]) throw new Error('Signature already exists')
-    if (!keyPair.compressed &&
+    if (kpPubKey.length !== 33 &&
       input.signType === scriptTypes.P2WPKH) throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH')
 
-    input.signatures[i] = keyPair.sign(signatureHash).toScriptSignature(hashType)
+    var signature = keyPair.sign(signatureHash)
+    if (Buffer.isBuffer(signature)) signature = ECSignature.fromRSBuffer(signature)
+
+    input.signatures[i] = signature.toScriptSignature(hashType)
     return true
   })
 


Test patch:
diff --git a/test/fixtures/ecsignature.json b/test/fixtures/ecsignature.json
index 363ae72a..ee948ad1 100644
--- a/test/fixtures/ecsignature.json
+++ b/test/fixtures/ecsignature.json
@@ -120,11 +120,11 @@
         "hex": "23987ceade6a304fc5823ab38f99fc3c5f772a2d3e89ea05931e2726105fc53b9e601fc3231f35962c714fcbce5c95b427496edc7ae8b3d12e93791d7629795b62"
       },
       {
-        "exception": "Invalid signature length",
+        "exception": "Expected Buffer\\(Length: 65\\), got Buffer\\(Length: 68\\)",
         "hex": "1c987ceade6a304fc5823ab38f99fc3c5f772a2d3e89ea05931e2726105fc53b9e601fc3231f35962c714fcbce5c95b427496edc7ae8b3d12e93791d7629795b62000000"
       },
       {
-        "exception": "Invalid signature length",
+        "exception": "Expected Buffer\\(Length: 65\\), got Buffer\\(Length: 59\\)",
         "hex": "1c987ceade6a304fc5823ab38f99fc3c5f772a2d3e89ea05931e2726105fc53b9e601fc3231f35962c714fcbce5c95b427496edc7ae8b3d12e9379"
       }
     ],
diff --git a/test/transaction_builder.js b/test/transaction_builder.js
index 82dda199..bcc331ec 100644
--- a/test/transaction_builder.js
+++ b/test/transaction_builder.js
@@ -294,6 +294,19 @@ describe('TransactionBuilder', function () {
   })
 
   describe('sign', function () {
+    it('supports the alternative abstract interface { publicKey, sign }', function () {
+      var keyPair = {
+        publicKey: Buffer.alloc(33, 0x03),
+        sign: function (hash) { return Buffer.alloc(64) }
+      }
+
+      var txb = new TransactionBuilder()
+      txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1)
+      txb.addOutput('1111111111111111111114oLvT2', 100000)
+      txb.sign(0, keyPair)
+      assert.equal(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000002c0930060201000201000121030303030303030303030303030303030303030303030303030303030303030303ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000')
+    })
+
     fixtures.invalid.sign.forEach(function (f) {
       it('throws on ' + f.exception + (f.description ? ' (' + f.description + ')' : ''), function () {
         var txb = construct(f, true)