bitcoinjs-lib/ts_src/transaction_builder.ts

967 lines
27 KiB
TypeScript
Raw Normal View History

2019-03-03 15:07:49 +01:00
import { Network } from './networks';
import * as networks from './networks';
import { reverseBuffer } from './bufferutils';
import { Transaction, Output } from './transaction';
import { ECPairInterface } from './ecpair';
import * as ECPair from './ecpair';
import * as types from './types';
import * as baddress from './address';
import * as bcrypto from './crypto';
import * as bscript from './script';
import { Payment } from './payments';
import * as payments from './payments';
import * as classify from './classify';
import { OPS as ops } from './script';
const typeforce = require('typeforce');
const SCRIPT_TYPES = classify.types;
type TxbSignatures = Array<Buffer> | Array<Buffer | undefined>;
type TxbPubkeys = Array<Buffer | undefined>;
type TxbWitness = Array<Buffer>;
type TxbScriptType = string;
type TxbScript = Buffer;
2014-06-16 08:05:31 +02:00
2018-12-28 03:56:03 +01:00
interface TxbInput {
2019-03-03 15:07:49 +01:00
value?: number;
hasWitness?: boolean;
signScript?: TxbScript;
signType?: TxbScriptType;
prevOutScript?: TxbScript;
redeemScript?: TxbScript;
redeemScriptType?: TxbScriptType;
prevOutType?: TxbScriptType;
pubkeys?: TxbPubkeys;
signatures?: TxbSignatures;
witness?: TxbWitness;
witnessScript?: TxbScript;
witnessScriptType?: TxbScriptType;
script?: TxbScript;
sequence?: number;
scriptSig?: TxbScript;
maxSignatures?: number;
2018-12-28 03:56:03 +01:00
}
interface TxbOutput {
2019-03-03 15:07:49 +01:00
type: string;
pubkeys?: TxbPubkeys;
signatures?: TxbSignatures;
maxSignatures?: number;
2018-12-28 03:56:03 +01:00
}
function txIsString(tx: Buffer | string | Transaction): tx is string {
2019-03-03 15:07:49 +01:00
return typeof tx === 'string' || tx instanceof String;
2018-12-28 03:56:03 +01:00
}
function txIsTransaction(tx: Buffer | string | Transaction): tx is Transaction {
2019-03-03 15:07:49 +01:00
return tx instanceof Transaction;
2018-12-28 03:56:03 +01:00
}
export class TransactionBuilder {
2019-03-03 15:07:49 +01:00
network: Network;
maximumFeeRate: number;
private __prevTxSet: { [index: string]: boolean };
private __inputs: Array<TxbInput>;
private __tx: Transaction;
2018-12-28 03:56:03 +01:00
2019-03-03 15:07:49 +01:00
constructor(network?: Network, maximumFeeRate?: number) {
this.__prevTxSet = {};
this.network = network || networks.bitcoin;
2018-12-28 03:56:03 +01:00
// WARNING: This is __NOT__ to be relied on, its just another potential safety mechanism (safety in-depth)
2019-03-03 15:07:49 +01:00
this.maximumFeeRate = maximumFeeRate || 2500;
2018-12-28 03:56:03 +01:00
2019-03-03 15:07:49 +01:00
this.__inputs = [];
this.__tx = new Transaction();
this.__tx.version = 2;
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
static fromTransaction(
transaction: Transaction,
network?: Network,
): TransactionBuilder {
const txb = new TransactionBuilder(network);
2018-12-28 03:56:03 +01:00
// Copy transaction fields
2019-03-03 15:07:49 +01:00
txb.setVersion(transaction.version);
txb.setLockTime(transaction.locktime);
2018-12-28 03:56:03 +01:00
// Copy outputs (done first to avoid signature invalidation)
transaction.outs.forEach(txOut => {
2019-03-03 15:07:49 +01:00
txb.addOutput(txOut.script, (<Output>txOut).value);
});
2018-12-28 03:56:03 +01:00
// Copy inputs
transaction.ins.forEach(txIn => {
txb.__addInputUnsafe(txIn.hash, txIn.index, {
sequence: txIn.sequence,
script: txIn.script,
2019-03-03 15:07:49 +01:00
witness: txIn.witness,
});
});
2018-12-28 03:56:03 +01:00
// fix some things not possible through the public API
txb.__inputs.forEach((input, i) => {
2019-03-03 15:07:49 +01:00
fixMultisigOrder(input, transaction, i);
});
2018-12-28 03:56:03 +01:00
2019-03-03 15:07:49 +01:00
return txb;
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
setLockTime(locktime: number): void {
typeforce(types.UInt32, locktime);
2018-12-28 03:56:03 +01:00
// if any signatures exist, throw
2019-03-03 15:07:49 +01:00
if (
this.__inputs.some(input => {
if (!input.signatures) return false;
2018-12-28 03:56:03 +01:00
2019-03-03 15:07:49 +01:00
return input.signatures.some(s => s !== undefined);
})
) {
throw new Error('No, this would invalidate signatures');
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
this.__tx.locktime = locktime;
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
setVersion(version: number): void {
typeforce(types.UInt32, version);
2018-12-28 03:56:03 +01:00
// XXX: this might eventually become more complex depending on what the versions represent
2019-03-03 15:07:49 +01:00
this.__tx.version = version;
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
addInput(
txHash: Buffer | string | Transaction,
vout: number,
sequence: number,
prevOutScript: Buffer,
): number {
2018-12-28 03:56:03 +01:00
if (!this.__canModifyInputs()) {
2019-03-03 15:07:49 +01:00
throw new Error('No, this would invalidate signatures');
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
let value: number | undefined = undefined;
2018-12-28 03:56:03 +01:00
// is it a hex string?
if (txIsString(txHash)) {
// transaction hashs's are displayed in reverse order, un-reverse it
2019-03-03 15:07:49 +01:00
txHash = reverseBuffer(Buffer.from(txHash, 'hex'));
2018-12-28 03:56:03 +01:00
2019-03-03 15:07:49 +01:00
// is it a Transaction object?
2018-12-28 03:56:03 +01:00
} else if (txIsTransaction(txHash)) {
2019-03-03 15:07:49 +01:00
const txOut = txHash.outs[vout];
prevOutScript = txOut.script;
value = (<Output>txOut).value;
2018-12-28 03:56:03 +01:00
2019-03-03 15:07:49 +01:00
txHash = <Buffer>txHash.getHash(false);
2018-12-28 03:56:03 +01:00
}
return this.__addInputUnsafe(txHash, vout, {
sequence: sequence,
prevOutScript: prevOutScript,
2019-03-03 15:07:49 +01:00
value: value,
});
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
private __addInputUnsafe(
txHash: Buffer,
vout: number,
options: TxbInput,
): number {
2018-12-28 03:56:03 +01:00
if (Transaction.isCoinbaseHash(txHash)) {
2019-03-03 15:07:49 +01:00
throw new Error('coinbase inputs not supported');
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
const prevTxOut = txHash.toString('hex') + ':' + vout;
if (this.__prevTxSet[prevTxOut] !== undefined)
throw new Error('Duplicate TxOut: ' + prevTxOut);
2018-12-28 03:56:03 +01:00
2019-03-03 15:07:49 +01:00
let input = <TxbInput>{};
2018-12-28 03:56:03 +01:00
// derive what we can from the scriptSig
if (options.script !== undefined) {
2019-03-03 15:07:49 +01:00
input = expandInput(options.script, options.witness || []);
2018-12-28 03:56:03 +01:00
}
// if an input value was given, retain it
if (options.value !== undefined) {
2019-03-03 15:07:49 +01:00
input.value = options.value;
2018-12-28 03:56:03 +01:00
}
// derive what we can from the previous transactions output script
if (!input.prevOutScript && options.prevOutScript) {
2019-03-03 15:07:49 +01:00
let prevOutType;
2018-12-28 03:56:03 +01:00
if (!input.pubkeys && !input.signatures) {
2019-03-03 15:07:49 +01:00
const expanded = expandOutput(options.prevOutScript);
2018-12-28 03:56:03 +01:00
if (expanded.pubkeys) {
2019-03-03 15:07:49 +01:00
input.pubkeys = expanded.pubkeys;
input.signatures = expanded.signatures;
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
prevOutType = expanded.type;
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
input.prevOutScript = options.prevOutScript;
input.prevOutType = prevOutType || classify.output(options.prevOutScript);
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
const vin = this.__tx.addInput(
txHash,
vout,
options.sequence,
options.scriptSig,
);
this.__inputs[vin] = input;
this.__prevTxSet[prevTxOut] = true;
return vin;
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
addOutput(scriptPubKey: string | Buffer, value: number): number {
2018-12-28 03:56:03 +01:00
if (!this.__canModifyOutputs()) {
2019-03-03 15:07:49 +01:00
throw new Error('No, this would invalidate signatures');
2018-12-28 03:56:03 +01:00
}
// Attempt to get a script if it's a base58 or bech32 address string
if (typeof scriptPubKey === 'string') {
2019-03-03 15:07:49 +01:00
scriptPubKey = baddress.toOutputScript(scriptPubKey, this.network);
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
return this.__tx.addOutput(scriptPubKey, value);
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
build(): Transaction {
return this.__build(false);
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
buildIncomplete(): Transaction {
return this.__build(true);
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
private __build(allowIncomplete?: boolean): Transaction {
2018-12-28 03:56:03 +01:00
if (!allowIncomplete) {
2019-03-03 15:07:49 +01:00
if (!this.__tx.ins.length) throw new Error('Transaction has no inputs');
if (!this.__tx.outs.length) throw new Error('Transaction has no outputs');
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
const tx = this.__tx.clone();
2018-12-28 03:56:03 +01:00
// create script signatures from inputs
this.__inputs.forEach((input, i) => {
2019-03-03 15:07:49 +01:00
if (!input.prevOutType && !allowIncomplete)
throw new Error('Transaction is not complete');
2018-12-28 03:56:03 +01:00
2019-03-03 15:07:49 +01:00
const result = build(input.prevOutType!, input, allowIncomplete);
2018-12-28 03:56:03 +01:00
if (!result) {
2019-03-03 15:07:49 +01:00
if (!allowIncomplete && input.prevOutType === SCRIPT_TYPES.NONSTANDARD)
throw new Error('Unknown input type');
if (!allowIncomplete) throw new Error('Not enough information');
return;
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
tx.setInputScript(i, result.input!);
tx.setWitness(i, result.witness!);
});
2018-12-28 03:56:03 +01:00
if (!allowIncomplete) {
// do not rely on this, its merely a last resort
if (this.__overMaximumFees(tx.virtualSize())) {
2019-03-03 15:07:49 +01:00
throw new Error('Transaction has absurd fees');
2018-12-28 03:56:03 +01:00
}
}
2019-03-03 15:07:49 +01:00
return tx;
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
sign(
vin: number,
keyPair: ECPairInterface,
redeemScript: Buffer,
hashType: number,
witnessValue: number,
witnessScript: Buffer,
) {
2018-12-28 03:56:03 +01:00
// TODO: remove keyPair.network matching in 4.0.0
2019-03-03 15:07:49 +01:00
if (keyPair.network && keyPair.network !== this.network)
throw new TypeError('Inconsistent network');
if (!this.__inputs[vin]) throw new Error('No input at index: ' + vin);
2018-12-28 03:56:03 +01:00
2019-03-03 15:07:49 +01:00
hashType = hashType || Transaction.SIGHASH_ALL;
if (this.__needsOutputs(hashType))
throw new Error('Transaction needs outputs');
2018-12-28 03:56:03 +01:00
2019-03-03 15:07:49 +01:00
const input = this.__inputs[vin];
2018-12-28 03:56:03 +01:00
// if redeemScript was previously provided, enforce consistency
2019-03-03 15:07:49 +01:00
if (
input.redeemScript !== undefined &&
redeemScript &&
!input.redeemScript.equals(redeemScript)
) {
throw new Error('Inconsistent redeemScript');
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
const ourPubKey = keyPair.publicKey || keyPair.getPublicKey!();
2018-12-28 03:56:03 +01:00
if (!canSign(input)) {
if (witnessValue !== undefined) {
2019-03-03 15:07:49 +01:00
if (input.value !== undefined && input.value !== witnessValue)
throw new Error("Input didn't match witnessValue");
typeforce(types.Satoshi, witnessValue);
input.value = witnessValue;
2018-12-28 03:56:03 +01:00
}
if (!canSign(input)) {
2019-03-03 15:07:49 +01:00
const prepared = prepareInput(
input,
ourPubKey,
redeemScript,
witnessScript,
);
2018-12-28 03:56:03 +01:00
// updates inline
2019-03-03 15:07:49 +01:00
Object.assign(input, prepared);
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
if (!canSign(input)) throw Error(input.prevOutType + ' not supported');
2018-12-28 03:56:03 +01:00
}
// ready to sign
2019-03-03 15:07:49 +01:00
let signatureHash: Buffer;
2018-12-28 03:56:03 +01:00
if (input.hasWitness) {
2019-03-03 15:07:49 +01:00
signatureHash = this.__tx.hashForWitnessV0(
vin,
<Buffer>input.signScript,
<number>input.value,
hashType,
);
2018-12-28 03:56:03 +01:00
} else {
2019-03-03 15:07:49 +01:00
signatureHash = this.__tx.hashForSignature(
vin,
<Buffer>input.signScript,
hashType,
);
2018-12-28 03:56:03 +01:00
}
// enforce in order signing of public keys
const signed = input.pubkeys!.some((pubKey, i) => {
2019-03-03 15:07:49 +01:00
if (!ourPubKey.equals(pubKey!)) return false;
if (input.signatures![i]) throw new Error('Signature already exists');
2018-12-28 03:56:03 +01:00
// TODO: add tests
if (ourPubKey.length !== 33 && input.hasWitness) {
2019-03-03 15:07:49 +01:00
throw new Error(
'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH',
);
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
const signature = keyPair.sign(signatureHash);
input.signatures![i] = bscript.signature.encode(signature, hashType);
return true;
});
2018-12-28 03:56:03 +01:00
2019-03-03 15:07:49 +01:00
if (!signed) throw new Error('Key pair cannot sign for this input');
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
private __canModifyInputs(): boolean {
2018-12-28 03:56:03 +01:00
return this.__inputs.every(input => {
2019-03-03 15:07:49 +01:00
if (!input.signatures) return true;
2018-12-28 03:56:03 +01:00
return input.signatures.every(signature => {
2019-03-03 15:07:49 +01:00
if (!signature) return true;
const hashType = signatureHashType(signature);
2018-12-28 03:56:03 +01:00
// if SIGHASH_ANYONECANPAY is set, signatures would not
// be invalidated by more inputs
2019-03-03 15:07:49 +01:00
return (hashType & Transaction.SIGHASH_ANYONECANPAY) !== 0;
});
});
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
private __needsOutputs(signingHashType: number): boolean {
2018-12-28 03:56:03 +01:00
if (signingHashType === Transaction.SIGHASH_ALL) {
2019-03-03 15:07:49 +01:00
return this.__tx.outs.length === 0;
2018-12-28 03:56:03 +01:00
}
// if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs
// .build() will fail, but .buildIncomplete() is OK
2019-03-03 15:07:49 +01:00
return (
this.__tx.outs.length === 0 &&
this.__inputs.some(input => {
if (!input.signatures) return false;
return input.signatures.some(signature => {
if (!signature) return false; // no signature, no issue
const hashType = signatureHashType(signature);
if (hashType & Transaction.SIGHASH_NONE) return false; // SIGHASH_NONE doesn't care about outputs
return true; // SIGHASH_* does care
});
2018-12-28 03:56:03 +01:00
})
2019-03-03 15:07:49 +01:00
);
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
private __canModifyOutputs(): boolean {
const nInputs = this.__tx.ins.length;
const nOutputs = this.__tx.outs.length;
2018-12-28 03:56:03 +01:00
return this.__inputs.every(input => {
2019-03-03 15:07:49 +01:00
if (input.signatures === undefined) return true;
2018-12-28 03:56:03 +01:00
return input.signatures.every(signature => {
2019-03-03 15:07:49 +01:00
if (!signature) return true;
const hashType = signatureHashType(signature);
2018-12-28 03:56:03 +01:00
2019-03-03 15:07:49 +01:00
const hashTypeMod = hashType & 0x1f;
if (hashTypeMod === Transaction.SIGHASH_NONE) return true;
2018-12-28 03:56:03 +01:00
if (hashTypeMod === Transaction.SIGHASH_SINGLE) {
// if SIGHASH_SINGLE is set, and nInputs > nOutputs
// some signatures would be invalidated by the addition
// of more outputs
2019-03-03 15:07:49 +01:00
return nInputs <= nOutputs;
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
return false;
});
});
2018-12-28 03:56:03 +01:00
}
2019-03-03 15:07:49 +01:00
private __overMaximumFees(bytes: number): boolean {
2018-12-28 03:56:03 +01:00
// not all inputs will have .value defined
2019-03-03 15:07:49 +01:00
const incoming = this.__inputs.reduce((a, x) => a + (x.value! >>> 0), 0);
2018-12-28 03:56:03 +01:00
// but all outputs do, and if we have any input value
// we can immediately determine if the outputs are too small
2019-03-03 15:07:49 +01:00
const outgoing = this.__tx.outs.reduce((a, x) => a + (<Output>x).value, 0);
const fee = incoming - outgoing;
const feeRate = fee / bytes;
2018-12-28 03:56:03 +01:00
2019-03-03 15:07:49 +01:00
return feeRate > this.maximumFeeRate;
2018-12-28 03:56:03 +01:00
}
}
2019-03-03 15:07:49 +01:00
function expandInput(
scriptSig: Buffer,
witnessStack: Array<Buffer>,
type?: string,
scriptPubKey?: Buffer,
): TxbInput {
if (scriptSig.length === 0 && witnessStack.length === 0) return {};
2018-06-27 09:29:18 +02:00
if (!type) {
2019-03-03 15:07:49 +01:00
let ssType: string | undefined = classify.input(scriptSig, true);
let wsType: string | undefined = classify.witness(witnessStack, true);
if (ssType === SCRIPT_TYPES.NONSTANDARD) ssType = undefined;
if (wsType === SCRIPT_TYPES.NONSTANDARD) wsType = undefined;
type = ssType || wsType;
2018-06-27 09:29:18 +02:00
}
2017-01-02 17:45:23 +01:00
switch (type) {
2018-06-27 09:29:18 +02:00
case SCRIPT_TYPES.P2WPKH: {
2019-03-03 15:07:49 +01:00
const { output, pubkey, signature } = payments.p2wpkh({
witness: witnessStack,
});
2018-06-27 09:29:18 +02:00
return {
prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2WPKH,
pubkeys: [pubkey],
2019-03-03 15:07:49 +01:00
signatures: [signature],
};
2018-06-27 09:29:18 +02:00
}
2018-06-27 09:29:18 +02:00
case SCRIPT_TYPES.P2PKH: {
2019-03-03 15:07:49 +01:00
const { output, pubkey, signature } = payments.p2pkh({
input: scriptSig,
});
2018-06-27 09:29:18 +02:00
return {
prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2PKH,
pubkeys: [pubkey],
2019-03-03 15:07:49 +01:00
signatures: [signature],
};
2018-06-27 09:29:18 +02:00
}
2018-06-27 09:29:18 +02:00
case SCRIPT_TYPES.P2PK: {
2019-03-03 15:07:49 +01:00
const { signature } = payments.p2pk({ input: scriptSig });
2018-06-27 09:29:18 +02:00
return {
prevOutType: SCRIPT_TYPES.P2PK,
pubkeys: [undefined],
2019-03-03 15:07:49 +01:00
signatures: [signature],
};
2017-01-02 17:45:23 +01:00
}
2018-09-03 06:06:30 +02:00
case SCRIPT_TYPES.P2MS: {
2019-03-03 15:07:49 +01:00
const { m, pubkeys, signatures } = payments.p2ms(
{
input: scriptSig,
output: scriptPubKey,
},
{ allowIncomplete: true },
);
2018-06-27 09:29:18 +02:00
return {
2018-09-03 06:06:30 +02:00
prevOutType: SCRIPT_TYPES.P2MS,
2018-06-27 09:29:18 +02:00
pubkeys: pubkeys,
signatures: signatures,
2019-03-03 15:07:49 +01:00
maxSignatures: m,
};
2017-01-02 17:45:23 +01:00
}
2018-06-27 09:29:18 +02:00
}
2017-01-02 17:45:23 +01:00
2018-06-27 09:29:18 +02:00
if (type === SCRIPT_TYPES.P2SH) {
const { output, redeem } = payments.p2sh({
input: scriptSig,
2019-03-03 15:07:49 +01:00
witness: witnessStack,
});
const outputType = classify.output(redeem!.output!);
const expanded = expandInput(
redeem!.input!,
redeem!.witness!,
outputType,
redeem!.output,
);
if (!expanded.prevOutType) return {};
2017-01-02 17:45:23 +01:00
2018-06-27 09:29:18 +02:00
return {
prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2SH,
redeemScript: redeem!.output,
2018-06-27 09:29:18 +02:00
redeemScriptType: expanded.prevOutType,
witnessScript: expanded.witnessScript,
witnessScriptType: expanded.witnessScriptType,
2017-01-02 17:45:23 +01:00
2018-06-27 09:29:18 +02:00
pubkeys: expanded.pubkeys,
2019-03-03 15:07:49 +01:00
signatures: expanded.signatures,
};
2017-01-02 17:45:23 +01:00
}
2018-06-27 09:29:18 +02:00
if (type === SCRIPT_TYPES.P2WSH) {
const { output, redeem } = payments.p2wsh({
input: scriptSig,
2019-03-03 15:07:49 +01:00
witness: witnessStack,
});
const outputType = classify.output(redeem!.output!);
let expanded;
2018-06-27 09:29:18 +02:00
if (outputType === SCRIPT_TYPES.P2WPKH) {
2019-03-03 15:07:49 +01:00
expanded = expandInput(redeem!.input!, redeem!.witness!, outputType);
2018-06-27 09:29:18 +02:00
} else {
2019-03-03 15:07:49 +01:00
expanded = expandInput(
bscript.compile(redeem!.witness!),
[],
outputType,
redeem!.output,
);
2018-06-27 09:29:18 +02:00
}
2019-03-03 15:07:49 +01:00
if (!expanded.prevOutType) return {};
2018-06-27 09:29:18 +02:00
return {
prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2WSH,
witnessScript: redeem!.output,
2018-06-27 09:29:18 +02:00
witnessScriptType: expanded.prevOutType,
2017-01-02 17:45:23 +01:00
2018-06-27 09:29:18 +02:00
pubkeys: expanded.pubkeys,
2019-03-03 15:07:49 +01:00
signatures: expanded.signatures,
};
2017-01-02 17:45:23 +01:00
}
2018-06-27 09:29:18 +02:00
return {
prevOutType: SCRIPT_TYPES.NONSTANDARD,
2019-03-03 15:07:49 +01:00
prevOutScript: scriptSig,
};
2017-01-02 17:45:23 +01:00
}
// could be done in expandInput, but requires the original Transaction for hashForSignature
2019-03-03 15:07:49 +01:00
function fixMultisigOrder(
input: TxbInput,
transaction: Transaction,
vin: number,
): void {
if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript)
return;
if (input.pubkeys!.length === input.signatures!.length) return;
2019-03-03 15:07:49 +01:00
const unmatched = input.signatures!.concat();
input.signatures = input.pubkeys!.map(pubKey => {
2019-03-03 15:07:49 +01:00
const keyPair = ECPair.fromPublicKey(pubKey!);
let match: Buffer | undefined;
// check for a signature
2018-12-28 03:56:03 +01:00
unmatched.some((signature, i) => {
// skip if undefined || OP_0
2019-03-03 15:07:49 +01:00
if (!signature) return false;
// TODO: avoid O(n) hashForSignature
2019-03-03 15:07:49 +01:00
const parsed = bscript.signature.decode(signature);
const hash = transaction.hashForSignature(
vin,
input.redeemScript!,
parsed.hashType,
);
// skip if signature does not match pubKey
2019-03-03 15:07:49 +01:00
if (!keyPair.verify(hash, parsed.signature)) return false;
// remove matched signature from unmatched
2019-03-03 15:07:49 +01:00
unmatched[i] = undefined;
match = signature;
2019-03-03 15:07:49 +01:00
return true;
});
2019-03-03 15:07:49 +01:00
return match;
});
}
2019-03-03 15:07:49 +01:00
function expandOutput(script: Buffer, ourPubKey?: Buffer): TxbOutput {
typeforce(types.Buffer, script);
const type = classify.output(script);
2018-06-27 09:29:18 +02:00
switch (type) {
case SCRIPT_TYPES.P2PKH: {
2019-03-03 15:07:49 +01:00
if (!ourPubKey) return { type };
2018-06-27 09:29:18 +02:00
// does our hash160(pubKey) match the output scripts?
2019-03-03 15:07:49 +01:00
const pkh1 = payments.p2pkh({ output: script }).hash;
const pkh2 = bcrypto.hash160(ourPubKey);
if (!pkh1!.equals(pkh2)) return { type };
2018-06-27 09:29:18 +02:00
return {
type,
pubkeys: [ourPubKey],
2019-03-03 15:07:49 +01:00
signatures: [undefined],
};
2018-06-27 09:29:18 +02:00
}
2018-06-27 09:29:18 +02:00
case SCRIPT_TYPES.P2WPKH: {
2019-03-03 15:07:49 +01:00
if (!ourPubKey) return { type };
2016-12-14 05:41:24 +01:00
2018-06-27 09:29:18 +02:00
// does our hash160(pubKey) match the output scripts?
2019-03-03 15:07:49 +01:00
const wpkh1 = payments.p2wpkh({ output: script }).hash;
const wpkh2 = bcrypto.hash160(ourPubKey);
if (!wpkh1!.equals(wpkh2)) return { type };
2016-12-14 05:41:24 +01:00
2018-06-27 09:29:18 +02:00
return {
type,
pubkeys: [ourPubKey],
2019-03-03 15:07:49 +01:00
signatures: [undefined],
};
2018-06-27 09:29:18 +02:00
}
2018-06-27 09:29:18 +02:00
case SCRIPT_TYPES.P2PK: {
2019-03-03 15:07:49 +01:00
const p2pk = payments.p2pk({ output: script });
2018-06-27 09:29:18 +02:00
return {
type,
pubkeys: [p2pk.pubkey],
2019-03-03 15:07:49 +01:00
signatures: [undefined],
};
2018-06-27 09:29:18 +02:00
}
2016-09-27 13:08:48 +02:00
2018-09-03 06:06:30 +02:00
case SCRIPT_TYPES.P2MS: {
2019-03-03 15:07:49 +01:00
const p2ms = payments.p2ms({ output: script });
2018-06-27 09:29:18 +02:00
return {
type,
pubkeys: p2ms.pubkeys,
signatures: p2ms.pubkeys!.map((): undefined => undefined),
2019-03-03 15:07:49 +01:00
maxSignatures: p2ms.m,
};
2018-06-27 09:29:18 +02:00
}
2016-09-27 13:08:48 +02:00
}
2019-03-03 15:07:49 +01:00
return { type };
}
2019-03-03 15:07:49 +01:00
function prepareInput(
input: TxbInput,
ourPubKey: Buffer,
redeemScript: Buffer,
witnessScript: Buffer,
): TxbInput {
2018-06-27 09:29:18 +02:00
if (redeemScript && witnessScript) {
2019-03-03 15:07:49 +01:00
const p2wsh = <Payment>(
payments.p2wsh({ redeem: { output: witnessScript } })
);
const p2wshAlt = <Payment>payments.p2wsh({ output: redeemScript });
const p2sh = <Payment>payments.p2sh({ redeem: { output: redeemScript } });
const p2shAlt = <Payment>payments.p2sh({ redeem: p2wsh });
2018-06-27 09:29:18 +02:00
// enforces P2SH(P2WSH(...))
2019-03-03 15:07:49 +01:00
if (!p2wsh.hash!.equals(p2wshAlt.hash!))
throw new Error('Witness script inconsistent with prevOutScript');
if (!p2sh.hash!.equals(p2shAlt.hash!))
throw new Error('Redeem script inconsistent with prevOutScript');
const expanded = expandOutput(p2wsh.redeem!.output!, ourPubKey);
if (!expanded.pubkeys)
throw new Error(
expanded.type +
' not supported as witnessScript (' +
bscript.toASM(witnessScript) +
')',
);
2018-12-28 03:56:03 +01:00
if (input.signatures && input.signatures.some(x => x !== undefined)) {
2019-03-03 15:07:49 +01:00
expanded.signatures = input.signatures;
2018-06-27 09:29:18 +02:00
}
2019-03-03 15:07:49 +01:00
let signScript = witnessScript;
if (expanded.type === SCRIPT_TYPES.P2WPKH)
throw new Error('P2SH(P2WSH(P2WPKH)) is a consensus failure');
2018-06-27 09:29:18 +02:00
return {
redeemScript,
2018-06-27 09:29:18 +02:00
redeemScriptType: SCRIPT_TYPES.P2WSH,
witnessScript,
2018-06-27 09:29:18 +02:00
witnessScriptType: expanded.type,
2018-06-27 09:29:18 +02:00
prevOutType: SCRIPT_TYPES.P2SH,
prevOutScript: p2sh.output,
2018-06-27 09:29:18 +02:00
hasWitness: true,
signScript,
2018-06-27 09:29:18 +02:00
signType: expanded.type,
2018-06-27 09:29:18 +02:00
pubkeys: expanded.pubkeys,
signatures: expanded.signatures,
2019-03-03 15:07:49 +01:00
maxSignatures: expanded.maxSignatures,
};
2018-06-27 09:29:18 +02:00
}
2018-06-27 09:29:18 +02:00
if (redeemScript) {
2019-03-03 15:07:49 +01:00
const p2sh = <Payment>payments.p2sh({ redeem: { output: redeemScript } });
2018-06-27 09:29:18 +02:00
if (input.prevOutScript) {
2019-03-03 15:07:49 +01:00
let p2shAlt;
2018-06-27 09:29:18 +02:00
try {
2019-03-03 15:07:49 +01:00
p2shAlt = <Payment>payments.p2sh({ output: input.prevOutScript });
} catch (e) {
throw new Error('PrevOutScript must be P2SH');
}
if (!p2sh.hash!.equals(p2shAlt.hash!))
throw new Error('Redeem script inconsistent with prevOutScript');
}
const expanded = expandOutput(p2sh.redeem!.output!, ourPubKey);
if (!expanded.pubkeys)
throw new Error(
expanded.type +
' not supported as redeemScript (' +
bscript.toASM(redeemScript) +
')',
);
2018-12-28 03:56:03 +01:00
if (input.signatures && input.signatures.some(x => x !== undefined)) {
2019-03-03 15:07:49 +01:00
expanded.signatures = input.signatures;
2018-06-27 09:29:18 +02:00
}
2019-03-03 15:07:49 +01:00
let signScript = redeemScript;
2018-06-27 09:29:18 +02:00
if (expanded.type === SCRIPT_TYPES.P2WPKH) {
2019-03-03 15:07:49 +01:00
signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output!;
2018-06-27 09:29:18 +02:00
}
2018-06-27 09:29:18 +02:00
return {
redeemScript,
2018-06-27 09:29:18 +02:00
redeemScriptType: expanded.type,
2018-06-27 09:29:18 +02:00
prevOutType: SCRIPT_TYPES.P2SH,
prevOutScript: p2sh.output,
hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH,
signScript,
2018-06-27 09:29:18 +02:00
signType: expanded.type,
2018-06-27 09:29:18 +02:00
pubkeys: expanded.pubkeys,
signatures: expanded.signatures,
2019-03-03 15:07:49 +01:00
maxSignatures: expanded.maxSignatures,
};
}
2018-06-27 09:29:18 +02:00
if (witnessScript) {
2019-03-03 15:07:49 +01:00
const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } });
2018-06-27 09:29:18 +02:00
if (input.prevOutScript) {
2019-03-03 15:07:49 +01:00
const p2wshAlt = payments.p2wsh({ output: input.prevOutScript });
if (!p2wsh.hash!.equals(p2wshAlt.hash!))
throw new Error('Witness script inconsistent with prevOutScript');
}
const expanded = expandOutput(p2wsh.redeem!.output!, ourPubKey);
if (!expanded.pubkeys)
throw new Error(
expanded.type +
' not supported as witnessScript (' +
bscript.toASM(witnessScript) +
')',
);
2018-12-28 03:56:03 +01:00
if (input.signatures && input.signatures.some(x => x !== undefined)) {
2019-03-03 15:07:49 +01:00
expanded.signatures = input.signatures;
2016-12-22 13:18:45 +01:00
}
2019-03-03 15:07:49 +01:00
let signScript = witnessScript;
if (expanded.type === SCRIPT_TYPES.P2WPKH)
throw new Error('P2WSH(P2WPKH) is a consensus failure');
2018-06-27 09:29:18 +02:00
return {
witnessScript,
2018-06-27 09:29:18 +02:00
witnessScriptType: expanded.type,
2018-06-27 09:29:18 +02:00
prevOutType: SCRIPT_TYPES.P2WSH,
prevOutScript: p2wsh.output,
2018-06-27 09:29:18 +02:00
hasWitness: true,
signScript,
2018-06-27 09:29:18 +02:00
signType: expanded.type,
2016-12-14 05:41:24 +01:00
2018-06-27 09:29:18 +02:00
pubkeys: expanded.pubkeys,
signatures: expanded.signatures,
2019-03-03 15:07:49 +01:00
maxSignatures: expanded.maxSignatures,
};
2018-06-27 09:29:18 +02:00
}
2018-06-27 09:29:18 +02:00
if (input.prevOutType && input.prevOutScript) {
// embedded scripts are not possible without extra information
2019-03-03 15:07:49 +01:00
if (input.prevOutType === SCRIPT_TYPES.P2SH)
throw new Error(
'PrevOutScript is ' + input.prevOutType + ', requires redeemScript',
);
if (input.prevOutType === SCRIPT_TYPES.P2WSH)
throw new Error(
'PrevOutScript is ' + input.prevOutType + ', requires witnessScript',
);
if (!input.prevOutScript) throw new Error('PrevOutScript is missing');
const expanded = expandOutput(input.prevOutScript, ourPubKey);
if (!expanded.pubkeys)
throw new Error(
expanded.type +
' not supported (' +
bscript.toASM(input.prevOutScript) +
')',
);
2018-12-28 03:56:03 +01:00
if (input.signatures && input.signatures.some(x => x !== undefined)) {
2019-03-03 15:07:49 +01:00
expanded.signatures = input.signatures;
2016-12-22 13:18:45 +01:00
}
2019-03-03 15:07:49 +01:00
let signScript = input.prevOutScript;
if (expanded.type === SCRIPT_TYPES.P2WPKH) {
2019-03-03 15:07:49 +01:00
signScript = <Buffer>(
payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output
);
}
2018-06-27 09:29:18 +02:00
return {
prevOutType: expanded.type,
prevOutScript: input.prevOutScript,
hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH,
signScript,
2018-06-27 09:29:18 +02:00
signType: expanded.type,
pubkeys: expanded.pubkeys,
signatures: expanded.signatures,
2019-03-03 15:07:49 +01:00
maxSignatures: expanded.maxSignatures,
};
2016-12-14 05:41:24 +01:00
}
2019-03-03 15:07:49 +01:00
const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output;
2018-06-27 09:29:18 +02:00
return {
prevOutType: SCRIPT_TYPES.P2PKH,
prevOutScript: prevOutScript,
2018-06-27 09:29:18 +02:00
hasWitness: false,
signScript: prevOutScript,
signType: SCRIPT_TYPES.P2PKH,
2018-06-27 09:29:18 +02:00
pubkeys: [ourPubKey],
2019-03-03 15:07:49 +01:00
signatures: [undefined],
};
2018-06-27 09:29:18 +02:00
}
2016-12-14 05:41:24 +01:00
2019-03-03 15:07:49 +01:00
function build(
type: string,
input: TxbInput,
allowIncomplete?: boolean,
): Payment | undefined {
const pubkeys = <Array<Buffer>>(input.pubkeys || []);
let signatures = <Array<Buffer>>(input.signatures || []);
2018-06-27 09:29:18 +02:00
switch (type) {
case SCRIPT_TYPES.P2PKH: {
2019-03-03 15:07:49 +01:00
if (pubkeys.length === 0) break;
if (signatures.length === 0) break;
2018-06-27 09:29:18 +02:00
2019-03-03 15:07:49 +01:00
return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] });
2018-06-27 09:29:18 +02:00
}
case SCRIPT_TYPES.P2WPKH: {
2019-03-03 15:07:49 +01:00
if (pubkeys.length === 0) break;
if (signatures.length === 0) break;
2018-06-27 09:29:18 +02:00
2019-03-03 15:07:49 +01:00
return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] });
2018-06-27 09:29:18 +02:00
}
case SCRIPT_TYPES.P2PK: {
2019-03-03 15:07:49 +01:00
if (pubkeys.length === 0) break;
if (signatures.length === 0) break;
2018-06-27 09:29:18 +02:00
2019-03-03 15:07:49 +01:00
return payments.p2pk({ signature: signatures[0] });
2018-06-27 09:29:18 +02:00
}
2018-09-03 06:06:30 +02:00
case SCRIPT_TYPES.P2MS: {
2019-03-03 15:07:49 +01:00
const m = input.maxSignatures;
2018-06-27 09:29:18 +02:00
if (allowIncomplete) {
2019-03-03 15:07:49 +01:00
signatures = signatures.map(x => x || ops.OP_0);
2018-06-27 09:29:18 +02:00
} else {
2019-03-03 15:07:49 +01:00
signatures = signatures.filter(x => x);
2018-06-27 09:29:18 +02:00
}
2018-09-26 06:57:03 +02:00
// if the transaction is not not complete (complete), or if signatures.length === m, validate
// otherwise, the number of OP_0's may be >= m, so don't validate (boo)
2019-03-03 15:07:49 +01:00
const validate = !allowIncomplete || m === signatures.length;
return payments.p2ms(
{ m, pubkeys, signatures },
{ allowIncomplete, validate },
);
2018-06-27 09:29:18 +02:00
}
case SCRIPT_TYPES.P2SH: {
2019-03-03 15:07:49 +01:00
const redeem = build(input.redeemScriptType!, input, allowIncomplete);
if (!redeem) return;
2018-06-27 09:29:18 +02:00
return payments.p2sh({
redeem: {
output: redeem.output || input.redeemScript,
input: redeem.input,
2019-03-03 15:07:49 +01:00
witness: redeem.witness,
},
});
2018-06-27 09:29:18 +02:00
}
case SCRIPT_TYPES.P2WSH: {
2019-03-03 15:07:49 +01:00
const redeem = build(input.witnessScriptType!, input, allowIncomplete);
if (!redeem) return;
2018-06-27 09:29:18 +02:00
return payments.p2wsh({
redeem: {
output: input.witnessScript,
input: redeem.input,
2019-03-03 15:07:49 +01:00
witness: redeem.witness,
},
});
2018-06-27 09:29:18 +02:00
}
2016-12-14 05:41:24 +01:00
}
}
2019-03-03 15:07:49 +01:00
function canSign(input: TxbInput): boolean {
return (
input.signScript !== undefined &&
2018-06-27 09:29:18 +02:00
input.signType !== undefined &&
input.pubkeys !== undefined &&
input.signatures !== undefined &&
2018-06-27 09:29:18 +02:00
input.signatures.length === input.pubkeys.length &&
input.pubkeys.length > 0 &&
2019-03-03 15:07:49 +01:00
(input.hasWitness === false || input.value !== undefined)
);
}
2019-03-03 15:07:49 +01:00
function signatureHashType(buffer: Buffer): number {
return buffer.readUInt8(buffer.length - 1);
}