PHP WebShell
Текущая директория: /opt/BitGoJS/modules/utxo-lib/src/bitgo/wallet
Просмотр файла: Psbt.ts
import * as assert from 'assert';
import { GlobalXpub, PartialSig, PsbtInput, TapScriptSig } from 'bip174/src/lib/interfaces';
import { checkForInput } from 'bip174/src/lib/utils';
import { BIP32Interface } from 'bip32';
import * as bs58check from 'bs58check';
import { UtxoPsbt } from '../UtxoPsbt';
import { UtxoTransaction } from '../UtxoTransaction';
import {
createOutputScript2of3,
getLeafHash,
ScriptType2Of3,
scriptTypeForChain,
toXOnlyPublicKey,
} from '../outputScripts';
import { DerivedWalletKeys, RootWalletKeys } from './WalletKeys';
import { toPrevOutputWithPrevTx } from '../Unspent';
import { createPsbtFromHex, createPsbtFromTransaction, createTransactionFromBuffer } from '../transaction';
import { isWalletUnspent, WalletUnspent } from './Unspent';
import {
getLeafVersion,
calculateScriptPathLevel,
isValidControlBock,
ParsedPubScriptP2ms,
ParsedPubScriptTaprootScriptPath,
parsePubScript2Of3,
ParsedPubScriptTaproot,
ParsedPubScriptTaprootKeyPath,
parsePubScript,
ParsedPubScriptP2shP2pk,
ParsedScriptType,
isPlaceholderSignature,
parseSignatureScript,
ParsedScriptType2Of3,
} from '../parseInput';
import { parsePsbtMusig2PartialSigs } from '../Musig2';
import { isTuple, Triple } from '../types';
import { createTaprootOutputScript } from '../../taproot';
import { opcodes as ops, script as bscript, TxInput } from 'bitcoinjs-lib';
import { opcodes, payments } from '../../index';
import { getPsbtInputSignatureCount, isPsbtInputFinalized } from '../PsbtUtil';
// only used for building `SignatureContainer`
type BaseSignatureContainer<T> = {
signatures: T;
};
type UnsignedSignatureContainer = BaseSignatureContainer<undefined>;
type HalfSignedSignatureContainer = BaseSignatureContainer<[Buffer]>;
type FullSignedSignatureContainer = BaseSignatureContainer<[Buffer, Buffer]>;
type SignatureContainer = UnsignedSignatureContainer | HalfSignedSignatureContainer | FullSignedSignatureContainer;
/**
* Contents of a pre-finalized PSBT Input for p2trMusig2 key path in the non-finalized state.
* T is [Buffer] for first signature, [Buffer, Buffer] for both signatures and `undefined` for no signatures.
*/
type BaseTaprootKeyPathSignatureContainer<T> = {
signatures: T;
/** Only contains participants that have added a signature */
participantPublicKeys: T;
};
type UnsignedTaprootKeyPathSignatureContainer = BaseTaprootKeyPathSignatureContainer<undefined>;
type HalfSignedTaprootKeyPathSignatureContainer = BaseTaprootKeyPathSignatureContainer<[Buffer]>;
type FullSignedTaprootKeyPathSignatureContainer = BaseTaprootKeyPathSignatureContainer<[Buffer, Buffer]>;
type TaprootKeyPathSignatureContainer =
| UnsignedTaprootKeyPathSignatureContainer
| HalfSignedTaprootKeyPathSignatureContainer
| FullSignedTaprootKeyPathSignatureContainer;
/**
* To hold parsed psbt data for p2ms based script types - p2sh, p2wsh, and p2shP2wsh
*/
export type ParsedPsbtP2ms = ParsedPubScriptP2ms & SignatureContainer;
/**
* To hold parsed psbt data for TaprootKeyPathSpend script type.
*/
export type ParsedPsbtTaprootKeyPath = ParsedPubScriptTaprootKeyPath & TaprootKeyPathSignatureContainer;
/**
* To hold parsed psbt data for TaprootScriptPathSpend script path script type.
*/
export type ParsedPsbtTaprootScriptPath = ParsedPubScriptTaprootScriptPath &
SignatureContainer & {
controlBlock: Buffer;
leafVersion: number;
/** Indicates the level inside the taptree. */
scriptPathLevel: number;
};
export type ParsedPsbtTaproot = ParsedPsbtTaprootKeyPath | ParsedPsbtTaprootScriptPath;
type P2shP2pkSignatureContainer = UnsignedSignatureContainer | HalfSignedSignatureContainer;
export type ParsedPsbtP2shP2pk = ParsedPubScriptP2shP2pk & P2shP2pkSignatureContainer;
interface WalletSigner {
walletKey: BIP32Interface;
rootKey: BIP32Interface;
}
/**
* psbt input index and its user, backup, bitgo signatures status
*/
export type SignatureValidation = [index: number, sigTriple: Triple<boolean>];
function getTaprootSigners(script: Buffer, walletKeys: DerivedWalletKeys): [WalletSigner, WalletSigner] {
const parsedPublicKeys = parsePubScript2Of3(script, 'taprootScriptPathSpend').publicKeys;
const walletSigners = parsedPublicKeys.map((publicKey) => {
const index = walletKeys.publicKeys.findIndex((walletPublicKey) =>
toXOnlyPublicKey(walletPublicKey).equals(publicKey)
);
if (index >= 0) {
return { walletKey: walletKeys.triple[index], rootKey: walletKeys.parent.triple[index] };
}
throw new Error('Taproot public key is not a wallet public key');
});
return [walletSigners[0], walletSigners[1]];
}
function updatePsbtInput(
psbt: UtxoPsbt,
inputIndex: number,
unspent: WalletUnspent<bigint>,
rootWalletKeys: RootWalletKeys
): void {
const input = checkForInput(psbt.data.inputs, inputIndex);
const signatureCount = getPsbtInputSignatureCount(input);
const scriptType = scriptTypeForChain(unspent.chain);
if (signatureCount === 0 && scriptType === 'p2tr') {
return;
}
const walletKeys = rootWalletKeys.deriveForChainAndIndex(unspent.chain, unspent.index);
if (scriptType === 'p2tr') {
if (!Array.isArray(input.tapLeafScript) || input.tapLeafScript.length === 0) {
throw new Error('Invalid PSBT state. Missing required fields.');
}
if (input.tapLeafScript.length > 1) {
throw new Error('Bitgo only supports a single tap leaf script per input');
}
const [signer, cosigner] = getTaprootSigners(input.tapLeafScript[0].script, walletKeys);
const leafHash = getLeafHash({
publicKeys: walletKeys.publicKeys,
signer: signer.walletKey.publicKey,
cosigner: cosigner.walletKey.publicKey,
});
psbt.updateInput(inputIndex, {
tapBip32Derivation: [signer, cosigner].map((walletSigner) => ({
leafHashes: [leafHash],
pubkey: toXOnlyPublicKey(walletSigner.walletKey.publicKey),
path: rootWalletKeys.getDerivationPath(walletSigner.rootKey, unspent.chain, unspent.index),
masterFingerprint: walletSigner.rootKey.fingerprint,
})),
});
} else {
if (signatureCount === 0) {
const { witnessScript, redeemScript } = createOutputScript2of3(walletKeys.publicKeys, scriptType);
if (witnessScript && psbt.data.inputs[inputIndex].witnessScript === undefined) {
psbt.updateInput(inputIndex, { witnessScript });
}
if (redeemScript && psbt.data.inputs[inputIndex].redeemScript === undefined) {
psbt.updateInput(inputIndex, { redeemScript });
}
}
psbt.updateInput(inputIndex, {
bip32Derivation: [0, 1, 2].map((idx) => ({
pubkey: walletKeys.triple[idx].publicKey,
path: walletKeys.paths[idx],
masterFingerprint: rootWalletKeys.triple[idx].fingerprint,
})),
});
}
}
/**
* @return PSBT filled with metatdata as per input params tx, unspents and rootWalletKeys.
* Unsigned PSBT for taproot input with witnessUtxo
* Unsigned PSBT for other input with witnessUtxo/nonWitnessUtxo, redeemScript/witnessScript, bip32Derivation
* Signed PSBT for taproot input with witnessUtxo, tapLeafScript, tapBip32Derivation, tapScriptSig
* Signed PSBT for other input with witnessUtxo/nonWitnessUtxo, redeemScript/witnessScript, bip32Derivation, partialSig
*/
export function toWalletPsbt(
tx: UtxoTransaction<bigint>,
unspents: WalletUnspent<bigint>[],
rootWalletKeys: RootWalletKeys
): UtxoPsbt {
const prevOutputs = unspents.map((u) => {
assert.notStrictEqual(scriptTypeForChain(u.chain), 'p2trMusig2');
return toPrevOutputWithPrevTx(u, tx.network);
});
const psbt = createPsbtFromTransaction(tx, prevOutputs);
unspents.forEach((u, i) => {
if (isWalletUnspent(u) && u.index !== undefined) {
updatePsbtInput(psbt, i, u, rootWalletKeys);
}
});
return psbt;
}
/**
* @param psbt
* @param inputIndex
* @param signer
* @param unspent
* @return signed PSBT with signer's key for unspent
*/
export function signWalletPsbt(
psbt: UtxoPsbt,
inputIndex: number,
signer: BIP32Interface,
unspent: WalletUnspent<bigint>
): void {
const scriptType = scriptTypeForChain(unspent.chain);
if (scriptType === 'p2tr' || scriptType === 'p2trMusig2') {
psbt.signTaprootInputHD(inputIndex, signer);
} else {
psbt.signInputHD(inputIndex, signer);
}
}
/**
* @returns script type of the input
*/
export function getPsbtInputScriptType(input: PsbtInput): ParsedScriptType {
const isP2pk = (script: Buffer) => {
try {
const chunks = bscript.decompile(script);
return (
chunks?.length === 2 &&
Buffer.isBuffer(chunks[0]) &&
bscript.isCanonicalPubKey(chunks[0]) &&
chunks[1] === opcodes.OP_CHECKSIG
);
} catch (e) {
return false;
}
};
let scriptType: ParsedScriptType | undefined;
if (Buffer.isBuffer(input.redeemScript) && Buffer.isBuffer(input.witnessScript)) {
scriptType = 'p2shP2wsh';
} else if (Buffer.isBuffer(input.redeemScript)) {
scriptType = isP2pk(input.redeemScript) ? 'p2shP2pk' : 'p2sh';
} else if (Buffer.isBuffer(input.witnessScript)) {
scriptType = 'p2wsh';
}
if (Array.isArray(input.tapLeafScript) && input.tapLeafScript.length > 0) {
if (scriptType) {
throw new Error(`Found both ${scriptType} and taprootScriptPath PSBT metadata.`);
}
if (input.tapLeafScript.length > 1) {
throw new Error('Bitgo only supports a single tap leaf script per input.');
}
scriptType = 'taprootScriptPathSpend';
}
if (input.tapInternalKey) {
if (scriptType) {
throw new Error(`Found both ${scriptType} and taprootKeyPath PSBT metadata.`);
}
scriptType = 'taprootKeyPathSpend';
}
if (scriptType) {
return scriptType;
}
throw new Error('could not parse input');
}
function parseTaprootKeyPathSignatures(input: PsbtInput): TaprootKeyPathSignatureContainer {
const partialSigs = parsePsbtMusig2PartialSigs(input);
if (!partialSigs) {
return { signatures: undefined, participantPublicKeys: undefined };
}
const signatures = partialSigs.map((pSig) => pSig.partialSig);
const participantPublicKeys = partialSigs.map((pSig) => pSig.participantPubKey);
return isTuple<Buffer>(signatures) && isTuple<Buffer>(participantPublicKeys)
? { signatures, participantPublicKeys }
: { signatures: [signatures[0]], participantPublicKeys: [participantPublicKeys[0]] };
}
function parsePartialOrTapScriptSignatures(sig: PartialSig[] | TapScriptSig[] | undefined): SignatureContainer {
if (!sig?.length) {
return { signatures: undefined };
}
if (sig.length > 2) {
throw new Error('unexpected signature count');
}
const signatures = sig.map((tSig) => tSig.signature);
return isTuple<Buffer>(signatures) ? { signatures } : { signatures: [signatures[0]] };
}
function parseSignatures(
input: PsbtInput,
scriptType: ParsedScriptType
): SignatureContainer | TaprootKeyPathSignatureContainer {
return scriptType === 'taprootKeyPathSpend'
? parseTaprootKeyPathSignatures(input)
: scriptType === 'taprootScriptPathSpend'
? parsePartialOrTapScriptSignatures(input.tapScriptSig)
: parsePartialOrTapScriptSignatures(input.partialSig);
}
function parseScript(
input: PsbtInput,
scriptType: ParsedScriptType
): ParsedPubScriptP2ms | ParsedPubScriptTaproot | ParsedPubScriptP2shP2pk {
let pubScript: Buffer | undefined;
if (scriptType === 'p2sh' || scriptType === 'p2shP2pk') {
pubScript = input.redeemScript;
} else if (scriptType === 'p2wsh' || scriptType === 'p2shP2wsh') {
pubScript = input.witnessScript;
} else if (scriptType === 'taprootScriptPathSpend') {
pubScript = input.tapLeafScript ? input.tapLeafScript[0].script : undefined;
} else if (scriptType === 'taprootKeyPathSpend') {
if (input.witnessUtxo?.script) {
pubScript = input.witnessUtxo.script;
} else if (input.tapInternalKey && input.tapMerkleRoot) {
pubScript = createTaprootOutputScript({ internalPubKey: input.tapInternalKey, taptreeRoot: input.tapMerkleRoot });
}
}
if (!pubScript) {
throw new Error(`Invalid PSBT state for ${scriptType}. Missing required fields.`);
}
return parsePubScript(pubScript, scriptType);
}
/**
* @return psbt metadata are parsed as per below conditions.
* redeemScript/witnessScript/tapLeafScript matches BitGo.
* signature and public key count matches BitGo.
* P2SH-P2PK => scriptType, redeemScript, public key, signature.
* P2SH => scriptType, redeemScript, public keys, signatures.
* PW2SH => scriptType, witnessScript, public keys, signatures.
* P2SH-PW2SH => scriptType, redeemScript, witnessScript, public keys, signatures.
* P2TR and P2TR MUSIG2 script path => scriptType (taprootScriptPathSpend), pubScript (leaf script), controlBlock,
* scriptPathLevel, leafVersion, public keys, signatures.
* P2TR MUSIG2 kep path => scriptType (taprootKeyPathSpend), pubScript (scriptPubKey), participant pub keys (signer),
* public key (tapOutputkey), signatures (partial signer sigs).
*/
export function parsePsbtInput(input: PsbtInput): ParsedPsbtP2ms | ParsedPsbtTaproot | ParsedPsbtP2shP2pk {
if (isPsbtInputFinalized(input)) {
throw new Error('Finalized PSBT parsing is not supported');
}
const scriptType = getPsbtInputScriptType(input);
const parsedPubScript = parseScript(input, scriptType);
const signatures = parseSignatures(input, scriptType);
if (parsedPubScript.scriptType === 'taprootKeyPathSpend' && 'participantPublicKeys' in signatures) {
return {
...parsedPubScript,
...signatures,
scriptType: parsedPubScript.scriptType,
};
}
if (parsedPubScript.scriptType === 'taprootScriptPathSpend') {
if (!input.tapLeafScript) {
throw new Error('Invalid PSBT state for taprootScriptPathSpend. Missing required fields.');
}
const controlBlock = input.tapLeafScript[0].controlBlock;
if (!isValidControlBock(controlBlock)) {
throw new Error('Invalid PSBT taprootScriptPathSpend controlBlock.');
}
const scriptPathLevel = calculateScriptPathLevel(controlBlock);
const leafVersion = getLeafVersion(controlBlock);
return {
...parsedPubScript,
...signatures,
scriptType: parsedPubScript.scriptType,
controlBlock,
scriptPathLevel,
leafVersion,
};
}
if (
parsedPubScript.scriptType === 'p2sh' ||
parsedPubScript.scriptType === 'p2wsh' ||
parsedPubScript.scriptType === 'p2shP2wsh'
) {
if (parsedPubScript.scriptType === 'p2shP2wsh') {
parsedPubScript.redeemScript = input.redeemScript;
}
return {
...parsedPubScript,
...signatures,
};
}
if (parsedPubScript.scriptType === 'p2shP2pk' && (!signatures.signatures || !isTuple(signatures.signatures))) {
return {
...parsedPubScript,
signatures: signatures.signatures,
};
}
throw new Error('invalid pub script');
}
/**
* Converts a parsed script type into an array of script types.
* @param parsedScriptType - The parsed script type.
* @returns An array of ScriptType2Of3 values corresponding to the parsed script type.
*/
export function toScriptType2Of3s(parsedScriptType: ParsedScriptType2Of3): ScriptType2Of3[] {
return parsedScriptType === 'taprootScriptPathSpend'
? ['p2trMusig2', 'p2tr']
: parsedScriptType === 'taprootKeyPathSpend'
? ['p2trMusig2']
: [parsedScriptType];
}
/**
* @returns strictly parse the input and get signature count.
* unsigned(0), half-signed(1) or fully-signed(2)
*/
export function getStrictSignatureCount(input: TxInput | PsbtInput): 0 | 1 | 2 {
const calculateSignatureCount = (
signatures: [Buffer | 0, Buffer | 0, Buffer | 0] | [Buffer, Buffer] | [Buffer] | undefined
): 0 | 1 | 2 => {
const count = signatures ? signatures.filter((s) => !isPlaceholderSignature(s)).length : 0;
if (count === 0 || count === 1 || count === 2) {
return count;
}
throw new Error('invalid signature count');
};
if ('hash' in input) {
if (input.script?.length || input.witness?.length) {
const parsedInput = parseSignatureScript(input);
return parsedInput.scriptType === 'taprootKeyPathSpend' ? 2 : calculateSignatureCount(parsedInput.signatures);
}
return 0;
} else {
return calculateSignatureCount(parsePsbtInput(input).signatures);
}
}
/**
* @returns strictly parse input and get signature count for all inputs.
* 0=unsigned, 1=half-signed or 2=fully-signed
*/
export function getStrictSignatureCounts(
tx: UtxoPsbt | UtxoTransaction<number | bigint> | PsbtInput[] | TxInput[]
): (0 | 1 | 2)[] {
const inputs = tx instanceof UtxoPsbt ? tx.data.inputs : tx instanceof UtxoTransaction ? tx.ins : tx;
return inputs.map((input, _) => getStrictSignatureCount(input));
}
/**
* @return true iff inputs array is of PsbtInputType type
* */
export function isPsbtInputArray(inputs: PsbtInput[] | TxInput[]): inputs is PsbtInput[] {
return !isTxInputArray(inputs);
}
/**
* @return true iff inputs array is of TxInput type
* */
export function isTxInputArray(inputs: PsbtInput[] | TxInput[]): inputs is TxInput[] {
assert(!!inputs.length, 'empty inputs array');
return 'hash' in inputs[0];
}
/**
* @returns true iff given psbt/transaction/tx-input-array/psbt-input-array contains at least one taproot key path spend input
*/
export function isTransactionWithKeyPathSpendInput(
data: UtxoPsbt | UtxoTransaction<bigint | number> | PsbtInput[] | TxInput[]
): boolean {
const inputs = data instanceof UtxoPsbt ? data.data.inputs : data instanceof UtxoTransaction ? data.ins : data;
if (!inputs.length) {
return false;
}
if (isPsbtInputArray(inputs)) {
return inputs.some((input, _) => getPsbtInputScriptType(input) === 'taprootKeyPathSpend');
}
return inputs.some((input, _) => {
// If the input is not signed, it cannot be a taprootKeyPathSpend input because you can only
// extract a fully signed psbt into a transaction with taprootKeyPathSpend inputs.
if (getStrictSignatureCount(input) === 0) {
return false;
}
return parseSignatureScript(input).scriptType === 'taprootKeyPathSpend';
});
}
/**
* Set the RootWalletKeys as the globalXpubs on the psbt
*
* We do all the matching of the (tap)bip32Derivations masterFingerprint to the fingerprint of the
* extendedPubkey.
*/
export function addXpubsToPsbt(psbt: UtxoPsbt, rootWalletKeys: RootWalletKeys): void {
const safeRootWalletKeys = new RootWalletKeys(
rootWalletKeys.triple.map((bip32) => bip32.neutered()) as Triple<BIP32Interface>,
rootWalletKeys.derivationPrefixes
);
const xPubs = safeRootWalletKeys.triple.map(
(bip32): GlobalXpub => ({
extendedPubkey: bs58check.decode(bip32.toBase58()),
masterFingerprint: bip32.fingerprint,
// TODO: BG-73797 - bip174 currently requires m prefix for this to be a valid globalXpub
path: 'm',
})
);
psbt.updateGlobal({ globalXpub: xPubs });
}
/**
* validates signatures for each 2 of 3 input against user, backup, bitgo keys derived from rootWalletKeys.
* @returns array of input index and its [is valid user sig exist, is valid backup sig exist, is valid user bitgo exist]
* For p2shP2pk input, [false, false, false] is returned since it is not a 2 of 3 sig input.
*/
export function getSignatureValidationArrayPsbt(psbt: UtxoPsbt, rootWalletKeys: RootWalletKeys): SignatureValidation[] {
return psbt.data.inputs.map((input, i) => {
const sigValArrayForInput: Triple<boolean> =
getPsbtInputScriptType(input) === 'p2shP2pk'
? [false, false, false]
: psbt.getSignatureValidationArray(i, { rootNodes: rootWalletKeys.triple });
return [i, sigValArrayForInput];
});
}
/**
* Extracts the half signed transaction from the psbt for p2ms based script types - p2sh, p2wsh, and p2shP2wsh.
* The purpose is to provide backward compatibility to keyternal (KRS) that only supports network transaction and p2ms script types.
*/
export function extractP2msOnlyHalfSignedTx(psbt: UtxoPsbt): UtxoTransaction<bigint> {
assert(!!(psbt.data.inputs.length && psbt.data.outputs.length), 'empty inputs or outputs');
const tx = psbt.getUnsignedTx();
function isP2msParsedPsbtInput(
parsed: ParsedPsbtP2ms | ParsedPsbtTaproot | ParsedPsbtP2shP2pk
): parsed is ParsedPsbtP2ms {
return ['p2sh', 'p2shP2wsh', 'p2wsh'].includes(parsed.scriptType);
}
psbt.data.inputs.forEach((input, i) => {
const parsed = parsePsbtInput(input);
assert(isP2msParsedPsbtInput(parsed), `unsupported script type ${parsed.scriptType}`);
assert(input.partialSig?.length === 1, `unexpected signature count ${input.partialSig?.length}`);
const [partialSig] = input.partialSig;
assert(
input.sighashType !== undefined && input.sighashType === bscript.signature.decode(partialSig.signature).hashType,
'signature sighash does not match input sighash type'
);
// type casting is to address the invalid type checking in payments.p2ms
const signatures = parsed.publicKeys.map((pk) =>
partialSig.pubkey.equals(pk) ? partialSig.signature : (ops.OP_0 as unknown as Buffer)
);
const isP2SH = !!parsed.redeemScript;
const isP2WSH = !!parsed.witnessScript;
const payment = payments.p2ms({ output: parsed.pubScript, signatures }, { validate: false, allowIncomplete: true });
const p2wsh = isP2WSH ? payments.p2wsh({ redeem: payment }) : undefined;
const p2sh = isP2SH ? payments.p2sh({ redeem: p2wsh || payment }) : undefined;
if (p2sh?.input) {
tx.setInputScript(i, p2sh.input);
}
if (p2wsh?.witness) {
tx.setWitness(i, p2wsh.witness);
}
});
return tx;
}
/**
* Clones the psbt without nonWitnessUtxo for non-segwit inputs and witnessUtxo is added instead.
* It is not BIP-174 compliant, so use it carefully.
*/
export function clonePsbtWithoutNonWitnessUtxo(psbt: UtxoPsbt): UtxoPsbt {
const newPsbt = createPsbtFromHex(psbt.toHex(), psbt.network);
const txInputs = psbt.txInputs;
psbt.data.inputs.forEach((input, i) => {
if (input.nonWitnessUtxo && !input.witnessUtxo) {
const tx = createTransactionFromBuffer(input.nonWitnessUtxo, psbt.network, { amountType: 'bigint' });
if (!txInputs[i].hash.equals(tx.getHash())) {
throw new Error(`Non-witness UTXO hash for input #${i} doesn't match the hash specified in the prevout`);
}
newPsbt.data.inputs[i].witnessUtxo = tx.outs[txInputs[i].index];
}
delete newPsbt.data.inputs[i].nonWitnessUtxo;
});
return newPsbt;
}
/**
* Returns true if there are non-segwit inputs in the PSBT that do not contain the
* nonWitnessUtxo.
*
* isPsbtLite(clonePsbtWithoutNonWitnessUtxo(psbt)) === true
*
* @param psbt
*/
export function isPsbtLite(psbt: UtxoPsbt): boolean {
let isFull = true;
const nonSegwitInputTypes = ['p2shP2pk', 'p2sh'];
psbt.data.inputs.forEach((input) => {
if (isFull && nonSegwitInputTypes.includes(getPsbtInputScriptType(input))) {
isFull = !!input.nonWitnessUtxo;
}
});
return !isFull;
}
/**
* Deletes witnessUtxo for non-segwit inputs to make the PSBT BIP-174 compliant.
*/
export function deleteWitnessUtxoForNonSegwitInputs(psbt: UtxoPsbt): void {
psbt.data.inputs.forEach((input, i) => {
const scriptType = getPsbtInputScriptType(input);
if (scriptType === 'p2sh' || scriptType === 'p2shP2pk') {
delete input.witnessUtxo;
}
});
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!