PHP WebShell
Текущая директория: /opt/BitGoJS/modules/utxo-lib/src/bitgo/wallet
Просмотр файла: Unspent.ts
import { getMainnet, Network, networks } from '../..';
import { UtxoTransactionBuilder } from '../UtxoTransactionBuilder';
import {
createKeyPathP2trMusig2,
createOutputScript2of3,
createSpendScriptP2tr,
createSpendScriptP2trMusig2,
scriptTypeForChain,
toXOnlyPublicKey,
} from '../outputScripts';
import { toOutputScript } from '../../address';
import {
getDefaultSigHash,
getSignatureVerifications,
signInput2Of3,
verifySignatureWithPublicKeys,
} from '../signature';
import { WalletUnspentSigner } from './WalletUnspentSigner';
import { KeyName, RootWalletKeys } from './WalletKeys';
import { UtxoTransaction } from '../UtxoTransaction';
import { Triple } from '../types';
import {
toOutput,
UnspentWithPrevTx,
Unspent,
isUnspentWithPrevTx,
toPrevOutput,
parseOutputId,
getOutputIdForInput,
} from '../Unspent';
import { ChainCode, isSegwit } from './chains';
import { UtxoPsbt } from '../UtxoPsbt';
import { encodePsbtMusig2Participants } from '../Musig2';
import { createTransactionFromBuffer } from '../transaction';
import { parseSignatureScript } from '../parseInput';
import { checkForInput } from 'bip174/src/lib/utils';
import { ProprietaryKeySubtype, PSBT_PROPRIETARY_IDENTIFIER } from '../PsbtUtil';
/** Final (non-replaceable) */
export const TX_INPUT_SEQUENCE_NUMBER_FINAL = 0xffffffff;
/** Non-Final (Replaceable)
* Reference: https://github.com/bitcoin/bitcoin/blob/v25.1/src/rpc/rawtransaction_util.cpp#L49
* */
export const MAX_BIP125_RBF_SEQUENCE = 0xffffffff - 2;
export interface WalletUnspent<TNumber extends number | bigint = number> extends Unspent<TNumber> {
chain: ChainCode;
index: number;
}
export interface NonWitnessWalletUnspent<TNumber extends number | bigint = number>
extends UnspentWithPrevTx<TNumber>,
WalletUnspent<TNumber> {}
export function isWalletUnspent<TNumber extends number | bigint>(u: Unspent<TNumber>): u is WalletUnspent<TNumber> {
return (u as WalletUnspent<TNumber>).chain !== undefined;
}
export function signInputWithUnspent<TNumber extends number | bigint>(
txBuilder: UtxoTransactionBuilder<TNumber>,
inputIndex: number,
unspent: WalletUnspent<TNumber>,
unspentSigner: WalletUnspentSigner<RootWalletKeys>
): void {
const { walletKeys, signer, cosigner } = unspentSigner.deriveForChainAndIndex(unspent.chain, unspent.index);
const scriptType = scriptTypeForChain(unspent.chain);
const pubScript = createOutputScript2of3(walletKeys.publicKeys, scriptType).scriptPubKey;
const pubScriptExpected = toOutputScript(unspent.address, txBuilder.network as Network);
if (!pubScript.equals(pubScriptExpected)) {
throw new Error(
`pubscript mismatch: expected ${pubScriptExpected.toString('hex')} got ${pubScript.toString('hex')}`
);
}
signInput2Of3<TNumber>(
txBuilder,
inputIndex,
scriptType,
walletKeys.publicKeys,
signer,
cosigner.publicKey,
unspent.value
);
}
/**
* @param tx
* @param inputIndex
* @param unspents
* @param walletKeys
* @return triple of booleans indicating a valid signature for each pubkey
*/
export function verifySignatureWithUnspent<TNumber extends number | bigint>(
tx: UtxoTransaction<TNumber>,
inputIndex: number,
unspents: Unspent<TNumber>[],
walletKeys: RootWalletKeys
): Triple<boolean> {
if (tx.ins.length !== unspents.length) {
throw new Error(`input length must match unspents length`);
}
const input = tx.ins[inputIndex];
/* istanbul ignore next */
if (!input) {
throw new Error(`no input at index ${inputIndex}`);
}
const unspent = unspents[inputIndex];
if (!isWalletUnspent(unspent) || (!input.script?.length && !input.witness?.length)) {
return [false, false, false];
}
const parsedInput = parseSignatureScript(input);
const prevOutputs = unspents.map((u) => toOutput(u, tx.network));
// If it is a taproot keyPathSpend input, the only valid signature combinations is user-bitgo. We can
// only verify that the aggregated signature is valid, not that the individual partial-signature is valid.
// Therefore, we can only say that either all partial signatures are valid, or none are.
if (parsedInput.scriptType === 'taprootKeyPathSpend') {
const result = getSignatureVerifications(tx, inputIndex, unspent.value, undefined, prevOutputs);
return result.length === 1 && result[0].signature ? [true, false, true] : [false, false, false];
}
return verifySignatureWithPublicKeys(
tx,
inputIndex,
prevOutputs,
walletKeys.deriveForChainAndIndex(unspent.chain, unspent.index).publicKeys
) as Triple<boolean>;
}
/**
* @deprecated
* Used in certain legacy signing methods that do not derive signing data from index/chain
*/
export interface WalletUnspentLegacy<TNumber extends number | bigint = number> extends WalletUnspent<TNumber> {
/** @deprecated - obviated by signWithUnspent */
redeemScript?: string;
/** @deprecated - obviated by verifyWithUnspent */
witnessScript?: string;
}
/**
* @param psbt
* @param inputIndex
* @param id Unspent ID
* @returns true iff the unspent ID on the unspent and psbt input match
*/
export function psbtIncludesUnspentAtIndex(psbt: UtxoPsbt, inputIndex: number, id: string): boolean {
checkForInput(psbt.data.inputs, inputIndex);
const { txid, vout } = parseOutputId(id);
const psbtOutPoint = getOutputIdForInput(psbt.txInputs[inputIndex]);
return psbtOutPoint.txid === txid && psbtOutPoint.vout === vout;
}
/**
* Update the psbt input at the given index
* @param psbt
* @param inputIndex
* @param u
* @param redeemScript Only overrides if there is no redeemScript in the input currently
*/
export function updateReplayProtectionUnspentToPsbt(
psbt: UtxoPsbt,
inputIndex: number,
u: Unspent<bigint>,
redeemScript?: Buffer,
customParams?: { skipNonWitnessUtxo?: boolean }
): void {
if (!psbtIncludesUnspentAtIndex(psbt, inputIndex, u.id)) {
throw new Error(`unspent does not correspond to psbt input`);
}
const input = checkForInput(psbt.data.inputs, inputIndex);
if (redeemScript && !input.redeemScript) {
psbt.updateInput(inputIndex, { redeemScript });
}
// Because Zcash directly hashes the value for non-segwit transactions, we do not need to check indirectly
// with the previous transaction. Therefore, we can treat Zcash non-segwit transactions as Bitcoin
// segwit transactions
const isZcash = getMainnet(psbt.network) === networks.zcash;
if (!isUnspentWithPrevTx(u) && !isZcash && !customParams?.skipNonWitnessUtxo) {
throw new Error('Error, require previous tx to add to PSBT');
}
if ((isZcash && !input.witnessUtxo) || customParams?.skipNonWitnessUtxo) {
const { script, value } = toPrevOutput(u, psbt.network);
psbt.updateInput(inputIndex, { witnessUtxo: { script, value } });
} else if (!isZcash && !input.nonWitnessUtxo) {
psbt.updateInput(inputIndex, { nonWitnessUtxo: (u as UnspentWithPrevTx<bigint>).prevTx });
}
const sighashType = getDefaultSigHash(psbt.network);
if (psbt.data.inputs[inputIndex].sighashType === undefined) {
psbt.updateInput(inputIndex, { sighashType });
}
}
function addUnspentToPsbt(
psbt: UtxoPsbt,
id: string,
{ sequenceNumber = TX_INPUT_SEQUENCE_NUMBER_FINAL }: { sequenceNumber?: number } = {}
): void {
const { txid, vout } = parseOutputId(id);
psbt.addInput({
hash: txid,
index: vout,
sequence: sequenceNumber,
});
}
export function addReplayProtectionUnspentToPsbt(
psbt: UtxoPsbt,
u: Unspent<bigint>,
redeemScript: Buffer,
customParams?: { skipNonWitnessUtxo?: boolean }
): void {
addUnspentToPsbt(psbt, u.id);
updateReplayProtectionUnspentToPsbt(psbt, psbt.inputCount - 1, u, redeemScript, customParams);
}
/**
* Update the PSBT with the unspent data for the input at the given index if the data is not there already.
*
* If skipNonWitnessUtxo is true, then the nonWitnessUtxo will not be added for an input that requires it (e.g. non-segwit)
* and instead the witnessUtxo will be added
*
* @param psbt
* @param inputIndex
* @param u
* @param rootWalletKeys
* @param signer
* @param cosigner
* @param customParams
*/
export function updateWalletUnspentForPsbt(
psbt: UtxoPsbt,
inputIndex: number,
u: WalletUnspent<bigint>,
rootWalletKeys: RootWalletKeys,
signer: KeyName,
cosigner: KeyName,
customParams?: { skipNonWitnessUtxo?: boolean }
): void {
if (!psbtIncludesUnspentAtIndex(psbt, inputIndex, u.id)) {
throw new Error(`unspent does not correspond to psbt input`);
}
const input = checkForInput(psbt.data.inputs, inputIndex);
// Because Zcash directly hashes the value for non-segwit transactions, we do not need to check indirectly
// with the previous transaction. Therefore, we can treat Zcash non-segwit transactions as Bitcoin
// segwit transactions
const isZcashOrSegwit = isSegwit(u.chain) || getMainnet(psbt.network) === networks.zcash;
if ((isZcashOrSegwit || customParams?.skipNonWitnessUtxo) && !input.witnessUtxo) {
const { script, value } = toPrevOutput(u, psbt.network);
psbt.updateInput(inputIndex, { witnessUtxo: { script, value } });
} else if (!isZcashOrSegwit) {
if (!isUnspentWithPrevTx(u)) {
throw new Error('Error, require previous tx to add to PSBT');
}
if (!input.witnessUtxo && !input.nonWitnessUtxo) {
// Force the litecoin transaction to have no MWEB advanced transaction flag
if (getMainnet(psbt.network) === networks.litecoin) {
u.prevTx = createTransactionFromBuffer(u.prevTx, psbt.network, { amountType: 'bigint' }).toBuffer();
}
psbt.updateInput(inputIndex, { nonWitnessUtxo: u.prevTx });
}
}
const walletKeys = rootWalletKeys.deriveForChainAndIndex(u.chain, u.index);
const scriptType = scriptTypeForChain(u.chain);
const sighashType = getDefaultSigHash(psbt.network, scriptType);
if (psbt.data.inputs[inputIndex].sighashType === undefined) {
psbt.updateInput(inputIndex, { sighashType });
}
const isBackupFlow = signer === 'backup' || cosigner === 'backup';
if (scriptType === 'p2tr' || (scriptType === 'p2trMusig2' && isBackupFlow)) {
if (input.tapLeafScript && input.tapBip32Derivation) {
return;
}
const createSpendScriptP2trFn = scriptType === 'p2tr' ? createSpendScriptP2tr : createSpendScriptP2trMusig2;
const { controlBlock, witnessScript, leafVersion, leafHash } = createSpendScriptP2trFn(walletKeys.publicKeys, [
walletKeys[signer].publicKey,
walletKeys[cosigner].publicKey,
]);
if (!input.tapLeafScript) {
psbt.updateInput(inputIndex, {
tapLeafScript: [{ controlBlock, script: witnessScript, leafVersion }],
});
}
if (!input.tapBip32Derivation) {
psbt.updateInput(inputIndex, {
tapBip32Derivation: [signer, cosigner].map((key) => ({
leafHashes: [leafHash],
pubkey: toXOnlyPublicKey(walletKeys[key].publicKey),
path: rootWalletKeys.getDerivationPath(rootWalletKeys[key], u.chain, u.index),
masterFingerprint: rootWalletKeys[key].fingerprint,
})),
});
}
} else if (scriptType === 'p2trMusig2') {
const {
internalPubkey: tapInternalKey,
outputPubkey: tapOutputKey,
taptreeRoot,
} = createKeyPathP2trMusig2(walletKeys.publicKeys);
if (
psbt.getProprietaryKeyVals(inputIndex, {
identifier: PSBT_PROPRIETARY_IDENTIFIER,
subtype: ProprietaryKeySubtype.MUSIG2_PARTICIPANT_PUB_KEYS,
}).length === 0
) {
const participantsKeyValData = encodePsbtMusig2Participants({
tapOutputKey,
tapInternalKey,
participantPubKeys: [walletKeys.user.publicKey, walletKeys.bitgo.publicKey],
});
psbt.addProprietaryKeyValToInput(inputIndex, participantsKeyValData);
}
if (!input.tapInternalKey) {
psbt.updateInput(inputIndex, {
tapInternalKey: tapInternalKey,
});
}
if (!input.tapMerkleRoot) {
psbt.updateInput(inputIndex, {
tapMerkleRoot: taptreeRoot,
});
}
if (!input.tapBip32Derivation) {
psbt.updateInput(inputIndex, {
tapBip32Derivation: [signer, cosigner].map((key) => ({
leafHashes: [],
pubkey: toXOnlyPublicKey(walletKeys[key].publicKey),
path: rootWalletKeys.getDerivationPath(rootWalletKeys[key], u.chain, u.index),
masterFingerprint: rootWalletKeys[key].fingerprint,
})),
});
}
} else {
if (!input.bip32Derivation) {
psbt.updateInput(inputIndex, {
bip32Derivation: [0, 1, 2].map((idx) => ({
pubkey: walletKeys.triple[idx].publicKey,
path: walletKeys.paths[idx],
masterFingerprint: rootWalletKeys.triple[idx].fingerprint,
})),
});
}
const { witnessScript, redeemScript } = createOutputScript2of3(walletKeys.publicKeys, scriptType);
if (witnessScript && !input.witnessScript) {
psbt.updateInput(inputIndex, { witnessScript });
}
if (redeemScript && !input.redeemScript) {
psbt.updateInput(inputIndex, { redeemScript });
}
}
}
export function addWalletUnspentToPsbt(
psbt: UtxoPsbt,
u: WalletUnspent<bigint>,
rootWalletKeys: RootWalletKeys,
signer: KeyName,
cosigner: KeyName,
customParams?: { isReplaceableByFee?: boolean; skipNonWitnessUtxo?: boolean }
): void {
let sequenceNumber = TX_INPUT_SEQUENCE_NUMBER_FINAL;
if (customParams && customParams.isReplaceableByFee) {
sequenceNumber = MAX_BIP125_RBF_SEQUENCE;
}
addUnspentToPsbt(psbt, u.id, { sequenceNumber });
updateWalletUnspentForPsbt(
psbt,
psbt.inputCount - 1,
u,
rootWalletKeys,
signer,
cosigner,
customParams ? { skipNonWitnessUtxo: customParams.skipNonWitnessUtxo } : {}
);
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!