diff --git a/src/ecsignature.js b/src/ecsignature.js index 897d7e8..ebdcc36 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 e2469e4..4892e06 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 }) diff --git a/test/fixtures/ecsignature.json b/test/fixtures/ecsignature.json index 363ae72..ee948ad 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 82dda19..bcc331e 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)