diff --git a/src/transaction_builder.js b/src/transaction_builder.js index d3b2673..cf27404 100644 --- a/src/transaction_builder.js +++ b/src/transaction_builder.js @@ -29,6 +29,7 @@ class TransactionBuilder { this.__INPUTS = []; this.__TX = new transaction_1.Transaction(); this.__TX.version = 2; + this.__USE_LOW_R = false; } static fromTransaction(transaction, network) { const txb = new TransactionBuilder(network); @@ -53,6 +54,14 @@ class TransactionBuilder { }); return txb; } + setLowR(setting) { + typeforce(typeforce.maybe(typeforce.Boolean), setting); + if (setting === undefined) { + setting = true; + } + this.__USE_LOW_R = setting; + return setting; + } setLockTime(locktime) { typeforce(types.UInt32, locktime); // if any signatures exist, throw @@ -159,7 +168,7 @@ class TransactionBuilder { if (ourPubKey.length !== 33 && input.hasWitness) { throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH'); } - const signature = keyPair.sign(signatureHash); + const signature = keyPair.sign(signatureHash, this.__USE_LOW_R); input.signatures[i] = bscript.signature.encode(signature, hashType); return true; }); diff --git a/test/transaction_builder.js b/test/transaction_builder.js index 28aa545..1af8272 100644 --- a/test/transaction_builder.js +++ b/test/transaction_builder.js @@ -338,6 +338,25 @@ describe('TransactionBuilder', () => { assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a47304402205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f02205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000') }) + it('supports low R signature signing', () => { + let txb = new TransactionBuilder() + txb.setVersion(1) + txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1) + txb.addOutput('1111111111111111111114oLvT2', 100000) + txb.sign(0, keyPair) + // high R + assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006b483045022100b872677f35c9c14ad9c41d83649fb049250f32574e0b2547d67e209ed14ff05d022059b36ad058be54e887a1a311d5c393cb4941f6b93a0b090845ec67094de8972b01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000') + + txb = new TransactionBuilder() + txb.setVersion(1) + txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1) + txb.addOutput('1111111111111111111114oLvT2', 100000) + txb.setLowR() + txb.sign(0, keyPair) + // low R + assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a473044022012a601efa8756ebe83e9ac7a7db061c3147e3b49d8be67685799fe51a4c8c62f02204d568d301d5ce14af390d566d4fd50e7b8ee48e71ec67786c029e721194dae3601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000') + }) + fixtures.invalid.sign.forEach(f => { it('throws ' + f.exception + (f.description ? ' (' + f.description + ')' : ''), () => { const txb = construct(f, true) diff --git a/ts_src/transaction_builder.ts b/ts_src/transaction_builder.ts index 6a49b3f..0665719 100644 --- a/ts_src/transaction_builder.ts +++ b/ts_src/transaction_builder.ts @@ -94,6 +94,7 @@ export class TransactionBuilder { private __PREV_TX_SET: { [index: string]: boolean }; private __INPUTS: TxbInput[]; private __TX: Transaction; + private __USE_LOW_R: boolean; // WARNING: maximumFeeRate is __NOT__ to be relied on, // it's just another potential safety mechanism (safety in-depth) @@ -105,6 +106,16 @@ export class TransactionBuilder { this.__INPUTS = []; this.__TX = new Transaction(); this.__TX.version = 2; + this.__USE_LOW_R = false; + } + + setLowR(setting?: boolean): boolean { + typeforce(typeforce.maybe(typeforce.Boolean), setting); + if (setting === undefined) { + setting = true; + } + this.__USE_LOW_R = setting; + return setting; } setLockTime(locktime: number): void { @@ -266,7 +277,7 @@ export class TransactionBuilder { ); } - const signature = keyPair.sign(signatureHash); + const signature = keyPair.sign(signatureHash, this.__USE_LOW_R); input.signatures![i] = bscript.signature.encode(signature, hashType!); return true; }); diff --git a/types/transaction_builder.d.ts b/types/transaction_builder.d.ts index 82c4ef0..f993807 100644 --- a/types/transaction_builder.d.ts +++ b/types/transaction_builder.d.ts @@ -9,7 +9,9 @@ export declare class TransactionBuilder { private __PREV_TX_SET; private __INPUTS; private __TX; + private __USE_LOW_R; constructor(network?: Network, maximumFeeRate?: number); + setLowR(setting?: boolean): boolean; setLockTime(locktime: number): void; setVersion(version: number): void; addInput(txHash: Buffer | string | Transaction, vout: number, sequence?: number, prevOutScript?: Buffer): number;