Add low R signing to TransactionBuilder

This commit is contained in:
junderw 2019-04-15 17:27:28 +09:00
parent b5577607d4
commit 352e9ef0a3
No known key found for this signature in database
GPG key ID: B256185D3A971908
4 changed files with 43 additions and 2 deletions

View file

@ -29,6 +29,7 @@ class TransactionBuilder {
this.__INPUTS = []; this.__INPUTS = [];
this.__TX = new transaction_1.Transaction(); this.__TX = new transaction_1.Transaction();
this.__TX.version = 2; this.__TX.version = 2;
this.__USE_LOW_R = false;
} }
static fromTransaction(transaction, network) { static fromTransaction(transaction, network) {
const txb = new TransactionBuilder(network); const txb = new TransactionBuilder(network);
@ -53,6 +54,14 @@ class TransactionBuilder {
}); });
return txb; return txb;
} }
setLowR(setting) {
typeforce(typeforce.maybe(typeforce.Boolean), setting);
if (setting === undefined) {
setting = true;
}
this.__USE_LOW_R = setting;
return setting;
}
setLockTime(locktime) { setLockTime(locktime) {
typeforce(types.UInt32, locktime); typeforce(types.UInt32, locktime);
// if any signatures exist, throw // if any signatures exist, throw
@ -159,7 +168,7 @@ class TransactionBuilder {
if (ourPubKey.length !== 33 && input.hasWitness) { if (ourPubKey.length !== 33 && input.hasWitness) {
throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH'); 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); input.signatures[i] = bscript.signature.encode(signature, hashType);
return true; return true;
}); });

View file

@ -338,6 +338,25 @@ describe('TransactionBuilder', () => {
assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a47304402205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f02205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000') 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 => { fixtures.invalid.sign.forEach(f => {
it('throws ' + f.exception + (f.description ? ' (' + f.description + ')' : ''), () => { it('throws ' + f.exception + (f.description ? ' (' + f.description + ')' : ''), () => {
const txb = construct(f, true) const txb = construct(f, true)

View file

@ -94,6 +94,7 @@ export class TransactionBuilder {
private __PREV_TX_SET: { [index: string]: boolean }; private __PREV_TX_SET: { [index: string]: boolean };
private __INPUTS: TxbInput[]; private __INPUTS: TxbInput[];
private __TX: Transaction; private __TX: Transaction;
private __USE_LOW_R: boolean;
// WARNING: maximumFeeRate is __NOT__ to be relied on, // WARNING: maximumFeeRate is __NOT__ to be relied on,
// it's just another potential safety mechanism (safety in-depth) // it's just another potential safety mechanism (safety in-depth)
@ -105,6 +106,16 @@ export class TransactionBuilder {
this.__INPUTS = []; this.__INPUTS = [];
this.__TX = new Transaction(); this.__TX = new Transaction();
this.__TX.version = 2; 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 { 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!); input.signatures![i] = bscript.signature.encode(signature, hashType!);
return true; return true;
}); });

View file

@ -9,7 +9,9 @@ export declare class TransactionBuilder {
private __PREV_TX_SET; private __PREV_TX_SET;
private __INPUTS; private __INPUTS;
private __TX; private __TX;
private __USE_LOW_R;
constructor(network?: Network, maximumFeeRate?: number); constructor(network?: Network, maximumFeeRate?: number);
setLowR(setting?: boolean): boolean;
setLockTime(locktime: number): void; setLockTime(locktime: number): void;
setVersion(version: number): void; setVersion(version: number): void;
addInput(txHash: Buffer | string | Transaction, vout: number, sequence?: number, prevOutScript?: Buffer): number; addInput(txHash: Buffer | string | Transaction, vout: number, sequence?: number, prevOutScript?: Buffer): number;