@bitgo/utxo-lib
Version:
Client-side Bitcoin JavaScript library
1,079 lines • 159 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TransactionBuilder = void 0;
const types = require("bitcoinjs-lib/src/types");
const _1 = require("./");
const bufferutils = require("bitcoinjs-lib/src/bufferutils");
const classify = require("./classify");
const _2 = require("./");
const _3 = require("./");
const _4 = require("./");
const _5 = require("./");
const _6 = require("./");
const _7 = require("./");
const _8 = require("./");
const secp256k1_1 = require("@bitgo/secp256k1");
const typeforce = require('typeforce');
const tfFullSigner = (obj) => {
return typeforce.Buffer(obj.publicKey) && typeof obj.sign === 'function' && typeof obj.signSchnorr === 'function';
};
const SCRIPT_TYPES = classify.types;
const PREVOUT_TYPES = new Set([
// Raw
'p2pkh',
'p2pk',
'p2wpkh',
'p2ms',
// P2SH wrapped
'p2sh-p2pkh',
'p2sh-p2pk',
'p2sh-p2wpkh',
'p2sh-p2ms',
// P2WSH wrapped
'p2wsh-p2pkh',
'p2wsh-p2pk',
'p2wsh-p2ms',
// P2SH-P2WSH wrapper
'p2sh-p2wsh-p2pkh',
'p2sh-p2wsh-p2pk',
'p2sh-p2wsh-p2ms',
// P2TR KeyPath
'p2tr',
// P2TR ScriptPath
'p2tr-p2ns',
]);
function tfMessage(type, value, message) {
try {
typeforce(type, value);
}
catch (err) {
throw new Error(message);
}
}
function txIsString(tx) {
return typeof tx === 'string' || tx instanceof String;
}
function txIsTransaction(tx) {
return tx instanceof _8.Transaction;
}
class TransactionBuilder {
static fromTransaction(transaction, network, prevOutputs) {
const txb = new TransactionBuilder(network);
// Copy transaction fields
txb.setVersion(transaction.version);
txb.setLockTime(transaction.locktime);
// Copy outputs (done first to avoid signature invalidation)
transaction.outs.forEach((txOut) => {
txb.addOutput(txOut.script, txOut.value);
});
// Copy inputs
transaction.ins.forEach((txIn) => {
txb.__addInputUnsafe(txIn.hash, txIn.index, {
sequence: txIn.sequence,
script: txIn.script,
witness: txIn.witness,
});
});
// fix some things not possible through the public API
txb.__INPUTS.forEach((input, i) => {
fixMultisigOrder(input, transaction, i, prevOutputs);
});
return txb;
}
// WARNING: maximumFeeRate is __NOT__ to be relied on,
// it's just another potential safety mechanism (safety in-depth)
constructor(network = _3.networks.bitcoin, maximumFeeRate = 2500) {
this.network = network;
this.maximumFeeRate = maximumFeeRate;
this.__PREV_TX_SET = {};
this.__INPUTS = [];
this.__TX = new _8.Transaction();
this.__TX.version = 2;
this.__USE_LOW_R = false;
}
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
if (this.__INPUTS.some((input) => {
if (!input.signatures)
return false;
return input.signatures.some((s) => s !== undefined);
})) {
throw new Error('No, this would invalidate signatures');
}
this.__TX.locktime = locktime;
}
setVersion(version) {
typeforce(types.UInt32, version);
// XXX: this might eventually become more complex depending on what the versions represent
this.__TX.version = version;
}
addInput(txHash, vout, sequence, prevOutScript, value) {
if (!this.__canModifyInputs()) {
throw new Error('No, this would invalidate signatures');
}
// is it a hex string?
if (txIsString(txHash)) {
// transaction hashs's are displayed in reverse order, un-reverse it
txHash = bufferutils.reverseBuffer(Buffer.from(txHash, 'hex'));
// is it a Transaction object?
}
else if (txIsTransaction(txHash)) {
const txOut = txHash.outs[vout];
prevOutScript = txOut.script;
value = txOut.value;
txHash = txHash.getHash(false);
}
return this.__addInputUnsafe(txHash, vout, {
sequence,
prevOutScript,
value,
});
}
addOutput(scriptPubKey, value) {
if (!this.__canModifyOutputs()) {
throw new Error('No, this would invalidate signatures');
}
// Attempt to get a script if it's a base58 or bech32 address string
if (typeof scriptPubKey === 'string') {
scriptPubKey = _1.address.toOutputScript(scriptPubKey, this.network);
}
return this.__TX.addOutput(scriptPubKey, value);
}
build() {
return this.__build(false);
}
buildIncomplete() {
return this.__build(true);
}
sign(signParams, keyPair, redeemScript, hashType, witnessValue, witnessScript, controlBlock, annex) {
trySign(getSigningData(this.network, this.__INPUTS, this.__needsOutputs.bind(this), this.__TX, signParams, keyPair, redeemScript, hashType, witnessValue, witnessScript, controlBlock, annex, this.__USE_LOW_R));
}
__addInputUnsafe(txHash, vout, options) {
if (_8.Transaction.isCoinbaseHash(txHash)) {
throw new Error('coinbase inputs not supported');
}
const prevTxOut = txHash.toString('hex') + ':' + vout;
if (this.__PREV_TX_SET[prevTxOut] !== undefined)
throw new Error('Duplicate TxOut: ' + prevTxOut);
let input = {};
// derive what we can from the scriptSig
if (options.script !== undefined || options.witness !== undefined) {
input = expandInput(options.script, options.witness);
}
// if an input value was given, retain it
if (options.value !== undefined) {
input.value = options.value;
}
// derive what we can from the previous transactions output script
if (!input.prevOutScript && options.prevOutScript) {
let prevOutType;
if (!input.pubkeys && !input.signatures) {
const expanded = expandOutput(options.prevOutScript);
if (expanded.pubkeys) {
input.pubkeys = expanded.pubkeys;
input.signatures = expanded.signatures;
}
prevOutType = expanded.type;
}
input.prevOutScript = options.prevOutScript;
input.prevOutType = prevOutType || classify.output(options.prevOutScript);
}
const vin = this.__TX.addInput(txHash, vout, options.sequence, options.scriptSig);
this.__INPUTS[vin] = input;
this.__PREV_TX_SET[prevTxOut] = true;
return vin;
}
__build(allowIncomplete) {
if (!allowIncomplete) {
if (!this.__TX.ins.length)
throw new Error('Transaction has no inputs');
if (!this.__TX.outs.length)
throw new Error('Transaction has no outputs');
}
const tx = this.__TX.clone();
// create script signatures from inputs
this.__INPUTS.forEach((input, i) => {
if (!input.prevOutType && !allowIncomplete)
throw new Error('Transaction is not complete');
const result = build(input.prevOutType, input, allowIncomplete);
if (!result) {
if (!allowIncomplete && input.prevOutType === SCRIPT_TYPES.NONSTANDARD)
throw new Error('Unknown input type');
if (!allowIncomplete)
throw new Error('Not enough information');
return;
}
if (result.input) {
tx.setInputScript(i, result.input);
}
tx.setWitness(i, result.witness);
});
if (!allowIncomplete) {
// do not rely on this, its merely a last resort
if (this.__overMaximumFees(tx.virtualSize())) {
throw new Error('Transaction has absurd fees');
}
}
return tx;
}
__canModifyInputs() {
return this.__INPUTS.every((input) => {
if (!input.signatures)
return true;
return input.signatures.every((signature) => {
if (!signature)
return true;
const hashType = signatureHashType(signature);
// if SIGHASH_ANYONECANPAY is set, signatures would not
// be invalidated by more inputs
return (hashType & _8.Transaction.SIGHASH_ANYONECANPAY) !== 0;
});
});
}
__needsOutputs(signingHashType) {
if (signingHashType === _8.Transaction.SIGHASH_ALL || signingHashType === _8.Transaction.SIGHASH_DEFAULT) {
return this.__TX.outs.length === 0;
}
// if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs
// .build() will fail, but .buildIncomplete() is OK
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 & _8.Transaction.SIGHASH_NONE)
return false; // SIGHASH_NONE doesn't care about outputs
return true; // SIGHASH_* does care
});
}));
}
__canModifyOutputs() {
const nInputs = this.__TX.ins.length;
const nOutputs = this.__TX.outs.length;
return this.__INPUTS.every((input) => {
if (input.signatures === undefined)
return true;
return input.signatures.every((signature) => {
if (!signature)
return true;
const hashType = signatureHashType(signature);
const hashTypeMod = hashType & 0x1f;
if (hashTypeMod === _8.Transaction.SIGHASH_NONE)
return true;
if (hashTypeMod === _8.Transaction.SIGHASH_SINGLE) {
// if SIGHASH_SINGLE is set, and nInputs > nOutputs
// some signatures would be invalidated by the addition
// of more outputs
return nInputs <= nOutputs;
}
return false;
});
});
}
__overMaximumFees(bytes) {
// not all inputs will have .value defined
const incoming = this.__INPUTS.reduce((a, x) => a + (typeof x.value !== 'undefined' ? BigInt(x.value) : BigInt(0)), BigInt(0));
// but all outputs do, and if we have any input value
// we can immediately determine if the outputs are too small
const outgoing = this.__TX.outs.reduce((a, x) => a + BigInt(x.value), BigInt(0));
const fee = incoming - outgoing;
const feeRate = Number(fee) / bytes; // assume fee fits within number
return feeRate > this.maximumFeeRate;
}
}
exports.TransactionBuilder = TransactionBuilder;
function expandInput(scriptSig, witnessStack = [], type, scriptPubKey) {
if (scriptSig && scriptSig.length === 0 && witnessStack.length === 0)
return {};
if (!type) {
let ssType = scriptSig ? classify.input(scriptSig, true) : undefined;
let wsType = classify.witness(witnessStack, true);
if (ssType === SCRIPT_TYPES.NONSTANDARD)
ssType = undefined;
if (wsType === SCRIPT_TYPES.NONSTANDARD)
wsType = undefined;
type = ssType || wsType;
}
switch (type) {
case SCRIPT_TYPES.P2WPKH: {
const { output, pubkey, signature } = _4.payments.p2wpkh({
witness: witnessStack,
});
return {
prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2WPKH,
pubkeys: [pubkey],
signatures: [signature],
};
}
case SCRIPT_TYPES.P2PKH: {
const { output, pubkey, signature } = _4.payments.p2pkh({
input: scriptSig,
});
return {
prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2PKH,
pubkeys: [pubkey],
signatures: [signature],
};
}
case SCRIPT_TYPES.P2PK: {
const { signature } = _4.payments.p2pk({ input: scriptSig });
return {
prevOutType: SCRIPT_TYPES.P2PK,
pubkeys: [undefined],
signatures: [signature],
};
}
case SCRIPT_TYPES.P2MS: {
const { m, pubkeys, signatures } = _4.payments.p2ms({
input: scriptSig,
output: scriptPubKey,
}, { allowIncomplete: true });
return {
prevOutType: SCRIPT_TYPES.P2MS,
pubkeys,
signatures,
maxSignatures: m,
};
}
case SCRIPT_TYPES.P2TR_NS: {
const { n, pubkeys, signatures } = _1.p2trPayments.p2tr_ns({
// Witness signatures are reverse of pubkeys, because it's a stack
signatures: witnessStack.length ? witnessStack.reverse() : undefined,
output: scriptPubKey,
}, { allowIncomplete: true, eccLib: secp256k1_1.ecc });
return {
prevOutType: SCRIPT_TYPES.P2TR_NS,
pubkeys,
signatures,
maxSignatures: n,
};
}
}
if (type === SCRIPT_TYPES.P2SH) {
const { output, redeem } = _4.payments.p2sh({
input: scriptSig,
witness: witnessStack,
});
const outputType = classify.output(redeem.output);
const expanded = expandInput(redeem.input, redeem.witness, outputType, redeem.output);
if (!expanded.prevOutType)
return {};
return {
prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2SH,
redeemScript: redeem.output,
redeemScriptType: expanded.prevOutType,
witnessScript: expanded.witnessScript,
witnessScriptType: expanded.witnessScriptType,
pubkeys: expanded.pubkeys,
signatures: expanded.signatures,
};
}
if (type === SCRIPT_TYPES.P2WSH) {
const { output, redeem } = _4.payments.p2wsh({
input: scriptSig,
witness: witnessStack,
});
const outputType = classify.output(redeem.output);
let expanded;
if (outputType === SCRIPT_TYPES.P2WPKH) {
expanded = expandInput(redeem.input, redeem.witness, outputType);
}
else {
expanded = expandInput(_5.script.compile(redeem.witness), [], outputType, redeem.output);
}
if (!expanded.prevOutType)
return {};
return {
prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2WSH,
witnessScript: redeem.output,
witnessScriptType: expanded.prevOutType,
pubkeys: expanded.pubkeys,
signatures: expanded.signatures,
};
}
if (type === SCRIPT_TYPES.P2TR) {
const parsedWitness = _7.taproot.parseTaprootWitness(witnessStack);
if (parsedWitness.spendType === 'Key') {
// key path spend, nothing to expand
const { signature, annex } = parsedWitness;
return {
prevOutType: SCRIPT_TYPES.P2TR,
signatures: [signature],
annex,
};
}
else {
// script path spend
const { tapscript, controlBlock, annex } = parsedWitness;
const prevOutScript = _1.p2trPayments.p2tr({
redeems: [{ output: tapscript }],
redeemIndex: 0,
controlBlock,
annex,
}, { eccLib: secp256k1_1.ecc }).output;
const witnessScriptType = classify.output(tapscript);
const { pubkeys, signatures } = expandInput(undefined, parsedWitness.scriptSig, witnessScriptType, tapscript);
return {
prevOutScript,
prevOutType: SCRIPT_TYPES.P2TR,
witnessScript: tapscript,
witnessScriptType,
controlBlock,
annex,
pubkeys,
signatures,
};
}
}
return {
prevOutType: SCRIPT_TYPES.NONSTANDARD,
prevOutScript: scriptSig,
};
}
// could be done in expandInput, but requires the original Transaction for hashForSignature
function fixMultisigOrder(input, transaction, vin, prevOutputs) {
if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript)
return;
if (input.pubkeys.length === input.signatures.length)
return;
const prevOutput = prevOutputs && prevOutputs[vin];
const unmatched = input.signatures.concat();
input.signatures = input.pubkeys.map((pubKey) => {
const keyPair = secp256k1_1.ECPair.fromPublicKey(pubKey);
let match;
// check for a signature
unmatched.some((signature, i) => {
// skip if undefined || OP_0
if (!signature)
return false;
// TODO: avoid O(n) hashForSignature
const parsed = _5.script.signature.decode(signature);
const hash = transaction.hashForSignature(vin, input.redeemScript, parsed.hashType, prevOutput?.value);
// skip if signature does not match pubKey
if (!keyPair.verify(hash, parsed.signature))
return false;
// remove matched signature from unmatched
unmatched[i] = undefined;
match = signature;
return true;
});
return match;
});
}
function expandOutput(script, ourPubKey, controlBlock) {
typeforce(types.Buffer, script);
const type = classify.output(script);
switch (type) {
case SCRIPT_TYPES.P2PKH: {
if (!ourPubKey)
return { type };
// does our hash160(pubKey) match the output scripts?
const pkh1 = _4.payments.p2pkh({ output: script }).hash;
const pkh2 = _2.crypto.hash160(ourPubKey);
if (!pkh1.equals(pkh2))
return { type };
return {
type,
pubkeys: [ourPubKey],
signatures: [undefined],
};
}
case SCRIPT_TYPES.P2WPKH: {
if (!ourPubKey)
return { type };
// does our hash160(pubKey) match the output scripts?
const wpkh1 = _4.payments.p2wpkh({ output: script }).hash;
const wpkh2 = _2.crypto.hash160(ourPubKey);
if (!wpkh1.equals(wpkh2))
return { type };
return {
type,
pubkeys: [ourPubKey],
signatures: [undefined],
};
}
case SCRIPT_TYPES.P2TR: {
if (!ourPubKey)
return { type };
// HACK ourPubKey to BIP340-style
if (ourPubKey.length === 33)
ourPubKey = ourPubKey.slice(1);
// TODO: support multiple pubkeys
const p2tr = _1.p2trPayments.p2tr({ pubkey: ourPubKey, controlBlock }, { eccLib: secp256k1_1.ecc });
// Does tweaked output for a single pubkey match?
if (!script.equals(p2tr.output))
return { type };
// P2TR KeyPath, single key
return {
type,
pubkeys: [ourPubKey],
signatures: [undefined],
};
}
case SCRIPT_TYPES.P2TR_NS: {
const p2trNs = _1.p2trPayments.p2tr_ns({ output: script }, { eccLib: secp256k1_1.ecc });
// P2TR ScriptPath
return {
type,
pubkeys: p2trNs.pubkeys,
signatures: p2trNs.pubkeys.map(() => undefined),
maxSignatures: p2trNs.pubkeys.length,
};
}
case SCRIPT_TYPES.P2PK: {
const p2pk = _4.payments.p2pk({ output: script });
return {
type,
pubkeys: [p2pk.pubkey],
signatures: [undefined],
};
}
case SCRIPT_TYPES.P2MS: {
const p2ms = _4.payments.p2ms({ output: script });
return {
type,
pubkeys: p2ms.pubkeys,
signatures: p2ms.pubkeys.map(() => undefined),
maxSignatures: p2ms.m,
};
}
}
return { type };
}
function prepareInput(input, ourPubKey, redeemScript, witnessScript, controlBlock, annex) {
if (redeemScript && witnessScript) {
const p2wsh = _4.payments.p2wsh({
redeem: { output: witnessScript },
});
const p2wshAlt = _4.payments.p2wsh({ output: redeemScript });
const p2sh = _4.payments.p2sh({ redeem: { output: redeemScript } });
const p2shAlt = _4.payments.p2sh({ redeem: p2wsh });
// enforces P2SH(P2WSH(...))
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 (' + _5.script.toASM(witnessScript) + ')');
}
if (input.signatures && input.signatures.some((x) => x !== undefined)) {
expanded.signatures = input.signatures;
}
const signScript = witnessScript;
if (expanded.type === SCRIPT_TYPES.P2WPKH)
throw new Error('P2SH(P2WSH(P2WPKH)) is a consensus failure');
return {
redeemScript,
redeemScriptType: SCRIPT_TYPES.P2WSH,
witnessScript,
witnessScriptType: expanded.type,
prevOutType: SCRIPT_TYPES.P2SH,
prevOutScript: p2sh.output,
witnessVersion: 0,
signScript,
signType: expanded.type,
pubkeys: expanded.pubkeys,
signatures: expanded.signatures,
maxSignatures: expanded.maxSignatures,
};
}
if (redeemScript) {
const p2sh = _4.payments.p2sh({ redeem: { output: redeemScript } });
if (input.prevOutScript) {
let p2shAlt;
try {
p2shAlt = _4.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 (' + _5.script.toASM(redeemScript) + ')');
}
if (input.signatures && input.signatures.some((x) => x !== undefined)) {
expanded.signatures = input.signatures;
}
let signScript = redeemScript;
if (expanded.type === SCRIPT_TYPES.P2WPKH) {
signScript = _4.payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output;
}
return {
redeemScript,
redeemScriptType: expanded.type,
prevOutType: SCRIPT_TYPES.P2SH,
prevOutScript: p2sh.output,
witnessVersion: expanded.type === SCRIPT_TYPES.P2WPKH ? 0 : undefined,
signScript,
signType: expanded.type,
pubkeys: expanded.pubkeys,
signatures: expanded.signatures,
maxSignatures: expanded.maxSignatures,
};
}
if (witnessScript && controlBlock) {
// P2TR ScriptPath
/* tslint:disable-next-line:no-shadowed-variable */
let prevOutScript = input.prevOutScript;
if (!prevOutScript) {
prevOutScript = _1.p2trPayments.p2tr({
redeems: [{ output: witnessScript }],
redeemIndex: 0,
controlBlock,
annex,
}, { eccLib: secp256k1_1.ecc }).output;
}
const expanded = expandOutput(witnessScript, ourPubKey);
if (!expanded.pubkeys) {
throw new Error(expanded.type + ' not supported as witnessScript (' + _5.script.toASM(witnessScript) + ')');
}
if (input.signatures && input.signatures.some((x) => x !== undefined)) {
expanded.signatures = input.signatures;
}
return {
witnessScript,
witnessScriptType: expanded.type,
prevOutType: SCRIPT_TYPES.P2TR,
prevOutScript,
witnessVersion: 1,
signScript: witnessScript,
signType: expanded.type,
pubkeys: expanded.pubkeys,
signatures: expanded.signatures,
maxSignatures: expanded.maxSignatures,
controlBlock,
annex,
};
}
if (witnessScript) {
const p2wsh = _4.payments.p2wsh({ redeem: { output: witnessScript } });
if (input.prevOutScript) {
const p2wshAlt = _4.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 (' + _5.script.toASM(witnessScript) + ')');
}
if (input.signatures && input.signatures.some((x) => x !== undefined)) {
expanded.signatures = input.signatures;
}
const signScript = witnessScript;
if (expanded.type === SCRIPT_TYPES.P2WPKH)
throw new Error('P2WSH(P2WPKH) is a consensus failure');
return {
witnessScript,
witnessScriptType: expanded.type,
prevOutType: SCRIPT_TYPES.P2WSH,
prevOutScript: p2wsh.output,
witnessVersion: 0,
signScript,
signType: expanded.type,
pubkeys: expanded.pubkeys,
signatures: expanded.signatures,
maxSignatures: expanded.maxSignatures,
};
}
if (input.prevOutType && input.prevOutScript) {
// embedded scripts are not possible without extra information
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');
}
const expanded = expandOutput(input.prevOutScript, ourPubKey);
if (!expanded.pubkeys) {
throw new Error(expanded.type + ' not supported (' + _5.script.toASM(input.prevOutScript) + ')');
}
if (input.signatures && input.signatures.some((x) => x !== undefined)) {
expanded.signatures = input.signatures;
}
let signScript = input.prevOutScript;
if (expanded.type === SCRIPT_TYPES.P2WPKH) {
signScript = _4.payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output;
}
let witnessVersion;
if (expanded.type === SCRIPT_TYPES.P2WPKH) {
witnessVersion = 0;
}
else if (expanded.type === SCRIPT_TYPES.P2TR) {
witnessVersion = 1;
}
return {
prevOutType: expanded.type,
prevOutScript: input.prevOutScript,
witnessVersion,
signScript,
signType: expanded.type,
pubkeys: expanded.pubkeys,
signatures: expanded.signatures,
maxSignatures: expanded.maxSignatures,
};
}
const prevOutScript = _4.payments.p2pkh({ pubkey: ourPubKey }).output;
return {
prevOutType: SCRIPT_TYPES.P2PKH,
prevOutScript,
signScript: prevOutScript,
signType: SCRIPT_TYPES.P2PKH,
pubkeys: [ourPubKey],
signatures: [undefined],
};
}
function build(type, input, allowIncomplete) {
const pubkeys = (input.pubkeys || []);
let signatures = (input.signatures || []);
switch (type) {
case SCRIPT_TYPES.P2PKH: {
if (pubkeys.length === 0)
break;
if (signatures.length === 0)
break;
return _4.payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] });
}
case SCRIPT_TYPES.P2WPKH: {
if (pubkeys.length === 0)
break;
if (signatures.length === 0)
break;
return _4.payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] });
}
case SCRIPT_TYPES.P2PK: {
if (pubkeys.length === 0)
break;
if (signatures.length === 0)
break;
return _4.payments.p2pk({ signature: signatures[0] });
}
case SCRIPT_TYPES.P2MS: {
const m = input.maxSignatures;
if (allowIncomplete) {
signatures = signatures.map((x) => x || _6.opcodes.OP_0);
}
else {
signatures = signatures.filter((x) => x);
}
// 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)
const validate = !allowIncomplete || m === signatures.length;
return _4.payments.p2ms({ m, pubkeys, signatures }, { allowIncomplete, validate });
}
case SCRIPT_TYPES.P2SH: {
const redeem = build(input.redeemScriptType, input, allowIncomplete);
if (!redeem)
return;
return _4.payments.p2sh({
redeem: {
output: redeem.output || input.redeemScript,
input: redeem.input,
witness: redeem.witness,
},
});
}
case SCRIPT_TYPES.P2WSH: {
const redeem = build(input.witnessScriptType, input, allowIncomplete);
if (!redeem)
return;
return _4.payments.p2wsh({
redeem: {
output: input.witnessScript,
input: redeem.input,
witness: redeem.witness,
},
});
}
case SCRIPT_TYPES.P2TR: {
if (input.witnessScriptType === SCRIPT_TYPES.P2TR_NS) {
// ScriptPath
const redeem = build(input.witnessScriptType, input, allowIncomplete);
return _1.p2trPayments.p2tr({
output: input.prevOutScript,
controlBlock: input.controlBlock,
annex: input.annex,
redeems: [redeem],
redeemIndex: 0,
}, { eccLib: secp256k1_1.ecc });
}
// KeyPath
if (signatures.length === 0)
break;
return _1.p2trPayments.p2tr({ pubkeys, signature: signatures[0] }, { eccLib: secp256k1_1.ecc });
}
case SCRIPT_TYPES.P2TR_NS: {
const m = input.maxSignatures;
if (allowIncomplete) {
signatures = signatures.map((x) => x || _6.opcodes.OP_0);
}
else {
signatures = signatures.filter((x) => x);
}
// 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)
const validate = !allowIncomplete || m === signatures.length;
return _1.p2trPayments.p2tr_ns({ pubkeys, signatures }, { allowIncomplete, validate, eccLib: secp256k1_1.ecc });
}
}
}
function canSign(input) {
return (input.signScript !== undefined &&
input.signType !== undefined &&
input.pubkeys !== undefined &&
input.signatures !== undefined &&
input.signatures.length === input.pubkeys.length &&
input.pubkeys.length > 0 &&
(input.witnessVersion === undefined || input.value !== undefined));
}
function signatureHashType(buffer) {
if (_5.script.isCanonicalSchnorrSignature(buffer) && buffer.length === 64) {
return _8.Transaction.SIGHASH_DEFAULT;
}
return buffer.readUInt8(buffer.length - 1);
}
function checkSignArgs(inputs, signParams) {
if (!PREVOUT_TYPES.has(signParams.prevOutScriptType)) {
throw new TypeError(`Unknown prevOutScriptType "${signParams.prevOutScriptType}"`);
}
tfMessage(typeforce.Number, signParams.vin, `sign must include vin parameter as Number (input index)`);
tfMessage(tfFullSigner, signParams.keyPair, `sign must include keyPair parameter as Signer interface`);
tfMessage(typeforce.maybe(typeforce.Number), signParams.hashType, `sign hashType parameter must be a number`);
const prevOutType = (inputs[signParams.vin] || []).prevOutType;
const posType = signParams.prevOutScriptType;
switch (posType) {
case 'p2pkh':
if (prevOutType && prevOutType !== 'pubkeyhash') {
throw new TypeError(`input #${signParams.vin} is not of type p2pkh: ${prevOutType}`);
}
tfMessage(typeforce.value(undefined), signParams.witnessScript, `${posType} requires NO witnessScript`);
tfMessage(typeforce.value(undefined), signParams.redeemScript, `${posType} requires NO redeemScript`);
tfMessage(typeforce.value(undefined), signParams.witnessValue, `${posType} requires NO witnessValue`);
break;
case 'p2pk':
if (prevOutType && prevOutType !== 'pubkey') {
throw new TypeError(`input #${signParams.vin} is not of type p2pk: ${prevOutType}`);
}
tfMessage(typeforce.value(undefined), signParams.witnessScript, `${posType} requires NO witnessScript`);
tfMessage(typeforce.value(undefined), signParams.redeemScript, `${posType} requires NO redeemScript`);
tfMessage(typeforce.value(undefined), signParams.witnessValue, `${posType} requires NO witnessValue`);
break;
case 'p2wpkh':
if (prevOutType && prevOutType !== 'witnesspubkeyhash') {
throw new TypeError(`input #${signParams.vin} is not of type p2wpkh: ${prevOutType}`);
}
tfMessage(typeforce.value(undefined), signParams.witnessScript, `${posType} requires NO witnessScript`);
tfMessage(typeforce.value(undefined), signParams.redeemScript, `${posType} requires NO redeemScript`);
tfMessage(types.Satoshi, signParams.witnessValue, `${posType} requires witnessValue`);
break;
case 'p2ms':
if (prevOutType && prevOutType !== 'multisig') {
throw new TypeError(`input #${signParams.vin} is not of type p2ms: ${prevOutType}`);
}
tfMessage(typeforce.value(undefined), signParams.witnessScript, `${posType} requires NO witnessScript`);
tfMessage(typeforce.value(undefined), signParams.redeemScript, `${posType} requires NO redeemScript`);
tfMessage(typeforce.value(undefined), signParams.witnessValue, `${posType} requires NO witnessValue`);
break;
case 'p2sh-p2wpkh':
if (prevOutType && prevOutType !== 'scripthash') {
throw new TypeError(`input #${signParams.vin} is not of type p2sh-p2wpkh: ${prevOutType}`);
}
tfMessage(typeforce.value(undefined), signParams.witnessScript, `${posType} requires NO witnessScript`);
tfMessage(typeforce.Buffer, signParams.redeemScript, `${posType} requires redeemScript`);
tfMessage(types.Satoshi, signParams.witnessValue, `${posType} requires witnessValue`);
break;
case 'p2sh-p2ms':
case 'p2sh-p2pk':
case 'p2sh-p2pkh':
if (prevOutType && prevOutType !== 'scripthash') {
throw new TypeError(`input #${signParams.vin} is not of type ${posType}: ${prevOutType}`);
}
tfMessage(typeforce.value(undefined), signParams.witnessScript, `${posType} requires NO witnessScript`);
tfMessage(typeforce.Buffer, signParams.redeemScript, `${posType} requires redeemScript`);
tfMessage(typeforce.value(undefined), signParams.witnessValue, `${posType} requires NO witnessValue`);
break;
case 'p2wsh-p2ms':
case 'p2wsh-p2pk':
case 'p2wsh-p2pkh':
if (prevOutType && prevOutType !== 'witnessscripthash') {
throw new TypeError(`input #${signParams.vin} is not of type ${posType}: ${prevOutType}`);
}
tfMessage(typeforce.Buffer, signParams.witnessScript, `${posType} requires witnessScript`);
tfMessage(typeforce.value(undefined), signParams.redeemScript, `${posType} requires NO redeemScript`);
tfMessage(types.Satoshi, signParams.witnessValue, `${posType} requires witnessValue`);
break;
case 'p2sh-p2wsh-p2ms':
case 'p2sh-p2wsh-p2pk':
case 'p2sh-p2wsh-p2pkh':
if (prevOutType && prevOutType !== 'scripthash') {
throw new TypeError(`input #${signParams.vin} is not of type ${posType}: ${prevOutType}`);
}
tfMessage(typeforce.Buffer, signParams.witnessScript, `${posType} requires witnessScript`);
tfMessage(typeforce.Buffer, signParams.redeemScript, `${posType} requires witnessScript`);
tfMessage(types.Satoshi, signParams.witnessValue, `${posType} requires witnessScript`);
break;
case 'p2tr':
if (prevOutType && prevOutType !== 'taproot') {
throw new TypeError(`input #${signParams.vin} is not of type ${posType}: ${prevOutType}`);
}
tfMessage(typeforce.value(undefined), signParams.witnessScript, `${posType} requires NO witnessScript`);
tfMessage(typeforce.value(undefined), signParams.redeemScript, `${posType} requires NO redeemScript`);
tfMessage(typeforce.value(undefined), signParams.witnessValue, `${posType} requires NO witnessValue`);
break;
case 'p2tr-p2ns':
if (prevOutType && prevOutType !== 'taproot') {
throw new TypeError(`input #${signParams.vin} is not of type ${posType}: ${prevOutType}`);
}
inputs[signParams.vin].prevOutType = inputs[signParams.vin].prevOutType || 'taproot';
tfMessage(typeforce.Buffer, signParams.witnessScript, `${posType} requires witnessScript`);
tfMessage(typeforce.Buffer, signParams.controlBlock, `${posType} requires controlBlock`);
tfMessage(typeforce.value(undefined), signParams.redeemScript, `${posType} requires NO redeemScript`);
break;
}
}
function trySign({ input, ourPubKey, keyPair, signatureHash, hashType, useLowR, taptreeRoot, }) {
if (input.witnessVersion === 1 && ourPubKey.length === 33)
ourPubKey = ourPubKey.slice(1);
// enforce in order signing of public keys
let signed = false;
for (const [i, pubKey] of input.pubkeys.entries()) {
if (!ourPubKey.equals(pubKey))
continue;
if (input.signatures[i] && input.signatures[i].length > 0)
throw new Error('Signature already exists');
// TODO: add tests
if (ourPubKey.length !== 33 && input.witnessVersion === 0) {
throw new Error('BIP143 (Witness v0) inputs require compressed pubkeys');
}
else if (ourPubKey.length !== 32 && input.witnessVersion === 1) {
throw new Error('BIP341 (Witness v1) inputs require x-only pubkeys');
}
if (input.witnessVersion === 1) {
if (!input.witnessScript) {
// FIXME: Workaround for not having proper tweaking support for key path
if (!keyPair.privateKey) {
throw new Error(`unexpected keypair`);
}
const privateKey = _7.taproot.tapTweakPrivkey(secp256k1_1.ecc, ourPubKey, keyPair.privateKey, taptreeRoot);
keyPair = secp256k1_1.ECPair.fromPrivateKey(Buffer.from(privateKey));
}
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#common-signature-message
const signature = keyPair.signSchnorr(signatureHash);
// SIGHASH_DEFAULT is omitted from the signature
if (hashType === _8.Transaction.SIGHASH_DEFAULT) {
input.signatures[i] = Buffer.from(signature);
}
else {
input.signatures[i] = Buffer.concat([signature, Buffer.of(hashType)]);
}
}
else {
const signature = keyPair.sign(signatureHash, useLowR);
input.signatures[i] = _5.script.signature.encode(signature, hashType);
}
signed = true;
}
if (!signed)
throw new Error('Key pair cannot sign for this input');
}
function getSigningData(network, inputs, needsOutputs, tx, signParams, keyPair, redeemScript, hashType, witnessValue, witnessScript, controlBlock, annex, useLowR) {
let vin;
if (typeof signParams === 'number') {
console.warn('DEPRECATED: TransactionBuilder sign method arguments ' + 'will change in v6, please use the TxbSignArg interface');
vin = signParams;
}
else if (typeof signParams === 'object') {
checkSignArgs(inputs, signParams);
({ vin, keyPair, redeemScript, hashType, witnessValue, witnessScript, controlBlock, annex } = signParams);
}
else {
throw new TypeError('TransactionBuilder sign first arg must be TxbSignArg or number');
}
if (keyPair === undefined) {
throw new Error('sign requires keypair');
}
if (!inputs[vin])
throw new Error('No input at index: ' + vin);
const input = inputs[vin];
// if redeemScript was previously provided, enforce consistency
if (input.redeemScript !== undefined && redeemScript && !input.redeemScript.equals(redeemScript)) {
throw new Error('Inconsistent redeemScript');
}
const ourPubKey = keyPair.publicKey || (keyPair.getPublicKey && keyPair.getPublicKey());
if (!canSign(input)) {
if (witnessValue !== undefined) {
if (input.value !== undefined && input.value !== witnessValue) {
throw new Error('Input did not match witnessValue');
}
typeforce(types.Satoshi, witnessValue);
input.value = witnessValue;
}
if (!canSign(input)) {
const prepared = prepareInput(input, ourPubKey, redeemScript, witnessScript, controlBlock, annex);
// updates inline
Object.assign(input, prepared);
}
if (!canSign(input))
throw Error(input.prevOutType + ' not supported');
}
// hashType can be 0 in Taproot, so can't use hashType || SIGHASH_ALL
if (input.witnessVersion === 1) {
hashType = hashType === undefined ? _8.Transaction.SIGHASH_DEFAULT : hashType;
}
else {
hashType = hashType || _8.Transaction.SIGHASH_ALL;
}
if (needsOutputs(hashType))
throw new Error('Transaction needs outputs');
// TODO: This is not the best place to do this, but might stick with it until PSBT
let leafHash;
let taptreeRoot;
if (controlBlock && witnessScript) {
leafHash = _7.taproot.getTapleafHash(secp256k1_1.ecc, controlBlock, witnessScript);
taptreeRoot = _7.taproot.getTaptreeRoot(secp256k1_1.ecc, controlBlock, witnessScript, leafHash);
}
// ready to sign
let signatureHash;
switch (input.witnessVersion) {
case undefined:
signatureHash = tx.hashForSignature(vin, input.signScript, hashType, input.value);
break;
case 0:
signatureHash = tx.hashForWitnessV0(vin, input.signScript, input.value, hashType);
break;
case 1:
signatureHash = tx.hashForWitnessV1(vin, inputs.map(({ prevOutScript }) => prevOutScript), inputs.map(({ value }) => value), hashType, leafHash);
break;
default:
throw new TypeError('Unsupported witness version');
}
return {
input,
ourPubKey,
keyPair,
signatureHash,
hashType,
useLowR: !!useLowR,
taptreeRoot,
};
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJhbnNhY3Rpb25fYnVpbGRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90cmFuc2FjdGlvbl9idWlsZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLGlEQUFpRDtBQUNqRCx5QkFBdUQ7QUFDdkQsNkRBQTZEO0FBQzdELHVDQUF1QztBQUN2Qyx5QkFBdUM7QUFDdkMseUJBQThCO0FBRTlCLHlCQUE4QjtBQUU5Qix5QkFBdUM7QUFDdkMseUJBQW9DO0FBQ3BDLHlCQUE2QjtBQUM3Qix5QkFBMkM7QUFDM0MsZ0RBQXlEO0FBVXpELE1BQU0sU0FBUyxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUV2QyxNQUFNLFlBQVksR0FBRyxDQUFDLEdBQVEsRUFBVyxFQUFFO0lBQ3pDLE9BQU8sU0FBUyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLElBQUksT0FBTyxHQUFHLENBQUMsSUFBSSxLQUFLLFVBQVUsSUFBSSxPQUFPLEdBQUcsQ0FBQyxXQUFXLEtBQUssVUFBVSxDQUFDO0FBQ3BILENBQUMsQ0FBQztBQUVGLE1BQU0sWUFBWSxHQUFHLFFBQVEsQ0FBQyxLQUFLLENBQUM7QUFFcEMsTUFBTSxhQUFhLEdBQWdCLElBQUksR0FBRyxDQUFDO0lBQ3pDLE1BQU07SUFDTixPQUFPO0lBQ1AsTUFBTTtJQUNOLFFBQVE7SUFDUixNQUFNO0lBQ04sZUFBZTtJQUNmLFlBQVk7SUFDWixXQUFXO0lBQ1gsYUFBYTtJQUNiLFdBQVc7SUFDWCxnQkFBZ0I7SUFDaEIsYUFBYTtJQUNiLFlBQVk7SUFDWixZQUFZO0lBQ1oscUJBQXFCO0lBQ3JCLGtCQUFrQjtJQUNsQixpQkFBaUI7SUFDakIsaUJBQWlCO0lBQ2pCLGVBQWU7SUFDZixNQUFNO0lBQ04sa0JBQWtCO0lBQ2xCLFdBQVc7Q0FDWixDQUFDLENBQUM7QUFrREgsU0FBUyxTQUFTLENBQUMsSUFBUyxFQUFFLEtBQVUsRUFBRSxPQUFlO0lBQ3ZE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