Check for signatures, add setSequence

This commit is contained in:
junderw 2019-07-04 17:35:39 +09:00
parent 3e7f490093
commit 45bd5b4751
No known key found for this signature in database
GPG key ID: B256185D3A971908
3 changed files with 124 additions and 0 deletions

View file

@ -80,16 +80,31 @@ class Psbt extends bip174_1.Psbt {
dpew(this, 'opts', false, true);
}
setVersion(version) {
check32Bit(version);
checkInputsForPartialSig(this.inputs, 'setVersion');
this.__TX.version = version;
this.__TX_BUF_CACHE = undefined;
return this;
}
setLocktime(locktime) {
check32Bit(locktime);
checkInputsForPartialSig(this.inputs, 'setLocktime');
this.__TX.locktime = locktime;
this.__TX_BUF_CACHE = undefined;
return this;
}
setSequence(inputIndex, sequence) {
check32Bit(sequence);
checkInputsForPartialSig(this.inputs, 'setSequence');
if (this.__TX.ins.length <= inputIndex) {
throw new Error('Input index too high');
}
this.__TX.ins[inputIndex].sequence = sequence;
this.__TX_BUF_CACHE = undefined;
return this;
}
addInput(inputData) {
checkInputsForPartialSig(this.inputs, 'addInput');
const self = this;
const inputAdder = (_inputData, txBuf) => {
if (
@ -119,6 +134,7 @@ class Psbt extends bip174_1.Psbt {
return super.addInput(inputData, inputAdder);
}
addOutput(outputData) {
checkInputsForPartialSig(this.inputs, 'addOutput');
const { address } = outputData;
if (typeof address === 'string') {
const { network } = this.opts;
@ -578,3 +594,47 @@ function checkTxEmpty(tx) {
throw new Error('Format Error: Transaction ScriptSigs are not empty');
}
}
function checkInputsForPartialSig(inputs, action) {
inputs.forEach(input => {
let throws = false;
if ((input.partialSig || []).length > 0) {
if (input.sighashType !== undefined) {
const whitelist = [];
const isAnyoneCanPay =
input.sighashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY;
if (isAnyoneCanPay) whitelist.push('addInput');
if (!isAnyoneCanPay && action === 'addInput') {
throws = true;
}
const hashType = input.sighashType & 0x1f;
switch (hashType) {
case transaction_1.Transaction.SIGHASH_ALL:
break;
case transaction_1.Transaction.SIGHASH_SINGLE:
case transaction_1.Transaction.SIGHASH_NONE:
whitelist.push('addOutput');
whitelist.push('setSequence');
break;
}
if (whitelist.indexOf(action) === -1) {
throws = true;
}
} else {
throws = true;
}
}
if (throws) {
throw new Error('Can not modify transaction, signatures exist.');
}
});
}
function check32Bit(num) {
if (
typeof num !== 'number' ||
num !== Math.floor(num) ||
num > 0xffffffff ||
num < 0
) {
throw new Error('Invalid 32 bit integer');
}
}

View file

@ -108,18 +108,34 @@ export class Psbt extends PsbtBase {
}
setVersion(version: number): this {
check32Bit(version);
checkInputsForPartialSig(this.inputs, 'setVersion');
this.__TX.version = version;
this.__TX_BUF_CACHE = undefined;
return this;
}
setLocktime(locktime: number): this {
check32Bit(locktime);
checkInputsForPartialSig(this.inputs, 'setLocktime');
this.__TX.locktime = locktime;
this.__TX_BUF_CACHE = undefined;
return this;
}
setSequence(inputIndex: number, sequence: number): this {
check32Bit(sequence);
checkInputsForPartialSig(this.inputs, 'setSequence');
if (this.__TX.ins.length <= inputIndex) {
throw new Error('Input index too high');
}
this.__TX.ins[inputIndex].sequence = sequence;
this.__TX_BUF_CACHE = undefined;
return this;
}
addInput(inputData: TransactionInput): this {
checkInputsForPartialSig(this.inputs, 'addInput');
const self = this;
const inputAdder = (
_inputData: TransactionInput,
@ -152,6 +168,7 @@ export class Psbt extends PsbtBase {
}
addOutput(outputData: TransactionOutput): this {
checkInputsForPartialSig(this.inputs, 'addOutput');
const { address } = outputData as any;
if (typeof address === 'string') {
const { network } = this.opts;
@ -734,3 +751,49 @@ function checkTxEmpty(tx: Transaction): void {
throw new Error('Format Error: Transaction ScriptSigs are not empty');
}
}
function checkInputsForPartialSig(inputs: PsbtInput[], action: string): void {
inputs.forEach(input => {
let throws = false;
if ((input.partialSig || []).length > 0) {
if (input.sighashType !== undefined) {
const whitelist: string[] = [];
const isAnyoneCanPay =
input.sighashType & Transaction.SIGHASH_ANYONECANPAY;
if (isAnyoneCanPay) whitelist.push('addInput');
if (!isAnyoneCanPay && action === 'addInput') {
throws = true;
}
const hashType = input.sighashType & 0x1f;
switch (hashType) {
case Transaction.SIGHASH_ALL:
break;
case Transaction.SIGHASH_SINGLE:
case Transaction.SIGHASH_NONE:
whitelist.push('addOutput');
whitelist.push('setSequence');
break;
}
if (whitelist.indexOf(action) === -1) {
throws = true;
}
} else {
throws = true;
}
}
if (throws) {
throw new Error('Can not modify transaction, signatures exist.');
}
});
}
function check32Bit(num: number): void {
if (
typeof num !== 'number' ||
num !== Math.floor(num) ||
num > 0xffffffff ||
num < 0
) {
throw new Error('Invalid 32 bit integer');
}
}

1
types/psbt.d.ts vendored
View file

@ -13,6 +13,7 @@ export declare class Psbt extends PsbtBase {
constructor(opts?: PsbtOptsOptional);
setVersion(version: number): this;
setLocktime(locktime: number): this;
setSequence(inputIndex: number, sequence: number): this;
addInput(inputData: TransactionInput): this;
addOutput(outputData: TransactionOutput): this;
extractTransaction(): Transaction;