PHP WebShell
Текущая директория: /opt/BitGoJS/modules/utxo-lib/src/bitgo/wallet
Просмотр файла: WalletOutput.ts
import * as assert from 'assert';
import { Payment, taproot } from 'bitcoinjs-lib';
import { PsbtOutput, PsbtOutputUpdate } from 'bip174/src/lib/interfaces';
import { UtxoPsbt } from '../UtxoPsbt';
import { RootWalletKeys, DerivedWalletKeys } from './WalletKeys';
import { ChainCode, scriptTypeForChain } from './chains';
import { getScriptIdFromPath, ScriptId } from './ScriptId';
import { createOutputScript2of3, createPaymentP2tr, createPaymentP2trMusig2, toXOnlyPublicKey } from '../outputScripts';
/**
* Get the BIP32 derivation data for a PSBT output.
*
* @param rootWalletKeys root wallet keys used for master fingerprints
* @param walletKeys derived wallet keys for the specific chain and index
* @param scriptType the script type to determine whether to use regular or taproot derivation
* @param payment optional payment object for taproot scripts to calculate leaf hashes
* @returns Object containing BIP32 derivation data
*/
export function getPsbtBip32DerivationOutputUpdate(
rootWalletKeys: RootWalletKeys,
walletKeys: DerivedWalletKeys,
scriptType: string,
payment?: Payment
): PsbtOutputUpdate {
const update: PsbtOutputUpdate = {};
if (scriptType === 'p2tr' || scriptType === 'p2trMusig2') {
if (!payment || !payment.redeems) {
throw new Error('Payment object with redeems is required for taproot derivation');
}
const allLeafHashes = payment.redeems.map((r) => taproot.hashTapLeaf(r.output!));
update.tapBip32Derivation = [0, 1, 2].map((idx) => {
const pubkey = toXOnlyPublicKey(walletKeys.triple[idx].publicKey);
const leafHashes: Buffer[] = [];
assert(payment.redeems);
payment.redeems.forEach((r: any, redeemIdx: number) => {
if (r.pubkeys!.find((pk: Buffer) => pk.equals(pubkey))) {
leafHashes.push(allLeafHashes[redeemIdx]);
}
});
return {
leafHashes,
pubkey,
path: walletKeys.paths[idx],
masterFingerprint: rootWalletKeys.triple[idx].fingerprint,
};
});
} else {
update.bip32Derivation = [0, 1, 2].map((idx) => ({
pubkey: walletKeys.triple[idx].publicKey,
path: walletKeys.paths[idx],
masterFingerprint: rootWalletKeys.triple[idx].fingerprint,
}));
}
return update;
}
/**
* Get the PSBT output update object from a PSBT output and output script.
*
* @param output the PSBT output to get update for
* @param outputScript the output script
* @param rootWalletKeys keys that will be able to spend the output
* @param chain chain code to use for deriving scripts (and to determine script type)
* @param index derivation index for the change address
* @returns PsbtOutputUpdate object with the required information
*/
export function getPsbtOutputUpdateFromPsbtOutput(
output: PsbtOutput,
outputScript: Buffer,
rootWalletKeys: RootWalletKeys,
chain: ChainCode,
index: number
): PsbtOutputUpdate {
const walletKeys = rootWalletKeys.deriveForChainAndIndex(chain, index);
const scriptType = scriptTypeForChain(chain);
const update: PsbtOutputUpdate = {};
if (scriptType === 'p2tr' || scriptType === 'p2trMusig2') {
const payment =
scriptType === 'p2tr' ? createPaymentP2tr(walletKeys.publicKeys) : createPaymentP2trMusig2(walletKeys.publicKeys);
if (!payment.output || !payment.output.equals(outputScript)) {
throw new Error(`cannot update a p2tr output where the scripts do not match - Failing.`);
}
if (!output.tapTree) {
update.tapTree = payment.tapTree;
}
if (!output.tapInternalKey) {
update.tapInternalKey = payment.internalPubkey;
}
if (!output.tapBip32Derivation) {
const derivationUpdate = getPsbtBip32DerivationOutputUpdate(rootWalletKeys, walletKeys, scriptType, payment);
update.tapBip32Derivation = derivationUpdate.tapBip32Derivation;
}
} else {
const { scriptPubKey, witnessScript, redeemScript } = createOutputScript2of3(walletKeys.publicKeys, scriptType);
if (!scriptPubKey.equals(outputScript)) {
throw new Error(`cannot update an output where the scripts do not match - Failing.`);
}
if (!output.bip32Derivation) {
const derivationUpdate = getPsbtBip32DerivationOutputUpdate(rootWalletKeys, walletKeys, scriptType);
update.bip32Derivation = derivationUpdate.bip32Derivation;
}
if (!output.witnessScript && witnessScript) {
update.witnessScript = witnessScript;
}
if (!output.redeemScript && redeemScript) {
update.redeemScript = redeemScript;
}
}
return update;
}
/**
* Get the PSBT output update object with the required information.
*
* @param psbt the PSBT to get output update for
* @param rootWalletKeys keys that will be able to spend the output
* @param outputIndex output index where to update the output
* @param chain chain code to use for deriving scripts (and to determine script
* type) chain is an API parameter in the BitGo API, and may be
* any valid ChainCode
* @param index derivation index for the change address
* @returns PsbtOutputUpdate object with the required information
*/
export function getPsbtOutputUpdate(
psbt: UtxoPsbt,
rootWalletKeys: RootWalletKeys,
outputIndex: number,
chain: ChainCode,
index: number
): PsbtOutputUpdate {
if (psbt.data.outputs.length <= outputIndex) {
throw new Error(
`outputIndex (${outputIndex}) is too large for the number of outputs (${psbt.data.outputs.length})`
);
}
const outputScript = psbt.getOutputScript(outputIndex);
const output = psbt.data.outputs[outputIndex];
return getPsbtOutputUpdateFromPsbtOutput(output, outputScript, rootWalletKeys, chain, index);
}
/**
* Update the wallet output with the required information when necessary. If the
* information is there already, it will skip over it.
*
* This function assumes that the output script and value have already been set.
*
* @param psbt the PSBT to update change output at
* @param rootWalletKeys keys that will be able to spend the output
* @param outputIndex output index where to update the output
* @param chain chain code to use for deriving scripts (and to determine script
* type) chain is an API parameter in the BitGo API, and may be
* any valid ChainCode
* @param index derivation index for the change address
*/
export function updateWalletOutputForPsbt(
psbt: UtxoPsbt,
rootWalletKeys: RootWalletKeys,
outputIndex: number,
chain: ChainCode,
index: number
): void {
psbt.updateOutput(outputIndex, getPsbtOutputUpdate(psbt, rootWalletKeys, outputIndex, chain, index));
}
/**
* Add a verifiable wallet output to the PSBT. The output and all data
* needed to verify it from public keys only are added to the PSBT.
* Typically these are change outputs.
*
* @param psbt the PSBT to add change output to
* @param rootWalletKeys keys that will be able to spend the output
* @param chain chain code to use for deriving scripts (and to determine script
* type) chain is an API parameter in the BitGo API, and may be
* any valid ChainCode
* @param index derivation index for the change address
* @param value value of the change output
*/
export function addWalletOutputToPsbt(
psbt: UtxoPsbt,
rootWalletKeys: RootWalletKeys,
chain: ChainCode,
index: number,
value: bigint
): void {
const walletKeys = rootWalletKeys.deriveForChainAndIndex(chain, index);
const scriptType = scriptTypeForChain(chain);
if (scriptType === 'p2tr' || scriptType === 'p2trMusig2') {
const payment =
scriptType === 'p2tr' ? createPaymentP2tr(walletKeys.publicKeys) : createPaymentP2trMusig2(walletKeys.publicKeys);
psbt.addOutput({ script: payment.output!, value });
} else {
const { scriptPubKey: script } = createOutputScript2of3(walletKeys.publicKeys, scriptType);
psbt.addOutput({ script, value });
}
updateWalletOutputForPsbt(psbt, rootWalletKeys, psbt.data.outputs.length - 1, chain, index);
}
/**
* Fold the script ids into a single script id, if they are all the same.
* @param scriptIds
*/
function foldScriptIds(scriptIds: ScriptId[]): ScriptId {
if (scriptIds.length === 0) {
throw new Error('cannot fold empty script ids');
}
scriptIds.forEach((scriptId, i) => {
if (scriptId.chain !== scriptIds[0].chain) {
throw new Error(`chain mismatch: ${scriptId.chain} != ${scriptIds[0].chain}`);
}
if (scriptId.index !== scriptIds[0].index) {
throw new Error(`index mismatch: ${scriptId.index} != ${scriptIds[0].index}`);
}
});
return scriptIds[0];
}
/**
* Get the script id from the output.
* The output can have either bip32Derivation or tapBip32Derivation, but not both.
* @param output
* @throws Error if neither or both bip32Derivation and tapBip32Derivation are present
* @throws Error if the output is empty
* @throws Error if we cannot fold the script ids into a single script id
*/
export function getScriptIdFromOutput(output: {
bip32Derivation?: { path: string }[];
tapBip32Derivation?: { path: string }[];
}): ScriptId {
if (output.bip32Derivation && output.tapBip32Derivation) {
throw new Error('cannot get script id from output with both bip32Derivation and tapBip32Derivation');
}
if (output.bip32Derivation) {
return foldScriptIds(output.bip32Derivation.map((d) => getScriptIdFromPath(d.path)));
}
if (output.tapBip32Derivation) {
return foldScriptIds(output.tapBip32Derivation.map((d) => getScriptIdFromPath(d.path)));
}
throw new Error('cannot get script id from output without bip32Derivation or tapBip32Derivation');
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!