diff --git a/CODEOWNERS b/CODEOWNERS index f302a3c80c..dfe862a6d1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -60,6 +60,7 @@ /modules/sdk-coin-baby/ @BitGo/ethalt-team /modules/sdk-coin-bera/ @BitGo/ethalt-team /modules/sdk-coin-bsc/ @BitGo/ethalt-team +/module/sdk-coin-canton/ @BitGo/ethalt-team /modules/sdk-coin-coredao/ @BitGo/ethalt-team /modules/sdk-coin-cosmos/ @BitGo/ethalt-team /modules/sdk-coin-cronos/ @BitGo/ethalt-team diff --git a/modules/abstract-utxo/src/abstractUtxoCoin.ts b/modules/abstract-utxo/src/abstractUtxoCoin.ts index d3368f686e..c153a6d318 100644 --- a/modules/abstract-utxo/src/abstractUtxoCoin.ts +++ b/modules/abstract-utxo/src/abstractUtxoCoin.ts @@ -920,35 +920,6 @@ export abstract class AbstractUtxoCoin extends BaseCoin { return isReplayProtectionUnspent(unspent, this.network); } - /** - * @deprecated - use utxolib.bitgo.getDefaultSigHash(network) instead - * @returns {number} - */ - get defaultSigHashType(): number { - return utxolib.bitgo.getDefaultSigHash(this.network); - } - - /** - * @deprecated - use utxolib.bitcoin.verifySignature() instead - */ - verifySignature( - transaction: any, - inputIndex: number, - amount: number, - verificationSettings: { - signatureIndex?: number; - publicKey?: string; - } = {} - ): boolean { - if (transaction.network !== this.network) { - throw new Error(`network mismatch`); - } - return utxolib.bitgo.verifySignature(transaction, inputIndex, amount, { - signatureIndex: verificationSettings.signatureIndex, - publicKey: verificationSettings.publicKey ? Buffer.from(verificationSettings.publicKey, 'hex') : undefined, - }); - } - /** * Decompose a raw psbt/transaction into useful information, such as the total amounts, * change amounts, and transaction outputs. diff --git a/modules/babylonlabs-io-btc-staking-ts/.gitignore b/modules/babylonlabs-io-btc-staking-ts/.gitignore index 6b0417d7d6..f59c3e6ee6 100644 --- a/modules/babylonlabs-io-btc-staking-ts/.gitignore +++ b/modules/babylonlabs-io-btc-staking-ts/.gitignore @@ -207,4 +207,4 @@ $RECYCLE.BIN/ *.swp *.swo -build/ \ No newline at end of file +build/ diff --git a/modules/babylonlabs-io-btc-staking-ts/jest.setup.js b/modules/babylonlabs-io-btc-staking-ts/jest.setup.js index eab17e50a6..1f446f4b85 100644 --- a/modules/babylonlabs-io-btc-staking-ts/jest.setup.js +++ b/modules/babylonlabs-io-btc-staking-ts/jest.setup.js @@ -1,8 +1,7 @@ const { initBTCCurve } = require("./src"); const originalTest = global.test; -const NUM_ITERATIONS = 3; -; +const NUM_ITERATIONS = parseInt(process.env.TEST_REPEAT_TIMES) || 1; initBTCCurve(); diff --git a/modules/babylonlabs-io-btc-staking-ts/package.json b/modules/babylonlabs-io-btc-staking-ts/package.json index af54fd71a8..b22a7dfcbc 100644 --- a/modules/babylonlabs-io-btc-staking-ts/package.json +++ b/modules/babylonlabs-io-btc-staking-ts/package.json @@ -1,6 +1,6 @@ { "name": "@bitgo/babylonlabs-io-btc-staking-ts", - "version": "2.4.1", + "version": "3.0.0", "description": "Library exposing methods for the creation and consumption of Bitcoin transactions pertaining to Babylon's Bitcoin Staking protocol.", "module": "dist/index.js", "main": "dist/index.cjs", @@ -27,7 +27,7 @@ "btc-staking" ], "engines": { - "node": ">=20 < 23" + "node": ">=18 < 23" }, "author": "Babylon Labs Ltd.", "license": "SEE LICENSE IN LICENSE", @@ -37,7 +37,7 @@ "nanoevents": "^9.1.0" }, "dependencies": { - "@babylonlabs-io/babylon-proto-ts": "1.0.0", + "@babylonlabs-io/babylon-proto-ts": "1.7.2", "@bitcoin-js/tiny-secp256k1-asmjs": "2.2.3", "@cosmjs/encoding": "^0.33.0", "bip174": "=2.1.1", diff --git a/modules/babylonlabs-io-btc-staking-ts/src/constants/fee.ts b/modules/babylonlabs-io-btc-staking-ts/src/constants/fee.ts index f68caf697a..1e89916cab 100644 --- a/modules/babylonlabs-io-btc-staking-ts/src/constants/fee.ts +++ b/modules/babylonlabs-io-btc-staking-ts/src/constants/fee.ts @@ -2,8 +2,12 @@ export const DEFAULT_INPUT_SIZE = 180; // Estimated size of a P2WPKH input in bytes export const P2WPKH_INPUT_SIZE = 68; -// Estimated size of a P2TR input in bytes +// Estimated size of a P2TR input in bytes. 42vb inputs + 16vb witness export const P2TR_INPUT_SIZE = 58; +// Estimated size of a P2TR input in bytes for staking expansion transactions. +// This value accounts for the witness size including covenant signatures +// and is calibrated for a typical covenant quorum of 6 signatures. +export const P2TR_STAKING_EXPANSION_INPUT_SIZE = 268; // Estimated size of a transaction buffer in bytes export const TX_BUFFER_SIZE_OVERHEAD = 11; // Buffer for estimation accuracy when fee rate <= 2 sat/byte diff --git a/modules/babylonlabs-io-btc-staking-ts/src/constants/registry.ts b/modules/babylonlabs-io-btc-staking-ts/src/constants/registry.ts index ecea14e494..85eedf87eb 100644 --- a/modules/babylonlabs-io-btc-staking-ts/src/constants/registry.ts +++ b/modules/babylonlabs-io-btc-staking-ts/src/constants/registry.ts @@ -1,3 +1,4 @@ export const BABYLON_REGISTRY_TYPE_URLS = { MsgCreateBTCDelegation: "/babylon.btcstaking.v1.MsgCreateBTCDelegation", + MsgBtcStakeExpand: "/babylon.btcstaking.v1.MsgBtcStakeExpand", }; diff --git a/modules/babylonlabs-io-btc-staking-ts/src/constants/staking.ts b/modules/babylonlabs-io-btc-staking-ts/src/constants/staking.ts new file mode 100644 index 0000000000..3cd2476e80 --- /dev/null +++ b/modules/babylonlabs-io-btc-staking-ts/src/constants/staking.ts @@ -0,0 +1,5 @@ +/** + * Staking module address for the Babylon Genesis chain. + * This address is derived deterministically from the module name and is the same across all environments. + */ +export const STAKING_MODULE_ADDRESS = "bbn13837feaxn8t0zvwcjwhw7lhpgdcx4s36eqteah"; \ No newline at end of file diff --git a/modules/babylonlabs-io-btc-staking-ts/src/staking/index.ts b/modules/babylonlabs-io-btc-staking-ts/src/staking/index.ts index e3f547be95..d3bec931d3 100644 --- a/modules/babylonlabs-io-btc-staking-ts/src/staking/index.ts +++ b/modules/babylonlabs-io-btc-staking-ts/src/staking/index.ts @@ -13,13 +13,11 @@ import { deriveStakingOutputInfo, findMatchingTxOutputIndex, toBuffers, - validateParams, - validateStakingTimelock, - validateStakingTxInputData, } from "../utils/staking"; -import { stakingPsbt, unbondingPsbt } from "./psbt"; +import { stakingExpansionPsbt, stakingPsbt, unbondingPsbt } from "./psbt"; import { StakingScriptData, StakingScripts } from "./stakingScript"; import { + stakingExpansionTransaction, slashEarlyUnbondedTransaction, slashTimelockUnbondedTransaction, stakingTransaction, @@ -28,6 +26,7 @@ import { withdrawSlashingTransaction, withdrawTimelockUnbondedTransaction, } from "./transactions"; +import { validateParams, validateStakingExpansionCovenantQuorum, validateStakingTimelock, validateStakingTxInputData } from "../utils/staking/validation"; export * from "./stakingScript"; export interface StakerInfo { @@ -171,6 +170,106 @@ export class Staking { } } + /** + * Creates a staking expansion transaction that extends an existing BTC stake + * to new finality providers or renews the timelock. + * + * This method implements RFC 037 BTC Stake Expansion, + * allowing existing active BTC staking transactions + * to extend their delegation to new finality providers without going through + * the full unbonding process. + * + * The expansion transaction: + * 1. Spends the previous staking transaction output as the first input + * 2. Uses funding UTXO as additional input to cover transaction fees or + * to increase the staking amount + * 3. Creates a new staking output with expanded finality provider coverage or + * renews the timelock + * 4. Has an output returning the remaining funds as change (if any) to the + * staker BTC address + * + * @param {number} stakingAmountSat - The total staking amount in satoshis + * (The amount had to be equal to the previous staking amount for now, this + * lib does not yet support increasing the staking amount at this stage) + * @param {UTXO[]} inputUTXOs - Available UTXOs to use for funding the + * expansion transaction fees. Only one will be selected for the expansion + * @param {number} feeRate - Fee rate in satoshis per byte for the + * expansion transaction + * @param {StakingParams} paramsForPreviousStakingTx - Staking parameters + * used in the previous staking transaction + * @param {Object} previousStakingTxInfo - Necessary information to spend the + * previous staking transaction. + * @returns {TransactionResult & { fundingUTXO: UTXO }} - An object containing + * the unsigned expansion transaction and calculated fee, and the funding UTXO + * @throws {StakingError} - If the transaction cannot be built or validation + * fails + */ + public createStakingExpansionTransaction( + stakingAmountSat: number, + inputUTXOs: UTXO[], + feeRate: number, + paramsForPreviousStakingTx: StakingParams, + previousStakingTxInfo: { + stakingTx: Transaction, + stakingInput: { + finalityProviderPksNoCoordHex: string[], + stakingTimelock: number, + }, + }, + ): TransactionResult & { + fundingUTXO: UTXO; + } { + validateStakingTxInputData( + stakingAmountSat, + this.stakingTimelock, + this.params, + inputUTXOs, + feeRate, + ); + validateStakingExpansionCovenantQuorum( + paramsForPreviousStakingTx, + this.params, + ); + + // Create a Staking instance for the previous staking transaction + // This allows us to build the scripts needed to spend the previous + // staking output + const previousStaking = new Staking( + this.network, + this.stakerInfo, + paramsForPreviousStakingTx, + previousStakingTxInfo.stakingInput.finalityProviderPksNoCoordHex, + previousStakingTxInfo.stakingInput.stakingTimelock, + ); + + // Build the expansion transaction using the stakingExpansionTransaction + // utility function. + // This creates a transaction that spends the previous staking output and + // creates new staking outputs + const { + transaction: stakingExpansionTx, + fee: stakingExpansionTxFee, + fundingUTXO, + } = stakingExpansionTransaction( + this.network, + this.buildScripts(), + stakingAmountSat, + this.stakerInfo.address, + feeRate, + inputUTXOs, + { + stakingTx: previousStakingTxInfo.stakingTx, + scripts: previousStaking.buildScripts(), + }, + ) + + return { + transaction: stakingExpansionTx, + fee: stakingExpansionTxFee, + fundingUTXO, + }; + } + /** * Create a staking psbt based on the existing staking transaction. * @@ -200,6 +299,76 @@ export class Staking { ); } + /** + * Convert a staking expansion transaction to a PSBT. + * + * @param {Transaction} stakingExpansionTx - The staking expansion + * transaction to convert + * @param {UTXO[]} inputUTXOs - Available UTXOs for the + * funding input (second input) + * @param {StakingParams} paramsForPreviousStakingTx - Staking parameters + * used for the previous staking transaction + * @param {Object} previousStakingTxInfo - Information about the previous + * staking transaction + * @returns {Psbt} The PSBT for the staking expansion transaction + * @throws {Error} If the previous staking output cannot be found or + * validation fails + */ + public toStakingExpansionPsbt( + stakingExpansionTx: Transaction, + inputUTXOs: UTXO[], + paramsForPreviousStakingTx: StakingParams, + previousStakingTxInfo: { + stakingTx: Transaction, + stakingInput: { + finalityProviderPksNoCoordHex: string[], + stakingTimelock: number, + }, + }, + ): Psbt { + // Reconstruct the previous staking instance to access its scripts and + // parameters. This is necessary because we need to identify which output + // in the previous staking transaction is the staking output (it could be + // at any output index) + const previousStaking = new Staking( + this.network, + this.stakerInfo, + paramsForPreviousStakingTx, + previousStakingTxInfo.stakingInput.finalityProviderPksNoCoordHex, + previousStakingTxInfo.stakingInput.stakingTimelock, + ); + + // Find the staking output address in the previous staking transaction + const previousScripts = previousStaking.buildScripts(); + const { outputAddress } = deriveStakingOutputInfo(previousScripts, this.network); + + // Find the output index in the previous staking transaction that matches + // the staking output address. + const previousStakingOutputIndex = findMatchingTxOutputIndex( + previousStakingTxInfo.stakingTx, + outputAddress, + this.network, + ); + + // Create and return the PSBT for the staking expansion transaction + // The PSBT will have two inputs: + // 1. The previous staking output + // 2. A funding UTXO from inputUTXOs (for additional funds) + return stakingExpansionPsbt( + this.network, + stakingExpansionTx, + { + stakingTx: previousStakingTxInfo.stakingTx, + outputIndex: previousStakingOutputIndex, + }, + inputUTXOs, + previousScripts, + isTaproot(this.stakerInfo.address, this.network) + ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") + : undefined, + ); + } + /** * Create an unbonding transaction for staking. * diff --git a/modules/babylonlabs-io-btc-staking-ts/src/staking/manager.ts b/modules/babylonlabs-io-btc-staking-ts/src/staking/manager.ts index 390d376998..7eee366df6 100644 --- a/modules/babylonlabs-io-btc-staking-ts/src/staking/manager.ts +++ b/modules/babylonlabs-io-btc-staking-ts/src/staking/manager.ts @@ -1,57 +1,56 @@ -import { - btccheckpoint, - btcstaking, - btcstakingtx, -} from "@babylonlabs-io/babylon-proto-ts"; +import { btccheckpoint, btcstaking, btcstakingtx } from '@babylonlabs-io/babylon-proto-ts'; import { BIP322Sig, BTCSigType, ProofOfPossessionBTC, -} from "@babylonlabs-io/babylon-proto-ts/dist/generated/babylon/btcstaking/v1/pop"; -import { Psbt, Transaction, networks } from "bitcoinjs-lib"; -import type { Emitter } from "nanoevents"; - -import { StakerInfo, Staking } from "."; -import { BABYLON_REGISTRY_TYPE_URLS } from "../constants/registry"; -import { StakingError, StakingErrorCode } from "../error"; -import { TransactionResult, UTXO } from "../types"; -import { ActionName } from "../types/action"; -import { Contract, ContractId } from "../types/contract"; -import { ManagerEvents } from "../types/events"; -import { - BabylonProvider, - BtcProvider, - InclusionProof, - StakingInputs, -} from "../types/manager"; -import { StakingParams, VersionedStakingParams } from "../types/params"; -import { reverseBuffer } from "../utils"; -import { isValidBabylonAddress } from "../utils/babylon"; -import { isNativeSegwit, isTaproot } from "../utils/btc"; +} from '@babylonlabs-io/babylon-proto-ts/dist/generated/babylon/btcstaking/v1/pop'; +import { Psbt, Transaction, networks } from 'bitcoinjs-lib'; +import type { Emitter } from 'nanoevents'; + +import { StakerInfo, Staking } from '.'; +import { BABYLON_REGISTRY_TYPE_URLS } from '../constants/registry'; +import { StakingError, StakingErrorCode } from '../error'; +import { TransactionResult, UTXO } from '../types'; +import { ActionName } from '../types/action'; +import { Contract, ContractId } from '../types/contract'; +import { ManagerEvents } from '../types/events'; +import { BabylonProvider, BtcProvider, InclusionProof, StakingInputs, UpgradeConfig } from '../types/manager'; +import { StakingParams, VersionedStakingParams } from '../types/params'; +import { reverseBuffer } from '../utils'; +import { isValidBabylonAddress } from '../utils/babylon'; +import { isNativeSegwit, isTaproot } from '../utils/btc'; +import { buildPopMessage } from '../utils/pop'; import { + clearTxSignatures, + deriveMerkleProof, deriveStakingOutputInfo, + extractFirstSchnorrSignatureFromTransaction, findMatchingTxOutputIndex, -} from "../utils/staking"; -import { - getBabylonParamByBtcHeight, - getBabylonParamByVersion, -} from "../utils/staking/param"; -import { createCovenantWitness } from "./transactions"; +} from '../utils/staking'; +import { getBabylonParamByBtcHeight, getBabylonParamByVersion } from '../utils/staking/param'; + +import { createCovenantWitness } from './transactions'; +import { validateStakingExpansionInputs } from '../utils/staking/validation'; export class BabylonBtcStakingManager { + private upgradeConfig?: UpgradeConfig; + constructor( protected network: networks.Network, protected stakingParams: VersionedStakingParams[], protected btcProvider: BtcProvider, protected babylonProvider: BabylonProvider, - protected ee?: Emitter + protected ee?: Emitter, + upgradeConfig?: UpgradeConfig ) { this.network = network; if (stakingParams.length === 0) { - throw new Error("No staking parameters provided"); + throw new Error('No staking parameters provided'); } this.stakingParams = stakingParams; + + this.upgradeConfig = upgradeConfig; } /** @@ -76,63 +75,212 @@ export class BabylonBtcStakingManager { babylonBtcTipHeight: number, inputUTXOs: UTXO[], feeRate: number, - babylonAddress: string, + babylonAddress: string ): Promise<{ signedBabylonTx: Uint8Array; stakingTx: Transaction; }> { if (babylonBtcTipHeight === 0) { - throw new Error("Babylon BTC tip height cannot be 0"); + throw new Error('Babylon BTC tip height cannot be 0'); } if (inputUTXOs.length === 0) { - throw new Error("No input UTXOs provided"); + throw new Error('No input UTXOs provided'); } if (!isValidBabylonAddress(babylonAddress)) { - throw new Error("Invalid Babylon address"); + throw new Error('Invalid Babylon address'); } // Get the Babylon params based on the BTC tip height from Babylon chain - const params = getBabylonParamByBtcHeight( - babylonBtcTipHeight, - this.stakingParams, - ); + const params = getBabylonParamByBtcHeight(babylonBtcTipHeight, this.stakingParams); const staking = new Staking( this.network, stakerBtcInfo, params, stakingInput.finalityProviderPksNoCoordHex, - stakingInput.stakingTimelock, + stakingInput.stakingTimelock ); // Create unsigned staking transaction - const { transaction } = staking.createStakingTransaction( + const { transaction } = staking.createStakingTransaction(stakingInput.stakingAmountSat, inputUTXOs, feeRate); + + // Create delegation message without including inclusion proof + const msg = await this.createBtcDelegationMsg( + 'delegation:create', + staking, + stakingInput, + transaction, + babylonAddress, + stakerBtcInfo, + params + ); + + this.ee?.emit('delegation:create', { + type: 'create-btc-delegation-msg', + }); + + return { + signedBabylonTx: await this.babylonProvider.signTransaction(msg), + stakingTx: transaction, + }; + } + + /** + * Create a signed staking expansion transaction that is ready to be sent to + * the Babylon chain. + */ + async stakingExpansionRegistrationBabylonTransaction( + stakerBtcInfo: StakerInfo, + stakingInput: StakingInputs, + babylonBtcTipHeight: number, + inputUTXOs: UTXO[], + feeRate: number, + babylonAddress: string, + // Previous staking transaction info + previousStakingTxInfo: { + stakingTx: Transaction; + paramVersion: number; + stakingInput: StakingInputs; + } + ): Promise<{ + signedBabylonTx: Uint8Array; + stakingTx: Transaction; + }> { + // Perform validation for the staking expansion inputs + validateStakingExpansionInputs({ + babylonBtcTipHeight, + inputUTXOs, + stakingInput, + previousStakingInput: previousStakingTxInfo.stakingInput, + babylonAddress, + }); + // Param for the expandsion staking transaction + const params = getBabylonParamByBtcHeight(babylonBtcTipHeight, this.stakingParams); + + const paramsForPreviousStakingTx = getBabylonParamByVersion(previousStakingTxInfo.paramVersion, this.stakingParams); + + const stakingInstance = new Staking( + this.network, + stakerBtcInfo, + params, + stakingInput.finalityProviderPksNoCoordHex, + stakingInput.stakingTimelock + ); + + const { transaction: stakingExpansionTx, fundingUTXO } = stakingInstance.createStakingExpansionTransaction( stakingInput.stakingAmountSat, inputUTXOs, feeRate, + paramsForPreviousStakingTx, + previousStakingTxInfo ); + let fundingTx; + try { + fundingTx = await this.btcProvider.getTransactionHex(fundingUTXO.txid); + } catch (error) { + throw StakingError.fromUnknown( + error, + StakingErrorCode.INVALID_INPUT, + 'Failed to retrieve funding transaction hex' + ); + } // Create delegation message without including inclusion proof const msg = await this.createBtcDelegationMsg( - "delegation:create", - staking, + 'delegation:expand', + stakingInstance, stakingInput, - transaction, + stakingExpansionTx, babylonAddress, stakerBtcInfo, params, + { + delegationExpansionInfo: { + previousStakingTx: previousStakingTxInfo.stakingTx, + fundingTx: Transaction.fromHex(fundingTx), + }, + } ); - this.ee?.emit("delegation:create", { - type: "create-btc-delegation-msg", + this.ee?.emit('delegation:expand', { + type: 'create-btc-delegation-msg', }); return { signedBabylonTx: await this.babylonProvider.signTransaction(msg), - stakingTx: transaction, + stakingTx: stakingExpansionTx, }; } + /** + * Estimates the transaction fee for a BTC staking expansion transaction. + * + * @param {StakerInfo} stakerBtcInfo - The staker's Bitcoin information + * including address and public key + * @param {number} babylonBtcTipHeight - The current Babylon BTC tip height + * used to determine staking parameters + * @param {StakingInputs} stakingInput - The new staking input parameters for + * the expansion + * @param {UTXO[]} inputUTXOs - Available UTXOs that can be used for funding + * the expansion transaction + * @param {number} feeRate - Fee rate in satoshis per byte for the expansion + * transaction + * @param {Object} previousStakingTxInfo - Information about the previous + * staking transaction being expanded + * @returns {number} - The estimated transaction fee in satoshis + * @throws {Error} - If validation fails or the fee cannot be calculated + */ + estimateBtcStakingExpansionFee( + stakerBtcInfo: StakerInfo, + babylonBtcTipHeight: number, + stakingInput: StakingInputs, + inputUTXOs: UTXO[], + feeRate: number, + previousStakingTxInfo: { + stakingTx: Transaction; + paramVersion: number; + stakingInput: StakingInputs; + } + ): number { + // Validate all input parameters before fee calculation + validateStakingExpansionInputs({ + babylonBtcTipHeight, + inputUTXOs, + stakingInput, + previousStakingInput: previousStakingTxInfo.stakingInput, + }); + + // Get the appropriate staking parameters based on the current Babylon BTC + // tip height. This ensures we use the correct parameters for the current + // network state + const params = getBabylonParamByBtcHeight(babylonBtcTipHeight, this.stakingParams); + + // Get the staking parameters that were used in the previous staking + // transaction. This is needed to properly reconstruct the previous staking + // scripts + const paramsForPreviousStakingTx = getBabylonParamByVersion(previousStakingTxInfo.paramVersion, this.stakingParams); + + // Create a Staking instance for the new expansion with current parameters + // This will be used to build the new staking scripts and calculate the + // transaction + const stakingInstance = new Staking( + this.network, + stakerBtcInfo, + params, + stakingInput.finalityProviderPksNoCoordHex, + stakingInput.stakingTimelock + ); + const { fee } = stakingInstance.createStakingExpansionTransaction( + stakingInput.stakingAmountSat, + inputUTXOs, + feeRate, + paramsForPreviousStakingTx, + previousStakingTxInfo + ); + + return fee; + } + /** * Creates a signed post-staking registration transaction that is ready to be * sent to the Babylon chain. This is used when a staking transaction is @@ -155,18 +303,15 @@ export class BabylonBtcStakingManager { stakingTxHeight: number, stakingInput: StakingInputs, inclusionProof: InclusionProof, - babylonAddress: string, + babylonAddress: string ): Promise<{ signedBabylonTx: Uint8Array; }> { // Get the Babylon params at the time of the staking transaction - const params = getBabylonParamByBtcHeight( - stakingTxHeight, - this.stakingParams, - ); + const params = getBabylonParamByBtcHeight(stakingTxHeight, this.stakingParams); if (!isValidBabylonAddress(babylonAddress)) { - throw new Error("Invalid Babylon address"); + throw new Error('Invalid Babylon address'); } const stakingInstance = new Staking( @@ -174,7 +319,7 @@ export class BabylonBtcStakingManager { stakerBtcInfo, params, stakingInput.finalityProviderPksNoCoordHex, - stakingInput.stakingTimelock, + stakingInput.stakingTimelock ); // Validate if the stakingTx is valid based on the retrieved Babylon param @@ -182,31 +327,28 @@ export class BabylonBtcStakingManager { const stakingOutputInfo = deriveStakingOutputInfo(scripts, this.network); // Error will be thrown if the expected staking output address is not found // in the stakingTx - findMatchingTxOutputIndex( - stakingTx, - stakingOutputInfo.outputAddress, - this.network, - ); + findMatchingTxOutputIndex(stakingTx, stakingOutputInfo.outputAddress, this.network); // Create delegation message const delegationMsg = await this.createBtcDelegationMsg( - "delegation:register", + 'delegation:register', stakingInstance, stakingInput, stakingTx, babylonAddress, stakerBtcInfo, params, - this.getInclusionProof(inclusionProof), + { + inclusionProof: this.getInclusionProof(inclusionProof), + } ); - this.ee?.emit("delegation:register", { - type: "create-btc-delegation-msg", + this.ee?.emit('delegation:register', { + type: 'create-btc-delegation-msg', }); return { - signedBabylonTx: - await this.babylonProvider.signTransaction(delegationMsg), + signedBabylonTx: await this.babylonProvider.signTransaction(delegationMsg), }; } @@ -229,30 +371,23 @@ export class BabylonBtcStakingManager { babylonBtcTipHeight: number, stakingInput: StakingInputs, inputUTXOs: UTXO[], - feeRate: number, + feeRate: number ): number { if (babylonBtcTipHeight === 0) { - throw new Error("Babylon BTC tip height cannot be 0"); + throw new Error('Babylon BTC tip height cannot be 0'); } // Get the param based on the tip height - const params = getBabylonParamByBtcHeight( - babylonBtcTipHeight, - this.stakingParams, - ); + const params = getBabylonParamByBtcHeight(babylonBtcTipHeight, this.stakingParams); const staking = new Staking( this.network, stakerBtcInfo, params, stakingInput.finalityProviderPksNoCoordHex, - stakingInput.stakingTimelock, + stakingInput.stakingTimelock ); - const { fee: stakingFee } = staking.createStakingTransaction( - stakingInput.stakingAmountSat, - inputUTXOs, - feeRate, - ); + const { fee: stakingFee } = staking.createStakingTransaction(stakingInput.stakingAmountSat, inputUTXOs, feeRate); return stakingFee; } @@ -275,15 +410,12 @@ export class BabylonBtcStakingManager { stakingInput: StakingInputs, unsignedStakingTx: Transaction, inputUTXOs: UTXO[], - stakingParamsVersion: number, + stakingParamsVersion: number ): Promise { - const params = getBabylonParamByVersion( - stakingParamsVersion, - this.stakingParams, - ); + const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams); if (inputUTXOs.length === 0) { - throw new Error("No input UTXOs provided"); + throw new Error('No input UTXOs provided'); } const staking = new Staking( @@ -291,7 +423,7 @@ export class BabylonBtcStakingManager { stakerBtcInfo, params, stakingInput.finalityProviderPksNoCoordHex, - stakingInput.stakingTimelock, + stakingInput.stakingTimelock ); const stakingPsbt = staking.toStakingPsbt(unsignedStakingTx, inputUTXOs); @@ -310,27 +442,172 @@ export class BabylonBtcStakingManager { }, ]; - this.ee?.emit("delegation:stake", { + this.ee?.emit('delegation:stake', { stakerPk: stakerBtcInfo.publicKeyNoCoordHex, finalityProviders: stakingInput.finalityProviderPksNoCoordHex, covenantPks: params.covenantNoCoordPks, covenantThreshold: params.covenantQuorum, unbondingTimeBlocks: params.unbondingTime, stakingDuration: stakingInput.stakingTimelock, - type: "staking", + type: 'staking', }); - const signedStakingPsbtHex = await this.btcProvider.signPsbt( - stakingPsbt.toHex(), + const signedStakingPsbtHex = await this.btcProvider.signPsbt(stakingPsbt.toHex(), { + contracts, + action: { + name: ActionName.SIGN_BTC_STAKING_TRANSACTION, + }, + }); + + return Psbt.fromHex(signedStakingPsbtHex).extractTransaction(); + } + + /** + * Creates a signed staking expansion transaction that is ready to be sent to + * the BTC network. + * + * @param {StakerInfo} stakerBtcInfo - The staker's BTC information including + * address and public key + * @param {StakingInputs} stakingInput - The staking inputs for the expansion + * @param {Transaction} unsignedStakingExpansionTx - The unsigned staking + * expansion transaction + * @param {UTXO[]} inputUTXOs - Available UTXOs for the funding input + * @param {number} stakingParamsVersion - The version of staking parameters + * that was used when registering the staking expansion delegation. + * @param {Object} previousStakingTxInfo - Information about the previous + * staking transaction + * @param {Array} covenantStakingExpansionSignatures - Covenant committee + * signatures for the expansion + * @returns {Promise} The fully signed staking expansion + * transaction + * @throws {Error} If signing fails, validation fails, or required data is + * missing + */ + async createSignedBtcStakingExpansionTransaction( + stakerBtcInfo: StakerInfo, + stakingInput: StakingInputs, + unsignedStakingExpansionTx: Transaction, + inputUTXOs: UTXO[], + stakingParamsVersion: number, + previousStakingTxInfo: { + stakingTx: Transaction; + paramVersion: number; + stakingInput: StakingInputs; + }, + covenantStakingExpansionSignatures: { + btcPkHex: string; + sigHex: string; + }[] + ): Promise { + validateStakingExpansionInputs({ + inputUTXOs, + stakingInput, + previousStakingInput: previousStakingTxInfo.stakingInput, + }); + + // Get the staking parameters for the current version + // These parameters define the covenant committee and other staking rules + const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams); + + // Validate that input UTXOs are provided for the funding input + if (inputUTXOs.length === 0) { + throw new Error('No input UTXOs provided'); + } + + // Create a new staking instance with the current parameters + // This will be used to build the PSBT for the expansion transaction + const staking = new Staking( + this.network, + stakerBtcInfo, + params, + stakingInput.finalityProviderPksNoCoordHex, + stakingInput.stakingTimelock + ); + + const previousParams = getBabylonParamByVersion(previousStakingTxInfo.paramVersion, this.stakingParams); + + // Create the PSBT for the staking expansion transaction + // This PSBT will have two inputs: the previous staking output and a + // funding UTXO + const stakingExpansionPsbt = staking.toStakingExpansionPsbt( + unsignedStakingExpansionTx, + inputUTXOs, + previousParams, + previousStakingTxInfo + ); + + // Define the contract information for the PSBT signing + const contracts: Contract[] = [ { - contracts, - action: { - name: ActionName.SIGN_BTC_STAKING_TRANSACTION, + id: ContractId.STAKING, + params: { + stakerPk: stakerBtcInfo.publicKeyNoCoordHex, + finalityProviders: stakingInput.finalityProviderPksNoCoordHex, + covenantPks: params.covenantNoCoordPks, + covenantThreshold: params.covenantQuorum, + minUnbondingTime: params.unbondingTime, + stakingDuration: stakingInput.stakingTimelock, }, }, + ]; + + // Emit an event to notify listeners about the staking expansion + // This can be used for logging, monitoring, or UI updates + this.ee?.emit('delegation:stake', { + stakerPk: stakerBtcInfo.publicKeyNoCoordHex, + finalityProviders: stakingInput.finalityProviderPksNoCoordHex, + covenantPks: params.covenantNoCoordPks, + covenantThreshold: params.covenantQuorum, + unbondingTimeBlocks: params.unbondingTime, + stakingDuration: stakingInput.stakingTimelock, + type: 'staking', + }); + + // Sign the PSBT using the BTC provider (wallet) + // The wallet will sign the transaction based on the contract information + // provided + const signedStakingPsbtHex = await this.btcProvider.signPsbt(stakingExpansionPsbt.toHex(), { + contracts, + action: { + name: ActionName.SIGN_BTC_STAKING_TRANSACTION, + }, + }); + + // Extract the signed transaction from the PSBT + const signedStakingExpansionTx = Psbt.fromHex(signedStakingPsbtHex).extractTransaction(); + + // Validate that the signed transaction hash matches the unsigned + // transaction hash + // This ensures that the signing process didn't change the transaction + // structure + if (signedStakingExpansionTx.getId() !== unsignedStakingExpansionTx.getId()) { + throw new Error('Staking expansion transaction hash does not match the computed hash'); + } + + // Add covenant committee signatures to the transaction + // Convert covenant public keys from hex strings to buffers + // The covenants committee is based on the params at the time of the previous + // staking transaction. Hence using the previous params here. + const covenantBuffers = previousParams.covenantNoCoordPks.map((covenant) => Buffer.from(covenant, 'hex')); + + // Create the witness that includes both the staker's signature and covenant + // signatures + // The witness is the data that proves the transaction is authorized + const witness = createCovenantWitness( + // The first input of the staking expansion transaction is the previous + // staking output. We will attach the covenant signatures to this input + // to unbond the previousstaking output. + signedStakingExpansionTx.ins[0].witness, + covenantBuffers, + covenantStakingExpansionSignatures, + previousParams.covenantQuorum ); - return Psbt.fromHex(signedStakingPsbtHex).extractTransaction(); + // Overwrite the witness to include the covenant staking expansion signatures + // This makes the transaction valid for submission to the Bitcoin network + signedStakingExpansionTx.ins[0].witness = witness; + + return signedStakingExpansionTx; } /** @@ -351,24 +628,20 @@ export class BabylonBtcStakingManager { stakerBtcInfo: StakerInfo, stakingInput: StakingInputs, stakingParamsVersion: number, - stakingTx: Transaction, + stakingTx: Transaction ): Promise { // Get the staking params at the time of the staking transaction - const params = getBabylonParamByVersion( - stakingParamsVersion, - this.stakingParams, - ); + const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams); const staking = new Staking( this.network, stakerBtcInfo, params, stakingInput.finalityProviderPksNoCoordHex, - stakingInput.stakingTimelock, + stakingInput.stakingTimelock ); - const { transaction: unbondingTx, fee } = - staking.createUnbondingTransaction(stakingTx); + const { transaction: unbondingTx, fee } = staking.createUnbondingTransaction(stakingTx); const psbt = staking.toUnbondingPsbt(unbondingTx, stakingTx); @@ -397,7 +670,7 @@ export class BabylonBtcStakingManager { }, ]; - this.ee?.emit("delegation:unbond", { + this.ee?.emit('delegation:unbond', { stakerPk: stakerBtcInfo.publicKeyNoCoordHex, finalityProviders: stakingInput.finalityProviderPksNoCoordHex, covenantPks: params.covenantNoCoordPks, @@ -405,22 +678,17 @@ export class BabylonBtcStakingManager { stakingDuration: stakingInput.stakingTimelock, unbondingTimeBlocks: params.unbondingTime, unbondingFeeSat: params.unbondingFeeSat, - type: "unbonding", + type: 'unbonding', }); - const signedUnbondingPsbtHex = await this.btcProvider.signPsbt( - psbt.toHex(), - { - contracts, - action: { - name: ActionName.SIGN_BTC_UNBONDING_TRANSACTION, - }, + const signedUnbondingPsbtHex = await this.btcProvider.signPsbt(psbt.toHex(), { + contracts, + action: { + name: ActionName.SIGN_BTC_UNBONDING_TRANSACTION, }, - ); + }); - const signedUnbondingTx = Psbt.fromHex( - signedUnbondingPsbtHex, - ).extractTransaction(); + const signedUnbondingTx = Psbt.fromHex(signedUnbondingPsbtHex).extractTransaction(); return { transaction: signedUnbondingTx, @@ -451,35 +719,27 @@ export class BabylonBtcStakingManager { covenantUnbondingSignatures: { btcPkHex: string; sigHex: string; - }[], + }[] ): Promise { // Get the staking params at the time of the staking transaction - const params = getBabylonParamByVersion( + const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams); + + const { transaction: signedUnbondingTx, fee } = await this.createPartialSignedBtcUnbondingTransaction( + stakerBtcInfo, + stakingInput, stakingParamsVersion, - this.stakingParams, + stakingTx ); - const { transaction: signedUnbondingTx, fee } = - await this.createPartialSignedBtcUnbondingTransaction( - stakerBtcInfo, - stakingInput, - stakingParamsVersion, - stakingTx, - ); - // Check the computed txid of the signed unbonding transaction is the same as // the txid of the unsigned unbonding transaction if (signedUnbondingTx.getId() !== unsignedUnbondingTx.getId()) { - throw new Error( - "Unbonding transaction hash does not match the computed hash", - ); + throw new Error('Unbonding transaction hash does not match the computed hash'); } // Add covenant unbonding signatures // Convert the params of covenants to buffer - const covenantBuffers = params.covenantNoCoordPks.map((covenant) => - Buffer.from(covenant, "hex"), - ); + const covenantBuffers = params.covenantNoCoordPks.map((covenant) => Buffer.from(covenant, 'hex')); const witness = createCovenantWitness( // Since unbonding transactions always have a single input and output, // we expect exactly one signature in TaprootScriptSpendSig when the @@ -487,7 +747,7 @@ export class BabylonBtcStakingManager { signedUnbondingTx.ins[0].witness, covenantBuffers, covenantUnbondingSignatures, - params.covenantQuorum, + params.covenantQuorum ); // Overwrite the witness to include the covenant unbonding signatures signedUnbondingTx.ins[0].witness = witness; @@ -515,23 +775,19 @@ export class BabylonBtcStakingManager { stakingInput: StakingInputs, stakingParamsVersion: number, earlyUnbondingTx: Transaction, - feeRate: number, + feeRate: number ): Promise { - const params = getBabylonParamByVersion( - stakingParamsVersion, - this.stakingParams, - ); + const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams); const staking = new Staking( this.network, stakerBtcInfo, params, stakingInput.finalityProviderPksNoCoordHex, - stakingInput.stakingTimelock, + stakingInput.stakingTimelock ); - const { psbt: unbondingPsbt, fee } = - staking.createWithdrawEarlyUnbondedTransaction(earlyUnbondingTx, feeRate); + const { psbt: unbondingPsbt, fee } = staking.createWithdrawEarlyUnbondedTransaction(earlyUnbondingTx, feeRate); const contracts: Contract[] = [ { @@ -543,21 +799,18 @@ export class BabylonBtcStakingManager { }, ]; - this.ee?.emit("delegation:withdraw", { + this.ee?.emit('delegation:withdraw', { stakerPk: stakerBtcInfo.publicKeyNoCoordHex, timelockBlocks: params.unbondingTime, - type: "early-unbonded", + type: 'early-unbonded', }); - const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt( - unbondingPsbt.toHex(), - { - contracts, - action: { - name: ActionName.SIGN_BTC_WITHDRAW_TRANSACTION, - }, + const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(unbondingPsbt.toHex(), { + contracts, + action: { + name: ActionName.SIGN_BTC_WITHDRAW_TRANSACTION, }, - ); + }); return { transaction: Psbt.fromHex(signedWithdrawalPsbtHex).extractTransaction(), @@ -584,25 +837,19 @@ export class BabylonBtcStakingManager { stakingInput: StakingInputs, stakingParamsVersion: number, stakingTx: Transaction, - feeRate: number, + feeRate: number ): Promise { - const params = getBabylonParamByVersion( - stakingParamsVersion, - this.stakingParams, - ); + const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams); const staking = new Staking( this.network, stakerBtcInfo, params, stakingInput.finalityProviderPksNoCoordHex, - stakingInput.stakingTimelock, + stakingInput.stakingTimelock ); - const { psbt, fee } = staking.createWithdrawStakingExpiredPsbt( - stakingTx, - feeRate, - ); + const { psbt, fee } = staking.createWithdrawStakingExpiredPsbt(stakingTx, feeRate); const contracts: Contract[] = [ { @@ -614,21 +861,18 @@ export class BabylonBtcStakingManager { }, ]; - this.ee?.emit("delegation:withdraw", { + this.ee?.emit('delegation:withdraw', { stakerPk: stakerBtcInfo.publicKeyNoCoordHex, timelockBlocks: stakingInput.stakingTimelock, - type: "staking-expired", + type: 'staking-expired', }); - const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt( - psbt.toHex(), - { - contracts, - action: { - name: ActionName.SIGN_BTC_WITHDRAW_TRANSACTION, - }, + const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(psbt.toHex(), { + contracts, + action: { + name: ActionName.SIGN_BTC_WITHDRAW_TRANSACTION, }, - ); + }); return { transaction: Psbt.fromHex(signedWithdrawalPsbtHex).extractTransaction(), @@ -655,25 +899,19 @@ export class BabylonBtcStakingManager { stakingInput: StakingInputs, stakingParamsVersion: number, slashingTx: Transaction, - feeRate: number, + feeRate: number ): Promise { - const params = getBabylonParamByVersion( - stakingParamsVersion, - this.stakingParams, - ); + const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams); const staking = new Staking( this.network, stakerBtcInfo, params, stakingInput.finalityProviderPksNoCoordHex, - stakingInput.stakingTimelock, + stakingInput.stakingTimelock ); - const { psbt, fee } = staking.createWithdrawSlashingPsbt( - slashingTx, - feeRate, - ); + const { psbt, fee } = staking.createWithdrawSlashingPsbt(slashingTx, feeRate); const contracts: Contract[] = [ { @@ -685,26 +923,21 @@ export class BabylonBtcStakingManager { }, ]; - this.ee?.emit("delegation:withdraw", { + this.ee?.emit('delegation:withdraw', { stakerPk: stakerBtcInfo.publicKeyNoCoordHex, timelockBlocks: params.unbondingTime, - type: "slashing", + type: 'slashing', }); - const signedWithrawSlashingPsbtHex = await this.btcProvider.signPsbt( - psbt.toHex(), - { - contracts, - action: { - name: ActionName.SIGN_BTC_WITHDRAW_TRANSACTION, - }, + const signedWithrawSlashingPsbtHex = await this.btcProvider.signPsbt(psbt.toHex(), { + contracts, + action: { + name: ActionName.SIGN_BTC_WITHDRAW_TRANSACTION, }, - ); + }); return { - transaction: Psbt.fromHex( - signedWithrawSlashingPsbtHex, - ).extractTransaction(), + transaction: Psbt.fromHex(signedWithrawSlashingPsbtHex).extractTransaction(), fee, }; } @@ -715,43 +948,58 @@ export class BabylonBtcStakingManager { * @returns The proof of possession. */ async createProofOfPossession( - channel: "delegation:create" | "delegation:register", + channel: 'delegation:create' | 'delegation:register' | 'delegation:expand', bech32Address: string, - stakerBtcAddress: string, + stakerBtcAddress: string ): Promise { let sigType: BTCSigType = BTCSigType.ECDSA; // For Taproot or Native SegWit addresses, use the BIP322 signature scheme // in the proof of possession as it uses the same signature type as the regular // input UTXO spend. For legacy addresses, use the ECDSA signature scheme. - if ( - isTaproot(stakerBtcAddress, this.network) || - isNativeSegwit(stakerBtcAddress, this.network) - ) { + if (isTaproot(stakerBtcAddress, this.network) || isNativeSegwit(stakerBtcAddress, this.network)) { sigType = BTCSigType.BIP322; } - this.ee?.emit(channel, { + const [chainId, babyTipHeight] = await Promise.all([ + this.babylonProvider.getChainId?.(), + this.babylonProvider.getCurrentHeight?.(), + ]); + + const upgradeConfig = this.upgradeConfig?.pop; + + // Get the message to sign for the proof of possession + const messageToSign = buildPopMessage( bech32Address, - type: "proof-of-possession", + babyTipHeight, + chainId, + upgradeConfig && { + upgradeHeight: upgradeConfig.upgradeHeight, + version: upgradeConfig.version, + } + ); + + this.ee?.emit(channel, { + messageToSign, + type: 'proof-of-possession', }); const signedBabylonAddress = await this.btcProvider.signMessage( - bech32Address, - sigType === BTCSigType.BIP322 ? "bip322-simple" : "ecdsa", + messageToSign, + sigType === BTCSigType.BIP322 ? 'bip322-simple' : 'ecdsa' ); let btcSig: Uint8Array; if (sigType === BTCSigType.BIP322) { const bip322Sig = BIP322Sig.fromPartial({ address: stakerBtcAddress, - sig: Buffer.from(signedBabylonAddress, "base64"), + sig: Buffer.from(signedBabylonAddress, 'base64'), }); // Encode the BIP322 protobuf message to a Uint8Array btcSig = BIP322Sig.encode(bip322Sig).finish(); } else { // Encode the ECDSA signature to a Uint8Array - btcSig = Buffer.from(signedBabylonAddress, "base64"); + btcSig = Buffer.from(signedBabylonAddress, 'base64'); } return { @@ -768,19 +1016,13 @@ export class BabylonBtcStakingManager { * @returns The unbonding, slashing, and unbonding slashing transactions and * PSBTs. */ - private async createDelegationTransactionsAndPsbts( - stakingInstance: Staking, - stakingTx: Transaction, - ) { - const { transaction: unbondingTx } = - stakingInstance.createUnbondingTransaction(stakingTx); + private async createDelegationTransactionsAndPsbts(stakingInstance: Staking, stakingTx: Transaction) { + const { transaction: unbondingTx } = stakingInstance.createUnbondingTransaction(stakingTx); // Create slashing transactions and extract signatures - const { psbt: slashingPsbt } = - stakingInstance.createStakingOutputSlashingPsbt(stakingTx); + const { psbt: slashingPsbt } = stakingInstance.createStakingOutputSlashingPsbt(stakingTx); - const { psbt: unbondingSlashingPsbt } = - stakingInstance.createUnbondingOutputSlashingPsbt(unbondingTx); + const { psbt: unbondingSlashingPsbt } = stakingInstance.createUnbondingOutputSlashingPsbt(unbondingTx); return { unbondingTx, @@ -799,31 +1041,43 @@ export class BabylonBtcStakingManager { * @param stakerBtcInfo - The staker's BTC information such as address and * public key * @param params - The staking parameters. - * @param inclusionProof - The inclusion proof of the staking transaction. + * @param options - The options for the BTC delegation. + * @param options.inclusionProof - The inclusion proof of the staking + * transaction. + * @param options.delegationExpansionInfo - The information for the BTC + * delegation expansion. * @returns The protobuf message. */ public async createBtcDelegationMsg( - channel: "delegation:create" | "delegation:register", + channel: 'delegation:create' | 'delegation:register' | 'delegation:expand', stakingInstance: Staking, stakingInput: StakingInputs, stakingTx: Transaction, bech32Address: string, stakerBtcInfo: StakerInfo, params: StakingParams, - inclusionProof?: btcstaking.InclusionProof, - ) { + options?: { + inclusionProof?: btcstaking.InclusionProof; + delegationExpansionInfo?: { + previousStakingTx: Transaction; + fundingTx: Transaction; + }; + } + ): Promise<{ + typeUrl: string; + value: btcstakingtx.MsgCreateBTCDelegation | btcstakingtx.MsgBtcStakeExpand; + }> { if (!params.slashing) { throw new StakingError( StakingErrorCode.INVALID_PARAMS, - "Slashing parameters are required for creating delegation message", + 'Slashing parameters are required for creating delegation message' ); } - const { unbondingTx, slashingPsbt, unbondingSlashingPsbt } = - await this.createDelegationTransactionsAndPsbts( - stakingInstance, - stakingTx, - ); + const { unbondingTx, slashingPsbt, unbondingSlashingPsbt } = await this.createDelegationTransactionsAndPsbts( + stakingInstance, + stakingTx + ); const slashingContracts: Contract[] = [ { @@ -864,26 +1118,20 @@ export class BabylonBtcStakingManager { stakingDuration: stakingInput.stakingTimelock, slashingFeeSat: params.slashing.minSlashingTxFeeSat, slashingPkScriptHex: params.slashing.slashingPkScriptHex, - type: "staking-slashing", + type: 'staking-slashing', }); - const signedSlashingPsbtHex = await this.btcProvider.signPsbt( - slashingPsbt.toHex(), - { - contracts: slashingContracts, - action: { - name: ActionName.SIGN_BTC_SLASHING_TRANSACTION, - }, + const signedSlashingPsbtHex = await this.btcProvider.signPsbt(slashingPsbt.toHex(), { + contracts: slashingContracts, + action: { + name: ActionName.SIGN_BTC_SLASHING_TRANSACTION, }, - ); + }); - const signedSlashingTx = Psbt.fromHex( - signedSlashingPsbtHex, - ).extractTransaction(); - const slashingSig = - extractFirstSchnorrSignatureFromTransaction(signedSlashingTx); + const signedSlashingTx = Psbt.fromHex(signedSlashingPsbtHex).extractTransaction(); + const slashingSig = extractFirstSchnorrSignatureFromTransaction(signedSlashingTx); if (!slashingSig) { - throw new Error("No signature found in the staking output slashing PSBT"); + throw new Error('No signature found in the staking output slashing PSBT'); } const unbondingSlashingContracts: Contract[] = [ @@ -925,68 +1173,61 @@ export class BabylonBtcStakingManager { unbondingFeeSat: params.unbondingFeeSat, slashingFeeSat: params.slashing.minSlashingTxFeeSat, slashingPkScriptHex: params.slashing.slashingPkScriptHex, - type: "unbonding-slashing", + type: 'unbonding-slashing', }); - const signedUnbondingSlashingPsbtHex = await this.btcProvider.signPsbt( - unbondingSlashingPsbt.toHex(), - { - contracts: unbondingSlashingContracts, - action: { - name: ActionName.SIGN_BTC_UNBONDING_SLASHING_TRANSACTION, - }, + const signedUnbondingSlashingPsbtHex = await this.btcProvider.signPsbt(unbondingSlashingPsbt.toHex(), { + contracts: unbondingSlashingContracts, + action: { + name: ActionName.SIGN_BTC_UNBONDING_SLASHING_TRANSACTION, }, - ); + }); - const signedUnbondingSlashingTx = Psbt.fromHex( - signedUnbondingSlashingPsbtHex, - ).extractTransaction(); - const unbondingSignatures = extractFirstSchnorrSignatureFromTransaction( - signedUnbondingSlashingTx, - ); + const signedUnbondingSlashingTx = Psbt.fromHex(signedUnbondingSlashingPsbtHex).extractTransaction(); + const unbondingSignatures = extractFirstSchnorrSignatureFromTransaction(signedUnbondingSlashingTx); if (!unbondingSignatures) { - throw new Error( - "No signature found in the unbonding output slashing PSBT", - ); + throw new Error('No signature found in the unbonding output slashing PSBT'); } // Create proof of possession - const proofOfPossession = await this.createProofOfPossession( - channel, - bech32Address, - stakerBtcInfo.address, - ); + const proofOfPossession = await this.createProofOfPossession(channel, bech32Address, stakerBtcInfo.address); + + const commonMsg = { + stakerAddr: bech32Address, + pop: proofOfPossession, + btcPk: Uint8Array.from(Buffer.from(stakerBtcInfo.publicKeyNoCoordHex, 'hex')), + fpBtcPkList: stakingInput.finalityProviderPksNoCoordHex.map((pk) => Uint8Array.from(Buffer.from(pk, 'hex'))), + stakingTime: stakingInput.stakingTimelock, + stakingValue: stakingInput.stakingAmountSat, + stakingTx: Uint8Array.from(stakingTx.toBuffer()), + slashingTx: Uint8Array.from(Buffer.from(clearTxSignatures(signedSlashingTx).toHex(), 'hex')), + delegatorSlashingSig: Uint8Array.from(slashingSig), + unbondingTime: params.unbondingTime, + unbondingTx: Uint8Array.from(unbondingTx.toBuffer()), + unbondingValue: stakingInput.stakingAmountSat - params.unbondingFeeSat, + unbondingSlashingTx: Uint8Array.from(Buffer.from(clearTxSignatures(signedUnbondingSlashingTx).toHex(), 'hex')), + delegatorUnbondingSlashingSig: Uint8Array.from(unbondingSignatures), + }; - // Prepare the final protobuf message - const msg: btcstakingtx.MsgCreateBTCDelegation = - btcstakingtx.MsgCreateBTCDelegation.fromPartial({ - stakerAddr: bech32Address, - pop: proofOfPossession, - btcPk: Uint8Array.from( - Buffer.from(stakerBtcInfo.publicKeyNoCoordHex, "hex"), - ), - fpBtcPkList: stakingInput.finalityProviderPksNoCoordHex.map((pk) => - Uint8Array.from(Buffer.from(pk, "hex")), - ), - stakingTime: stakingInput.stakingTimelock, - stakingValue: stakingInput.stakingAmountSat, - stakingTx: Uint8Array.from(stakingTx.toBuffer()), - slashingTx: Uint8Array.from( - Buffer.from(clearTxSignatures(signedSlashingTx).toHex(), "hex"), - ), - delegatorSlashingSig: Uint8Array.from(slashingSig), - unbondingTime: params.unbondingTime, - unbondingTx: Uint8Array.from(unbondingTx.toBuffer()), - unbondingValue: stakingInput.stakingAmountSat - params.unbondingFeeSat, - unbondingSlashingTx: Uint8Array.from( - Buffer.from( - clearTxSignatures(signedUnbondingSlashingTx).toHex(), - "hex", - ), - ), - delegatorUnbondingSlashingSig: Uint8Array.from(unbondingSignatures), - stakingTxInclusionProof: inclusionProof, + // If the delegation is an expansion, we use the MsgBtcStakeExpand message + if (options?.delegationExpansionInfo) { + const fundingTx = Uint8Array.from(options.delegationExpansionInfo.fundingTx.toBuffer()); + const msg = btcstakingtx.MsgBtcStakeExpand.fromPartial({ + ...commonMsg, + previousStakingTxHash: options.delegationExpansionInfo.previousStakingTx.getId(), + fundingTx, }); + return { + typeUrl: BABYLON_REGISTRY_TYPE_URLS.MsgBtcStakeExpand, + value: msg, + }; + } + + // Otherwise, it's a new staking delegation + const msg: btcstakingtx.MsgCreateBTCDelegation = btcstakingtx.MsgCreateBTCDelegation.fromPartial({ + ...commonMsg, + stakingTxInclusionProof: options?.inclusionProof, + }); return { typeUrl: BABYLON_REGISTRY_TYPE_URLS.MsgCreateBTCDelegation, @@ -1000,101 +1241,33 @@ export class BabylonBtcStakingManager { * @param inclusionProof - The inclusion proof. * @returns The inclusion proof. */ - private getInclusionProof( - inclusionProof: InclusionProof, - ): btcstaking.InclusionProof { + private getInclusionProof(inclusionProof: InclusionProof): btcstaking.InclusionProof { const { pos, merkle, blockHashHex } = inclusionProof; const proofHex = deriveMerkleProof(merkle); - const hash = reverseBuffer( - Uint8Array.from(Buffer.from(blockHashHex, "hex")), - ); - const inclusionProofKey: btccheckpoint.TransactionKey = - btccheckpoint.TransactionKey.fromPartial({ - index: pos, - hash, - }); + const hash = reverseBuffer(Uint8Array.from(Buffer.from(blockHashHex, 'hex'))); + const inclusionProofKey: btccheckpoint.TransactionKey = btccheckpoint.TransactionKey.fromPartial({ + index: pos, + hash, + }); return btcstaking.InclusionProof.fromPartial({ key: inclusionProofKey, - proof: Uint8Array.from(Buffer.from(proofHex, "hex")), + proof: Uint8Array.from(Buffer.from(proofHex, 'hex')), }); } } -/** - * Extracts the first valid Schnorr signature from a signed transaction. - * - * Since we only handle transactions with a single input and request a signature - * for one public key, there can be at most one signature from the Bitcoin node. - * A valid Schnorr signature is exactly 64 bytes in length. - * - * @param singedTransaction - The signed Bitcoin transaction to extract the signature from - * @returns The first valid 64-byte Schnorr signature found in the transaction witness data, - * or undefined if no valid signature exists - */ -const extractFirstSchnorrSignatureFromTransaction = ( - singedTransaction: Transaction, -): Buffer | undefined => { - // Loop through each input to extract the witness signature - for (const input of singedTransaction.ins) { - if (input.witness && input.witness.length > 0) { - const schnorrSignature = input.witness[0]; - - // Check that it's a 64-byte Schnorr signature - if (schnorrSignature.length === 64) { - return schnorrSignature; // Return the first valid signature found - } - } - } - return undefined; -}; - -/** - * Strips all signatures from a transaction by clearing both the script and - * witness data. This is due to the fact that we only need the raw unsigned - * transaction structure. The signatures are sent in a separate protobuf field - * when creating the delegation message in the Babylon. - * @param tx - The transaction to strip signatures from - * @returns A copy of the transaction with all signatures removed - */ -const clearTxSignatures = (tx: Transaction): Transaction => { - tx.ins.forEach((input) => { - input.script = Buffer.alloc(0); - input.witness = []; - }); - return tx; -}; - -/** - * Derives the merkle proof from the list of hex strings. Note the - * sibling hashes are reversed from hex before concatenation. - * @param merkle - The merkle proof hex strings. - * @returns The merkle proof in hex string format. - */ -const deriveMerkleProof = (merkle: string[]) => { - const proofHex = merkle.reduce((acc: string, m: string) => { - return acc + Buffer.from(m, "hex").reverse().toString("hex"); - }, ""); - return proofHex; -}; - /** * Get the staker signature from the unbonding transaction * This is used mostly for unbonding transactions from phase-1(Observable) * @param unbondingTx - The unbonding transaction * @returns The staker signature */ -export const getUnbondingTxStakerSignature = ( - unbondingTx: Transaction, -): string => { +export const getUnbondingTxStakerSignature = (unbondingTx: Transaction): string => { try { // There is only one input and one output in the unbonding transaction - return unbondingTx.ins[0].witness[0].toString("hex"); + return unbondingTx.ins[0].witness[0].toString('hex'); } catch (error) { - throw StakingError.fromUnknown( - error, - StakingErrorCode.INVALID_INPUT, - "Failed to get staker signature", - ); + throw StakingError.fromUnknown(error, StakingErrorCode.INVALID_INPUT, 'Failed to get staker signature'); } }; diff --git a/modules/babylonlabs-io-btc-staking-ts/src/staking/observable/index.ts b/modules/babylonlabs-io-btc-staking-ts/src/staking/observable/index.ts index c4cba94a6e..e84d05e220 100644 --- a/modules/babylonlabs-io-btc-staking-ts/src/staking/observable/index.ts +++ b/modules/babylonlabs-io-btc-staking-ts/src/staking/observable/index.ts @@ -3,7 +3,8 @@ import { UTXO } from "../../types/UTXO"; import { StakingError, StakingErrorCode } from "../../error"; import { stakingTransaction } from "../transactions"; import { isTaproot } from "../../utils/btc"; -import { toBuffers, validateStakingTxInputData } from "../../utils/staking"; +import { toBuffers } from "../../utils/staking"; +import { validateStakingTxInputData } from "../../utils/staking/validation"; import { TransactionResult } from "../../types/transaction"; import { ObservableStakingScriptData, ObservableStakingScripts } from "./observableStakingScript"; import { StakerInfo, Staking } from ".."; diff --git a/modules/babylonlabs-io-btc-staking-ts/src/staking/psbt.ts b/modules/babylonlabs-io-btc-staking-ts/src/staking/psbt.ts index 9e6caf4b05..03d18ee7d1 100644 --- a/modules/babylonlabs-io-btc-staking-ts/src/staking/psbt.ts +++ b/modules/babylonlabs-io-btc-staking-ts/src/staking/psbt.ts @@ -7,6 +7,8 @@ import { UTXO } from "../types/UTXO"; import { deriveUnbondingOutputInfo } from "../utils/staking"; import { findInputUTXO } from "../utils/utxo/findInputUTXO"; import { getPsbtInputFields } from "../utils/utxo/getPsbtInputFields"; +import { BitcoinScriptType, getScriptType } from "../utils/utxo/getScriptType"; +import { StakingScripts } from "./stakingScript"; /** * Convert a staking transaction to a PSBT. @@ -53,6 +55,145 @@ export const stakingPsbt = ( return psbt; }; +/** + * Convert a staking expansion transaction to a PSBT. + * + * @param {networks.Network} network - The Bitcoin network to use for the PSBT + * @param {Transaction} stakingTx - The staking expansion transaction to convert + * @param {Object} previousStakingTxInfo - Information about the previous staking transaction + * @param {Transaction} previousStakingTxInfo.stakingTx - The previous staking transaction + * @param {number} previousStakingTxInfo.outputIndex - The index of the staking output in the previous transaction + * @param {UTXO[]} inputUTXOs - Available UTXOs for the funding input + * @param {Buffer} [publicKeyNoCoord] - The staker's public key without coordinate (for Taproot) + * @returns {Psbt} The PSBT for the staking expansion transaction + * @throws {Error} If validation fails or required data is missing + */ +export const stakingExpansionPsbt = ( + network: networks.Network, + stakingTx: Transaction, + previousStakingTxInfo: { + stakingTx: Transaction, + outputIndex: number, + }, + inputUTXOs: UTXO[], + previousScripts: StakingScripts, + publicKeyNoCoord?: Buffer, +): Psbt => { + // Initialize PSBT with the specified network + const psbt = new Psbt({ network }); + + // Set transaction version and locktime if provided + if (stakingTx.version !== undefined) psbt.setVersion(stakingTx.version); + if (stakingTx.locktime !== undefined) psbt.setLocktime(stakingTx.locktime); + + // Validate the public key format if provided + if ( + publicKeyNoCoord && publicKeyNoCoord.length !== NO_COORD_PK_BYTE_LENGTH + ) { + throw new Error("Invalid public key"); + } + + // Extract the previous staking output from the previous staking transaction + const previousStakingOutput = previousStakingTxInfo.stakingTx.outs[ + previousStakingTxInfo.outputIndex + ]; + if (!previousStakingOutput) { + throw new Error("Previous staking output not found"); + }; + + // Validate that the previous staking output is a Taproot (P2TR) script + if ( + getScriptType(previousStakingOutput.script) !== BitcoinScriptType.P2TR + ) { + throw new Error("Previous staking output script type is not P2TR"); + } + + // Validate that the staking expansion transaction has exactly 2 inputs + // Input 0: Previous staking output (existing stake) + // Input 1: Funding UTXO (additional funds for fees or staking amount) + if (stakingTx.ins.length !== 2) { + throw new Error( + "Staking expansion transaction must have exactly 2 inputs", + ); + } + + // Validate the first input matches the previous staking transaction + const txInputs = stakingTx.ins; + + // Check that the first input references the correct previous staking + // transaction + if ( + Buffer.from(txInputs[0].hash).reverse().toString("hex") !== previousStakingTxInfo.stakingTx.getId() + ) { + throw new Error("Previous staking input hash does not match"); + } + // Check that the first input references the correct output index + else if (txInputs[0].index !== previousStakingTxInfo.outputIndex) { + throw new Error("Previous staking input index does not match"); + } + + // Build input tapleaf script that spends the previous staking output + const inputScriptTree: Taptree = [ + { output: previousScripts.slashingScript }, + [{ output: previousScripts.unbondingScript }, { output: previousScripts.timelockScript }], + ]; + const inputRedeem = { + output: previousScripts.unbondingScript, + redeemVersion: REDEEM_VERSION, + }; + const p2tr = payments.p2tr({ + internalPubkey, + scriptTree: inputScriptTree, + redeem: inputRedeem, + network, + }); + + if (!p2tr.witness || p2tr.witness.length === 0) { + throw new Error( + "Failed to create P2TR witness for expansion transaction input" + ); + } + + const inputTapLeafScript = { + leafVersion: inputRedeem.redeemVersion, + script: inputRedeem.output, + controlBlock: p2tr.witness[p2tr.witness.length - 1], + }; + + // Add the previous staking input to the PSBT + // This input spends the existing staking output + psbt.addInput({ + hash: txInputs[0].hash, + index: txInputs[0].index, + sequence: txInputs[0].sequence, + witnessUtxo: { + script: previousStakingOutput.script, + value: previousStakingOutput.value, + }, + tapInternalKey: internalPubkey, + tapLeafScript: [inputTapLeafScript], + }); + + // Add the second input (funding UTXO) to the PSBT + // This input provides additional funds for fees or staking amount + const inputUTXO = findInputUTXO(inputUTXOs, txInputs[1]); + const psbtInputData = getPsbtInputFields(inputUTXO, publicKeyNoCoord); + + psbt.addInput({ + hash: txInputs[1].hash, + index: txInputs[1].index, + sequence: txInputs[1].sequence, + ...psbtInputData, + }); + + // Add all outputs from the staking expansion transaction to the PSBT + stakingTx.outs.forEach((o) => { + psbt.addOutput({ script: o.script, value: o.value }); + }); + + return psbt; +}; + export const unbondingPsbt = ( scripts: { unbondingScript: Buffer; diff --git a/modules/babylonlabs-io-btc-staking-ts/src/staking/transactions.ts b/modules/babylonlabs-io-btc-staking-ts/src/staking/transactions.ts index 2b70a95418..4031bcac1d 100644 --- a/modules/babylonlabs-io-btc-staking-ts/src/staking/transactions.ts +++ b/modules/babylonlabs-io-btc-staking-ts/src/staking/transactions.ts @@ -1,4 +1,6 @@ -import { Psbt, Transaction, networks, payments, script, address, opcodes } from "bitcoinjs-lib"; +import { + Psbt, Transaction, networks, payments, script, address, opcodes +} from "bitcoinjs-lib"; import { Taptree } from "bitcoinjs-lib/src/types"; import { BTC_DUST_SAT } from "../constants/dustSat"; @@ -6,9 +8,18 @@ import { internalPubkey } from "../constants/internalPubkey"; import { UTXO } from "../types/UTXO"; import { PsbtResult, TransactionResult } from "../types/transaction"; import { isValidBitcoinAddress, transactionIdToHash } from "../utils/btc"; -import { getStakingTxInputUTXOsAndFees, getWithdrawTxFee } from "../utils/fee"; +import { + getStakingExpansionTxFundingUTXOAndFees, + getStakingTxInputUTXOsAndFees, + getWithdrawTxFee, +} from "../utils/fee"; import { inputValueSum } from "../utils/fee/utils"; -import { buildStakingTransactionOutputs, deriveUnbondingOutputInfo } from "../utils/staking"; +import { + buildStakingTransactionOutputs, + deriveStakingOutputInfo, + deriveUnbondingOutputInfo, + findMatchingTxOutputIndex, +} from "../utils/staking"; import { NON_RBF_SEQUENCE, TRANSACTION_VERSION } from "../constants/psbt"; import { CovenantSignature } from "../types/covenantSignatures"; import { REDEEM_VERSION } from "../constants/transaction"; @@ -125,6 +136,144 @@ export function stakingTransaction( }; } +/** + * Expand an existing staking transaction with additional finality providers + * or renew timelock. + * + * This function builds a Bitcoin transaction that: + * 1. Spends the previous staking transaction output as the first input + * 2. Uses a funding UTXO as the second input to cover transaction fees + * 3. Creates new staking outputs where the timelock is renewed or FPs added + * 4. Returns any remaining funds as change + * + * @param network - Bitcoin network (mainnet, testnet, etc.) + * @param scripts - Scripts for the new staking outputs + * @param amount - Total staking amount (must equal previous staking amount, + * we only support equal amounts for now) + * @param changeAddress - Bitcoin address to receive change from funding UTXO + * @param feeRate - Fee rate in satoshis per byte + * @param inputUTXOs - Available UTXOs to use for funding the expansion + * @param previousStakingTxInfo - Details of the previous staking transaction + * being expanded + * @returns {TransactionResult & { fundingUTXO: UTXO }} containing the built + * transaction and calculated fee, and the funding UTXO + */ +export function stakingExpansionTransaction( + network: networks.Network, + scripts: { + timelockScript: Buffer; + unbondingScript: Buffer; + slashingScript: Buffer; + }, + amount: number, + changeAddress: string, + feeRate: number, + inputUTXOs: UTXO[], + previousStakingTxInfo: { + stakingTx: Transaction, + scripts: { + timelockScript: Buffer; + unbondingScript: Buffer; + slashingScript: Buffer; + }, + }, +): TransactionResult & { + fundingUTXO: UTXO; +} { + // Validate input parameters + if (amount <= 0 || feeRate <= 0) { + throw new Error("Amount and fee rate must be bigger than 0"); + } else if (!isValidBitcoinAddress(changeAddress, network)) { + throw new Error("Invalid BTC change address"); + } + + // Derive the output address and amount from the previous staking transaction + // scripts. This helps us locate the specific output in the previous + // transaction + const previousStakingOutputInfo = deriveStakingOutputInfo( + previousStakingTxInfo.scripts, network + ); + + // Find the output index of the previous staking transaction in the + // transaction outputs. This method will throw an error if the output + // is not found. + const previousStakingOutputIndex = findMatchingTxOutputIndex( + previousStakingTxInfo.stakingTx, + previousStakingOutputInfo.outputAddress, + network + ); + + // Extract the actual staking amount from the previous transaction output + const previousStakingAmount = previousStakingTxInfo.stakingTx.outs[ + previousStakingOutputIndex + ].value; + + // Validate that the expansion amount matches the previous staking amount + // According to Babylon protocol, expansion amount must be >= previous amount + // Currently, this library only supports equal amounts (no stake increase) + if (amount !== previousStakingAmount) { + throw new Error( + "Expansion staking transaction amount must be equal to the previous " + + "staking amount. Increase of the staking amount is not supported yet.", + ); + } + + // Build the staking outputs for the expansion transaction + // These outputs will contain the new scripts with expanded timelock or FPs + const stakingOutputs = buildStakingTransactionOutputs( + scripts, network, amount, + ); + + // Select a single funding UTXO and calculate the required fee + // The funding UTXO will be used as the second input to cover transaction fees + const { selectedUTXO, fee } = getStakingExpansionTxFundingUTXOAndFees( + inputUTXOs, + feeRate, + stakingOutputs, + ); + + // Initialize the transaction with the standard version + const tx = new Transaction(); + tx.version = TRANSACTION_VERSION; + + // Add the first input: previous staking transaction output + // This is the existing stake that we're expanding + tx.addInput( + previousStakingTxInfo.stakingTx.getHash(), + previousStakingOutputIndex, + NON_RBF_SEQUENCE, + ); + + // Add the second input: selected funding UTXO + // This provides the funds to cover transaction fees + tx.addInput( + transactionIdToHash(selectedUTXO.txid), + selectedUTXO.vout, + NON_RBF_SEQUENCE, + ); + + // Add all staking outputs to the transaction + // These represent the expanded stake with new finality provider coverage + stakingOutputs.forEach((o) => { + tx.addOutput(o.scriptPubKey, o.value); + }); + + // Add a change output if there are remaining funds from the funding UTXO + // Only create change if the remaining amount is above the dust threshold + if (selectedUTXO.value - fee > BTC_DUST_SAT) { + tx.addOutput( + address.toOutputScript(changeAddress, network), + selectedUTXO.value - fee, + ); + } + + return { + transaction: tx, + fee, + fundingUTXO: selectedUTXO, + }; +} + /** * Constructs a withdrawal transaction for manually unbonded delegation. * @@ -703,15 +852,19 @@ export const createCovenantWitness = ( + `got: ${covenantSigs.length}` ); } - // Verify all btcPkHex from covenantSigs exist in paramsCovenants - for (const sig of covenantSigs) { + // Filter out the signatures that are not in the params covenants + const filteredCovenantSigs = covenantSigs.filter((sig) => { const btcPkHexBuf = Buffer.from(sig.btcPkHex, "hex"); - if (!paramsCovenants.some(covenant => covenant.equals(btcPkHexBuf))) { - throw new Error( - `Covenant signature public key ${sig.btcPkHex} not found in params covenants` - ); - } + return paramsCovenants.some(covenant => covenant.equals(btcPkHexBuf)); + }); + + if (filteredCovenantSigs.length < covenantQuorum) { + throw new Error( + `Not enough valid covenant signatures. Required: ${covenantQuorum}, ` + + `got: ${filteredCovenantSigs.length}` + ); } + // We only take exactly covenantQuorum number of signatures, even if more are provided. // Including extra signatures will cause the unbonding transaction to fail validation. // This is because the witness script expects exactly covenantQuorum number of signatures diff --git a/modules/babylonlabs-io-btc-staking-ts/src/types/events.ts b/modules/babylonlabs-io-btc-staking-ts/src/types/events.ts index af3ddf25b5..81c1a643af 100644 --- a/modules/babylonlabs-io-btc-staking-ts/src/types/events.ts +++ b/modules/babylonlabs-io-btc-staking-ts/src/types/events.ts @@ -15,6 +15,7 @@ export interface ManagerEvents { "delegation:stake": (data?: EventData) => void; "delegation:unbond": (data?: EventData) => void; "delegation:withdraw": (data?: EventData) => void; + "delegation:expand": (data?: EventData) => void; } export type DelegationEvent = keyof ManagerEvents; diff --git a/modules/babylonlabs-io-btc-staking-ts/src/types/manager.ts b/modules/babylonlabs-io-btc-staking-ts/src/types/manager.ts index cf83ccbc03..bf3aeae7a5 100644 --- a/modules/babylonlabs-io-btc-staking-ts/src/types/manager.ts +++ b/modules/babylonlabs-io-btc-staking-ts/src/types/manager.ts @@ -22,6 +22,9 @@ export interface BtcProvider { message: string, type: "ecdsa" | "bip322-simple", ) => Promise; + + // Get the transaction hex from the transaction ID + getTransactionHex(txid: string): Promise; } export interface BabylonProvider { @@ -39,6 +42,20 @@ export interface BabylonProvider { typeUrl: string; value: T; }) => Promise; + + /** + * Gets the current height of the Babylon Genesis chain. + * + * @returns {Promise} The current Babylon chain height + */ + getCurrentHeight?: () => Promise; + + /** + * Gets the chain ID of the Babylon Genesis chain. + * + * @returns {Promise} The Babylon chain ID + */ + getChainId?: () => Promise; } export interface StakingInputs { @@ -61,3 +78,31 @@ export interface InclusionProof { // The block hash of the block that contains the transaction blockHashHex: string; } + +/** + * Upgrade configuration for Babylon POP (Proof of Possession) context. + * This is used to determine when to switch to the new POP context format + * based on the Babylon chain height and version. + */ +export interface UpgradeConfig { + /** + * POP context upgrade configuration. + */ + pop?: PopUpgradeConfig; +} + +/** + * Configuration for POP context upgrade. + * - upgradeHeight: The Babylon chain height at which the POP context upgrade is activated. + * - version: The version of the POP context to use after the upgrade. + */ +export interface PopUpgradeConfig { + /** + * The Babylon chain height at which the POP context upgrade is activated. + */ + upgradeHeight: number; + /** + * The version of the POP context to use after the upgrade. + */ + version: number; +} \ No newline at end of file diff --git a/modules/babylonlabs-io-btc-staking-ts/src/utils/fee/index.ts b/modules/babylonlabs-io-btc-staking-ts/src/utils/fee/index.ts index 4f53a73785..30a6c160f8 100644 --- a/modules/babylonlabs-io-btc-staking-ts/src/utils/fee/index.ts +++ b/modules/babylonlabs-io-btc-staking-ts/src/utils/fee/index.ts @@ -6,6 +6,7 @@ import { OP_RETURN_OUTPUT_VALUE_SIZE, OP_RETURN_VALUE_SERIALIZE_SIZE, P2TR_INPUT_SIZE, + P2TR_STAKING_EXPANSION_INPUT_SIZE, TX_BUFFER_SIZE_OVERHEAD, WALLET_RELAY_FEE_RATE_THRESHOLD, WITHDRAW_TX_BUFFER_SIZE, @@ -94,6 +95,99 @@ export const getStakingTxInputUTXOsAndFees = ( }; }; +/** + * Calculates the required funding UTXO and fees for a staking expansion transaction. + * + * This function selects a single UTXO from available UTXOs to cover: + * 1. Transaction fees for the expansion + * 2. Any additional staking amount beyond the previous stake + * + * @param availableUTXOs - List of available UTXOs to choose from for funding + * @param previousStakingTx - Details of the previous staking transaction being expanded + * @param stakingAmount - Total staking amount for the expansion (includes previous + additional) + * @param feeRate - Fee rate in satoshis per byte + * @param outputs - Transaction outputs for the expansion + * @returns Object containing the selected funding UTXO and calculated fee + */ +export const getStakingExpansionTxFundingUTXOAndFees = ( + availableUTXOs: UTXO[], + feeRate: number, + outputs: TransactionOutput[], +): { + selectedUTXO: UTXO; + fee: number; +} => { + // Validate that we have UTXOs to work with + if (availableUTXOs.length === 0) { + throw new Error("Insufficient funds"); + } + + // Filter out invalid UTXOs by checking if their script can be decompiled + // This ensures we only work with properly formatted Bitcoin scripts + const validUTXOs = availableUTXOs.filter((utxo) => { + const script = Buffer.from(utxo.scriptPubKey, "hex"); + const decompiledScript = bitcoinScript.decompile(script); + return decompiledScript && decompiledScript.length > 0; + }); + + if (validUTXOs.length === 0) { + throw new Error("Insufficient funds: no valid UTXOs available for staking"); + } + + // Sort available UTXOs from lowest to highest value for optimal selection + // This helps us avoid selecting large UTXOs which can be used + // for other activities. + const sortedUTXOs = validUTXOs.sort((a, b) => a.value - b.value); + + // Iterate through UTXOs to find one that can cover the required fees + for (const utxo of sortedUTXOs) { + // Calculate the estimated transaction size including: + // - Base transaction size (additional UTXOs + Outputs) + // - Previous staking transaction output as the input for the expansion tx + // Note: Staking transactions use P2TR (Taproot) format, + // hence P2TR_STAKING_EXPANSION_INPUT_SIZE accounts for the witness size + // including covenant signatures and is calibrated for a typical covenant + // quorum of 6 signatures. + const estimatedSize = getEstimatedSize( + [utxo], + outputs, + ) + P2TR_STAKING_EXPANSION_INPUT_SIZE; + + // Calculate base fee: size * rate + buffer fee for network congestion + let estimatedFee = estimatedSize * feeRate + rateBasedTxBufferFee(feeRate); + + // Check if this UTXO has enough value to cover the estimated fee + // We are selecting a UTXO that can only cover the fee as + // in the case of stake expansion we only want the additional UTXO to cover + // the staking fee. + // TODO: In the future, we will want to support selecting a UTXO for an increased + // staking amount. + if (utxo.value >= estimatedFee) { + // Check if there will be change left after paying the fee + // If change amount is above dust threshold, we need to add a change output + // which increases the transaction size and fee + if (utxo.value - estimatedFee > BTC_DUST_SAT) { + // Add fee for the change output + estimatedFee += getEstimatedChangeOutputSize() * feeRate; + } + // Finally, ensure the estimated fee is not greater than the UTXO value + if (utxo.value >= estimatedFee) { + return { + selectedUTXO: utxo, + fee: estimatedFee, + }; + } + // If the UTXO value is less than the estimated fee, we need to continue + // searching for a UTXO that can cover the fees. + } + } + + // If no UTXO can cover the fees, throw an error + throw new Error( + "Insufficient funds: unable to find a UTXO to cover the fees for the staking expansion transaction.", + ); +}; + /** * Calculates the estimated fee for a withdrawal transaction. diff --git a/modules/babylonlabs-io-btc-staking-ts/src/utils/pop.ts b/modules/babylonlabs-io-btc-staking-ts/src/utils/pop.ts new file mode 100644 index 0000000000..0bfc789eb0 --- /dev/null +++ b/modules/babylonlabs-io-btc-staking-ts/src/utils/pop.ts @@ -0,0 +1,59 @@ +import { sha256 } from "bitcoinjs-lib/src/crypto"; + +import { STAKING_MODULE_ADDRESS } from "../constants/staking"; + +/** + * Creates the context string for the staker POP following RFC-036. + * See: https://github.com/babylonlabs-io/pm/blob/main/rfc/rfc-036-replay-attack-protection.md + * @param chainId - The Babylon chain ID + * @param popContextVersion - The POP context version (defaults to 0) + * @returns The hex encoded SHA-256 hash of the context string. + */ +export function createStakerPopContext( + chainId: string, + popContextVersion: number = 0, +): string { + // Context string format following RFC-036: + // Format: btcstaking/{version}/{operation_type}/{chain_id}/{module_address} + // + // Fields: + // - btcstaking: Protocol identifier for Bitcoin staking operations + // - version: POP context version (integer, defaults to 0) + // - operation_type: Type of operation ("staker_pop" for staker proof of possession) + // - chain_id: The Babylon chain ID for domain separation + // - module_address: The staking module address for additional context + const contextString = `btcstaking/${popContextVersion}/staker_pop/${chainId}/${STAKING_MODULE_ADDRESS}`; + return sha256(Buffer.from(contextString, "utf8")).toString("hex"); +} + +/** + * Creates the POP message to sign based on upgrade configuration and current height. + * RFC-036: If the Babylon tip height is greater than or equal to the POP context + * upgrade height, use the new context format, otherwise use legacy format. + * @param currentHeight - The current Babylon tip height + * @param bech32Address - The staker's bech32 address + * @param chainId - The Babylon chain ID + * @param upgradeConfig - Optional upgrade configuration with height and version + * @returns The message to sign (either just the address or context hash + address) + */ +export function buildPopMessage( + bech32Address: string, + currentHeight?: number, + chainId?: string, + upgradeConfig?: { upgradeHeight: number; version: number }, +): string { + // RFC-036: If upgrade is configured and current height >= upgrade height, use new context format + // https://github.com/babylonlabs-io/pm/blob/main/rfc/rfc-036-replay-attack-protection.md + if ( + chainId !== undefined && + upgradeConfig?.upgradeHeight !== undefined && + upgradeConfig.version !== undefined && + currentHeight !== undefined && + currentHeight >= upgradeConfig.upgradeHeight + ) { + const contextHash = createStakerPopContext(chainId, upgradeConfig.version); + return contextHash + bech32Address; + } + + return bech32Address; +} \ No newline at end of file diff --git a/modules/babylonlabs-io-btc-staking-ts/src/utils/staking/index.ts b/modules/babylonlabs-io-btc-staking-ts/src/utils/staking/index.ts index d4e0034110..e86d3740d2 100644 --- a/modules/babylonlabs-io-btc-staking-ts/src/utils/staking/index.ts +++ b/modules/babylonlabs-io-btc-staking-ts/src/utils/staking/index.ts @@ -1,13 +1,8 @@ import { address, networks, payments, Transaction } from "bitcoinjs-lib"; import { Taptree } from "bitcoinjs-lib/src/types"; import { internalPubkey } from "../../constants/internalPubkey"; -import { MIN_UNBONDING_OUTPUT_VALUE } from "../../constants/unbonding"; import { StakingError, StakingErrorCode } from "../../error"; -import { StakingParams } from "../../types/params"; import { TransactionOutput } from "../../types/psbtOutputs"; -import { UTXO } from "../../types/UTXO"; -import { isValidNoCoordPublicKey } from "../btc"; - export interface OutputInfo { scriptPubKey: Buffer; outputAddress: string; @@ -204,190 +199,78 @@ export const findMatchingTxOutputIndex = ( }; /** - * Validate the staking transaction input data. + * toBuffers converts an array of strings to an array of buffers. * - * @param {number} stakingAmountSat - The staking amount in satoshis. - * @param {number} timelock - The staking time in blocks. - * @param {StakingParams} params - The staking parameters. - * @param {UTXO[]} inputUTXOs - The input UTXOs. - * @param {number} feeRate - The Bitcoin fee rate in sat/vbyte - * @throws {StakingError} - If the input data is invalid. + * @param {string[]} inputs - The input strings. + * @returns {Buffer[]} - The buffers. + * @throws {StakingError} - If the values cannot be converted to buffers. */ -export const validateStakingTxInputData = ( - stakingAmountSat: number, - timelock: number, - params: StakingParams, - inputUTXOs: UTXO[], - feeRate: number, -) => { - if ( - stakingAmountSat < params.minStakingAmountSat || - stakingAmountSat > params.maxStakingAmountSat - ) { - throw new StakingError( - StakingErrorCode.INVALID_INPUT, - "Invalid staking amount", - ); - } - - if ( - timelock < params.minStakingTimeBlocks || - timelock > params.maxStakingTimeBlocks - ) { - throw new StakingError(StakingErrorCode.INVALID_INPUT, "Invalid timelock"); - } - - if (inputUTXOs.length == 0) { - throw new StakingError( +export const toBuffers = (inputs: string[]): Buffer[] => { + try { + return inputs.map((i) => Buffer.from(i, "hex")); + } catch (error) { + throw StakingError.fromUnknown( + error, StakingErrorCode.INVALID_INPUT, - "No input UTXOs provided", + "Cannot convert values to buffers", ); } - if (feeRate <= 0) { - throw new StakingError(StakingErrorCode.INVALID_INPUT, "Invalid fee rate"); - } }; + /** - * Validate the staking parameters. - * Extend this method to add additional validation for staking parameters based - * on the staking type. - * @param {StakingParams} params - The staking parameters. - * @throws {StakingError} - If the parameters are invalid. + * Strips all signatures from a transaction by clearing both the script and + * witness data. This is due to the fact that we only need the raw unsigned + * transaction structure. The signatures are sent in a separate protobuf field + * when creating the delegation message in the Babylon. + * @param tx - The transaction to strip signatures from + * @returns A copy of the transaction with all signatures removed */ -export const validateParams = (params: StakingParams) => { - // Check covenant public keys - if (params.covenantNoCoordPks.length == 0) { - throw new StakingError( - StakingErrorCode.INVALID_PARAMS, - "Could not find any covenant public keys", - ); - } - if (params.covenantNoCoordPks.length < params.covenantQuorum) { - throw new StakingError( - StakingErrorCode.INVALID_PARAMS, - "Covenant public keys must be greater than or equal to the quorum", - ); - } - params.covenantNoCoordPks.forEach((pk) => { - if (!isValidNoCoordPublicKey(pk)) { - throw new StakingError( - StakingErrorCode.INVALID_PARAMS, - "Covenant public key should contains no coordinate", - ); - } +export const clearTxSignatures = (tx: Transaction): Transaction => { + tx.ins.forEach((input) => { + input.script = Buffer.alloc(0); + input.witness = []; }); - // Check other parameters - if (params.unbondingTime <= 0) { - throw new StakingError( - StakingErrorCode.INVALID_PARAMS, - "Unbonding time must be greater than 0", - ); - } - if (params.unbondingFeeSat <= 0) { - throw new StakingError( - StakingErrorCode.INVALID_PARAMS, - "Unbonding fee must be greater than 0", - ); - } - if (params.maxStakingAmountSat < params.minStakingAmountSat) { - throw new StakingError( - StakingErrorCode.INVALID_PARAMS, - "Max staking amount must be greater or equal to min staking amount", - ); - } - if ( - params.minStakingAmountSat < - params.unbondingFeeSat + MIN_UNBONDING_OUTPUT_VALUE - ) { - throw new StakingError( - StakingErrorCode.INVALID_PARAMS, - `Min staking amount must be greater than unbonding fee plus ${MIN_UNBONDING_OUTPUT_VALUE}`, - ); - } - if (params.maxStakingTimeBlocks < params.minStakingTimeBlocks) { - throw new StakingError( - StakingErrorCode.INVALID_PARAMS, - "Max staking time must be greater or equal to min staking time", - ); - } - if (params.minStakingTimeBlocks <= 0) { - throw new StakingError( - StakingErrorCode.INVALID_PARAMS, - "Min staking time must be greater than 0", - ); - } - if (params.covenantQuorum <= 0) { - throw new StakingError( - StakingErrorCode.INVALID_PARAMS, - "Covenant quorum must be greater than 0", - ); - } - if (params.slashing) { - if (params.slashing.slashingRate <= 0) { - throw new StakingError( - StakingErrorCode.INVALID_PARAMS, - "Slashing rate must be greater than 0", - ); - } - if (params.slashing.slashingRate > 1) { - throw new StakingError( - StakingErrorCode.INVALID_PARAMS, - "Slashing rate must be less or equal to 1", - ); - } - if (params.slashing.slashingPkScriptHex.length == 0) { - throw new StakingError( - StakingErrorCode.INVALID_PARAMS, - "Slashing public key script is missing", - ); - } - if (params.slashing.minSlashingTxFeeSat <= 0) { - throw new StakingError( - StakingErrorCode.INVALID_PARAMS, - "Minimum slashing transaction fee must be greater than 0", - ); - } - } + return tx; }; /** - * Validate the staking timelock. - * - * @param {number} stakingTimelock - The staking timelock. - * @param {StakingParams} params - The staking parameters. - * @throws {StakingError} - If the staking timelock is invalid. + * Derives the merkle proof from the list of hex strings. Note the + * sibling hashes are reversed from hex before concatenation. + * @param merkle - The merkle proof hex strings. + * @returns The merkle proof in hex string format. */ -export const validateStakingTimelock = ( - stakingTimelock: number, - params: StakingParams, -) => { - if ( - stakingTimelock < params.minStakingTimeBlocks || - stakingTimelock > params.maxStakingTimeBlocks - ) { - throw new StakingError( - StakingErrorCode.INVALID_INPUT, - "Staking transaction timelock is out of range", - ); - } +export const deriveMerkleProof = (merkle: string[]) => { + const proofHex = merkle.reduce((acc: string, m: string) => { + return acc + Buffer.from(m, "hex").reverse().toString("hex"); + }, ""); + return proofHex; }; /** - * toBuffers converts an array of strings to an array of buffers. + * Extracts the first valid Schnorr signature from a signed transaction. * - * @param {string[]} inputs - The input strings. - * @returns {Buffer[]} - The buffers. - * @throws {StakingError} - If the values cannot be converted to buffers. + * Since we only handle transactions with a single input and request a signature + * for one public key, there can be at most one signature from the Bitcoin node. + * A valid Schnorr signature is exactly 64 bytes in length. + * + * @param singedTransaction - The signed Bitcoin transaction to extract the signature from + * @returns The first valid 64-byte Schnorr signature found in the transaction witness data, + * or undefined if no valid signature exists */ -export const toBuffers = (inputs: string[]): Buffer[] => { - try { - return inputs.map((i) => Buffer.from(i, "hex")); - } catch (error) { - throw StakingError.fromUnknown( - error, - StakingErrorCode.INVALID_INPUT, - "Cannot convert values to buffers", - ); +export const extractFirstSchnorrSignatureFromTransaction = ( + singedTransaction: Transaction, +): Buffer | undefined => { + // Loop through each input to extract the witness signature + for (const input of singedTransaction.ins) { + if (input.witness && input.witness.length > 0) { + const schnorrSignature = input.witness[0]; + + // Check that it's a 64-byte Schnorr signature + if (schnorrSignature.length === 64) { + return schnorrSignature; // Return the first valid signature found + } + } } + return undefined; }; diff --git a/modules/babylonlabs-io-btc-staking-ts/src/utils/staking/validation.ts b/modules/babylonlabs-io-btc-staking-ts/src/utils/staking/validation.ts new file mode 100644 index 0000000000..73d0dcc061 --- /dev/null +++ b/modules/babylonlabs-io-btc-staking-ts/src/utils/staking/validation.ts @@ -0,0 +1,280 @@ +import { MIN_UNBONDING_OUTPUT_VALUE } from "../../constants/unbonding"; +import { StakingError, StakingErrorCode } from "../../error"; +import { StakingInputs, StakingParams, UTXO } from "../../types"; +import { isValidBabylonAddress } from "../babylon"; +import { isValidNoCoordPublicKey } from "../btc"; + +/** + * Validates the staking expansion input + * @param babylonBtcTipHeight - The Babylon BTC tip height + * @param inputUTXOs - The input UTXOs + * @param stakingInput - The staking input + * @param previousStakingInput - The previous staking input + * @param babylonAddress - The Babylon address + * @returns true if validation passes, throws error if validation fails + */ +export const validateStakingExpansionInputs = ( + { + babylonBtcTipHeight, + inputUTXOs, + stakingInput, + previousStakingInput, + babylonAddress, + }: { + babylonBtcTipHeight?: number, + inputUTXOs: UTXO[], + stakingInput: StakingInputs, + previousStakingInput: StakingInputs, + babylonAddress?: string, + } +) => { + if (babylonBtcTipHeight === 0) { + throw new StakingError( + StakingErrorCode.INVALID_INPUT, + "Babylon BTC tip height cannot be 0", + ); + } + if (!inputUTXOs || inputUTXOs.length === 0) { + throw new StakingError( + StakingErrorCode.INVALID_INPUT, + "No input UTXOs provided", + ); + } + if (babylonAddress && !isValidBabylonAddress(babylonAddress)) { + throw new StakingError( + StakingErrorCode.INVALID_INPUT, + "Invalid Babylon address", + ); + } + + // TODO: We currently don't support increasing the staking amount + if (stakingInput.stakingAmountSat !== previousStakingInput.stakingAmountSat) { + throw new StakingError( + StakingErrorCode.INVALID_INPUT, + "Staking expansion amount must equal the previous staking amount", + ); + } + // Check the previous staking transaction's finality providers + // are a subset of the new staking input's finality providers + const currentFPs = stakingInput.finalityProviderPksNoCoordHex; + const previousFPs = previousStakingInput.finalityProviderPksNoCoordHex; + + // Check if all previous finality providers are included in the current + // staking + const missingPreviousFPs = previousFPs.filter(prevFp => !currentFPs.includes(prevFp)); + + if (missingPreviousFPs.length > 0) { + throw new StakingError( + StakingErrorCode.INVALID_INPUT, + `Invalid staking expansion: all finality providers from the previous + staking must be included. Missing: ${missingPreviousFPs.join(", ")}`, + ); + } +} + +/** + * Validate the staking transaction input data. + * + * @param {number} stakingAmountSat - The staking amount in satoshis. + * @param {number} timelock - The staking time in blocks. + * @param {StakingParams} params - The staking parameters. + * @param {UTXO[]} inputUTXOs - The input UTXOs. + * @param {number} feeRate - The Bitcoin fee rate in sat/vbyte + * @throws {StakingError} - If the input data is invalid. + */ +export const validateStakingTxInputData = ( + stakingAmountSat: number, + timelock: number, + params: StakingParams, + inputUTXOs: UTXO[], + feeRate: number, +) => { + if ( + stakingAmountSat < params.minStakingAmountSat || + stakingAmountSat > params.maxStakingAmountSat + ) { + throw new StakingError( + StakingErrorCode.INVALID_INPUT, + "Invalid staking amount", + ); + } + + if ( + timelock < params.minStakingTimeBlocks || + timelock > params.maxStakingTimeBlocks + ) { + throw new StakingError(StakingErrorCode.INVALID_INPUT, "Invalid timelock"); + } + + if (inputUTXOs.length == 0) { + throw new StakingError( + StakingErrorCode.INVALID_INPUT, + "No input UTXOs provided", + ); + } + if (feeRate <= 0) { + throw new StakingError(StakingErrorCode.INVALID_INPUT, "Invalid fee rate"); + } +}; + +/** + * Validate the staking parameters. + * Extend this method to add additional validation for staking parameters based + * on the staking type. + * @param {StakingParams} params - The staking parameters. + * @throws {StakingError} - If the parameters are invalid. + */ +export const validateParams = (params: StakingParams) => { + // Check covenant public keys + if (params.covenantNoCoordPks.length == 0) { + throw new StakingError( + StakingErrorCode.INVALID_PARAMS, + "Could not find any covenant public keys", + ); + } + if (params.covenantNoCoordPks.length < params.covenantQuorum) { + throw new StakingError( + StakingErrorCode.INVALID_PARAMS, + "Covenant public keys must be greater than or equal to the quorum", + ); + } + params.covenantNoCoordPks.forEach((pk) => { + if (!isValidNoCoordPublicKey(pk)) { + throw new StakingError( + StakingErrorCode.INVALID_PARAMS, + "Covenant public key should contains no coordinate", + ); + } + }); + // Check other parameters + if (params.unbondingTime <= 0) { + throw new StakingError( + StakingErrorCode.INVALID_PARAMS, + "Unbonding time must be greater than 0", + ); + } + if (params.unbondingFeeSat <= 0) { + throw new StakingError( + StakingErrorCode.INVALID_PARAMS, + "Unbonding fee must be greater than 0", + ); + } + if (params.maxStakingAmountSat < params.minStakingAmountSat) { + throw new StakingError( + StakingErrorCode.INVALID_PARAMS, + "Max staking amount must be greater or equal to min staking amount", + ); + } + if ( + params.minStakingAmountSat < + params.unbondingFeeSat + MIN_UNBONDING_OUTPUT_VALUE + ) { + throw new StakingError( + StakingErrorCode.INVALID_PARAMS, + `Min staking amount must be greater than unbonding fee plus ${MIN_UNBONDING_OUTPUT_VALUE}`, + ); + } + if (params.maxStakingTimeBlocks < params.minStakingTimeBlocks) { + throw new StakingError( + StakingErrorCode.INVALID_PARAMS, + "Max staking time must be greater or equal to min staking time", + ); + } + if (params.minStakingTimeBlocks <= 0) { + throw new StakingError( + StakingErrorCode.INVALID_PARAMS, + "Min staking time must be greater than 0", + ); + } + if (params.covenantQuorum <= 0) { + throw new StakingError( + StakingErrorCode.INVALID_PARAMS, + "Covenant quorum must be greater than 0", + ); + } + if (params.slashing) { + if (params.slashing.slashingRate <= 0) { + throw new StakingError( + StakingErrorCode.INVALID_PARAMS, + "Slashing rate must be greater than 0", + ); + } + if (params.slashing.slashingRate > 1) { + throw new StakingError( + StakingErrorCode.INVALID_PARAMS, + "Slashing rate must be less or equal to 1", + ); + } + if (params.slashing.slashingPkScriptHex.length == 0) { + throw new StakingError( + StakingErrorCode.INVALID_PARAMS, + "Slashing public key script is missing", + ); + } + if (params.slashing.minSlashingTxFeeSat <= 0) { + throw new StakingError( + StakingErrorCode.INVALID_PARAMS, + "Minimum slashing transaction fee must be greater than 0", + ); + } + } +}; + +/** + * Validate the staking timelock. + * + * @param {number} stakingTimelock - The staking timelock. + * @param {StakingParams} params - The staking parameters. + * @throws {StakingError} - If the staking timelock is invalid. + */ +export const validateStakingTimelock = ( + stakingTimelock: number, + params: StakingParams, +) => { + if ( + stakingTimelock < params.minStakingTimeBlocks || + stakingTimelock > params.maxStakingTimeBlocks + ) { + throw new StakingError( + StakingErrorCode.INVALID_INPUT, + "Staking transaction timelock is out of range", + ); + } +}; + +/** + * Validate the staking expansion covenant quorum. + * + * The quorum is the number of covenant members that must be active in the + * previous staking transaction in order to expand the staking. + * + * If the quorum is not met, the staking expansion will fail. + * + * @param {StakingParams} paramsForPreviousStakingTx - The staking parameters + * for the previous staking transaction. + * @param {StakingParams} paramsForCurrentStakingTx - The staking parameters + * for the current staking transaction. + * @throws {StakingError} - If the staking expansion covenant quorum is invalid. + */ +export const validateStakingExpansionCovenantQuorum = ( + paramsForPreviousStakingTx: StakingParams, + paramsForCurrentStakingTx: StakingParams, +) => { + const previousCovenantMembers = paramsForPreviousStakingTx.covenantNoCoordPks; + const currentCovenantMembers = paramsForCurrentStakingTx.covenantNoCoordPks; + const requiredQuorum = paramsForPreviousStakingTx.covenantQuorum; + + // Count how many previous covenant members are still active + const activePreviousMembers = previousCovenantMembers.filter( + prevMember => currentCovenantMembers.includes(prevMember) + ).length; + + if (activePreviousMembers < requiredQuorum) { + throw new StakingError( + StakingErrorCode.INVALID_INPUT, + `Staking expansion failed: insufficient covenant quorum. ` + + `Required: ${requiredQuorum}, Available: ${activePreviousMembers}. ` + + `Too many covenant members have rotated out.` + ); + } +} \ No newline at end of file diff --git a/modules/express/test/unit/typedRoutes/fanoutUnspents.ts b/modules/express/test/unit/typedRoutes/fanoutUnspents.ts index 4be3452b5b..77b85a1520 100644 --- a/modules/express/test/unit/typedRoutes/fanoutUnspents.ts +++ b/modules/express/test/unit/typedRoutes/fanoutUnspents.ts @@ -7,8 +7,614 @@ import { PutFanoutUnspents, } from '../../../src/typedRoutes/api/v1/fanoutUnspents'; import { assertDecode } from './common'; +import 'should'; +import 'should-http'; +import 'should-sinon'; +import * as sinon from 'sinon'; +import { BitGo } from 'bitgo'; +import { setupAgent } from '../../lib/testutil'; describe('FanoutUnspents codec tests', function () { + describe('fanoutUnspents', function () { + const agent = setupAgent(); + const walletId = '68c02f96aa757d9212bd1a536f123456'; + + const mockFanoutResponse = { + status: 'accepted', + tx: '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + hash: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', + instant: false, + fee: 10000, + feeRate: 20000, + travelInfos: [], + }; + + afterEach(function () { + sinon.restore(); + }); + + it('should successfully fanout unspents', async function () { + const requestBody = { + target: 10, + walletPassphrase: 'test_passphrase_12345', + }; + + // Create mock wallet with fanOutUnspents method (note: capital O) + const mockWallet = { + fanOutUnspents: sinon.stub().resolves(mockFanoutResponse), + }; + + // Stub the wallets().get() chain + const walletsGetStub = sinon.stub().resolves(mockWallet); + + const mockWallets = { + get: walletsGetStub, + }; + + // For V1, bitgo.wallets() is called directly (no coin parameter) + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + // Make the request to Express + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Verify the response + assert.strictEqual(result.status, 200); + result.body.should.have.property('status'); + result.body.should.have.property('tx'); + result.body.should.have.property('hash'); + assert.strictEqual(result.body.status, mockFanoutResponse.status); + assert.strictEqual(result.body.tx, mockFanoutResponse.tx); + assert.strictEqual(result.body.hash, mockFanoutResponse.hash); + + // This ensures the response structure matches the typed definition + const decodedResponse = assertDecode(FanoutUnspentsResponse, result.body); + assert.strictEqual(decodedResponse.status, mockFanoutResponse.status); + assert.strictEqual(decodedResponse.tx, mockFanoutResponse.tx); + assert.strictEqual(decodedResponse.hash, mockFanoutResponse.hash); + + // Verify that the correct BitGoJS methods were called + assert.strictEqual(walletsGetStub.calledOnceWith({ id: walletId }), true); + assert.strictEqual(mockWallet.fanOutUnspents.calledOnce, true); + }); + + it('should successfully fanout unspents with xprv', async function () { + const requestBody = { + target: 20, + xprv: 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2', + }; + + const mockWallet = { + fanOutUnspents: sinon.stub().resolves(mockFanoutResponse), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + assert.strictEqual(result.status, 200); + + const decodedResponse = assertDecode(FanoutUnspentsResponse, result.body); + assert.strictEqual(decodedResponse.status, mockFanoutResponse.status); + }); + + it('should successfully fanout unspents with all optional fields', async function () { + const requestBody = { + target: 15, + walletPassphrase: 'test_passphrase', + validate: false, + minConfirms: 2, + }; + + const mockWallet = { + fanOutUnspents: sinon.stub().resolves(mockFanoutResponse), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + assert.strictEqual(result.status, 200); + + const decodedResponse = assertDecode(FanoutUnspentsResponse, result.body); + assert.strictEqual(decodedResponse.status, mockFanoutResponse.status); + }); + + it('should return instant transaction response', async function () { + const requestBody = { + target: 10, + walletPassphrase: 'test_passphrase', + }; + + const mockInstantResponse = { + ...mockFanoutResponse, + instant: true, + instantId: 'inst-123456', + }; + + const mockWallet = { + fanOutUnspents: sinon.stub().resolves(mockInstantResponse), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + assert.strictEqual(result.status, 200); + result.body.should.have.property('instant'); + result.body.should.have.property('instantId'); + assert.strictEqual(result.body.instant, true); + assert.strictEqual(result.body.instantId, 'inst-123456'); + + const decodedResponse = assertDecode(FanoutUnspentsResponse, result.body); + assert.strictEqual(decodedResponse.instant, true); + assert.strictEqual(decodedResponse.instantId, 'inst-123456'); + }); + + // ========================================== + // ERROR AND EDGE CASE TESTS + // ========================================== + + describe('Error Cases', function () { + it('should handle wallet not found error', async function () { + const requestBody = { + target: 10, + walletPassphrase: 'test_passphrase', + }; + + const walletsGetStub = sinon.stub().rejects(new Error('Wallet not found')); + const mockWallets = { get: walletsGetStub }; + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + assert.strictEqual(result.status, 500); + result.body.should.have.property('error'); + }); + + it('should handle fanoutUnspents failure', async function () { + const requestBody = { + target: 10, + walletPassphrase: 'wrong_passphrase', + }; + + const mockWallet = { + fanOutUnspents: sinon.stub().rejects(new Error('Invalid passphrase')), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + assert.strictEqual(result.status, 500); + result.body.should.have.property('error'); + }); + + it('should handle insufficient unspents error', async function () { + const requestBody = { + target: 100, + walletPassphrase: 'test_passphrase', + }; + + const mockWallet = { + fanOutUnspents: sinon.stub().rejects(new Error('Insufficient unspents to fanout')), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + assert.strictEqual(result.status, 500); + result.body.should.have.property('error'); + }); + + it('should handle wallets() method error', async function () { + const requestBody = { + target: 10, + walletPassphrase: 'test_passphrase', + }; + + sinon.stub(BitGo.prototype, 'wallets').throws(new Error('Wallets service unavailable')); + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + assert.strictEqual(result.status, 500); + result.body.should.have.property('error'); + }); + }); + + describe('Invalid Request Body', function () { + it('should reject request with empty body', async function () { + // Make the request with empty body (missing required 'target') + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send({}); + + // io-ts validation should fail + assert.ok(result.status >= 400); + }); + + it('should reject request with missing target', async function () { + const requestBody = { + walletPassphrase: 'test_passphrase', + }; + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Should fail validation + assert.ok(result.status >= 400); + }); + + it('should reject request with invalid target type', async function () { + const requestBody = { + target: '10', // string instead of number + walletPassphrase: 'test_passphrase', + }; + + // Make the request + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Should fail validation + assert.ok(result.status >= 400); + }); + + it('should reject request with invalid walletPassphrase type', async function () { + const requestBody = { + target: 10, + walletPassphrase: 123, // number instead of string + }; + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + assert.ok(result.status >= 400); + }); + + it('should reject request with invalid validate type', async function () { + const requestBody = { + target: 10, + validate: 'true', // string instead of boolean + }; + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + assert.ok(result.status >= 400); + }); + + it('should reject request with invalid minConfirms type', async function () { + const requestBody = { + target: 10, + minConfirms: '2', // string instead of number + }; + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + assert.ok(result.status >= 400); + }); + + it('should handle request with malformed JSON', async function () { + // Make the request with malformed JSON + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send('{ invalid json ]'); + + // Should fail parsing + assert.ok(result.status >= 400); + }); + }); + + describe('Edge Cases', function () { + it('should handle target value at minimum boundary (2)', async function () { + const requestBody = { + target: 2, + walletPassphrase: 'test_passphrase', + }; + + const mockWallet = { + fanOutUnspents: sinon.stub().resolves(mockFanoutResponse), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Should succeed with minimum valid target value + assert.strictEqual(result.status, 200); + const decodedResponse = assertDecode(FanoutUnspentsResponse, result.body); + assert.strictEqual(decodedResponse.status, mockFanoutResponse.status); + }); + + it('should handle target value at maximum boundary (300)', async function () { + const requestBody = { + target: 300, + walletPassphrase: 'test_passphrase', + }; + + const mockWallet = { + fanOutUnspents: sinon.stub().resolves(mockFanoutResponse), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Should succeed with maximum valid target value + assert.strictEqual(result.status, 200); + const decodedResponse = assertDecode(FanoutUnspentsResponse, result.body); + assert.strictEqual(decodedResponse.status, mockFanoutResponse.status); + }); + + it('should handle very long wallet ID', async function () { + const veryLongWalletId = 'a'.repeat(1000); + const requestBody = { + target: 10, + walletPassphrase: 'test_passphrase', + }; + + const walletsGetStub = sinon.stub().rejects(new Error('Invalid wallet ID')); + const mockWallets = { get: walletsGetStub }; + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + const result = await agent + .put(`/api/v1/wallet/${veryLongWalletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + assert.ok(result.status >= 400); + }); + + it('should handle wallet ID with special characters', async function () { + const specialCharWalletId = '../../../etc/passwd'; + const requestBody = { + target: 10, + walletPassphrase: 'test_passphrase', + }; + + const walletsGetStub = sinon.stub().rejects(new Error('Invalid wallet ID')); + const mockWallets = { get: walletsGetStub }; + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + const result = await agent + .put(`/api/v1/wallet/${encodeURIComponent(specialCharWalletId)}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + assert.ok(result.status >= 400); + }); + + it('should handle both walletPassphrase and xprv provided', async function () { + const requestBody = { + target: 10, + walletPassphrase: 'test_passphrase', + xprv: 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2', + }; + + const mockWallet = { + fanOutUnspents: sinon.stub().resolves(mockFanoutResponse), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Should succeed - SDK handles priority of auth methods + assert.strictEqual(result.status, 200); + const decodedResponse = assertDecode(FanoutUnspentsResponse, result.body); + assert.strictEqual(decodedResponse.status, mockFanoutResponse.status); + }); + + it('should handle zero minConfirms', async function () { + const requestBody = { + target: 10, + walletPassphrase: 'test_passphrase', + minConfirms: 0, + }; + + const mockWallet = { + fanOutUnspents: sinon.stub().resolves(mockFanoutResponse), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Should succeed - zero minConfirms is valid (includes unconfirmed) + assert.strictEqual(result.status, 200); + const decodedResponse = assertDecode(FanoutUnspentsResponse, result.body); + assert.strictEqual(decodedResponse.status, mockFanoutResponse.status); + }); + + it('should handle negative target value', async function () { + const requestBody = { + target: -5, + walletPassphrase: 'test_passphrase', + }; + + const mockWallet = { + fanOutUnspents: sinon.stub().rejects(new Error('Invalid target value')), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + assert.ok(result.status >= 400); + }); + }); + + describe('Response Validation Edge Cases', function () { + it('should reject response with missing required field in FanoutUnspentsResponse', async function () { + const requestBody = { + target: 10, + walletPassphrase: 'test_passphrase', + }; + + // Mock returns invalid response (missing required fields) + const invalidResponse = { + status: 'accepted', + tx: '0x123...', + // missing other required fields + }; + + const mockWallet = { + fanOutUnspents: sinon.stub().resolves(invalidResponse), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Even if SDK returns 200, response should fail codec validation + if (result.status === 200) { + assert.throws(() => { + assertDecode(FanoutUnspentsResponse, result.body); + }); + } + }); + + it('should reject response with wrong type in field', async function () { + const requestBody = { + target: 10, + walletPassphrase: 'test_passphrase', + }; + + // Mock returns invalid response (wrong field type) + const invalidResponse = { + status: 123, // Wrong type! Should be string + tx: '0x123...', + hash: 'abc123', + instant: false, + fee: 10000, + feeRate: 20000, + travelInfos: [], + }; + + const mockWallet = { + fanOutUnspents: sinon.stub().resolves(invalidResponse), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + sinon.stub(BitGo.prototype, 'wallets').returns(mockWallets as any); + + const result = await agent + .put(`/api/v1/wallet/${walletId}/fanoutunspents`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Response codec validation should catch type mismatch + if (result.status === 200) { + assert.throws(() => { + assertDecode(FanoutUnspentsResponse, result.body); + }); + } + }); + }); + }); + describe('FanoutUnspentsRequestParams', function () { it('should validate params with required id', function () { const validParams = { diff --git a/modules/express/test/unit/typedRoutes/walletSignTx.ts b/modules/express/test/unit/typedRoutes/walletSignTx.ts index f648563e1b..e06e88ba2b 100644 --- a/modules/express/test/unit/typedRoutes/walletSignTx.ts +++ b/modules/express/test/unit/typedRoutes/walletSignTx.ts @@ -12,8 +12,740 @@ import { PostWalletSignTx, } from '../../../src/typedRoutes/api/v2/walletSignTx'; import { assertDecode } from './common'; +import 'should'; +import 'should-http'; +import 'should-sinon'; +import * as sinon from 'sinon'; +import { BitGo } from 'bitgo'; +import { setupAgent } from '../../lib/testutil'; describe('WalletSignTx codec tests', function () { + describe('walletSignTx', function () { + const agent = setupAgent(); + const coin = 'tbtc'; + const walletId = '68c02f96aa757d9212bd1a536f123456'; + + const mockFullySignedResponse = { + txHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + }; + + afterEach(function () { + sinon.restore(); + }); + + it('should successfully sign a wallet transaction', async function () { + const requestBody = { + txPrebuild: { + txHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + walletId: walletId, + }, + prv: 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2', + }; + + // Create mock wallet with signTransaction method + const mockWallet = { + signTransaction: sinon.stub().resolves(mockFullySignedResponse), + }; + + // Stub the wallets().get() chain + const walletsGetStub = sinon.stub().resolves(mockWallet); + + const mockWallets = { + get: walletsGetStub, + }; + + const mockCoin = { + wallets: sinon.stub().returns(mockWallets), + }; + + // Stub BitGo.prototype.coin to return our mock coin + const coinStub = sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); + + // Make the request to Express + const result = await agent + .post(`/api/v2/${coin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Verify the response + assert.strictEqual(result.status, 200); + result.body.should.have.property('txHex'); + assert.strictEqual(result.body.txHex, mockFullySignedResponse.txHex); + + // This ensures the response structure matches the typed definition + const decodedResponse = assertDecode(FullySignedTransactionResponse, result.body); + assert.strictEqual(decodedResponse.txHex, mockFullySignedResponse.txHex); + + // Verify that the correct BitGoJS methods were called + assert.strictEqual(coinStub.calledOnceWith(coin), true); + assert.strictEqual(mockCoin.wallets.calledOnce, true); + assert.strictEqual(walletsGetStub.calledOnceWith({ id: walletId }), true); + assert.strictEqual(mockWallet.signTransaction.calledOnce, true); + }); + + it('should successfully sign a half-signed account transaction', async function () { + const requestBody = { + txPrebuild: { + txBase64: + 'AQAAAAFz2JT3Xvjk8jKcYcMrKR8tPMRm5+/Q6J2sMgtz7QDpAAAAAAD+////AoCWmAAAAAAAGXapFJA29QPQaHHwR3Uriuhw2A6tHkPgiKwAAAAAAAEBH9cQ2QAAAAAAAXapFCf/zr8zPrMftHGIRsOt0Cf+wdOyiKwA', + walletId: walletId, + }, + prv: 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2', + }; + + const mockHalfSignedResponse = { + halfSigned: { + txBase64: + 'AQAAAAFz2JT3Xvjk8jKcYcMrKR8tPMRm5+/Q6J2sMgtz7QDpAAAAAAD+////AoCWmAAAAAAAGXapFJA29QPQaHHwR3Uriuhw2A6tHkPgiKwAAAAAAAEBH9cQ2QAAAAAAAXapFCf/zr8zPrMftHGIRsOt0Cf+wdOyiKwA', + }, + }; + + // Create mock wallet with signTransaction method + const mockWallet = { + signTransaction: sinon.stub().resolves(mockHalfSignedResponse), + }; + + // Stub the wallets().get() chain + const walletsGetStub = sinon.stub().resolves(mockWallet); + + const mockWallets = { + get: walletsGetStub, + }; + + const mockCoin = { + wallets: sinon.stub().returns(mockWallets), + }; + + // Stub BitGo.prototype.coin to return our mock coin + const coinStub = sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); + + // Make the request to Express + const result = await agent + .post(`/api/v2/${coin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Verify the response + assert.strictEqual(result.status, 200); + result.body.should.have.property('halfSigned'); + result.body.halfSigned.should.have.property('txBase64'); + assert.strictEqual(result.body.halfSigned.txBase64, mockHalfSignedResponse.halfSigned.txBase64); + + // This ensures the response structure matches the typed definition + const decodedResponse = assertDecode(HalfSignedAccountTransactionResponse, result.body); + assert.strictEqual(decodedResponse.halfSigned.txBase64, mockHalfSignedResponse.halfSigned.txBase64); + + // Verify that the correct BitGoJS methods were called + assert.strictEqual(coinStub.calledOnceWith(coin), true); + assert.strictEqual(mockCoin.wallets.calledOnce, true); + assert.strictEqual(walletsGetStub.calledOnceWith({ id: walletId }), true); + assert.strictEqual(mockWallet.signTransaction.calledOnce, true); + }); + + it('should successfully sign a half-signed UTXO transaction', async function () { + const requestBody = { + txPrebuild: { + txHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + walletId: walletId, + }, + prv: 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2', + }; + + const mockHalfSignedUtxoResponse = { + txHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f00000000484730440220abc123def456...', + }; + + // Create mock wallet with signTransaction method + const mockWallet = { + signTransaction: sinon.stub().resolves(mockHalfSignedUtxoResponse), + }; + + // Stub the wallets().get() chain + const walletsGetStub = sinon.stub().resolves(mockWallet); + + const mockWallets = { + get: walletsGetStub, + }; + + const mockCoin = { + wallets: sinon.stub().returns(mockWallets), + }; + + // Stub BitGo.prototype.coin to return our mock coin + const coinStub = sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); + + // Make the request to Express + const result = await agent + .post(`/api/v2/${coin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Verify the response + assert.strictEqual(result.status, 200); + result.body.should.have.property('txHex'); + assert.strictEqual(result.body.txHex, mockHalfSignedUtxoResponse.txHex); + + // This ensures the response structure matches the typed definition for UTXO half-signed + const decodedResponse = assertDecode(HalfSignedUtxoTransactionResponse, result.body); + assert.strictEqual(decodedResponse.txHex, mockHalfSignedUtxoResponse.txHex); + + // Verify that the correct BitGoJS methods were called + assert.strictEqual(coinStub.calledOnceWith(coin), true); + assert.strictEqual(mockCoin.wallets.calledOnce, true); + assert.strictEqual(walletsGetStub.calledOnceWith({ id: walletId }), true); + assert.strictEqual(mockWallet.signTransaction.calledOnce, true); + }); + + it('should successfully return a transaction request ID', async function () { + const requestBody = { + txPrebuild: { + txHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + walletId: walletId, + }, + walletPassphrase: 'test_passphrase_12345', + apiVersion: 'lite' as const, + }; + + const mockTxRequestResponse = { + txRequestId: '5a1341e7c8421dc90710673b3166bbd5', + }; + + // Create mock wallet with signTransaction method + const mockWallet = { + signTransaction: sinon.stub().resolves(mockTxRequestResponse), + }; + + // Stub the wallets().get() chain + const walletsGetStub = sinon.stub().resolves(mockWallet); + + const mockWallets = { + get: walletsGetStub, + }; + + const mockCoin = { + wallets: sinon.stub().returns(mockWallets), + }; + + // Stub BitGo.prototype.coin to return our mock coin + const coinStub = sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); + + // Make the request to Express + const result = await agent + .post(`/api/v2/${coin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Verify the response + assert.strictEqual(result.status, 200); + result.body.should.have.property('txRequestId'); + assert.strictEqual(result.body.txRequestId, mockTxRequestResponse.txRequestId); + + // This ensures the response structure matches the typed definition + const decodedResponse = assertDecode(SignedTransactionRequestResponse, result.body); + assert.strictEqual(decodedResponse.txRequestId, mockTxRequestResponse.txRequestId); + + // Verify that the correct BitGoJS methods were called + assert.strictEqual(coinStub.calledOnceWith(coin), true); + assert.strictEqual(mockCoin.wallets.calledOnce, true); + assert.strictEqual(walletsGetStub.calledOnceWith({ id: walletId }), true); + assert.strictEqual(mockWallet.signTransaction.calledOnce, true); + }); + + it('should successfully return a full transaction request (TxRequestResponse)', async function () { + const requestBody = { + txPrebuild: { + txHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + walletId: walletId, + txRequestId: 'tx-req-5a1341e7c8421dc90710673b3166bbd5', + }, + walletPassphrase: 'test_passphrase_12345', + apiVersion: 'full' as const, + multisigTypeVersion: 'MPCv2' as const, + }; + + const mockTxRequestFullResponse = { + txRequestId: '5a1341e7c8421dc90710673b3166bbd5', + walletId: walletId, + walletType: 'hot', + version: 1, + state: 'pendingUserSignature', + date: '2023-01-01T00:00:00.000Z', + createdDate: '2023-01-01T00:00:00.000Z', + userId: '5a1341e7c8421dc90710673b3166bbd5', + initiatedBy: '5a1341e7c8421dc90710673b3166bbd5', + updatedBy: '5a1341e7c8421dc90710673b3166bbd5', + intents: [], + enterpriseId: '5a1341e7c8421dc90710673b3166bbd5', + intent: {}, + pendingApprovalId: '5a1341e7c8421dc90710673b3166bbd5', + policiesChecked: true, + signatureShares: [ + { + from: 'user', + to: 'bitgo', + share: 'user_signature_share_abc123', + }, + ], + commitmentShares: [ + { + from: 'user', + to: 'bitgo', + share: 'user_commitment_share_abc123', + type: 'commitment', + }, + ], + txHashes: ['hash1', 'hash2'], + unsignedTxs: [ + { + serializedTxHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + signableHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + derivationPath: "m/44'/0'/0'/0/0", + }, + ], + apiVersion: 'full', + latest: true, + }; + + // Create mock wallet with signTransaction method + const mockWallet = { + signTransaction: sinon.stub().resolves(mockTxRequestFullResponse), + }; + + // Stub the wallets().get() chain + const walletsGetStub = sinon.stub().resolves(mockWallet); + + const mockWallets = { + get: walletsGetStub, + }; + + const mockCoin = { + wallets: sinon.stub().returns(mockWallets), + }; + + // Stub BitGo.prototype.coin to return our mock coin + const coinStub = sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); + + // Make the request to Express + const result = await agent + .post(`/api/v2/${coin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Verify the response + assert.strictEqual(result.status, 200); + result.body.should.have.property('txRequestId'); + result.body.should.have.property('walletId'); + result.body.should.have.property('version'); + result.body.should.have.property('state'); + result.body.should.have.property('intents'); + result.body.should.have.property('latest'); + assert.strictEqual(result.body.txRequestId, mockTxRequestFullResponse.txRequestId); + assert.strictEqual(result.body.walletId, mockTxRequestFullResponse.walletId); + assert.strictEqual(result.body.version, mockTxRequestFullResponse.version); + assert.strictEqual(result.body.state, mockTxRequestFullResponse.state); + assert.strictEqual(result.body.latest, mockTxRequestFullResponse.latest); + + // Verify TSS-specific fields + result.body.should.have.property('signatureShares'); + result.body.should.have.property('commitmentShares'); + result.body.should.have.property('unsignedTxs'); + result.body.signatureShares.should.be.Array(); + result.body.signatureShares.should.have.length(1); + result.body.commitmentShares.should.be.Array(); + result.body.commitmentShares.should.have.length(1); + result.body.unsignedTxs.should.be.Array(); + result.body.unsignedTxs.should.have.length(1); + + // This ensures the transaction request response structure matches the typed definition + const decodedResponse = assertDecode(TxRequestResponse, result.body); + assert.strictEqual(decodedResponse.txRequestId, mockTxRequestFullResponse.txRequestId); + assert.strictEqual(decodedResponse.walletId, mockTxRequestFullResponse.walletId); + assert.strictEqual(decodedResponse.version, mockTxRequestFullResponse.version); + assert.strictEqual(decodedResponse.state, mockTxRequestFullResponse.state); + assert.strictEqual(decodedResponse.latest, mockTxRequestFullResponse.latest); + + // Verify that the correct BitGoJS methods were called + assert.strictEqual(coinStub.calledOnceWith(coin), true); + assert.strictEqual(mockCoin.wallets.calledOnce, true); + assert.strictEqual(walletsGetStub.calledOnceWith({ id: walletId }), true); + assert.strictEqual(mockWallet.signTransaction.calledOnce, true); + }); + + // ========================================== + // ERROR AND EDGE CASE TESTS + // ========================================== + + describe('Error Cases', function () { + it('should handle wallet not found error', async function () { + const requestBody = { + txPrebuild: { + txHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + walletId: walletId, + }, + prv: 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2', + }; + + // Stub wallets().get() to reject with wallet not found error + const walletsGetStub = sinon.stub().rejects(new Error('Wallet not found')); + + const mockWallets = { + get: walletsGetStub, + }; + + const mockCoin = { + wallets: sinon.stub().returns(mockWallets), + }; + + sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); + + // Make the request to Express + const result = await agent + .post(`/api/v2/${coin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Verify error response + assert.strictEqual(result.status, 500); + result.body.should.have.property('error'); + }); + + it('should handle signTransaction failure', async function () { + const requestBody = { + txPrebuild: { + txHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + walletId: walletId, + }, + prv: 'wrong_private_key', + }; + + // Create mock wallet where signTransaction fails + const mockWallet = { + signTransaction: sinon.stub().rejects(new Error('Invalid private key')), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + + const mockWallets = { + get: walletsGetStub, + }; + + const mockCoin = { + wallets: sinon.stub().returns(mockWallets), + }; + + sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); + + // Make the request to Express + const result = await agent + .post(`/api/v2/${coin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Verify error response + assert.strictEqual(result.status, 500); + result.body.should.have.property('error'); + }); + + it('should handle invalid coin error', async function () { + const invalidCoin = 'invalid_coin_xyz'; + const requestBody = { + txPrebuild: { + txHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + walletId: walletId, + }, + prv: 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2', + }; + + // Stub coin() to throw error for invalid coin + sinon.stub(BitGo.prototype, 'coin').throws(new Error(`Coin ${invalidCoin} is not supported`)); + + // Make the request to Express + const result = await agent + .post(`/api/v2/${invalidCoin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Verify error response + assert.strictEqual(result.status, 500); + result.body.should.have.property('error'); + }); + }); + + describe('Invalid Request Body', function () { + it('should reject request with empty body', async function () { + // Make the request with empty body + const result = await agent + .post(`/api/v2/${coin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send({}); + + // io-ts validation should fail + // Note: Depending on your route config, this might be 400 or 500 + assert.ok(result.status >= 400); + }); + + it('should reject request with invalid txPrebuild type', async function () { + const requestBody = { + txPrebuild: 'invalid_string_instead_of_object', // Wrong type! + prv: 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2', + }; + + // Make the request + const result = await agent + .post(`/api/v2/${coin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Should fail validation + assert.ok(result.status >= 400); + }); + + it('should handle request with invalid apiVersion', async function () { + const requestBody = { + txPrebuild: { + txHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + walletId: walletId, + }, + walletPassphrase: 'test_passphrase_12345', + apiVersion: 'invalid_version', // Invalid value + }; + + // Make the request + const result = await agent + .post(`/api/v2/${coin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Should fail validation + assert.ok(result.status >= 400); + }); + + it('should handle request with malformed JSON', async function () { + // Make the request with malformed JSON + const result = await agent + .post(`/api/v2/${coin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send('{ invalid json ]'); + + // Should fail parsing + assert.ok(result.status >= 400); + }); + }); + + describe('Edge Cases', function () { + it('should handle empty txPrebuild object', async function () { + const requestBody = { + txPrebuild: {}, // Empty object + prv: 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2', + }; + + const mockWallet = { + signTransaction: sinon.stub().rejects(new Error('Missing transaction data')), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + const mockCoin = { wallets: sinon.stub().returns(mockWallets) }; + sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); + + const result = await agent + .post(`/api/v2/${coin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Should handle empty txPrebuild gracefully + assert.ok(result.status >= 400); + }); + + it('should handle very long wallet ID', async function () { + const veryLongWalletId = 'a'.repeat(1000); + const requestBody = { + txPrebuild: { + txHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + walletId: veryLongWalletId, + }, + prv: 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2', + }; + + const walletsGetStub = sinon.stub().rejects(new Error('Invalid wallet ID')); + const mockWallets = { get: walletsGetStub }; + const mockCoin = { wallets: sinon.stub().returns(mockWallets) }; + sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); + + const result = await agent + .post(`/api/v2/${coin}/wallet/${veryLongWalletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Should handle gracefully + assert.ok(result.status >= 400); + }); + + it('should handle wallet ID with special characters', async function () { + const specialCharWalletId = '../../../etc/passwd'; + const requestBody = { + txPrebuild: { + txHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + }, + prv: 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2', + }; + + const walletsGetStub = sinon.stub().rejects(new Error('Invalid wallet ID')); + const mockWallets = { get: walletsGetStub }; + const mockCoin = { wallets: sinon.stub().returns(mockWallets) }; + sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); + + const result = await agent + .post(`/api/v2/${coin}/wallet/${encodeURIComponent(specialCharWalletId)}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Should handle special characters safely + assert.ok(result.status >= 400); + }); + + it('should handle txRequestId in both txPrebuild and body', async function () { + const requestBody = { + txPrebuild: { + txHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + walletId: walletId, + txRequestId: 'tx-req-in-prebuild', + }, + txRequestId: 'tx-req-in-body', // Also in body + prv: 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2', + }; + + const mockWallet = { + signTransaction: sinon.stub().resolves(mockFullySignedResponse), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + const mockCoin = { wallets: sinon.stub().returns(mockWallets) }; + sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); + + const result = await agent + .post(`/api/v2/${coin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Should handle gracefully (accept or reject consistently) + assert.ok(result.status === 200 || result.status >= 400); + }); + }); + + describe('Response Validation Edge Cases', function () { + it('should reject response with missing required field in FullySignedTransactionResponse', async function () { + const requestBody = { + txPrebuild: { + txHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + walletId: walletId, + }, + prv: 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2', + }; + + // Mock returns invalid response (missing txHex) + const invalidResponse = {}; + + const mockWallet = { + signTransaction: sinon.stub().resolves(invalidResponse), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + const mockCoin = { wallets: sinon.stub().returns(mockWallets) }; + sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); + + const result = await agent + .post(`/api/v2/${coin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Even if SDK returns 200, response should fail codec validation + // This depends on where validation happens + assert.ok(result.status === 200 || result.status >= 400); + + // If status is 200 but response is invalid, codec validation should catch it + if (result.status === 200) { + assert.throws(() => { + assertDecode(FullySignedTransactionResponse, result.body); + }); + } + }); + + it('should reject response with wrong type in txHex field', async function () { + const requestBody = { + txPrebuild: { + txHex: + '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000', + walletId: walletId, + }, + prv: 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2', + }; + + // Mock returns invalid response (txHex is number instead of string) + const invalidResponse = { + txHex: 12345, // Wrong type! + }; + + const mockWallet = { + signTransaction: sinon.stub().resolves(invalidResponse), + }; + + const walletsGetStub = sinon.stub().resolves(mockWallet); + const mockWallets = { get: walletsGetStub }; + const mockCoin = { wallets: sinon.stub().returns(mockWallets) }; + sinon.stub(BitGo.prototype, 'coin').returns(mockCoin as any); + + const result = await agent + .post(`/api/v2/${coin}/wallet/${walletId}/signtx`) + .set('Authorization', 'Bearer test_access_token_12345') + .set('Content-Type', 'application/json') + .send(requestBody); + + // Response codec validation should catch type mismatch + if (result.status === 200) { + assert.throws(() => { + assertDecode(FullySignedTransactionResponse, result.body); + }); + } + }); + }); + }); + describe('WalletSignTxParams', function () { it('should validate params with required coin and id', function () { const validParams = { diff --git a/modules/sdk-coin-ada/src/lib/transactionBuilder.ts b/modules/sdk-coin-ada/src/lib/transactionBuilder.ts index 0afefe52ad..4c22e4b6a8 100644 --- a/modules/sdk-coin-ada/src/lib/transactionBuilder.ts +++ b/modules/sdk-coin-ada/src/lib/transactionBuilder.ts @@ -26,8 +26,6 @@ const FEE_COEFFICIENTS = { B_COEFFICIENT: '155381', /** Additional safety margin for the fee */ SAFETY_MARGIN: '440', - /* Min fee required for token transaction */ - MIN_TOKEN_TRANSACTION_FEE: '1000000', }; export abstract class TransactionBuilder extends BaseTransactionBuilder { @@ -379,15 +377,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder { ); // Calculate the fee based off our dummy transaction - let fee = CardanoWasm.min_fee(txDraft, linearFee).checked_add(BigNum.from_str(FEE_COEFFICIENTS.SAFETY_MARGIN)); - /** - * In some cases especially with token transactions the calculated fee can be very low than the fee expected from the node - * So, ensure a minimum fee is always set - */ - const minTokenFee = BigNum.from_str(FEE_COEFFICIENTS.MIN_TOKEN_TRANSACTION_FEE); - if (fee.less_than(minTokenFee)) { - fee = minTokenFee; - } + const fee = CardanoWasm.min_fee(txDraft, linearFee).checked_add(BigNum.from_str(FEE_COEFFICIENTS.SAFETY_MARGIN)); this._fee = fee; } diff --git a/modules/sdk-coin-ada/test/unit/tokenWithdrawal.ts b/modules/sdk-coin-ada/test/unit/tokenWithdrawal.ts index 0079704ee8..9755bb1965 100644 --- a/modules/sdk-coin-ada/test/unit/tokenWithdrawal.ts +++ b/modules/sdk-coin-ada/test/unit/tokenWithdrawal.ts @@ -39,7 +39,7 @@ describe('ADA Token Operations', async () => { totalInput - 1500000 /* min ada for change token utxo */ - 1500000 /* min ada for recipient token utxo*/ - - 1000000 + 173597 ) /* fee */ .toString(); const expectedChangeToken = '80'; @@ -127,7 +127,7 @@ describe('ADA Token Operations', async () => { 1500000 /* min ada for change token utxo */ - 1500000 /* min ada for recipient token utxo*/ - 1500000 /* min ada for unsupported token change utxo */ - - 1000000 + 179889 ) /* fee */ .toString(); const expectedChangeToken = '80'; diff --git a/modules/sdk-coin-canton/src/lib/transactionBuilderFactory.ts b/modules/sdk-coin-canton/src/lib/transactionBuilderFactory.ts index b963ec4b4d..2ff86d2d85 100644 --- a/modules/sdk-coin-canton/src/lib/transactionBuilderFactory.ts +++ b/modules/sdk-coin-canton/src/lib/transactionBuilderFactory.ts @@ -1,6 +1,8 @@ -import { BaseTransactionBuilderFactory } from '@bitgo/sdk-core'; +import { BaseTransaction, BaseTransactionBuilderFactory } from '@bitgo/sdk-core'; import { TransactionBuilder } from './transactionBuilder'; import { TransferBuilder } from './transferBuilder'; +import { WalletInitBuilder } from './walletInitBuilder'; +import { WalletInitTransaction } from './walletInitialization/walletInitTransaction'; export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { /** @inheritdoc */ @@ -14,7 +16,17 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { } /** @inheritdoc */ - getWalletInitializationBuilder(): void { - throw new Error('Method not implemented.'); + getWalletInitializationBuilder(tx?: WalletInitTransaction): WalletInitBuilder { + return TransactionBuilderFactory.initializeBuilder(tx, new WalletInitBuilder(this._coinConfig)); + } + + private static initializeBuilder( + tx: TTx | undefined, + builder: TBuilder + ): TBuilder { + if (tx) { + builder.initBuilder(tx); + } + return builder; } } diff --git a/modules/sdk-coin-opeth/src/opeth.ts b/modules/sdk-coin-opeth/src/opeth.ts index 2ff8a0066f..0b7c3aadd5 100644 --- a/modules/sdk-coin-opeth/src/opeth.ts +++ b/modules/sdk-coin-opeth/src/opeth.ts @@ -32,6 +32,16 @@ export class Opeth extends AbstractEthLikeNewCoins { return 'ecdsa'; } + /** @inheritDoc */ + supportsMessageSigning(): boolean { + return true; + } + + /** @inheritDoc */ + supportsSigningTypedData(): boolean { + return true; + } + /** * Make a query to Optimism Etherscan for information such as balance, token balance, solidity calls * @param {Object} query key-value pairs of parameters to append after /api diff --git a/modules/sdk-coin-polyx/src/lib/iface.ts b/modules/sdk-coin-polyx/src/lib/iface.ts index 5b007db8fe..65cb3b549d 100644 --- a/modules/sdk-coin-polyx/src/lib/iface.ts +++ b/modules/sdk-coin-polyx/src/lib/iface.ts @@ -12,6 +12,8 @@ export interface TxData extends Interface.TxData { assetId?: string; fromDID?: string; toDID?: string; + instructionId?: string; + portfolioDID?: string; } /** @@ -49,6 +51,8 @@ export const MethodNames = { PreApproveAsset: 'preApproveAsset' as const, AddAndAffirmWithMediators: 'addAndAffirmWithMediators' as const, + + RejectInstruction: 'rejectInstruction' as const, } as const; // Create a type that represents the keys of this object @@ -85,6 +89,12 @@ export interface AddAndAffirmWithMediatorsArgs extends Args { mediators: []; } +export interface RejectInstructionBuilderArgs extends Args { + id: string; + portfolio: { did: string; kind: PortfolioKind.Default }; + numberOfAssets: { fungible: number; nonFungible: number; offChain: number }; +} + export interface TxMethod extends Omit { args: | Interface.TransferArgs @@ -100,7 +110,8 @@ export interface TxMethod extends Omit { | Interface.BatchArgs | RegisterDidWithCDDArgs | PreApproveAssetArgs - | AddAndAffirmWithMediatorsArgs; + | AddAndAffirmWithMediatorsArgs + | RejectInstructionBuilderArgs; name: MethodNamesValues; } diff --git a/modules/sdk-coin-polyx/src/lib/index.ts b/modules/sdk-coin-polyx/src/lib/index.ts index e33bdeec8f..2c936f4363 100644 --- a/modules/sdk-coin-polyx/src/lib/index.ts +++ b/modules/sdk-coin-polyx/src/lib/index.ts @@ -14,6 +14,7 @@ export { TransferBuilder } from './transferBuilder'; export { RegisterDidWithCDDBuilder } from './registerDidWithCDDBuilder'; export { PreApproveAssetBuilder } from './preApproveAssetBuilder'; export { TokenTransferBuilder } from './tokenTransferBuilder'; +export { RejectInstructionBuilder } from './rejectInstructionBuilder'; export { Transaction as PolyxTransaction } from './transaction'; export { BondExtraBuilder } from './bondExtraBuilder'; export { BatchStakingBuilder as BatchBuilder } from './batchStakingBuilder'; diff --git a/modules/sdk-coin-polyx/src/lib/rejectInstructionBuilder.ts b/modules/sdk-coin-polyx/src/lib/rejectInstructionBuilder.ts new file mode 100644 index 0000000000..eb01557f49 --- /dev/null +++ b/modules/sdk-coin-polyx/src/lib/rejectInstructionBuilder.ts @@ -0,0 +1,103 @@ +import { TransactionType } from '@bitgo/sdk-core'; +import { BaseCoin as CoinConfig } from '@bitgo/statics'; +import { PolyxBaseBuilder } from './baseBuilder'; +import { TxMethod, MethodNames, RejectInstructionBuilderArgs, PortfolioKind } from './iface'; +import { Transaction } from './transaction'; +import { Interface } from '@bitgo/abstract-substrate'; +import { RejectInstructionTransactionSchema } from './txnSchema'; +import { DecodedSignedTx, DecodedSigningPayload, defineMethod, UnsignedTransaction } from '@substrate/txwrapper-core'; + +export class RejectInstructionBuilder extends PolyxBaseBuilder { + protected _instructionId: string; + protected _portfolioDID: string; + + constructor(_coinConfig: Readonly) { + super(_coinConfig); + this._transaction = new Transaction(_coinConfig); + } + + protected get transactionType(): TransactionType { + return TransactionType.RejectInstruction; + } + + protected buildTransaction(): UnsignedTransaction { + const baseTxInfo = this.createBaseTxInfo(); + return this.rejectInstruction( + { + id: this._instructionId, + portfolio: { + did: this._portfolioDID, + kind: PortfolioKind.Default, + }, + numberOfAssets: { + fungible: 1, + nonFungible: 0, + offChain: 0, + }, + }, + baseTxInfo + ); + } + + /** + * @param instructionId - The ID of the instruction to be rejected + * @returns {this} + */ + instructionId(instructionId: string): this { + this._instructionId = instructionId; + return this; + } + + /** + * @param portfolioDID - The DID of the portfolio associated with the instruction + * @returns {this} + */ + portfolioDID(portfolioDID: string): this { + this._portfolioDID = portfolioDID; + return this; + } + + /** @inheritdoc */ + protected fromImplementation(rawTransaction: string): Transaction { + const tx = super.fromImplementation(rawTransaction); + if (this._method?.name === MethodNames.RejectInstruction) { + const txMethod = this._method.args as RejectInstructionBuilderArgs; + this._instructionId = txMethod.id as string; + this._portfolioDID = txMethod.portfolio.did as string; + } else { + throw new Error(`Cannot build from transaction with method ${this._method?.name} for RejectInstructionBuilder`); + } + return tx; + } + + /** @inheritdoc */ + validateDecodedTransaction(decodedTxn: DecodedSigningPayload | DecodedSignedTx, rawTransaction?: string): void { + if (decodedTxn.method?.name === MethodNames.RejectInstruction) { + const txMethod = decodedTxn.method.args as RejectInstructionBuilderArgs; + const id = txMethod.id; + const portfolio = txMethod.portfolio; + + const validationResult = RejectInstructionTransactionSchema.validate({ + id, + portfolio, + }); + if (validationResult.error) { + throw new Error(`Invalid transaction: ${validationResult.error.message}`); + } + } + } + + private rejectInstruction(args: RejectInstructionBuilderArgs, info: Interface.CreateBaseTxInfo): UnsignedTransaction { + return defineMethod( + { + method: { + args, + name: 'rejectInstruction', + pallet: 'settlement', + }, + ...info.baseTxInfo, + }, + info.options + ); + } +} diff --git a/modules/sdk-coin-polyx/src/lib/tokenTransferBuilder.ts b/modules/sdk-coin-polyx/src/lib/tokenTransferBuilder.ts index 3851ab44e6..741ec39513 100644 --- a/modules/sdk-coin-polyx/src/lib/tokenTransferBuilder.ts +++ b/modules/sdk-coin-polyx/src/lib/tokenTransferBuilder.ts @@ -137,7 +137,6 @@ export class TokenTransferBuilder extends PolyxBaseBuilder { + let builder: RejectInstructionBuilder; + + const sender = accounts.account1; + const instructionId = '14100'; + const portfolioDID = '0x1208d7851e6698249aea40742701ee1ef6cdcced260a7c49c1cca1a9db836342'; + + beforeEach(() => { + const config = buildTestConfig(); + builder = new RejectInstructionBuilder(config).material(utils.getMaterial(config.network.type)); + }); + + describe('build rejectInstruction transaction', () => { + it('should build a rejectInstruction transaction', async () => { + builder + .instructionId(instructionId) + .portfolioDID(portfolioDID) + .sender({ address: sender.address }) + .validity({ firstValid: 3933, maxDuration: 64 }) + .referenceBlock('0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d') + .sequenceId({ name: 'Nonce', keyword: 'nonce', value: 200 }) + .fee({ amount: 0, type: 'tip' }); + builder.addSignature({ pub: sender.publicKey }, Buffer.from(mockTssSignature, 'hex')); + const tx = await builder.build(); + const txJson = tx.toJson(); + should.deepEqual(txJson.instructionId, instructionId); + should.deepEqual(txJson.portfolioDID, portfolioDID); + should.deepEqual(txJson.sender, sender.address); + should.deepEqual(txJson.blockNumber, 3933); + should.deepEqual(txJson.referenceBlock, '0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d'); + should.deepEqual(txJson.genesisHash, genesisHash); + should.deepEqual(txJson.specVersion, Number(testnetMaterial.specVersion)); + should.deepEqual(txJson.nonce, 200); + should.deepEqual(txJson.tip, 0); + should.deepEqual(txJson.transactionVersion, Number(testnetMaterial.txVersion)); + should.deepEqual(txJson.chainName, testnetMaterial.chainName); + should.deepEqual(txJson.eraPeriod, 64); + }); + + it('should build an unsigned rejectInstruction transaction', async () => { + builder + .instructionId(instructionId) + .portfolioDID(portfolioDID) + .sender({ address: sender.address }) + .validity({ firstValid: 3933, maxDuration: 64 }) + .referenceBlock('0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d') + .sequenceId({ name: 'Nonce', keyword: 'nonce', value: 200 }) + .fee({ amount: 0, type: 'tip' }); + const tx = await builder.build(); + const txJson = tx.toJson(); + should.deepEqual(txJson.instructionId, instructionId); + should.deepEqual(txJson.portfolioDID, portfolioDID); + should.deepEqual(txJson.sender, sender.address); + should.deepEqual(txJson.blockNumber, 3933); + should.deepEqual(txJson.referenceBlock, '0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d'); + should.deepEqual(txJson.genesisHash, genesisHash); + should.deepEqual(txJson.specVersion, Number(testnetMaterial.specVersion)); + should.deepEqual(txJson.nonce, 200); + should.deepEqual(txJson.tip, 0); + should.deepEqual(txJson.transactionVersion, Number(testnetMaterial.txVersion)); + should.deepEqual(txJson.chainName, chainName); + should.deepEqual(txJson.eraPeriod, 64); + }); + + it('should build from raw signed tx', async () => { + if (rawTx.rejectInstruction?.signed) { + builder.from(rawTx.rejectInstruction.signed); + builder + .validity({ firstValid: 3933, maxDuration: 64 }) + .referenceBlock('0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d'); + const tx = await builder.build(); + const txJson = tx.toJson(); + should.exist(txJson.instructionId); + should.deepEqual(txJson.instructionId, instructionId); + should.exist(txJson.portfolioDID); + should.deepEqual(txJson.portfolioDID, portfolioDID); + should.deepEqual(txJson.blockNumber, 3933); + should.deepEqual(txJson.referenceBlock, '0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d'); + should.deepEqual(txJson.genesisHash, genesisHash); + should.deepEqual(txJson.specVersion, Number(testnetMaterial.specVersion)); + should.deepEqual(txJson.transactionVersion, Number(testnetMaterial.txVersion)); + should.deepEqual(txJson.chainName, chainName); + should.deepEqual(txJson.eraPeriod, 64); + } + }); + + it('should build from raw unsigned tx', async () => { + if (rawTx.rejectInstruction?.unsigned) { + builder.from(rawTx.rejectInstruction.unsigned); + builder + .validity({ firstValid: 3933, maxDuration: 64 }) + .referenceBlock('0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d') + .sender({ address: sender.address }) + .addSignature({ pub: sender.publicKey }, Buffer.from(mockTssSignature, 'hex')); + + const tx = await builder.build(); + const txJson = tx.toJson(); + should.exist(txJson.instructionId); + should.deepEqual(txJson.instructionId, instructionId); + should.exist(txJson.portfolioDID); + should.deepEqual(txJson.portfolioDID, portfolioDID); + should.deepEqual(txJson.sender, sender.address); + should.deepEqual(txJson.blockNumber, 3933); + should.deepEqual(txJson.referenceBlock, '0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d'); + should.deepEqual(txJson.genesisHash, genesisHash); + should.deepEqual(txJson.specVersion, Number(testnetMaterial.specVersion)); + should.deepEqual(txJson.eraPeriod, 64); + should.deepEqual(txJson.transactionVersion, Number(testnetMaterial.txVersion)); + should.deepEqual(txJson.chainName, chainName); + } + }); + + it('should validate instruction ID is set', async () => { + builder + .portfolioDID(portfolioDID) + .sender({ address: sender.address }) + .validity({ firstValid: 3933, maxDuration: 64 }) + .referenceBlock('0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d') + .sequenceId({ name: 'Nonce', keyword: 'nonce', value: 200 }) + .fee({ amount: 0, type: 'tip' }); + + await builder.build().should.be.rejected(); + }); + }); +}); diff --git a/modules/sdk-core/src/account-lib/baseCoin/enum.ts b/modules/sdk-core/src/account-lib/baseCoin/enum.ts index f5cb498b58..89c2fd8ab0 100644 --- a/modules/sdk-core/src/account-lib/baseCoin/enum.ts +++ b/modules/sdk-core/src/account-lib/baseCoin/enum.ts @@ -107,6 +107,9 @@ export enum TransactionType { // cspr stakingUnlock, + + // polyx + RejectInstruction, } /** diff --git a/modules/sdk-core/src/bitgo/errors.ts b/modules/sdk-core/src/bitgo/errors.ts index ada8982024..862a01c5ab 100644 --- a/modules/sdk-core/src/bitgo/errors.ts +++ b/modules/sdk-core/src/bitgo/errors.ts @@ -1,6 +1,9 @@ // Descriptive error types for common issues which may arise // during the operation of BitGoJS or BitGoExpress import { BitGoJsError } from '../bitgojsError'; +import { IRequestTracer } from '../api/types'; +import { TransactionParams } from './baseCoin'; +import { SendManyOptions } from './wallet'; // re-export for backwards compat export { BitGoJsError }; @@ -197,3 +200,182 @@ export class ApiResponseError extends BitGoJsError { this.needsOTP = needsOTP; } } + +/** + * Interface for token approval information used in suspicious transaction detection + * + * @interface TokenApproval + * @property {string} [tokenName] - Optional human-readable name of the token + * @property {string} tokenAddress - The contract address of the token being approved + * @property {Object} authorizingAmount - The amount being authorized for spending + * @property {string} authorizingAddress - The address being authorized to spend the tokens + */ +export interface TokenApproval { + tokenName?: string; + tokenAddress: string; + authorizingAmount: { type: 'unlimited' } | { type: 'limited'; amount: number }; + authorizingAddress: string; +} + +/** + * Interface for mismatched recipient information detected during transaction verification + * + * @interface MismatchedRecipient + * @property {string} address - The recipient address that was found in the transaction + * @property {string} amount - The amount being sent to this recipient + * @property {string | TokenTransferRecipientParams} [data] - Optional transaction data or token transfer parameters + * @property {string} [tokenName] - Optional name of the token being transferred + * @property {TokenTransferRecipientParams} [tokenData] - Optional structured token transfer data + */ +export type MismatchedRecipient = NonNullable[0]; + +/** + * Interface for contract interaction data payload used in suspicious transaction detection + * + * @interface ContractDataPayload + * @property {string} address - The contract address being interacted with + * @property {string} rawContractPayload - The raw contract payload in serialized form specific to the blockchain + * @property {unknown} decodedContractPayload - The decoded contract payload, structure varies by coin/chain implementation + */ +export interface ContractDataPayload { + address: string; + // The raw contract payload in serialized form of the chain + rawContractPayload: string; + // To be defined on a per-coin basis + decodedContractPayload: unknown; +} + +/** + * Base error class for transaction intent mismatch detection + * + * This error is thrown when a transaction does not match the user's original intent, + * indicating potential security issues or malicious modifications. + * + * @class TxIntentMismatchError + * @extends {BitGoJsError} + * @property {string | IRequestTracer} id - Transaction ID or request tracer for tracking + * @property {TransactionParams[]} txParams - Array of transaction parameters that were analyzed + * @property {string} txHex - The raw transaction in hexadecimal format + */ +export class TxIntentMismatchError extends BitGoJsError { + public readonly id: string | IRequestTracer; + public readonly txParams: TransactionParams[]; + public readonly txHex: string; + + /** + * Creates an instance of TxIntentMismatchError + * + * @param {string} message - Error message describing the intent mismatch + * @param {string | IRequestTracer} id - Transaction ID or request tracer + * @param {TransactionParams[]} txParams - Transaction parameters that were analyzed + * @param {string} txHex - Raw transaction hex string + */ + public constructor(message: string, id: string | IRequestTracer, txParams: TransactionParams[], txHex: string) { + super(message); + this.id = id; + this.txParams = txParams; + this.txHex = txHex; + } +} + +/** + * Error thrown when transaction recipients don't match the user's intent + * + * This error occurs when the transaction contains recipients or amounts that differ + * from what the user originally intended to send. + * + * @class TxIntentMismatchRecipientError + * @extends {TxIntentMismatchError} + * @property {MismatchedRecipient[]} mismatchedRecipients - Array of recipients that don't match user intent + */ +export class TxIntentMismatchRecipientError extends TxIntentMismatchError { + public readonly mismatchedRecipients: MismatchedRecipient[]; + + /** + * Creates an instance of TxIntentMismatchRecipientError + * + * @param {string} message - Error message describing the recipient intent mismatch + * @param {string | IRequestTracer} id - Transaction ID or request tracer + * @param {TransactionParams[]} txParams - Transaction parameters that were analyzed + * @param {string} txHex - Raw transaction hex string + * @param {MismatchedRecipient[]} mismatchedRecipients - Array of recipients that don't match user intent + */ + public constructor( + message: string, + id: string | IRequestTracer, + txParams: TransactionParams[], + txHex: string, + mismatchedRecipients: MismatchedRecipient[] + ) { + super(message, id, txParams, txHex); + this.mismatchedRecipients = mismatchedRecipients; + } +} + +/** + * Error thrown when contract interaction doesn't match the user's intent + * + * This error occurs when a transaction interacts with a smart contract but the + * contract call data or method doesn't match what the user intended. + * + * @class TxIntentMismatchContractError + * @extends {TxIntentMismatchError} + * @property {ContractDataPayload} mismatchedDataPayload - The contract interaction data that doesn't match user intent + */ +export class TxIntentMismatchContractError extends TxIntentMismatchError { + public readonly mismatchedDataPayload: ContractDataPayload; + + /** + * Creates an instance of TxIntentMismatchContractError + * + * @param {string} message - Error message describing the contract intent mismatch + * @param {string | IRequestTracer} id - Transaction ID or request tracer + * @param {TransactionParams[]} txParams - Transaction parameters that were analyzed + * @param {string} txHex - Raw transaction hex string + * @param {ContractDataPayload} mismatchedDataPayload - The contract interaction data that doesn't match user intent + */ + public constructor( + message: string, + id: string | IRequestTracer, + txParams: TransactionParams[], + txHex: string, + mismatchedDataPayload: ContractDataPayload + ) { + super(message, id, txParams, txHex); + this.mismatchedDataPayload = mismatchedDataPayload; + } +} + +/** + * Error thrown when token approval doesn't match the user's intent + * + * This error occurs when a transaction contains a token approval that the user + * did not intend to authorize, potentially indicating malicious activity. + * + * @class TxIntentMismatchApprovalError + * @extends {TxIntentMismatchError} + * @property {TokenApproval} tokenApproval - Details of the token approval that doesn't match user intent + */ +export class TxIntentMismatchApprovalError extends TxIntentMismatchError { + public readonly tokenApproval: TokenApproval; + + /** + * Creates an instance of TxIntentMismatchApprovalError + * + * @param {string} message - Error message describing the approval intent mismatch + * @param {string | IRequestTracer} id - Transaction ID or request tracer + * @param {TransactionParams[]} txParams - Transaction parameters that were analyzed + * @param {string} txHex - Raw transaction hex string + * @param {TokenApproval} tokenApproval - Details of the token approval that doesn't match user intent + */ + public constructor( + message: string, + id: string | IRequestTracer, + txParams: TransactionParams[], + txHex: string, + tokenApproval: TokenApproval + ) { + super(message, id, txParams, txHex); + this.tokenApproval = tokenApproval; + } +} diff --git a/modules/sdk-core/test/unit/bitgo/errors.ts b/modules/sdk-core/test/unit/bitgo/errors.ts new file mode 100644 index 0000000000..bf57684f50 --- /dev/null +++ b/modules/sdk-core/test/unit/bitgo/errors.ts @@ -0,0 +1,199 @@ +import should from 'should'; +import { + TxIntentMismatchError, + TxIntentMismatchRecipientError, + TxIntentMismatchContractError, + TxIntentMismatchApprovalError, + MismatchedRecipient, + ContractDataPayload, + TokenApproval, +} from '../../../src/bitgo/errors'; + +describe('Transaction Intent Mismatch Errors', () => { + const mockTransactionId = '0x1234567890abcdef'; + const mockTxParams: any[] = [ + { address: '0xrecipient1', amount: '1000000000000000000' }, + { address: '0xrecipient2', amount: '2000000000000000000' }, + ]; + const mockTxHex = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'; + + describe('TxIntentMismatchError', () => { + it('should create base transaction intent mismatch error with all required properties', () => { + const message = 'Transaction does not match user intent'; + const error = new TxIntentMismatchError(message, mockTransactionId, mockTxParams, mockTxHex); + + should.exist(error); + should.equal(error.message, message); + should.equal(error.name, 'TxIntentMismatchError'); + should.equal(error.id, mockTransactionId); + should.deepEqual(error.txParams, mockTxParams); + should.equal(error.txHex, mockTxHex); + }); + + it('should be an instance of Error', () => { + const error = new TxIntentMismatchError('Test message', mockTransactionId, mockTxParams, mockTxHex); + + should(error).be.instanceOf(Error); + }); + }); + + describe('TxIntentMismatchRecipientError', () => { + it('should create recipient intent mismatch error with mismatched recipients', () => { + const message = 'Transaction recipients do not match user intent'; + const mismatchedRecipients: MismatchedRecipient[] = [ + { address: '0xexpected1', amount: '1000' }, + { address: '0xexpected2', amount: '2000' }, + ]; + + const error = new TxIntentMismatchRecipientError( + message, + mockTransactionId, + mockTxParams, + mockTxHex, + mismatchedRecipients + ); + + should.exist(error); + should.equal(error.message, message); + should.equal(error.name, 'TxIntentMismatchRecipientError'); + should.equal(error.id, mockTransactionId); + should.deepEqual(error.txParams, mockTxParams); + should.equal(error.txHex, mockTxHex); + should.deepEqual(error.mismatchedRecipients, mismatchedRecipients); + }); + + it('should be an instance of TxIntentMismatchError', () => { + const error = new TxIntentMismatchRecipientError('Test message', mockTransactionId, mockTxParams, mockTxHex, []); + + should(error).be.instanceOf(TxIntentMismatchError); + should(error).be.instanceOf(Error); + }); + }); + + describe('TxIntentMismatchContractError', () => { + it('should create contract intent mismatch error with mismatched data payload', () => { + const message = 'Contract interaction does not match user intent'; + const mismatchedDataPayload: ContractDataPayload = { + address: '0xcontract123', + rawContractPayload: '0xabcdef', + decodedContractPayload: { method: 'transfer', params: ['0xrecipient', '1000'] }, + }; + + const error = new TxIntentMismatchContractError( + message, + mockTransactionId, + mockTxParams, + mockTxHex, + mismatchedDataPayload + ); + + should.exist(error); + should.equal(error.message, message); + should.equal(error.name, 'TxIntentMismatchContractError'); + should.equal(error.id, mockTransactionId); + should.deepEqual(error.txParams, mockTxParams); + should.equal(error.txHex, mockTxHex); + should.deepEqual(error.mismatchedDataPayload, mismatchedDataPayload); + }); + + it('should be an instance of TxIntentMismatchError', () => { + const error = new TxIntentMismatchContractError('Test message', mockTransactionId, mockTxParams, mockTxHex, { + address: '0xtest', + rawContractPayload: '0x', + decodedContractPayload: {}, + }); + + should(error).be.instanceOf(TxIntentMismatchError); + should(error).be.instanceOf(Error); + }); + }); + + describe('TxIntentMismatchApprovalError', () => { + it('should create approval intent mismatch error with token approval details', () => { + const message = 'Token approval does not match user intent'; + const tokenApproval: TokenApproval = { + tokenName: 'TestToken', + tokenAddress: '0xtoken123', + authorizingAmount: { type: 'unlimited' }, + authorizingAddress: '0xspender456', + }; + + const error = new TxIntentMismatchApprovalError( + message, + mockTransactionId, + mockTxParams, + mockTxHex, + tokenApproval + ); + + should.exist(error); + should.equal(error.message, message); + should.equal(error.name, 'TxIntentMismatchApprovalError'); + should.equal(error.id, mockTransactionId); + should.deepEqual(error.txParams, mockTxParams); + should.equal(error.txHex, mockTxHex); + should.deepEqual(error.tokenApproval, tokenApproval); + }); + + it('should be an instance of TxIntentMismatchError', () => { + const error = new TxIntentMismatchApprovalError('Test message', mockTransactionId, mockTxParams, mockTxHex, { + tokenAddress: '0xtoken', + authorizingAmount: { type: 'limited', amount: 1000 }, + authorizingAddress: '0xspender', + }); + + should(error).be.instanceOf(TxIntentMismatchError); + should(error).be.instanceOf(Error); + }); + }); + + describe('Error inheritance and properties', () => { + it('should maintain proper inheritance chain', () => { + const baseError = new TxIntentMismatchError('Base error', mockTransactionId, mockTxParams, mockTxHex); + const recipientError = new TxIntentMismatchRecipientError( + 'Recipient error', + mockTransactionId, + mockTxParams, + mockTxHex, + [] + ); + const contractError = new TxIntentMismatchContractError( + 'Contract error', + mockTransactionId, + mockTxParams, + mockTxHex, + { address: '0xtest', rawContractPayload: '0x', decodedContractPayload: {} } + ); + const approvalError = new TxIntentMismatchApprovalError( + 'Approval error', + mockTransactionId, + mockTxParams, + mockTxHex, + { + tokenAddress: '0xtoken', + authorizingAmount: { type: 'limited', amount: 1000 }, + authorizingAddress: '0xspender', + } + ); + + // All should be instances of Error + should(baseError).be.instanceOf(Error); + should(recipientError).be.instanceOf(Error); + should(contractError).be.instanceOf(Error); + should(approvalError).be.instanceOf(Error); + + // All should be instances of TxIntentMismatchError + should(recipientError).be.instanceOf(TxIntentMismatchError); + should(contractError).be.instanceOf(TxIntentMismatchError); + should(approvalError).be.instanceOf(TxIntentMismatchError); + }); + + it('should preserve stack trace', () => { + const error = new TxIntentMismatchError('Test error', mockTransactionId, mockTxParams, mockTxHex); + + should.exist(error.stack); + should(error.stack).be.a.String(); + should(error.stack).containEql('TxIntentMismatchError'); + }); + }); +}); diff --git a/modules/utxo-lib/src/testutil/mock.ts b/modules/utxo-lib/src/testutil/mock.ts index 21ac8e5491..9f27700cde 100644 --- a/modules/utxo-lib/src/testutil/mock.ts +++ b/modules/utxo-lib/src/testutil/mock.ts @@ -1,10 +1,11 @@ import { ok as assert } from 'assert'; import { BIP32Interface } from '@bitgo/secp256k1'; -import * as utxolib from '..'; +import { payments } from '..'; import { getMainnet, Network, networks } from '../networks'; import { ChainCode, + withUnsafeNonSegwit, createPsbtForNetwork, fromOutput, fromOutputWithPrevTx, @@ -37,7 +38,7 @@ export function mockPrevTx( const keypair = getKey('mock-prev-tx'); const pubkey = keypair.publicKey; assert(keypair.privateKey); - const payment = utxolib.payments.p2pkh({ pubkey }); + const payment = payments.p2pkh({ pubkey }); const destOutput = payment.output; if (!destOutput) throw new Error('Impossible, payment we just constructed has no output'); @@ -54,7 +55,7 @@ export function mockPrevTx( witnessUtxo: { script: destOutput, value: value * (BigInt(vout) + BigInt(1)) + BigInt(1000) }, }); // Don't require the prevTx for signing and finalizing for non-segwit input - utxolib.bitgo.withUnsafeNonSegwit(psbtFromNetwork, () => { + withUnsafeNonSegwit(psbtFromNetwork, () => { psbtFromNetwork.signInput(0, keypair); psbtFromNetwork.validateSignaturesOfAllInputs(); psbtFromNetwork.finalizeAllInputs(); diff --git a/modules/utxo-lib/src/testutil/psbt.ts b/modules/utxo-lib/src/testutil/psbt.ts index 21b4574951..00df35a01d 100644 --- a/modules/utxo-lib/src/testutil/psbt.ts +++ b/modules/utxo-lib/src/testutil/psbt.ts @@ -212,7 +212,9 @@ export function constructPsbt( return psbt; } - psbt.setAllInputsMusig2NonceHD(rootWalletKeys['user']); + /* use fixed sessionId for deterministic nonce creation */ + const sessionId = Buffer.alloc(32); + psbt.setAllInputsMusig2NonceHD(rootWalletKeys['user'], { sessionId }); psbt.setAllInputsMusig2NonceHD(rootWalletKeys['bitgo'], { deterministic }); signAllPsbtInputs(psbt, inputs, rootWalletKeys, 'halfsigned', { signers, skipNonWitnessUtxo }); diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoin.fullsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoin.fullsigned.json new file mode 100644 index 0000000000..fe98f87217 --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoin.fullsigned.json @@ -0,0 +1,433 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "cHNidP8BAP3qAQEAAAAH2Z764Zn8TVTlglqOM2ijlenrMpkVz7XYjcMRUH0YSDYAAAAAAP////8eyXKaGyU3OjDw/m4IebMRNqVPL4yYYYxGhidIoIxeeAEAAAAA/////7sN++ulm7pp484HpUNG+ATFQ/pG2CjtU50RIxrbqot7AgAAAAD/////pmKl/+v1i/PmXlNFbGfAKcwVSiQJdA2M03/e51WcXv0DAAAAAP/////TlDdc7iOzuZUUiMBANSkVeoWwWO3ANRVG2etIB+ih0wQAAAAA/////w0vfzSG7a78arksIffK+QCFxN5K2sSREG/YoOT1Y96jBQAAAAD/////l0QdmajWbxJKs8neJrh70ArrFUcFHIQqiBZcGwie6QIGAAAAAP////8FhAMAAAAAAAAXqRTZCUdEBMEko9BMP7/2H6pJz0PFi4eEAwAAAAAAABepFAVBJxIdN/MiZs2WfEhUBJdqZ63wh4QDAAAAAAAAIgAgukjj+YdEY5Q/kBkqRfGuiVt3k2KnYpr3rWqdeoNePU+EAwAAAAAAACJRILJyJ/XK3AVq/qSwK0uXuaAVF4YjTCatWI3M8TTniTHGhAMAAAAAAAAiUSBPc7VWE5nrOXtatBPgkMwMSP9wOmGv5Hz31Hoj1DuXiwAAAABPAQSIsh4AAAAAAAAAAAA6ki4p8MjrDbKmBITL3LYx9rEHycquP/3PPn0uwfa80AMSFIcV82HatoWmadQkMeXW0/lzQE2rnJ/RuVCyea12NATMGK4ITwEEiLIeAAAAAAAAAAAAbR1lbT3dkcGUwEVlo2A3AqIQFs7RSiZfOJgtYnXme2QD07rCMTp8ayHLsRsUsNEDQfkiwKQDqL2Mh/Dcgg81r24E9lzYaU8BBIiyHgAAAAAAAAAAAMsE/WOrNNkP5kZriA4qAsz4qGM3QxKZGviRGxqqtEM0AzbvIo/+m47/+6BSwy0zRmDdH4Nmz4/kSuWqZytrYpCVBPLvA4kAAQC+AQAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAABrSDBFAiEAjXonDnk/0zb8oQtmEpxkwAgV3Z08RUaxLWmaszUgBzACIH1sXdRvXJxlwI/Slem059tFN3mSjHZRKAOWNLCO1GehASEDvQw0sjc+OHV1zdsQPsA5FsMQRPJQjiYf41l6JoDsvR//////AegDAAAAAAAAF6kUVnzXtE+fOgfDE483v5hLYPu68kOHAAAAACICA+FSTX9vxXqz6su2WbeHEGeApHXR20g5UsIxC36aOJdbSDBFAiEA3TGiXQ6+qQ5nkQFo73wrODyiYZLNXIcJ8CmAt8qQRygCIDeVxl7g8HO4BkTQyP+h1ChFeuKvPfW12BF5SXgdEMRiASICA/b0B2S9XWPyAKJ3iIOs916W8VCVyZgmPAhycNDJfn5/RzBEAiAeeVRcAR40sbhytLBX5mJi2GiB2mp/hT5+V6mk0SxtwQIgGlOLIXXHtxYcdtoYOUIg81SuJyn8wckCt0xZzXngFTEBAQMEAQAAAAEEaVIhA/b0B2S9XWPyAKJ3iIOs916W8VCVyZgmPAhycNDJfn5/IQNf+3q8cBWeBGn0uYmm1eF4WikEFp/wULL0aP5dPV278iED4VJNf2/FerPqy7ZZt4cQZ4CkddHbSDlSwjELfpo4l1tTriIGA1/7erxwFZ4EafS5iabV4XhaKQQWn/BQsvRo/l09XbvyFMwYrggAAAAAAAAAAAEAAAAAAAAAIgYD4VJNf2/FerPqy7ZZt4cQZ4CkddHbSDlSwjELfpo4l1sU9lzYaQAAAAAAAAAAAQAAAAAAAAAiBgP29AdkvV1j8gCid4iDrPdelvFQlcmYJjwIcnDQyX5+fxTy7wOJAAAAAAAAAAABAAAAAAAAAAABASDoAwAAAAAAABepFA4msl0M6ub5z/PtrMqg0acNp2APhyICAiNFMbyRGaNqeUbpWut0kV3Qh8e0T1F7ju63P4mg/xqZSDBFAiEA30H7y/PH93GCYnvLfNF5c7jKWMFrgd6Y6UwCk9zzOsICIGAXFxkU/fvID5M6s7Wq5OPTbpaHV+bAeI/76q68aOIeASICAqWF8/q0m175U0bZMiIaIhvVXsGR8VUz4PJws1gldPA1RzBEAiBfdgwn51mKnoCira8dklxn0ubCSux3PlzOVtI0FZqx5QIgHOl9VlloGriB4QrMnIAhWH+EOhUzbDqLb7LvwFOSi4UBAQMEAQAAAAEEIgAgRtSAChOTMwGWCFOZ7UoXALTC/TjFIYjDo6kfch88lgABBWlSIQKlhfP6tJte+VNG2TIiGiIb1V7BkfFVM+DycLNYJXTwNSECxPeGbP1ZlrwQaKljE8oV6cy16YRYOgc0Dd2uevAUYFshAiNFMbyRGaNqeUbpWut0kV3Qh8e0T1F7ju63P4mg/xqZU64iBgIjRTG8kRmjanlG6VrrdJFd0IfHtE9Re47utz+JoP8amRT2XNhpAAAAAAAAAAALAAAAAQAAACIGAqWF8/q0m175U0bZMiIaIhvVXsGR8VUz4PJws1gldPA1FPLvA4kAAAAAAAAAAAsAAAABAAAAIgYCxPeGbP1ZlrwQaKljE8oV6cy16YRYOgc0Dd2uevAUYFsUzBiuCAAAAAAAAAAACwAAAAEAAAAAAQEr6AMAAAAAAAAiACDbBCfxgitHd2cOZnPCivAvbaHVr+cacfXJBkRwsin6/SICAugKCIhcNTZ2uCDNwpDm2BoKyVoD6Ku/S7eu6/tw/rQYRzBEAiBkQQicjHGkkmtJpasSv6qGxLZKu8UZs9IX1o/nG8Q77gIgPhfQdSwLQR6Ud8IsL0VWpi2jnmAJXG4HdL9Cqo9JG00BIgIDUZ5XKrRoVg0fjQ+HaZ0JgwjhZpBfXcrkOQ8GD6qPHONIMEUCIQDTsD+3dVMUThQXHHsbUXo78z34c3C2XjN/ClCERWImHgIgWFvAkTtWrZaBYdND8yHXf1+U/zz2JUWTLxUJI5oVo44BAQMEAQAAAAEFaVIhA1GeVyq0aFYNH40Ph2mdCYMI4WaQX13K5DkPBg+qjxzjIQOgW8JjkSIdaF4Y2q/zah6Vw0+ZRRJTwWzUJlDbJ4p5ESEC6AoIiFw1Nna4IM3CkObYGgrJWgPoq79Lt67r+3D+tBhTriIGAugKCIhcNTZ2uCDNwpDm2BoKyVoD6Ku/S7eu6/tw/rQYFPZc2GkAAAAAAAAAABUAAAACAAAAIgYDUZ5XKrRoVg0fjQ+HaZ0JgwjhZpBfXcrkOQ8GD6qPHOMU8u8DiQAAAAAAAAAAFQAAAAIAAAAiBgOgW8JjkSIdaF4Y2q/zah6Vw0+ZRRJTwWzUJlDbJ4p5ERTMGK4IAAAAAAAAAAAVAAAAAgAAAAABASvoAwAAAAAAACJRIDlLlNBh17d4/tiiBM4F7QtElU7BYi+TT4QRThFz8zAnAQMEAAAAAEEUc3PXI84Kh/j91mhDvhLa6dUZOfaBiNWtlpIL719R9JaD1/mOnvD3M2oG9KARUYOp+ZbLrBIo6ap2OPRH8CSfMECDsbt8bOWGjIpyPE01BZGJSAmxM1WG9gy/zMbdTii4Ywp0Y3sbJC5aOJVA25TyO8vt7px4ODDLKvr/Hydj29t7QRSqiMoZT9pDSGUEuGrJtx8Q/EHeexcTh0g5Qn9054wtWoPX+Y6e8Pczagb0oBFRg6n5lsusEijpqnY49EfwJJ8wQOfasNJ3znxBkzE2tE3KvvQA8dDmIqIlX5hn2Ae8jgAIz9MHZtNtc2yN96oykX3lnQgytIHEmfCReHYTRTIwbUVCFcC8F+orv2/0a9XAw3gL4qY4gOt7t4KznthvKbmfSIKZThsXixtV08aqx7A540It/bnJ111Nb8kI6XQ+iLZR5I2DRSBzc9cjzgqH+P3WaEO+Etrp1Rk59oGI1a2WkgvvX1H0lq0gqojKGU/aQ0hlBLhqybcfEPxB3nsXE4dIOUJ/dOeMLVqswCEWc3PXI84Kh/j91mhDvhLa6dUZOfaBiNWtlpIL719R9JY1AYPX+Y6e8Pczagb0oBFRg6n5lsusEijpqnY49EfwJJ8w8u8DiQAAAAAAAAAAHwAAAAMAAAAhFqqIyhlP2kNIZQS4asm3HxD8Qd57FxOHSDlCf3TnjC1aNQGD1/mOnvD3M2oG9KARUYOp+ZbLrBIo6ap2OPRH8CSfMPZc2GkAAAAAAAAAAB8AAAADAAAAAAEBK+gDAAAAAAAAIlEgKbv69DhbENwrvnq01Q6Jzr6PUHlocnsZaJ4mOAg8xVsBAwQAAAAAQRQDXJ1jLbLduPkM88z8kxgivLHyRWKyOuBBq/EuVLymTUKQacgyD/XxyKwqCxBH/xRQuMDTJeHRLxxSaE2Ro7n7QE2OUBKLXGHdRJs1Km1vPWEeFjsHlMjC0FfSVAGVe7B6cEIOM24Y9rJV1bhpVwwIcJHDDBoz8MrApA2Q0FFMaaRBFGQxUasD86l7uG01kvEvjSWoAm2JzUc0L8G0nF4uY0eLQpBpyDIP9fHIrCoLEEf/FFC4wNMl4dEvHFJoTZGjuftACh4BP21X+4skiysZsIs0waPNX/beSs3xndTODgLUIVSFbdVp9GaPKrrMu1DOganS9T79+2J8LLFM+SxBiaNdp0IVwaEHpAP3ogfnsfVS0/0/VfZK5A4UnQ0m9IaK1/rslJ63Y343ysbl8iNHq2jodrVZEX2Vl+57N/uYPCq55RAjyXZFIGQxUasD86l7uG01kvEvjSWoAm2JzUc0L8G0nF4uY0eLrSADXJ1jLbLduPkM88z8kxgivLHyRWKyOuBBq/EuVLymTazAIRYDXJ1jLbLduPkM88z8kxgivLHyRWKyOuBBq/EuVLymTTUBQpBpyDIP9fHIrCoLEEf/FFC4wNMl4dEvHFJoTZGjufvMGK4IAAAAAAAAAAApAAAABAAAACEWZDFRqwPzqXu4bTWS8S+NJagCbYnNRzQvwbScXi5jR4s1AUKQacgyD/XxyKwqCxBH/xRQuMDTJeHRLxxSaE2Ro7n78u8DiQAAAAAAAAAAKQAAAAQAAAAAAQEr6AMAAAAAAAAiUSAVxYFQJvalSxAZT8aYDxhmoC2ewShTPHmXzbQom/PvFgEDBAAAAAAhFg/epp5Ao63vPNx/pvOvAvTJ2eMlRQPJamorSqZud4FxFQD2XNhpAAAAAAAAAAApAAAABQAAACEWHZeKF0hv+eR8gpkCaeUx/GOYFBnUznPui9LJlmHFOVMVAPLvA4kAAAAAAAAAACkAAAAFAAAAARcg61rSmoWu0k3iiA53TKr2JPnLG+CcZ+1K77ube8Et3xoBGCCypJfqTQtS9QEa4TXQu+DZ69d3uyjnM8vMExNLcxBqGkj8BUJJVEdPARXFgVAm9qVLEBlPxpgPGGagLZ7BKFM8eZfNtCib8+8W61rSmoWu0k3iiA53TKr2JPnLG+CcZ+1K77ube8Et3xpCAh2XihdIb/nkfIKZAmnlMfxjmBQZ1M5z7ovSyZZhxTlTAg/epp5Ao63vPNx/pvOvAvTJ2eMlRQPJamorSqZud4FxSfwFQklUR08CAg/epp5Ao63vPNx/pvOvAvTJ2eMlRQPJamorSqZud4FxFcWBUCb2pUsQGU/GmA8YZqAtnsEoUzx5l820KJvz7xZCAoJpSXQN/0XUCLHxnZTHIPU0EeAsUlsoqzxZO2tTD+A3A3S4oP/Kqu5rdy2sX3wj7zNnCzLsd8bUHvs8Nt8hZaCUSfwFQklUR08CAh2XihdIb/nkfIKZAmnlMfxjmBQZ1M5z7ovSyZZhxTlTFcWBUCb2pUsQGU/GmA8YZqAtnsEoUzx5l820KJvz7xZCA6Sq9G86C8Oac4VfqHWy8vBL2wYjWvuu34V7WT3dxjy0As238ak+xSYoIZjYNEIzcXV+j0OTLQO59Dw/c3gwCuUISfwFQklUR08DAg/epp5Ao63vPNx/pvOvAvTJ2eMlRQPJamorSqZud4FxFcWBUCb2pUsQGU/GmA8YZqAtnsEoUzx5l820KJvz7xYg+9w5w7j/yk5srzKY+hpL5Ua5kWPC8hvTdNIlTsWzwPRJ/AVCSVRHTwMCHZeKF0hv+eR8gpkCaeUx/GOYFBnUznPui9LJlmHFOVMVxYFQJvalSxAZT8aYDxhmoC2ewShTPHmXzbQom/PvFiAc2KDAWYsNiPiJ/fmjFA+PCI4hh4A6L6ruPxPAFj63bAABAP2JAQEAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAakcwRAIgKpG65tXFQ6tRzNBAn1RGm1Od0uVp+QlJuL5jfqvTkCgCIG0489h7WYJ1vvbZEORhTkRSVzMgyj3eoVDkkBqjeB98ASEDvQw0sjc+OHV1zdsQPsA5FsMQRPJQjiYf41l6JoDsvR//////B+gDAAAAAAAAGXapFIg62NGv/finPL2vcje1xauewsMmiKzoAwAAAAAAABl2qRSIOtjRr/34pzy9r3I3tcWrnsLDJois6AMAAAAAAAAZdqkUiDrY0a/9+Kc8va9yN7XFq57CwyaIrOgDAAAAAAAAGXapFIg62NGv/finPL2vcje1xauewsMmiKzoAwAAAAAAABl2qRSIOtjRr/34pzy9r3I3tcWrnsLDJois6AMAAAAAAAAZdqkUiDrY0a/9+Kc8va9yN7XFq57CwyaIrOgDAAAAAAAAF6kUILNwlNgqUTRR/wzNnbI6ugW8XvOHAAAAACICAzbvIo/+m47/+6BSwy0zRmDdH4Nmz4/kSuWqZytrYpCVSDBFAiEA0qertsBWPDAA43QSqbl/Y1LRfkCWBFb+gw0mTwR0+LYCIDjVzb6IoxneXXt9uPV9CpJSS+skJym0amF621MPL6DQAQEDBAEAAAABBCMhAzbvIo/+m47/+6BSwy0zRmDdH4Nmz4/kSuWqZytrYpCVrAABAGlSIQLMTQ+kEcrCREhvjrLAjgNf90EPRgo1nKf4gQmRvTtCCSEC1y/Q0NkCk0NK1fyhYPJ44DxhRJeqTkJc9FTiwTMPlqshA0TYhBNt9VAgKGX/vGIYx/nIj+bOOclFeYGQuts4p1LxU64iAgLMTQ+kEcrCREhvjrLAjgNf90EPRgo1nKf4gQmRvTtCCRTy7wOJAAAAAAAAAAAAAAAAAAAAACICAtcv0NDZApNDStX8oWDyeOA8YUSXqk5CXPRU4sEzD5arFMwYrggAAAAAAAAAAAAAAAAAAAAAIgIDRNiEE231UCAoZf+8YhjH+ciP5s45yUV5gZC62zinUvEU9lzYaQAAAAAAAAAAAAAAAAAAAAAAAQAiACApKwUjUaPG+A1RuuZCzIghGCiT2+7avI9dDpZ3R2AmwQEBaVIhAy4R+YVSaF/pB/MfnnJgyQbo3nAPDDyfLxQQYj3N/RnAIQNUV1fONc8e37E7nixKL5zgCrjDa+4SvKIzfOWD/SiuyCECo8+2Zl/kT3UgtqG03hpNRmQ7OF6x/R4Ktw77llQpdVRTriICAqPPtmZf5E91ILahtN4aTUZkOzhesf0eCrcO+5ZUKXVUFPZc2GkAAAAAAAAAAAoAAAABAAAAIgIDLhH5hVJoX+kH8x+ecmDJBujecA8MPJ8vFBBiPc39GcAU8u8DiQAAAAAAAAAACgAAAAEAAAAiAgNUV1fONc8e37E7nixKL5zgCrjDa+4SvKIzfOWD/SiuyBTMGK4IAAAAAAAAAAAKAAAAAQAAAAABAWlSIQLW0RQL0sD5FMswyZKqL80oP0cakoHO7J7GxbxEjmhpACEDuFWodY7aaGNkhEWp08+5U4isoXRiGtmPCZocT5Mm/schApX3tSfS6lN6TUKDBBCr9DQHMb+bIOVJcgAZM7mqI1NsU64iAgKV97Un0upTek1CgwQQq/Q0BzG/myDlSXIAGTO5qiNTbBT2XNhpAAAAAAAAAAAUAAAAAgAAACICAtbRFAvSwPkUyzDJkqovzSg/RxqSgc7snsbFvESOaGkAFPLvA4kAAAAAAAAAABQAAAACAAAAIgIDuFWodY7aaGNkhEWp08+5U4isoXRiGtmPCZocT5Mm/scUzBiuCAAAAAAAAAAAFAAAAAIAAAAAAQUgjqWAFuyr48+EkOxl13kUFXH7nlwS6skF0YDw4QKUM5QBBtUBwEQgmeQ2VcLPPZUDYrtqOkRjgOgLeh9w5SiE61drE1bcbkCtIKhtf+Z/aZQRMUPMFjRMsemXuMyOHe4jZRuXx5wJr5rdrALARCCZ5DZVws89lQNiu2o6RGOA6At6H3DlKITrV2sTVtxuQK0gOgk1MOoVnXPI/5MJnuCpu56ao54LK4RkLUe0+A1IfLusAsBEIDoJNTDqFZ1zyP+TCZ7gqbuemqOeCyuEZC1HtPgNSHy7rSCobX/mf2mUETFDzBY0TLHpl7jMjh3uI2Ubl8ecCa+a3awhBzoJNTDqFZ1zyP+TCZ7gqbuemqOeCyuEZC1HtPgNSHy7VQLPlgXK7DgkNHvXCtVud2CV+mPzvGgE0EMM7P/k4lPYzrwOO32VKtlWu3dZ3iFzWCx1ANjyn+TBreZxGznXk1gazBiuCAAAAAAAAAAAHgAAAAMAAAAhB5nkNlXCzz2VA2K7ajpEY4DoC3ofcOUohOtXaxNW3G5AVQKNZgmyVDU0gQ4SGcV8G6H7HMKxp0JybAqn830D0CNAGs+WBcrsOCQ0e9cK1W53YJX6Y/O8aATQQwzs/+TiU9jO8u8DiQAAAAAAAAAAHgAAAAMAAAAhB6htf+Z/aZQRMUPMFjRMsemXuMyOHe4jZRuXx5wJr5rdVQKNZgmyVDU0gQ4SGcV8G6H7HMKxp0JybAqn830D0CNAGrwOO32VKtlWu3dZ3iFzWCx1ANjyn+TBreZxGznXk1ga9lzYaQAAAAAAAAAAHgAAAAMAAAAAAQUgMbWvFBIcepl+H1jccXj4slieMw/lqdyMFlSfkMu+nsUBBo4BwEQgTo0Og0H+lXM6/KPb8YUt53aV4oeVl5fG2BWfv7ArSLatIMFgwnr8xIiMWT8Y3uLXmFZXUIwDcFq28PY101LLr2SZrAHARCDBYMJ6/MSIjFk/GN7i15hWV1CMA3BatvD2NdNSy69kma0gaDYIkIyL/pXjjwmQaKFwFAMyEp7rA0gLHkgG1PT0PCysIQdOjQ6DQf6Vczr8o9vxhS3ndpXih5WXl8bYFZ+/sCtItjUBDzlQClRSD67v6E5aT5WVlDUjMH7yU6fdxxMYmm9aMvDy7wOJAAAAAAAAAAAoAAAABAAAACEHaDYIkIyL/pXjjwmQaKFwFAMyEp7rA0gLHkgG1PT0PCw1AdejVvt3sY5DvIYVgORcAETAhX4xsb3nYIPZCAwz0//P9lzYaQAAAAAAAAAAKAAAAAQAAAAhB8Fgwnr8xIiMWT8Y3uLXmFZXUIwDcFq28PY101LLr2SZVQIPOVAKVFIPru/oTlpPlZWUNSMwfvJTp93HExiab1oy8NejVvt3sY5DvIYVgORcAETAhX4xsb3nYIPZCAwz0//PzBiuCAAAAAAAAAAAKAAAAAQAAAAA", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006b4830450221008d7a270e793fd336fca10b66129c64c00815dd9d3c4546b12d699ab33520073002207d6c5dd46f5c9c65c08fd295e9b4e7db453779928c765128039634b08ed467a1012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "304402201e79545c011e34b1b872b4b057e66262d86881da6a7f853e7e57a9a4d12c6dc102201a538b2175c7b7161c76da18394220f354ae2729fcc1c902b74c59cd79e0153101" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "signature": "3045022100dd31a25d0ebea90e67910168ef7c2b383ca26192cd5c8709f02980b7ca90472802203795c65ee0f073b80644d0c8ffa1d428457ae2af3df5b5d8117949781d10c46201" + } + ] + }, + { + "type": "p2shP2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "a9140e26b25d0ceae6f9cff3edaccaa0d1a70da7600f87", + "value": "1000" + }, + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "02a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f035", + "path": "0/0/11/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b", + "path": "0/0/11/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a99", + "path": "0/0/11/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f0352102c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b2102234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a9953ae", + "redeemScript": "002046d4800a1393330196085399ed4a1700b4c2fd38c52188c3a3a91f721f3c9600", + "partialSig": [ + { + "pubkey": "02a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f035", + "signature": "304402205f760c27e7598a9e80a2adaf1d925c67d2e6c24aec773e5cce56d234159ab1e502201ce97d5659681ab881e10acc9c8021587f843a15336c3a8b6fb2efc053928b8501" + }, + { + "pubkey": "02234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a99", + "signature": "3045022100df41fbcbf3c7f77182627bcb7cd17973b8ca58c16b81de98e94c0293dcf33ac202206017171914fdfbc80f933ab3b5aae4e3d36e968757e6c0788ffbeaaebc68e21e01" + } + ] + }, + { + "type": "p2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "0020db0427f1822b4777670e6673c28af02f6da1d5afe71a71f5c9064470b229fafd", + "value": "1000" + }, + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce3", + "path": "0/0/21/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a7911", + "path": "0/0/21/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb418", + "path": "0/0/21/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522103519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce32103a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a79112102e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb41853ae", + "partialSig": [ + { + "pubkey": "03519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce3", + "signature": "3045022100d3b03fb77553144e14171c7b1b517a3bf33df87370b65e337f0a50844562261e0220585bc0913b56ad968161d343f321d77f5f94ff3cf62545932f1509239a15a38e01" + }, + { + "pubkey": "02e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb418", + "signature": "304402206441089c8c71a4926b49a5ab12bfaa86c4b64abbc519b3d217d68fe71bc43bee02203e17d0752c0b411e9477c22c2f4556a62da39e60095c6e0774bf42aa8f491b4d01" + } + ] + }, + { + "type": "p2tr", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "5120394b94d061d7b778fed8a204ce05ed0b44954ec1622f934f84114e1173f33027", + "value": "1000" + }, + "sighashType": 0, + "tapLeafScript": [ + { + "controlBlock": "c0bc17ea2bbf6ff46bd5c0c3780be2a63880eb7bb782b39ed86f29b99f4882994e1b178b1b55d3c6aac7b039e3422dfdb9c9d75d4d6fc908e9743e88b651e48d83", + "script": "207373d723ce0a87f8fdd66843be12dae9d51939f68188d5ad96920bef5f51f496ad20aa88ca194fda43486504b86ac9b71f10fc41de7b1713874839427f74e78c2d5aac", + "leafVersion": 192 + } + ], + "tapBip32Derivation": [ + { + "leafHashes": [ + "83d7f98e9ef0f7336a06f4a0115183a9f996cbac1228e9aa7638f447f0249f30" + ], + "pubkey": "7373d723ce0a87f8fdd66843be12dae9d51939f68188d5ad96920bef5f51f496", + "path": "0/0/31/3", + "masterFingerprint": "f2ef0389" + }, + { + "leafHashes": [ + "83d7f98e9ef0f7336a06f4a0115183a9f996cbac1228e9aa7638f447f0249f30" + ], + "pubkey": "aa88ca194fda43486504b86ac9b71f10fc41de7b1713874839427f74e78c2d5a", + "path": "0/0/31/3", + "masterFingerprint": "f65cd869" + } + ], + "tapScriptSig": [ + { + "pubkey": "7373d723ce0a87f8fdd66843be12dae9d51939f68188d5ad96920bef5f51f496", + "signature": "83b1bb7c6ce5868c8a723c4d350591894809b1335586f60cbfccc6dd4e28b8630a74637b1b242e5a389540db94f23bcbedee9c783830cb2afaff1f2763dbdb7b", + "leafHash": "83d7f98e9ef0f7336a06f4a0115183a9f996cbac1228e9aa7638f447f0249f30" + }, + { + "pubkey": "aa88ca194fda43486504b86ac9b71f10fc41de7b1713874839427f74e78c2d5a", + "signature": "e7dab0d277ce7c41933136b44dcabef400f1d0e622a2255f9867d807bc8e0008cfd30766d36d736c8df7aa32917de59d0832b481c499f0917876134532306d45", + "leafHash": "83d7f98e9ef0f7336a06f4a0115183a9f996cbac1228e9aa7638f447f0249f30" + } + ] + }, + { + "type": "p2trMusig2", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "512029bbfaf4385b10dc2bbe7ab4d50e89cebe8f507968727b19689e2638083cc55b", + "value": "1000" + }, + "sighashType": 0, + "tapLeafScript": [ + { + "controlBlock": "c1a107a403f7a207e7b1f552d3fd3f55f64ae40e149d0d26f4868ad7faec949eb7637e37cac6e5f22347ab68e876b559117d9597ee7b37fb983c2ab9e51023c976", + "script": "20643151ab03f3a97bb86d3592f12f8d25a8026d89cd47342fc1b49c5e2e63478bad20035c9d632db2ddb8f90cf3ccfc931822bcb1f24562b23ae041abf12e54bca64dac", + "leafVersion": 192 + } + ], + "tapBip32Derivation": [ + { + "leafHashes": [ + "429069c8320ff5f1c8ac2a0b1047ff1450b8c0d325e1d12f1c52684d91a3b9fb" + ], + "pubkey": "643151ab03f3a97bb86d3592f12f8d25a8026d89cd47342fc1b49c5e2e63478b", + "path": "0/0/41/4", + "masterFingerprint": "f2ef0389" + }, + { + "leafHashes": [ + "429069c8320ff5f1c8ac2a0b1047ff1450b8c0d325e1d12f1c52684d91a3b9fb" + ], + "pubkey": "035c9d632db2ddb8f90cf3ccfc931822bcb1f24562b23ae041abf12e54bca64d", + "path": "0/0/41/4", + "masterFingerprint": "cc18ae08" + } + ], + "tapScriptSig": [ + { + "pubkey": "643151ab03f3a97bb86d3592f12f8d25a8026d89cd47342fc1b49c5e2e63478b", + "signature": "0a1e013f6d57fb8b248b2b19b08b34c1a3cd5ff6de4acdf19dd4ce0e02d42154856dd569f4668f2abaccbb50ce81a9d2f53efdfb627c2cb14cf92c4189a35da7", + "leafHash": "429069c8320ff5f1c8ac2a0b1047ff1450b8c0d325e1d12f1c52684d91a3b9fb" + }, + { + "pubkey": "035c9d632db2ddb8f90cf3ccfc931822bcb1f24562b23ae041abf12e54bca64d", + "signature": "4d8e50128b5c61dd449b352a6d6f3d611e163b0794c8c2d057d25401957bb07a70420e336e18f6b255d5b869570c087091c30c1a33f0cac0a40d90d0514c69a4", + "leafHash": "429069c8320ff5f1c8ac2a0b1047ff1450b8c0d325e1d12f1c52684d91a3b9fb" + } + ] + }, + { + "type": "taprootKeyPathSpend", + "unknownKeyVals": [ + { + "key": "fc05424954474f0115c5815026f6a54b10194fc6980f1866a02d9ec128533c7997cdb4289bf3ef16eb5ad29a85aed24de2880e774caaf624f9cb1be09c67ed4aefbb9b7bc12ddf1a", + "value": "021d978a17486ff9e47c82990269e531fc63981419d4ce73ee8bd2c99661c53953020fdea69e40a3adef3cdc7fa6f3af02f4c9d9e3254503c96a6a2b4aa66e778171" + }, + { + "key": "fc05424954474f02021d978a17486ff9e47c82990269e531fc63981419d4ce73ee8bd2c99661c5395315c5815026f6a54b10194fc6980f1866a02d9ec128533c7997cdb4289bf3ef16", + "value": "03a4aaf46f3a0bc39a73855fa875b2f2f04bdb06235afbaedf857b593dddc63cb402cdb7f1a93ec526282198d834423371757e8f43932d03b9f43c3f7378300ae508" + }, + { + "key": "fc05424954474f02020fdea69e40a3adef3cdc7fa6f3af02f4c9d9e3254503c96a6a2b4aa66e77817115c5815026f6a54b10194fc6980f1866a02d9ec128533c7997cdb4289bf3ef16", + "value": "02826949740dff45d408b1f19d94c720f53411e02c525b28ab3c593b6b530fe0370374b8a0ffcaaaee6b772dac5f7c23ef33670b32ec77c6d41efb3c36df2165a094" + }, + { + "key": "fc05424954474f03021d978a17486ff9e47c82990269e531fc63981419d4ce73ee8bd2c99661c5395315c5815026f6a54b10194fc6980f1866a02d9ec128533c7997cdb4289bf3ef16", + "value": "1cd8a0c0598b0d88f889fdf9a3140f8f088e2187803a2faaee3f13c0163eb76c" + }, + { + "key": "fc05424954474f03020fdea69e40a3adef3cdc7fa6f3af02f4c9d9e3254503c96a6a2b4aa66e77817115c5815026f6a54b10194fc6980f1866a02d9ec128533c7997cdb4289bf3ef16", + "value": "fbdc39c3b8ffca4e6caf3298fa1a4be546b99163c2f21bd374d2254ec5b3c0f4" + } + ], + "witnessUtxo": { + "script": "512015c5815026f6a54b10194fc6980f1866a02d9ec128533c7997cdb4289bf3ef16", + "value": "1000" + }, + "sighashType": 0, + "tapInternalKey": "eb5ad29a85aed24de2880e774caaf624f9cb1be09c67ed4aefbb9b7bc12ddf1a", + "tapMerkleRoot": "b2a497ea4d0b52f5011ae135d0bbe0d9ebd777bb28e733cbcc13134b73106a1a", + "tapBip32Derivation": [ + { + "leafHashes": [], + "pubkey": "1d978a17486ff9e47c82990269e531fc63981419d4ce73ee8bd2c99661c53953", + "path": "0/0/41/5", + "masterFingerprint": "f2ef0389" + }, + { + "leafHashes": [], + "pubkey": "0fdea69e40a3adef3cdc7fa6f3af02f4c9d9e3254503c96a6a2b4aa66e778171", + "path": "0/0/41/5", + "masterFingerprint": "f65cd869" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006a47304402202a91bae6d5c543ab51ccd0409f54469b539dd2e569f90949b8be637eabd3902802206d38f3d87b598275bef6d910e4614e4452573320ca3ddea150e4901aa3781f7c012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff07e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 1, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3045022100d2a7abb6c0563c3000e37412a9b97f6352d17e40960456fe830d264f0474f8b6022038d5cdbe88a319de5d7b7db8f57d0a92524beb242729b46a617adb530f2fa0d001" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c0", + "path": "0/0/10/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec8", + "path": "0/0/10/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb9654297554", + "path": "0/0/10/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "5221032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c02103545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec82102a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb965429755453ae", + "redeemScript": "0020292b052351a3c6f80d51bae642cc8821182893dbeedabc8f5d0e9677476026c1" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e686900", + "path": "0/0/20/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7", + "path": "0/0/20/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c", + "path": "0/0/20/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e6869002103b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7210295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c53ae" + }, + { + "unknownKeyVals": [], + "tapTree": { + "leaves": [ + { + "script": "2099e43655c2cf3d950362bb6a3a446380e80b7a1f70e52884eb576b1356dc6e40ad20a86d7fe67f6994113143cc16344cb1e997b8cc8e1dee23651b97c79c09af9addac", + "leafVersion": 192, + "depth": 1 + }, + { + "script": "2099e43655c2cf3d950362bb6a3a446380e80b7a1f70e52884eb576b1356dc6e40ad203a093530ea159d73c8ff93099ee0a9bb9e9aa39e0b2b84642d47b4f80d487cbbac", + "leafVersion": 192, + "depth": 2 + }, + { + "script": "203a093530ea159d73c8ff93099ee0a9bb9e9aa39e0b2b84642d47b4f80d487cbbad20a86d7fe67f6994113143cc16344cb1e997b8cc8e1dee23651b97c79c09af9addac", + "leafVersion": 192, + "depth": 2 + } + ] + }, + "tapInternalKey": "8ea58016ecabe3cf8490ec65d779141571fb9e5c12eac905d180f0e102943394", + "tapBip32Derivation": [ + { + "leafHashes": [ + "8d6609b2543534810e1219c57c1ba1fb1cc2b1a742726c0aa7f37d03d023401a", + "cf9605caec3824347bd70ad56e776095fa63f3bc6804d0430cecffe4e253d8ce" + ], + "pubkey": "99e43655c2cf3d950362bb6a3a446380e80b7a1f70e52884eb576b1356dc6e40", + "path": "0/0/30/3", + "masterFingerprint": "f2ef0389" + }, + { + "leafHashes": [ + "cf9605caec3824347bd70ad56e776095fa63f3bc6804d0430cecffe4e253d8ce", + "bc0e3b7d952ad956bb7759de2173582c7500d8f29fe4c1ade6711b39d793581a" + ], + "pubkey": "3a093530ea159d73c8ff93099ee0a9bb9e9aa39e0b2b84642d47b4f80d487cbb", + "path": "0/0/30/3", + "masterFingerprint": "cc18ae08" + }, + { + "leafHashes": [ + "8d6609b2543534810e1219c57c1ba1fb1cc2b1a742726c0aa7f37d03d023401a", + "bc0e3b7d952ad956bb7759de2173582c7500d8f29fe4c1ade6711b39d793581a" + ], + "pubkey": "a86d7fe67f6994113143cc16344cb1e997b8cc8e1dee23651b97c79c09af9add", + "path": "0/0/30/3", + "masterFingerprint": "f65cd869" + } + ] + }, + { + "unknownKeyVals": [], + "tapTree": { + "leaves": [ + { + "script": "204e8d0e8341fe95733afca3dbf1852de77695e287959797c6d8159fbfb02b48b6ad20c160c27afcc4888c593f18dee2d7985657508c03705ab6f0f635d352cbaf6499ac", + "leafVersion": 192, + "depth": 1 + }, + { + "script": "20c160c27afcc4888c593f18dee2d7985657508c03705ab6f0f635d352cbaf6499ad20683608908c8bfe95e38f099068a170140332129eeb03480b1e4806d4f4f43c2cac", + "leafVersion": 192, + "depth": 1 + } + ] + }, + "tapInternalKey": "31b5af14121c7a997e1f58dc7178f8b2589e330fe5a9dc8c16549f90cbbe9ec5", + "tapBip32Derivation": [ + { + "leafHashes": [ + "0f39500a54520faeefe84e5a4f9595943523307ef253a7ddc713189a6f5a32f0" + ], + "pubkey": "4e8d0e8341fe95733afca3dbf1852de77695e287959797c6d8159fbfb02b48b6", + "path": "0/0/40/4", + "masterFingerprint": "f2ef0389" + }, + { + "leafHashes": [ + "0f39500a54520faeefe84e5a4f9595943523307ef253a7ddc713189a6f5a32f0", + "d7a356fb77b18e43bc861580e45c0044c0857e31b1bde76083d9080c33d3ffcf" + ], + "pubkey": "c160c27afcc4888c593f18dee2d7985657508c03705ab6f0f635d352cbaf6499", + "path": "0/0/40/4", + "masterFingerprint": "cc18ae08" + }, + { + "leafHashes": [ + "d7a356fb77b18e43bc861580e45c0044c0857e31b1bde76083d9080c33d3ffcf" + ], + "pubkey": "683608908c8bfe95e38f099068a170140332129eeb03480b1e4806d4f4f43c2c", + "path": "0/0/40/4", + "masterFingerprint": "f65cd869" + } + ] + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoin.halfsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoin.halfsigned.json new file mode 100644 index 0000000000..1276658284 --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoin.halfsigned.json @@ -0,0 +1,407 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006b4830450221008d7a270e793fd336fca10b66129c64c00815dd9d3c4546b12d699ab33520073002207d6c5dd46f5c9c65c08fd295e9b4e7db453779928c765128039634b08ed467a1012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "304402201e79545c011e34b1b872b4b057e66262d86881da6a7f853e7e57a9a4d12c6dc102201a538b2175c7b7161c76da18394220f354ae2729fcc1c902b74c59cd79e0153101" + } + ] + }, + { + "type": "p2shP2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "a9140e26b25d0ceae6f9cff3edaccaa0d1a70da7600f87", + "value": "1000" + }, + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "02a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f035", + "path": "0/0/11/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b", + "path": "0/0/11/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a99", + "path": "0/0/11/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f0352102c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b2102234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a9953ae", + "redeemScript": "002046d4800a1393330196085399ed4a1700b4c2fd38c52188c3a3a91f721f3c9600", + "partialSig": [ + { + "pubkey": "02a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f035", + "signature": "304402205f760c27e7598a9e80a2adaf1d925c67d2e6c24aec773e5cce56d234159ab1e502201ce97d5659681ab881e10acc9c8021587f843a15336c3a8b6fb2efc053928b8501" + } + ] + }, + { + "type": "p2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "0020db0427f1822b4777670e6673c28af02f6da1d5afe71a71f5c9064470b229fafd", + "value": "1000" + }, + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce3", + "path": "0/0/21/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a7911", + "path": "0/0/21/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb418", + "path": "0/0/21/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522103519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce32103a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a79112102e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb41853ae", + "partialSig": [ + { + "pubkey": "03519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce3", + "signature": "3045022100d3b03fb77553144e14171c7b1b517a3bf33df87370b65e337f0a50844562261e0220585bc0913b56ad968161d343f321d77f5f94ff3cf62545932f1509239a15a38e01" + } + ] + }, + { + "type": "p2tr", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "5120394b94d061d7b778fed8a204ce05ed0b44954ec1622f934f84114e1173f33027", + "value": "1000" + }, + "sighashType": 0, + "tapLeafScript": [ + { + "controlBlock": "c0bc17ea2bbf6ff46bd5c0c3780be2a63880eb7bb782b39ed86f29b99f4882994e1b178b1b55d3c6aac7b039e3422dfdb9c9d75d4d6fc908e9743e88b651e48d83", + "script": "207373d723ce0a87f8fdd66843be12dae9d51939f68188d5ad96920bef5f51f496ad20aa88ca194fda43486504b86ac9b71f10fc41de7b1713874839427f74e78c2d5aac", + "leafVersion": 192 + } + ], + "tapBip32Derivation": [ + { + "leafHashes": [ + "83d7f98e9ef0f7336a06f4a0115183a9f996cbac1228e9aa7638f447f0249f30" + ], + "pubkey": "7373d723ce0a87f8fdd66843be12dae9d51939f68188d5ad96920bef5f51f496", + "path": "0/0/31/3", + "masterFingerprint": "f2ef0389" + }, + { + "leafHashes": [ + "83d7f98e9ef0f7336a06f4a0115183a9f996cbac1228e9aa7638f447f0249f30" + ], + "pubkey": "aa88ca194fda43486504b86ac9b71f10fc41de7b1713874839427f74e78c2d5a", + "path": "0/0/31/3", + "masterFingerprint": "f65cd869" + } + ], + "tapScriptSig": [ + { + "pubkey": "7373d723ce0a87f8fdd66843be12dae9d51939f68188d5ad96920bef5f51f496", + "signature": "83b1bb7c6ce5868c8a723c4d350591894809b1335586f60cbfccc6dd4e28b8630a74637b1b242e5a389540db94f23bcbedee9c783830cb2afaff1f2763dbdb7b", + "leafHash": "83d7f98e9ef0f7336a06f4a0115183a9f996cbac1228e9aa7638f447f0249f30" + } + ] + }, + { + "type": "p2trMusig2", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "512029bbfaf4385b10dc2bbe7ab4d50e89cebe8f507968727b19689e2638083cc55b", + "value": "1000" + }, + "sighashType": 0, + "tapLeafScript": [ + { + "controlBlock": "c1a107a403f7a207e7b1f552d3fd3f55f64ae40e149d0d26f4868ad7faec949eb7637e37cac6e5f22347ab68e876b559117d9597ee7b37fb983c2ab9e51023c976", + "script": "20643151ab03f3a97bb86d3592f12f8d25a8026d89cd47342fc1b49c5e2e63478bad20035c9d632db2ddb8f90cf3ccfc931822bcb1f24562b23ae041abf12e54bca64dac", + "leafVersion": 192 + } + ], + "tapBip32Derivation": [ + { + "leafHashes": [ + "429069c8320ff5f1c8ac2a0b1047ff1450b8c0d325e1d12f1c52684d91a3b9fb" + ], + "pubkey": "643151ab03f3a97bb86d3592f12f8d25a8026d89cd47342fc1b49c5e2e63478b", + "path": "0/0/41/4", + "masterFingerprint": "f2ef0389" + }, + { + "leafHashes": [ + "429069c8320ff5f1c8ac2a0b1047ff1450b8c0d325e1d12f1c52684d91a3b9fb" + ], + "pubkey": "035c9d632db2ddb8f90cf3ccfc931822bcb1f24562b23ae041abf12e54bca64d", + "path": "0/0/41/4", + "masterFingerprint": "cc18ae08" + } + ], + "tapScriptSig": [ + { + "pubkey": "643151ab03f3a97bb86d3592f12f8d25a8026d89cd47342fc1b49c5e2e63478b", + "signature": "0a1e013f6d57fb8b248b2b19b08b34c1a3cd5ff6de4acdf19dd4ce0e02d42154856dd569f4668f2abaccbb50ce81a9d2f53efdfb627c2cb14cf92c4189a35da7", + "leafHash": "429069c8320ff5f1c8ac2a0b1047ff1450b8c0d325e1d12f1c52684d91a3b9fb" + } + ] + }, + { + "type": "taprootKeyPathSpend", + "unknownKeyVals": [ + { + "key": "fc05424954474f0115c5815026f6a54b10194fc6980f1866a02d9ec128533c7997cdb4289bf3ef16eb5ad29a85aed24de2880e774caaf624f9cb1be09c67ed4aefbb9b7bc12ddf1a", + "value": "021d978a17486ff9e47c82990269e531fc63981419d4ce73ee8bd2c99661c53953020fdea69e40a3adef3cdc7fa6f3af02f4c9d9e3254503c96a6a2b4aa66e778171" + }, + { + "key": "fc05424954474f02021d978a17486ff9e47c82990269e531fc63981419d4ce73ee8bd2c99661c5395315c5815026f6a54b10194fc6980f1866a02d9ec128533c7997cdb4289bf3ef16", + "value": "03a4aaf46f3a0bc39a73855fa875b2f2f04bdb06235afbaedf857b593dddc63cb402cdb7f1a93ec526282198d834423371757e8f43932d03b9f43c3f7378300ae508" + }, + { + "key": "fc05424954474f02020fdea69e40a3adef3cdc7fa6f3af02f4c9d9e3254503c96a6a2b4aa66e77817115c5815026f6a54b10194fc6980f1866a02d9ec128533c7997cdb4289bf3ef16", + "value": "02826949740dff45d408b1f19d94c720f53411e02c525b28ab3c593b6b530fe0370374b8a0ffcaaaee6b772dac5f7c23ef33670b32ec77c6d41efb3c36df2165a094" + }, + { + "key": "fc05424954474f03021d978a17486ff9e47c82990269e531fc63981419d4ce73ee8bd2c99661c5395315c5815026f6a54b10194fc6980f1866a02d9ec128533c7997cdb4289bf3ef16", + "value": "1cd8a0c0598b0d88f889fdf9a3140f8f088e2187803a2faaee3f13c0163eb76c" + } + ], + "witnessUtxo": { + "script": "512015c5815026f6a54b10194fc6980f1866a02d9ec128533c7997cdb4289bf3ef16", + "value": "1000" + }, + "sighashType": 0, + "tapInternalKey": "eb5ad29a85aed24de2880e774caaf624f9cb1be09c67ed4aefbb9b7bc12ddf1a", + "tapMerkleRoot": "b2a497ea4d0b52f5011ae135d0bbe0d9ebd777bb28e733cbcc13134b73106a1a", + "tapBip32Derivation": [ + { + "leafHashes": [], + "pubkey": "1d978a17486ff9e47c82990269e531fc63981419d4ce73ee8bd2c99661c53953", + "path": "0/0/41/5", + "masterFingerprint": "f2ef0389" + }, + { + "leafHashes": [], + "pubkey": "0fdea69e40a3adef3cdc7fa6f3af02f4c9d9e3254503c96a6a2b4aa66e778171", + "path": "0/0/41/5", + "masterFingerprint": "f65cd869" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006a47304402202a91bae6d5c543ab51ccd0409f54469b539dd2e569f90949b8be637eabd3902802206d38f3d87b598275bef6d910e4614e4452573320ca3ddea150e4901aa3781f7c012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff07e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 1, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3045022100d2a7abb6c0563c3000e37412a9b97f6352d17e40960456fe830d264f0474f8b6022038d5cdbe88a319de5d7b7db8f57d0a92524beb242729b46a617adb530f2fa0d001" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c0", + "path": "0/0/10/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec8", + "path": "0/0/10/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb9654297554", + "path": "0/0/10/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "5221032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c02103545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec82102a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb965429755453ae", + "redeemScript": "0020292b052351a3c6f80d51bae642cc8821182893dbeedabc8f5d0e9677476026c1" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e686900", + "path": "0/0/20/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7", + "path": "0/0/20/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c", + "path": "0/0/20/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e6869002103b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7210295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c53ae" + }, + { + "unknownKeyVals": [], + "tapTree": { + "leaves": [ + { + "script": "2099e43655c2cf3d950362bb6a3a446380e80b7a1f70e52884eb576b1356dc6e40ad20a86d7fe67f6994113143cc16344cb1e997b8cc8e1dee23651b97c79c09af9addac", + "leafVersion": 192, + "depth": 1 + }, + { + "script": "2099e43655c2cf3d950362bb6a3a446380e80b7a1f70e52884eb576b1356dc6e40ad203a093530ea159d73c8ff93099ee0a9bb9e9aa39e0b2b84642d47b4f80d487cbbac", + "leafVersion": 192, + "depth": 2 + }, + { + "script": "203a093530ea159d73c8ff93099ee0a9bb9e9aa39e0b2b84642d47b4f80d487cbbad20a86d7fe67f6994113143cc16344cb1e997b8cc8e1dee23651b97c79c09af9addac", + "leafVersion": 192, + "depth": 2 + } + ] + }, + "tapInternalKey": "8ea58016ecabe3cf8490ec65d779141571fb9e5c12eac905d180f0e102943394", + "tapBip32Derivation": [ + { + "leafHashes": [ + "8d6609b2543534810e1219c57c1ba1fb1cc2b1a742726c0aa7f37d03d023401a", + "cf9605caec3824347bd70ad56e776095fa63f3bc6804d0430cecffe4e253d8ce" + ], + "pubkey": "99e43655c2cf3d950362bb6a3a446380e80b7a1f70e52884eb576b1356dc6e40", + "path": "0/0/30/3", + "masterFingerprint": "f2ef0389" + }, + { + "leafHashes": [ + "cf9605caec3824347bd70ad56e776095fa63f3bc6804d0430cecffe4e253d8ce", + "bc0e3b7d952ad956bb7759de2173582c7500d8f29fe4c1ade6711b39d793581a" + ], + "pubkey": "3a093530ea159d73c8ff93099ee0a9bb9e9aa39e0b2b84642d47b4f80d487cbb", + "path": "0/0/30/3", + "masterFingerprint": "cc18ae08" + }, + { + "leafHashes": [ + "8d6609b2543534810e1219c57c1ba1fb1cc2b1a742726c0aa7f37d03d023401a", + "bc0e3b7d952ad956bb7759de2173582c7500d8f29fe4c1ade6711b39d793581a" + ], + "pubkey": "a86d7fe67f6994113143cc16344cb1e997b8cc8e1dee23651b97c79c09af9add", + "path": "0/0/30/3", + "masterFingerprint": "f65cd869" + } + ] + }, + { + "unknownKeyVals": [], + "tapTree": { + "leaves": [ + { + "script": "204e8d0e8341fe95733afca3dbf1852de77695e287959797c6d8159fbfb02b48b6ad20c160c27afcc4888c593f18dee2d7985657508c03705ab6f0f635d352cbaf6499ac", + "leafVersion": 192, + "depth": 1 + }, + { + "script": "20c160c27afcc4888c593f18dee2d7985657508c03705ab6f0f635d352cbaf6499ad20683608908c8bfe95e38f099068a170140332129eeb03480b1e4806d4f4f43c2cac", + "leafVersion": 192, + "depth": 1 + } + ] + }, + "tapInternalKey": "31b5af14121c7a997e1f58dc7178f8b2589e330fe5a9dc8c16549f90cbbe9ec5", + "tapBip32Derivation": [ + { + "leafHashes": [ + "0f39500a54520faeefe84e5a4f9595943523307ef253a7ddc713189a6f5a32f0" + ], + "pubkey": "4e8d0e8341fe95733afca3dbf1852de77695e287959797c6d8159fbfb02b48b6", + "path": "0/0/40/4", + "masterFingerprint": "f2ef0389" + }, + { + "leafHashes": [ + "0f39500a54520faeefe84e5a4f9595943523307ef253a7ddc713189a6f5a32f0", + "d7a356fb77b18e43bc861580e45c0044c0857e31b1bde76083d9080c33d3ffcf" + ], + "pubkey": "c160c27afcc4888c593f18dee2d7985657508c03705ab6f0f635d352cbaf6499", + "path": "0/0/40/4", + "masterFingerprint": "cc18ae08" + }, + { + "leafHashes": [ + "d7a356fb77b18e43bc861580e45c0044c0857e31b1bde76083d9080c33d3ffcf" + ], + "pubkey": "683608908c8bfe95e38f099068a170140332129eeb03480b1e4806d4f4f43c2c", + "path": "0/0/40/4", + "masterFingerprint": "f65cd869" + } + ] + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoin.unsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoin.unsigned.json new file mode 100644 index 0000000000..e27c789416 --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoin.unsigned.json @@ -0,0 +1,357 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006b4830450221008d7a270e793fd336fca10b66129c64c00815dd9d3c4546b12d699ab33520073002207d6c5dd46f5c9c65c08fd295e9b4e7db453779928c765128039634b08ed467a1012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae" + }, + { + "type": "p2shP2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "a9140e26b25d0ceae6f9cff3edaccaa0d1a70da7600f87", + "value": "1000" + }, + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "02a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f035", + "path": "0/0/11/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b", + "path": "0/0/11/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a99", + "path": "0/0/11/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f0352102c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b2102234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a9953ae", + "redeemScript": "002046d4800a1393330196085399ed4a1700b4c2fd38c52188c3a3a91f721f3c9600" + }, + { + "type": "p2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "0020db0427f1822b4777670e6673c28af02f6da1d5afe71a71f5c9064470b229fafd", + "value": "1000" + }, + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce3", + "path": "0/0/21/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a7911", + "path": "0/0/21/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb418", + "path": "0/0/21/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522103519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce32103a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a79112102e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb41853ae" + }, + { + "type": "p2tr", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "5120394b94d061d7b778fed8a204ce05ed0b44954ec1622f934f84114e1173f33027", + "value": "1000" + }, + "sighashType": 0, + "tapLeafScript": [ + { + "controlBlock": "c0bc17ea2bbf6ff46bd5c0c3780be2a63880eb7bb782b39ed86f29b99f4882994e1b178b1b55d3c6aac7b039e3422dfdb9c9d75d4d6fc908e9743e88b651e48d83", + "script": "207373d723ce0a87f8fdd66843be12dae9d51939f68188d5ad96920bef5f51f496ad20aa88ca194fda43486504b86ac9b71f10fc41de7b1713874839427f74e78c2d5aac", + "leafVersion": 192 + } + ], + "tapBip32Derivation": [ + { + "leafHashes": [ + "83d7f98e9ef0f7336a06f4a0115183a9f996cbac1228e9aa7638f447f0249f30" + ], + "pubkey": "7373d723ce0a87f8fdd66843be12dae9d51939f68188d5ad96920bef5f51f496", + "path": "0/0/31/3", + "masterFingerprint": "f2ef0389" + }, + { + "leafHashes": [ + "83d7f98e9ef0f7336a06f4a0115183a9f996cbac1228e9aa7638f447f0249f30" + ], + "pubkey": "aa88ca194fda43486504b86ac9b71f10fc41de7b1713874839427f74e78c2d5a", + "path": "0/0/31/3", + "masterFingerprint": "f65cd869" + } + ] + }, + { + "type": "p2trMusig2", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "512029bbfaf4385b10dc2bbe7ab4d50e89cebe8f507968727b19689e2638083cc55b", + "value": "1000" + }, + "sighashType": 0, + "tapLeafScript": [ + { + "controlBlock": "c1a107a403f7a207e7b1f552d3fd3f55f64ae40e149d0d26f4868ad7faec949eb7637e37cac6e5f22347ab68e876b559117d9597ee7b37fb983c2ab9e51023c976", + "script": "20643151ab03f3a97bb86d3592f12f8d25a8026d89cd47342fc1b49c5e2e63478bad20035c9d632db2ddb8f90cf3ccfc931822bcb1f24562b23ae041abf12e54bca64dac", + "leafVersion": 192 + } + ], + "tapBip32Derivation": [ + { + "leafHashes": [ + "429069c8320ff5f1c8ac2a0b1047ff1450b8c0d325e1d12f1c52684d91a3b9fb" + ], + "pubkey": "643151ab03f3a97bb86d3592f12f8d25a8026d89cd47342fc1b49c5e2e63478b", + "path": "0/0/41/4", + "masterFingerprint": "f2ef0389" + }, + { + "leafHashes": [ + "429069c8320ff5f1c8ac2a0b1047ff1450b8c0d325e1d12f1c52684d91a3b9fb" + ], + "pubkey": "035c9d632db2ddb8f90cf3ccfc931822bcb1f24562b23ae041abf12e54bca64d", + "path": "0/0/41/4", + "masterFingerprint": "cc18ae08" + } + ] + }, + { + "type": "taprootKeyPathSpend", + "unknownKeyVals": [ + { + "key": "fc05424954474f0115c5815026f6a54b10194fc6980f1866a02d9ec128533c7997cdb4289bf3ef16eb5ad29a85aed24de2880e774caaf624f9cb1be09c67ed4aefbb9b7bc12ddf1a", + "value": "021d978a17486ff9e47c82990269e531fc63981419d4ce73ee8bd2c99661c53953020fdea69e40a3adef3cdc7fa6f3af02f4c9d9e3254503c96a6a2b4aa66e778171" + } + ], + "witnessUtxo": { + "script": "512015c5815026f6a54b10194fc6980f1866a02d9ec128533c7997cdb4289bf3ef16", + "value": "1000" + }, + "sighashType": 0, + "tapInternalKey": "eb5ad29a85aed24de2880e774caaf624f9cb1be09c67ed4aefbb9b7bc12ddf1a", + "tapMerkleRoot": "b2a497ea4d0b52f5011ae135d0bbe0d9ebd777bb28e733cbcc13134b73106a1a", + "tapBip32Derivation": [ + { + "leafHashes": [], + "pubkey": "1d978a17486ff9e47c82990269e531fc63981419d4ce73ee8bd2c99661c53953", + "path": "0/0/41/5", + "masterFingerprint": "f2ef0389" + }, + { + "leafHashes": [], + "pubkey": "0fdea69e40a3adef3cdc7fa6f3af02f4c9d9e3254503c96a6a2b4aa66e778171", + "path": "0/0/41/5", + "masterFingerprint": "f65cd869" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006a47304402202a91bae6d5c543ab51ccd0409f54469b539dd2e569f90949b8be637eabd3902802206d38f3d87b598275bef6d910e4614e4452573320ca3ddea150e4901aa3781f7c012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff07e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 1 + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c0", + "path": "0/0/10/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec8", + "path": "0/0/10/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb9654297554", + "path": "0/0/10/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "5221032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c02103545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec82102a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb965429755453ae", + "redeemScript": "0020292b052351a3c6f80d51bae642cc8821182893dbeedabc8f5d0e9677476026c1" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e686900", + "path": "0/0/20/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7", + "path": "0/0/20/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c", + "path": "0/0/20/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e6869002103b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7210295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c53ae" + }, + { + "unknownKeyVals": [], + "tapTree": { + "leaves": [ + { + "script": "2099e43655c2cf3d950362bb6a3a446380e80b7a1f70e52884eb576b1356dc6e40ad20a86d7fe67f6994113143cc16344cb1e997b8cc8e1dee23651b97c79c09af9addac", + "leafVersion": 192, + "depth": 1 + }, + { + "script": "2099e43655c2cf3d950362bb6a3a446380e80b7a1f70e52884eb576b1356dc6e40ad203a093530ea159d73c8ff93099ee0a9bb9e9aa39e0b2b84642d47b4f80d487cbbac", + "leafVersion": 192, + "depth": 2 + }, + { + "script": "203a093530ea159d73c8ff93099ee0a9bb9e9aa39e0b2b84642d47b4f80d487cbbad20a86d7fe67f6994113143cc16344cb1e997b8cc8e1dee23651b97c79c09af9addac", + "leafVersion": 192, + "depth": 2 + } + ] + }, + "tapInternalKey": "8ea58016ecabe3cf8490ec65d779141571fb9e5c12eac905d180f0e102943394", + "tapBip32Derivation": [ + { + "leafHashes": [ + "8d6609b2543534810e1219c57c1ba1fb1cc2b1a742726c0aa7f37d03d023401a", + "cf9605caec3824347bd70ad56e776095fa63f3bc6804d0430cecffe4e253d8ce" + ], + "pubkey": "99e43655c2cf3d950362bb6a3a446380e80b7a1f70e52884eb576b1356dc6e40", + "path": "0/0/30/3", + "masterFingerprint": "f2ef0389" + }, + { + "leafHashes": [ + "cf9605caec3824347bd70ad56e776095fa63f3bc6804d0430cecffe4e253d8ce", + "bc0e3b7d952ad956bb7759de2173582c7500d8f29fe4c1ade6711b39d793581a" + ], + "pubkey": "3a093530ea159d73c8ff93099ee0a9bb9e9aa39e0b2b84642d47b4f80d487cbb", + "path": "0/0/30/3", + "masterFingerprint": "cc18ae08" + }, + { + "leafHashes": [ + "8d6609b2543534810e1219c57c1ba1fb1cc2b1a742726c0aa7f37d03d023401a", + "bc0e3b7d952ad956bb7759de2173582c7500d8f29fe4c1ade6711b39d793581a" + ], + "pubkey": "a86d7fe67f6994113143cc16344cb1e997b8cc8e1dee23651b97c79c09af9add", + "path": "0/0/30/3", + "masterFingerprint": "f65cd869" + } + ] + }, + { + "unknownKeyVals": [], + "tapTree": { + "leaves": [ + { + "script": "204e8d0e8341fe95733afca3dbf1852de77695e287959797c6d8159fbfb02b48b6ad20c160c27afcc4888c593f18dee2d7985657508c03705ab6f0f635d352cbaf6499ac", + "leafVersion": 192, + "depth": 1 + }, + { + "script": "20c160c27afcc4888c593f18dee2d7985657508c03705ab6f0f635d352cbaf6499ad20683608908c8bfe95e38f099068a170140332129eeb03480b1e4806d4f4f43c2cac", + "leafVersion": 192, + "depth": 1 + } + ] + }, + "tapInternalKey": "31b5af14121c7a997e1f58dc7178f8b2589e330fe5a9dc8c16549f90cbbe9ec5", + "tapBip32Derivation": [ + { + "leafHashes": [ + "0f39500a54520faeefe84e5a4f9595943523307ef253a7ddc713189a6f5a32f0" + ], + "pubkey": "4e8d0e8341fe95733afca3dbf1852de77695e287959797c6d8159fbfb02b48b6", + "path": "0/0/40/4", + "masterFingerprint": "f2ef0389" + }, + { + "leafHashes": [ + "0f39500a54520faeefe84e5a4f9595943523307ef253a7ddc713189a6f5a32f0", + "d7a356fb77b18e43bc861580e45c0044c0857e31b1bde76083d9080c33d3ffcf" + ], + "pubkey": "c160c27afcc4888c593f18dee2d7985657508c03705ab6f0f635d352cbaf6499", + "path": "0/0/40/4", + "masterFingerprint": "cc18ae08" + }, + { + "leafHashes": [ + "d7a356fb77b18e43bc861580e45c0044c0857e31b1bde76083d9080c33d3ffcf" + ], + "pubkey": "683608908c8bfe95e38f099068a170140332129eeb03480b1e4806d4f4f43c2c", + "path": "0/0/40/4", + "masterFingerprint": "f65cd869" + } + ] + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoincash.fullsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoincash.fullsigned.json new file mode 100644 index 0000000000..d999a7d59c --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoincash.fullsigned.json @@ -0,0 +1,80 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a47304402201ea585fcd078b8d1bd09dafaeb73d52087ab176fd167ffd6083e7c527139e6be02201a27c70abaf3101fcd532cc0e9916d83fe1262e2db1258be6d9661a616aa6583012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 65, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "304502210087631f54bea97ba6fb135afc0e6aa5000f405f8c7f4075fb44624fcb100110f102203f3c26a632eef131b32c235537e8d08f0044b2ecfcf4763bbcee40a9ee04e10c41" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "signature": "3045022100cc77cb1d7cc21ef86ed84521781a6359549ba5e6d736e46b48e38181e04e11ef022044803e3cd31f6f7d7bd7911ea907bd0861598e824bdcbeb5e3b03114aafb2c7441" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a473044022014b73c08c0183770986da34a9757eaed867e62d118188a649ce11356da7f10c102202d91e5a60cf22dc2736f4fa52333a276f60ed6d6a332fb6e8ffc94deddbba444012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff02e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 65, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3045022100864e2eb6f32fab5f80e6b321549d0b2d2a335de655be7c734bc2f89bc1f1085a02207b748db3cf0ba9cd4ce564c81da20fa9c3e01f7976cdc05a3704004ce60e921241" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoincash.halfsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoincash.halfsigned.json new file mode 100644 index 0000000000..182359629d --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoincash.halfsigned.json @@ -0,0 +1,76 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a47304402201ea585fcd078b8d1bd09dafaeb73d52087ab176fd167ffd6083e7c527139e6be02201a27c70abaf3101fcd532cc0e9916d83fe1262e2db1258be6d9661a616aa6583012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 65, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "304502210087631f54bea97ba6fb135afc0e6aa5000f405f8c7f4075fb44624fcb100110f102203f3c26a632eef131b32c235537e8d08f0044b2ecfcf4763bbcee40a9ee04e10c41" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a473044022014b73c08c0183770986da34a9757eaed867e62d118188a649ce11356da7f10c102202d91e5a60cf22dc2736f4fa52333a276f60ed6d6a332fb6e8ffc94deddbba444012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff02e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 65, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3045022100864e2eb6f32fab5f80e6b321549d0b2d2a335de655be7c734bc2f89bc1f1085a02207b748db3cf0ba9cd4ce564c81da20fa9c3e01f7976cdc05a3704004ce60e921241" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoincash.unsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoincash.unsigned.json new file mode 100644 index 0000000000..d8477d0b38 --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoincash.unsigned.json @@ -0,0 +1,64 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "cHNidP8BAHwCAAAAAlx5eyWbgnIrhOwGKs52BsAJLUxtcR60t3VnvhO0cMavAAAAAAD/////f7uvM8nANwD38dz5OjQDmJdlZkbCQouPqoi39X0dPZ8BAAAAAP////8BhAMAAAAAAAAXqRTZCUdEBMEko9BMP7/2H6pJz0PFi4cAAAAATwEEiLIeAAAAAAAAAAAAOpIuKfDI6w2ypgSEy9y2MfaxB8nKrj/9zz59LsH2vNADEhSHFfNh2raFpmnUJDHl1tP5c0BNq5yf0blQsnmtdjQEzBiuCE8BBIiyHgAAAAAAAAAAAG0dZW093ZHBlMBFZaNgNwKiEBbO0UomXziYLWJ15ntkA9O6wjE6fGshy7EbFLDRA0H5IsCkA6i9jIfw3IIPNa9uBPZc2GlPAQSIsh4AAAAAAAAAAADLBP1jqzTZD+ZGa4gOKgLM+KhjN0MSmRr4kRsaqrRDNAM27yKP/puO//ugUsMtM0Zg3R+DZs+P5Erlqmcra2KQlQTy7wOJAAEAvQIAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAakcwRAIgHqWF/NB4uNG9Cdr663PVIIerF2/RZ//WCD58UnE55r4CIBonxwq68xAfzVMswOmRbYP+EmLi2xJYvm2WYaYWqmWDASEDvQw0sjc+OHV1zdsQPsA5FsMQRPJQjiYf41l6JoDsvR//////AegDAAAAAAAAF6kUVnzXtE+fOgfDE483v5hLYPu68kOHAAAAAAEDBEEAAAABBGlSIQP29AdkvV1j8gCid4iDrPdelvFQlcmYJjwIcnDQyX5+fyEDX/t6vHAVngRp9LmJptXheFopBBaf8FCy9Gj+XT1du/IhA+FSTX9vxXqz6su2WbeHEGeApHXR20g5UsIxC36aOJdbU64iBgNf+3q8cBWeBGn0uYmm1eF4WikEFp/wULL0aP5dPV278hTMGK4IAAAAAAAAAAABAAAAAAAAACIGA+FSTX9vxXqz6su2WbeHEGeApHXR20g5UsIxC36aOJdbFPZc2GkAAAAAAAAAAAEAAAAAAAAAIgYD9vQHZL1dY/IAoneIg6z3XpbxUJXJmCY8CHJw0Ml+fn8U8u8DiQAAAAAAAAAAAQAAAAAAAAAAAQDfAgAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAABqRzBEAiAUtzwIwBg3cJhto0qXV+rthn5i0RgYimSc4RNW2n8QwQIgLZHlpgzyLcJzb0+lIzOidvYO1tajMvtuj/yU3t27pEQBIQO9DDSyNz44dXXN2xA+wDkWwxBE8lCOJh/jWXomgOy9H/////8C6AMAAAAAAAAZdqkUiDrY0a/9+Kc8va9yN7XFq57CwyaIrOgDAAAAAAAAF6kUILNwlNgqUTRR/wzNnbI6ugW8XvOHAAAAAAEDBEEAAAABBCMhAzbvIo/+m47/+6BSwy0zRmDdH4Nmz4/kSuWqZytrYpCVrAABAGlSIQLMTQ+kEcrCREhvjrLAjgNf90EPRgo1nKf4gQmRvTtCCSEC1y/Q0NkCk0NK1fyhYPJ44DxhRJeqTkJc9FTiwTMPlqshA0TYhBNt9VAgKGX/vGIYx/nIj+bOOclFeYGQuts4p1LxU64iAgLMTQ+kEcrCREhvjrLAjgNf90EPRgo1nKf4gQmRvTtCCRTy7wOJAAAAAAAAAAAAAAAAAAAAACICAtcv0NDZApNDStX8oWDyeOA8YUSXqk5CXPRU4sEzD5arFMwYrggAAAAAAAAAAAAAAAAAAAAAIgIDRNiEE231UCAoZf+8YhjH+ciP5s45yUV5gZC62zinUvEU9lzYaQAAAAAAAAAAAAAAAAAAAAAA", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a47304402201ea585fcd078b8d1bd09dafaeb73d52087ab176fd167ffd6083e7c527139e6be02201a27c70abaf3101fcd532cc0e9916d83fe1262e2db1258be6d9661a616aa6583012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 65, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae" + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a473044022014b73c08c0183770986da34a9757eaed867e62d118188a649ce11356da7f10c102202d91e5a60cf22dc2736f4fa52333a276f60ed6d6a332fb6e8ffc94deddbba444012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff02e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 65 + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoingold.fullsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoingold.fullsigned.json new file mode 100644 index 0000000000..a7b97137d4 --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoingold.fullsigned.json @@ -0,0 +1,198 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a47304402201ea585fcd078b8d1bd09dafaeb73d52087ab176fd167ffd6083e7c527139e6be02201a27c70abaf3101fcd532cc0e9916d83fe1262e2db1258be6d9661a616aa6583012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 65, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "3045022100e0e1ba72052b3f3c4b193b1f21aff66e8bfb821131f9b7680bea781ca6d637430220285ca143f06cd4e32aeac9bff16ef92abb95df0b08649709266686ad4b180d7641" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "signature": "3045022100dd4c0b374aca63840594e44c995835b3630eae3f6111d3eea5faecb49f2e40540220384f3653dce80d9dc2879d508dce3f1ccc8d19b19153737bb23a274941c422d441" + } + ] + }, + { + "type": "p2shP2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "a9140e26b25d0ceae6f9cff3edaccaa0d1a70da7600f87", + "value": "1000" + }, + "sighashType": 65, + "bip32Derivation": [ + { + "pubkey": "02a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f035", + "path": "0/0/11/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b", + "path": "0/0/11/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a99", + "path": "0/0/11/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f0352102c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b2102234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a9953ae", + "redeemScript": "002046d4800a1393330196085399ed4a1700b4c2fd38c52188c3a3a91f721f3c9600", + "partialSig": [ + { + "pubkey": "02a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f035", + "signature": "304402206eb6a084505d7badc1afef554e859c65df31ad5b7bdbc62a14943d5b4f95307102200cba48e9c5676562ab30d73094448a0eb35eee73bfac5311140cd944332dca9b41" + }, + { + "pubkey": "02234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a99", + "signature": "3045022100c3ae94dbb976bcd38566308b4d9350da86ebe044c610b36b913f6476524568cd022044dfd32108f0afe46a63384f42a4990ba73b9ebaddd88801daf3e3ebd83e223c41" + } + ] + }, + { + "type": "p2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "0020db0427f1822b4777670e6673c28af02f6da1d5afe71a71f5c9064470b229fafd", + "value": "1000" + }, + "sighashType": 65, + "bip32Derivation": [ + { + "pubkey": "03519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce3", + "path": "0/0/21/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a7911", + "path": "0/0/21/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb418", + "path": "0/0/21/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522103519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce32103a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a79112102e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb41853ae", + "partialSig": [ + { + "pubkey": "03519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce3", + "signature": "3045022100f8cc6761ff5f70b95b4bb6e50f087be6e3899794b93048f08078c7bce89d5834022011cbeb43e5bd55fc96b39be2bc46112ecd6080fd6f148153dea1dc9cb8dcc5ea41" + }, + { + "pubkey": "02e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb418", + "signature": "3044022071c4e85d66d2ea91d771458924e674b4ca40f6dc804a31db86d80d51eefc46c5022029bb9741909af81f3205edf62c51342ba3c4444907b78f56fac41155ead0cf3b41" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a4730440220522ac04b6830004156683ec4912e0c1517f1eb80337ca6475a1425f80f248c1002200ef79b195b8f1b37ac42a89a67111e1470eba98db566f9018dd2ca0199bfd1c1012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff04e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 65, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3045022100d9fc432b5d223325c038441974d75517582a9f0e8ba10d3188b2092e97c7624e0220028e3307eb02b1916c1a0089ea6ff1a255e6d2bd0eab2c994dfb433d6eea439641" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c0", + "path": "0/0/10/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec8", + "path": "0/0/10/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb9654297554", + "path": "0/0/10/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "5221032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c02103545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec82102a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb965429755453ae", + "redeemScript": "0020292b052351a3c6f80d51bae642cc8821182893dbeedabc8f5d0e9677476026c1" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e686900", + "path": "0/0/20/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7", + "path": "0/0/20/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c", + "path": "0/0/20/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e6869002103b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7210295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c53ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoingold.halfsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoingold.halfsigned.json new file mode 100644 index 0000000000..33424b4e06 --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoingold.halfsigned.json @@ -0,0 +1,186 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a47304402201ea585fcd078b8d1bd09dafaeb73d52087ab176fd167ffd6083e7c527139e6be02201a27c70abaf3101fcd532cc0e9916d83fe1262e2db1258be6d9661a616aa6583012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 65, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "3045022100e0e1ba72052b3f3c4b193b1f21aff66e8bfb821131f9b7680bea781ca6d637430220285ca143f06cd4e32aeac9bff16ef92abb95df0b08649709266686ad4b180d7641" + } + ] + }, + { + "type": "p2shP2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "a9140e26b25d0ceae6f9cff3edaccaa0d1a70da7600f87", + "value": "1000" + }, + "sighashType": 65, + "bip32Derivation": [ + { + "pubkey": "02a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f035", + "path": "0/0/11/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b", + "path": "0/0/11/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a99", + "path": "0/0/11/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f0352102c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b2102234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a9953ae", + "redeemScript": "002046d4800a1393330196085399ed4a1700b4c2fd38c52188c3a3a91f721f3c9600", + "partialSig": [ + { + "pubkey": "02a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f035", + "signature": "304402206eb6a084505d7badc1afef554e859c65df31ad5b7bdbc62a14943d5b4f95307102200cba48e9c5676562ab30d73094448a0eb35eee73bfac5311140cd944332dca9b41" + } + ] + }, + { + "type": "p2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "0020db0427f1822b4777670e6673c28af02f6da1d5afe71a71f5c9064470b229fafd", + "value": "1000" + }, + "sighashType": 65, + "bip32Derivation": [ + { + "pubkey": "03519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce3", + "path": "0/0/21/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a7911", + "path": "0/0/21/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb418", + "path": "0/0/21/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522103519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce32103a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a79112102e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb41853ae", + "partialSig": [ + { + "pubkey": "03519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce3", + "signature": "3045022100f8cc6761ff5f70b95b4bb6e50f087be6e3899794b93048f08078c7bce89d5834022011cbeb43e5bd55fc96b39be2bc46112ecd6080fd6f148153dea1dc9cb8dcc5ea41" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a4730440220522ac04b6830004156683ec4912e0c1517f1eb80337ca6475a1425f80f248c1002200ef79b195b8f1b37ac42a89a67111e1470eba98db566f9018dd2ca0199bfd1c1012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff04e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 65, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3045022100d9fc432b5d223325c038441974d75517582a9f0e8ba10d3188b2092e97c7624e0220028e3307eb02b1916c1a0089ea6ff1a255e6d2bd0eab2c994dfb433d6eea439641" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c0", + "path": "0/0/10/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec8", + "path": "0/0/10/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb9654297554", + "path": "0/0/10/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "5221032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c02103545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec82102a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb965429755453ae", + "redeemScript": "0020292b052351a3c6f80d51bae642cc8821182893dbeedabc8f5d0e9677476026c1" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e686900", + "path": "0/0/20/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7", + "path": "0/0/20/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c", + "path": "0/0/20/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e6869002103b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7210295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c53ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoingold.unsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoingold.unsigned.json new file mode 100644 index 0000000000..8142032dfe --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.bitcoingold.unsigned.json @@ -0,0 +1,162 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a47304402201ea585fcd078b8d1bd09dafaeb73d52087ab176fd167ffd6083e7c527139e6be02201a27c70abaf3101fcd532cc0e9916d83fe1262e2db1258be6d9661a616aa6583012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 65, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae" + }, + { + "type": "p2shP2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "a9140e26b25d0ceae6f9cff3edaccaa0d1a70da7600f87", + "value": "1000" + }, + "sighashType": 65, + "bip32Derivation": [ + { + "pubkey": "02a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f035", + "path": "0/0/11/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b", + "path": "0/0/11/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a99", + "path": "0/0/11/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f0352102c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b2102234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a9953ae", + "redeemScript": "002046d4800a1393330196085399ed4a1700b4c2fd38c52188c3a3a91f721f3c9600" + }, + { + "type": "p2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "0020db0427f1822b4777670e6673c28af02f6da1d5afe71a71f5c9064470b229fafd", + "value": "1000" + }, + "sighashType": 65, + "bip32Derivation": [ + { + "pubkey": "03519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce3", + "path": "0/0/21/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a7911", + "path": "0/0/21/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb418", + "path": "0/0/21/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522103519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce32103a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a79112102e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb41853ae" + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a4730440220522ac04b6830004156683ec4912e0c1517f1eb80337ca6475a1425f80f248c1002200ef79b195b8f1b37ac42a89a67111e1470eba98db566f9018dd2ca0199bfd1c1012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff04e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 65 + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c0", + "path": "0/0/10/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec8", + "path": "0/0/10/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb9654297554", + "path": "0/0/10/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "5221032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c02103545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec82102a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb965429755453ae", + "redeemScript": "0020292b052351a3c6f80d51bae642cc8821182893dbeedabc8f5d0e9677476026c1" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e686900", + "path": "0/0/20/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7", + "path": "0/0/20/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c", + "path": "0/0/20/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e6869002103b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7210295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c53ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dash.fullsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dash.fullsigned.json new file mode 100644 index 0000000000..6a8b97a31a --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dash.fullsigned.json @@ -0,0 +1,80 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006b4830450221008d7a270e793fd336fca10b66129c64c00815dd9d3c4546b12d699ab33520073002207d6c5dd46f5c9c65c08fd295e9b4e7db453779928c765128039634b08ed467a1012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "30450221008efa965c7055588939c15bc3e6336bad8b0d3ccc6804287ee5b6fb72d00bfefc02207c685a7d7145c742ce89c62798babf3f8b414feb0d60a75531f1e43e9b29e8d101" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "signature": "3045022100bfb1edb5d009f56cdd188c5654cddc8dddcc8f45d03c8a73c208e810f971b1e0022004e8a330bcd62c7e4d34871de3414151fa9c62be678453f35f14dadb82d35dea01" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006a4730440220691753a27fe3a3baff26ddc6b3c9cdb2046909cab0e220463f9a0a58d5e1d23e02200c3d4ac45187c47deea2ac437eb415d6543bbbe8870e4a52f2b4c671f14d8317012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff02e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 1, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3045022100bb1445d743db3c846b926dc47ae3c18abc8010f7ba55f385c7cd7094b6d2bdb2022019fab8b02d4a0100ac2ed9a6f3e56912faa4b660d52618f93ccd2a287a6e5b6801" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dash.halfsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dash.halfsigned.json new file mode 100644 index 0000000000..e536f5126a --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dash.halfsigned.json @@ -0,0 +1,76 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006b4830450221008d7a270e793fd336fca10b66129c64c00815dd9d3c4546b12d699ab33520073002207d6c5dd46f5c9c65c08fd295e9b4e7db453779928c765128039634b08ed467a1012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "30450221008efa965c7055588939c15bc3e6336bad8b0d3ccc6804287ee5b6fb72d00bfefc02207c685a7d7145c742ce89c62798babf3f8b414feb0d60a75531f1e43e9b29e8d101" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006a4730440220691753a27fe3a3baff26ddc6b3c9cdb2046909cab0e220463f9a0a58d5e1d23e02200c3d4ac45187c47deea2ac437eb415d6543bbbe8870e4a52f2b4c671f14d8317012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff02e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 1, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3045022100bb1445d743db3c846b926dc47ae3c18abc8010f7ba55f385c7cd7094b6d2bdb2022019fab8b02d4a0100ac2ed9a6f3e56912faa4b660d52618f93ccd2a287a6e5b6801" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dash.unsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dash.unsigned.json new file mode 100644 index 0000000000..e38bf99feb --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dash.unsigned.json @@ -0,0 +1,64 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "cHNidP8BAHwBAAAAAtme+uGZ/E1U5YJajjNoo5Xp6zKZFc+12I3DEVB9GEg2AAAAAAD/////J50715OdJjBhFagFjUqI0QUVoLZ1/IpfiTEcLoYp/sYBAAAAAP////8BhAMAAAAAAAAXqRTZCUdEBMEko9BMP7/2H6pJz0PFi4cAAAAATwEEiLIeAAAAAAAAAAAAOpIuKfDI6w2ypgSEy9y2MfaxB8nKrj/9zz59LsH2vNADEhSHFfNh2raFpmnUJDHl1tP5c0BNq5yf0blQsnmtdjQEzBiuCE8BBIiyHgAAAAAAAAAAAG0dZW093ZHBlMBFZaNgNwKiEBbO0UomXziYLWJ15ntkA9O6wjE6fGshy7EbFLDRA0H5IsCkA6i9jIfw3IIPNa9uBPZc2GlPAQSIsh4AAAAAAAAAAADLBP1jqzTZD+ZGa4gOKgLM+KhjN0MSmRr4kRsaqrRDNAM27yKP/puO//ugUsMtM0Zg3R+DZs+P5Erlqmcra2KQlQTy7wOJAAEAvgEAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAa0gwRQIhAI16Jw55P9M2/KELZhKcZMAIFd2dPEVGsS1pmrM1IAcwAiB9bF3Ub1ycZcCP0pXptOfbRTd5kox2USgDljSwjtRnoQEhA70MNLI3Pjh1dc3bED7AORbDEETyUI4mH+NZeiaA7L0f/////wHoAwAAAAAAABepFFZ817RPnzoHwxOPN7+YS2D7uvJDhwAAAAABAwQBAAAAAQRpUiED9vQHZL1dY/IAoneIg6z3XpbxUJXJmCY8CHJw0Ml+fn8hA1/7erxwFZ4EafS5iabV4XhaKQQWn/BQsvRo/l09XbvyIQPhUk1/b8V6s+rLtlm3hxBngKR10dtIOVLCMQt+mjiXW1OuIgYDX/t6vHAVngRp9LmJptXheFopBBaf8FCy9Gj+XT1du/IUzBiuCAAAAAAAAAAAAQAAAAAAAAAiBgPhUk1/b8V6s+rLtlm3hxBngKR10dtIOVLCMQt+mjiXWxT2XNhpAAAAAAAAAAABAAAAAAAAACIGA/b0B2S9XWPyAKJ3iIOs916W8VCVyZgmPAhycNDJfn5/FPLvA4kAAAAAAAAAAAEAAAAAAAAAAAEA3wEAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAakcwRAIgaRdTon/jo7r/Jt3Gs8nNsgRpCcqw4iBGP5oKWNXh0j4CIAw9SsRRh8R97qKsQ360FdZUO7vohw5KUvK0xnHxTYMXASEDvQw0sjc+OHV1zdsQPsA5FsMQRPJQjiYf41l6JoDsvR//////AugDAAAAAAAAGXapFIg62NGv/finPL2vcje1xauewsMmiKzoAwAAAAAAABepFCCzcJTYKlE0Uf8MzZ2yOroFvF7zhwAAAAABAwQBAAAAAQQjIQM27yKP/puO//ugUsMtM0Zg3R+DZs+P5Erlqmcra2KQlawAAQBpUiECzE0PpBHKwkRIb46ywI4DX/dBD0YKNZyn+IEJkb07QgkhAtcv0NDZApNDStX8oWDyeOA8YUSXqk5CXPRU4sEzD5arIQNE2IQTbfVQIChl/7xiGMf5yI/mzjnJRXmBkLrbOKdS8VOuIgICzE0PpBHKwkRIb46ywI4DX/dBD0YKNZyn+IEJkb07QgkU8u8DiQAAAAAAAAAAAAAAAAAAAAAiAgLXL9DQ2QKTQ0rV/KFg8njgPGFEl6pOQlz0VOLBMw+WqxTMGK4IAAAAAAAAAAAAAAAAAAAAACICA0TYhBNt9VAgKGX/vGIYx/nIj+bOOclFeYGQuts4p1LxFPZc2GkAAAAAAAAAAAAAAAAAAAAAAA==", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006b4830450221008d7a270e793fd336fca10b66129c64c00815dd9d3c4546b12d699ab33520073002207d6c5dd46f5c9c65c08fd295e9b4e7db453779928c765128039634b08ed467a1012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae" + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006a4730440220691753a27fe3a3baff26ddc6b3c9cdb2046909cab0e220463f9a0a58d5e1d23e02200c3d4ac45187c47deea2ac437eb415d6543bbbe8870e4a52f2b4c671f14d8317012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff02e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 1 + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dogecoin.fullsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dogecoin.fullsigned.json new file mode 100644 index 0000000000..6a8b97a31a --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dogecoin.fullsigned.json @@ -0,0 +1,80 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006b4830450221008d7a270e793fd336fca10b66129c64c00815dd9d3c4546b12d699ab33520073002207d6c5dd46f5c9c65c08fd295e9b4e7db453779928c765128039634b08ed467a1012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "30450221008efa965c7055588939c15bc3e6336bad8b0d3ccc6804287ee5b6fb72d00bfefc02207c685a7d7145c742ce89c62798babf3f8b414feb0d60a75531f1e43e9b29e8d101" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "signature": "3045022100bfb1edb5d009f56cdd188c5654cddc8dddcc8f45d03c8a73c208e810f971b1e0022004e8a330bcd62c7e4d34871de3414151fa9c62be678453f35f14dadb82d35dea01" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006a4730440220691753a27fe3a3baff26ddc6b3c9cdb2046909cab0e220463f9a0a58d5e1d23e02200c3d4ac45187c47deea2ac437eb415d6543bbbe8870e4a52f2b4c671f14d8317012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff02e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 1, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3045022100bb1445d743db3c846b926dc47ae3c18abc8010f7ba55f385c7cd7094b6d2bdb2022019fab8b02d4a0100ac2ed9a6f3e56912faa4b660d52618f93ccd2a287a6e5b6801" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dogecoin.halfsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dogecoin.halfsigned.json new file mode 100644 index 0000000000..e536f5126a --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dogecoin.halfsigned.json @@ -0,0 +1,76 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006b4830450221008d7a270e793fd336fca10b66129c64c00815dd9d3c4546b12d699ab33520073002207d6c5dd46f5c9c65c08fd295e9b4e7db453779928c765128039634b08ed467a1012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "30450221008efa965c7055588939c15bc3e6336bad8b0d3ccc6804287ee5b6fb72d00bfefc02207c685a7d7145c742ce89c62798babf3f8b414feb0d60a75531f1e43e9b29e8d101" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006a4730440220691753a27fe3a3baff26ddc6b3c9cdb2046909cab0e220463f9a0a58d5e1d23e02200c3d4ac45187c47deea2ac437eb415d6543bbbe8870e4a52f2b4c671f14d8317012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff02e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 1, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3045022100bb1445d743db3c846b926dc47ae3c18abc8010f7ba55f385c7cd7094b6d2bdb2022019fab8b02d4a0100ac2ed9a6f3e56912faa4b660d52618f93ccd2a287a6e5b6801" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dogecoin.unsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dogecoin.unsigned.json new file mode 100644 index 0000000000..e38bf99feb --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.dogecoin.unsigned.json @@ -0,0 +1,64 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "cHNidP8BAHwBAAAAAtme+uGZ/E1U5YJajjNoo5Xp6zKZFc+12I3DEVB9GEg2AAAAAAD/////J50715OdJjBhFagFjUqI0QUVoLZ1/IpfiTEcLoYp/sYBAAAAAP////8BhAMAAAAAAAAXqRTZCUdEBMEko9BMP7/2H6pJz0PFi4cAAAAATwEEiLIeAAAAAAAAAAAAOpIuKfDI6w2ypgSEy9y2MfaxB8nKrj/9zz59LsH2vNADEhSHFfNh2raFpmnUJDHl1tP5c0BNq5yf0blQsnmtdjQEzBiuCE8BBIiyHgAAAAAAAAAAAG0dZW093ZHBlMBFZaNgNwKiEBbO0UomXziYLWJ15ntkA9O6wjE6fGshy7EbFLDRA0H5IsCkA6i9jIfw3IIPNa9uBPZc2GlPAQSIsh4AAAAAAAAAAADLBP1jqzTZD+ZGa4gOKgLM+KhjN0MSmRr4kRsaqrRDNAM27yKP/puO//ugUsMtM0Zg3R+DZs+P5Erlqmcra2KQlQTy7wOJAAEAvgEAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAa0gwRQIhAI16Jw55P9M2/KELZhKcZMAIFd2dPEVGsS1pmrM1IAcwAiB9bF3Ub1ycZcCP0pXptOfbRTd5kox2USgDljSwjtRnoQEhA70MNLI3Pjh1dc3bED7AORbDEETyUI4mH+NZeiaA7L0f/////wHoAwAAAAAAABepFFZ817RPnzoHwxOPN7+YS2D7uvJDhwAAAAABAwQBAAAAAQRpUiED9vQHZL1dY/IAoneIg6z3XpbxUJXJmCY8CHJw0Ml+fn8hA1/7erxwFZ4EafS5iabV4XhaKQQWn/BQsvRo/l09XbvyIQPhUk1/b8V6s+rLtlm3hxBngKR10dtIOVLCMQt+mjiXW1OuIgYDX/t6vHAVngRp9LmJptXheFopBBaf8FCy9Gj+XT1du/IUzBiuCAAAAAAAAAAAAQAAAAAAAAAiBgPhUk1/b8V6s+rLtlm3hxBngKR10dtIOVLCMQt+mjiXWxT2XNhpAAAAAAAAAAABAAAAAAAAACIGA/b0B2S9XWPyAKJ3iIOs916W8VCVyZgmPAhycNDJfn5/FPLvA4kAAAAAAAAAAAEAAAAAAAAAAAEA3wEAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAakcwRAIgaRdTon/jo7r/Jt3Gs8nNsgRpCcqw4iBGP5oKWNXh0j4CIAw9SsRRh8R97qKsQ360FdZUO7vohw5KUvK0xnHxTYMXASEDvQw0sjc+OHV1zdsQPsA5FsMQRPJQjiYf41l6JoDsvR//////AugDAAAAAAAAGXapFIg62NGv/finPL2vcje1xauewsMmiKzoAwAAAAAAABepFCCzcJTYKlE0Uf8MzZ2yOroFvF7zhwAAAAABAwQBAAAAAQQjIQM27yKP/puO//ugUsMtM0Zg3R+DZs+P5Erlqmcra2KQlawAAQBpUiECzE0PpBHKwkRIb46ywI4DX/dBD0YKNZyn+IEJkb07QgkhAtcv0NDZApNDStX8oWDyeOA8YUSXqk5CXPRU4sEzD5arIQNE2IQTbfVQIChl/7xiGMf5yI/mzjnJRXmBkLrbOKdS8VOuIgICzE0PpBHKwkRIb46ywI4DX/dBD0YKNZyn+IEJkb07QgkU8u8DiQAAAAAAAAAAAAAAAAAAAAAiAgLXL9DQ2QKTQ0rV/KFg8njgPGFEl6pOQlz0VOLBMw+WqxTMGK4IAAAAAAAAAAAAAAAAAAAAACICA0TYhBNt9VAgKGX/vGIYx/nIj+bOOclFeYGQuts4p1LxFPZc2GkAAAAAAAAAAAAAAAAAAAAAAA==", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006b4830450221008d7a270e793fd336fca10b66129c64c00815dd9d3c4546b12d699ab33520073002207d6c5dd46f5c9c65c08fd295e9b4e7db453779928c765128039634b08ed467a1012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae" + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006a4730440220691753a27fe3a3baff26ddc6b3c9cdb2046909cab0e220463f9a0a58d5e1d23e02200c3d4ac45187c47deea2ac437eb415d6543bbbe8870e4a52f2b4c671f14d8317012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff02e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 1 + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.ecash.fullsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.ecash.fullsigned.json new file mode 100644 index 0000000000..d999a7d59c --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.ecash.fullsigned.json @@ -0,0 +1,80 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a47304402201ea585fcd078b8d1bd09dafaeb73d52087ab176fd167ffd6083e7c527139e6be02201a27c70abaf3101fcd532cc0e9916d83fe1262e2db1258be6d9661a616aa6583012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 65, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "304502210087631f54bea97ba6fb135afc0e6aa5000f405f8c7f4075fb44624fcb100110f102203f3c26a632eef131b32c235537e8d08f0044b2ecfcf4763bbcee40a9ee04e10c41" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "signature": "3045022100cc77cb1d7cc21ef86ed84521781a6359549ba5e6d736e46b48e38181e04e11ef022044803e3cd31f6f7d7bd7911ea907bd0861598e824bdcbeb5e3b03114aafb2c7441" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a473044022014b73c08c0183770986da34a9757eaed867e62d118188a649ce11356da7f10c102202d91e5a60cf22dc2736f4fa52333a276f60ed6d6a332fb6e8ffc94deddbba444012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff02e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 65, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3045022100864e2eb6f32fab5f80e6b321549d0b2d2a335de655be7c734bc2f89bc1f1085a02207b748db3cf0ba9cd4ce564c81da20fa9c3e01f7976cdc05a3704004ce60e921241" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.ecash.halfsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.ecash.halfsigned.json new file mode 100644 index 0000000000..182359629d --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.ecash.halfsigned.json @@ -0,0 +1,76 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a47304402201ea585fcd078b8d1bd09dafaeb73d52087ab176fd167ffd6083e7c527139e6be02201a27c70abaf3101fcd532cc0e9916d83fe1262e2db1258be6d9661a616aa6583012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 65, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "304502210087631f54bea97ba6fb135afc0e6aa5000f405f8c7f4075fb44624fcb100110f102203f3c26a632eef131b32c235537e8d08f0044b2ecfcf4763bbcee40a9ee04e10c41" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a473044022014b73c08c0183770986da34a9757eaed867e62d118188a649ce11356da7f10c102202d91e5a60cf22dc2736f4fa52333a276f60ed6d6a332fb6e8ffc94deddbba444012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff02e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 65, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3045022100864e2eb6f32fab5f80e6b321549d0b2d2a335de655be7c734bc2f89bc1f1085a02207b748db3cf0ba9cd4ce564c81da20fa9c3e01f7976cdc05a3704004ce60e921241" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.ecash.unsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.ecash.unsigned.json new file mode 100644 index 0000000000..d8477d0b38 --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.ecash.unsigned.json @@ -0,0 +1,64 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "cHNidP8BAHwCAAAAAlx5eyWbgnIrhOwGKs52BsAJLUxtcR60t3VnvhO0cMavAAAAAAD/////f7uvM8nANwD38dz5OjQDmJdlZkbCQouPqoi39X0dPZ8BAAAAAP////8BhAMAAAAAAAAXqRTZCUdEBMEko9BMP7/2H6pJz0PFi4cAAAAATwEEiLIeAAAAAAAAAAAAOpIuKfDI6w2ypgSEy9y2MfaxB8nKrj/9zz59LsH2vNADEhSHFfNh2raFpmnUJDHl1tP5c0BNq5yf0blQsnmtdjQEzBiuCE8BBIiyHgAAAAAAAAAAAG0dZW093ZHBlMBFZaNgNwKiEBbO0UomXziYLWJ15ntkA9O6wjE6fGshy7EbFLDRA0H5IsCkA6i9jIfw3IIPNa9uBPZc2GlPAQSIsh4AAAAAAAAAAADLBP1jqzTZD+ZGa4gOKgLM+KhjN0MSmRr4kRsaqrRDNAM27yKP/puO//ugUsMtM0Zg3R+DZs+P5Erlqmcra2KQlQTy7wOJAAEAvQIAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAakcwRAIgHqWF/NB4uNG9Cdr663PVIIerF2/RZ//WCD58UnE55r4CIBonxwq68xAfzVMswOmRbYP+EmLi2xJYvm2WYaYWqmWDASEDvQw0sjc+OHV1zdsQPsA5FsMQRPJQjiYf41l6JoDsvR//////AegDAAAAAAAAF6kUVnzXtE+fOgfDE483v5hLYPu68kOHAAAAAAEDBEEAAAABBGlSIQP29AdkvV1j8gCid4iDrPdelvFQlcmYJjwIcnDQyX5+fyEDX/t6vHAVngRp9LmJptXheFopBBaf8FCy9Gj+XT1du/IhA+FSTX9vxXqz6su2WbeHEGeApHXR20g5UsIxC36aOJdbU64iBgNf+3q8cBWeBGn0uYmm1eF4WikEFp/wULL0aP5dPV278hTMGK4IAAAAAAAAAAABAAAAAAAAACIGA+FSTX9vxXqz6su2WbeHEGeApHXR20g5UsIxC36aOJdbFPZc2GkAAAAAAAAAAAEAAAAAAAAAIgYD9vQHZL1dY/IAoneIg6z3XpbxUJXJmCY8CHJw0Ml+fn8U8u8DiQAAAAAAAAAAAQAAAAAAAAAAAQDfAgAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAABqRzBEAiAUtzwIwBg3cJhto0qXV+rthn5i0RgYimSc4RNW2n8QwQIgLZHlpgzyLcJzb0+lIzOidvYO1tajMvtuj/yU3t27pEQBIQO9DDSyNz44dXXN2xA+wDkWwxBE8lCOJh/jWXomgOy9H/////8C6AMAAAAAAAAZdqkUiDrY0a/9+Kc8va9yN7XFq57CwyaIrOgDAAAAAAAAF6kUILNwlNgqUTRR/wzNnbI6ugW8XvOHAAAAAAEDBEEAAAABBCMhAzbvIo/+m47/+6BSwy0zRmDdH4Nmz4/kSuWqZytrYpCVrAABAGlSIQLMTQ+kEcrCREhvjrLAjgNf90EPRgo1nKf4gQmRvTtCCSEC1y/Q0NkCk0NK1fyhYPJ44DxhRJeqTkJc9FTiwTMPlqshA0TYhBNt9VAgKGX/vGIYx/nIj+bOOclFeYGQuts4p1LxU64iAgLMTQ+kEcrCREhvjrLAjgNf90EPRgo1nKf4gQmRvTtCCRTy7wOJAAAAAAAAAAAAAAAAAAAAACICAtcv0NDZApNDStX8oWDyeOA8YUSXqk5CXPRU4sEzD5arFMwYrggAAAAAAAAAAAAAAAAAAAAAIgIDRNiEE231UCAoZf+8YhjH+ciP5s45yUV5gZC62zinUvEU9lzYaQAAAAAAAAAAAAAAAAAAAAAA", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a47304402201ea585fcd078b8d1bd09dafaeb73d52087ab176fd167ffd6083e7c527139e6be02201a27c70abaf3101fcd532cc0e9916d83fe1262e2db1258be6d9661a616aa6583012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 65, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae" + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "02000000010101010101010101010101010101010101010101010101010101010101010101000000006a473044022014b73c08c0183770986da34a9757eaed867e62d118188a649ce11356da7f10c102202d91e5a60cf22dc2736f4fa52333a276f60ed6d6a332fb6e8ffc94deddbba444012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff02e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 65 + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.litecoin.fullsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.litecoin.fullsigned.json new file mode 100644 index 0000000000..27215929d1 --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.litecoin.fullsigned.json @@ -0,0 +1,198 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006b4830450221008d7a270e793fd336fca10b66129c64c00815dd9d3c4546b12d699ab33520073002207d6c5dd46f5c9c65c08fd295e9b4e7db453779928c765128039634b08ed467a1012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "3045022100e9f193db9830d516af5598045b9404d096e89c7a192f77ad220fa3a487308b0002205dea2fd75f700b3beec6e77a4bdd21a6ac9b9a355c1ea958a6864305e203d9fe01" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "signature": "3044022025cd881a29cca29960c0361d18f741681bdb0d8c0f55c1f8d78daf03757ce8af02205d93f55191a736ab373b05cfeb099440234d99a2720188c3ac13150cb6e0ac4c01" + } + ] + }, + { + "type": "p2shP2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "a9140e26b25d0ceae6f9cff3edaccaa0d1a70da7600f87", + "value": "1000" + }, + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "02a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f035", + "path": "0/0/11/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b", + "path": "0/0/11/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a99", + "path": "0/0/11/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f0352102c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b2102234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a9953ae", + "redeemScript": "002046d4800a1393330196085399ed4a1700b4c2fd38c52188c3a3a91f721f3c9600", + "partialSig": [ + { + "pubkey": "02a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f035", + "signature": "3045022100a4bfba654b915a5ca895c5339ce5edab81bd67b65c805997cad9762840d3c7060220021f8ffb89ebea032cb339ecbd8494c84f955ed894963992b4f31e91e8df0a7401" + }, + { + "pubkey": "02234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a99", + "signature": "3044022050f1e19eaec828c7856135b98b6e1af9c600229241ec8507d50dae85ff71823902207c084363fe89081b49d929851b62fa9b9d44c70ec3931e8bb473c8d84e35b2e401" + } + ] + }, + { + "type": "p2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "0020db0427f1822b4777670e6673c28af02f6da1d5afe71a71f5c9064470b229fafd", + "value": "1000" + }, + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce3", + "path": "0/0/21/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a7911", + "path": "0/0/21/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb418", + "path": "0/0/21/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522103519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce32103a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a79112102e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb41853ae", + "partialSig": [ + { + "pubkey": "03519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce3", + "signature": "304402204db56669993c9644b84a54892958b7c15d4897829d19a673f7a337bdfab834c202203c6aca862b8ab1a2a3b570689836f09a191b97bcee9ddd8e19271e7a53f6a80401" + }, + { + "pubkey": "02e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb418", + "signature": "3044022021df7a10ad4a6838c6553897cd6779fa232e89a600058f946a0c4e86a871bb21022049919108dcbda4b4318fb87d5b1c5f929bd9a82f9ef49fadd7b3445a9cf3207001" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006b483045022100ae347b03f4709e3d255f6dbfde83db5ffecd51574db382e366ad1c5357465025022050bad0e6959c77bebaa90e298de536f401278064dbc0f14c2dea64281e82ee12012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff04e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 1, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3045022100e5bcb83e8f66f30bf6ea886184ef607557cb166ca1d3de3253c293e1865c775202205577e3e2acd1fd7ea93b1e624169db1d52e97030680bc062095cf7ac667d4eca01" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c0", + "path": "0/0/10/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec8", + "path": "0/0/10/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb9654297554", + "path": "0/0/10/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "5221032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c02103545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec82102a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb965429755453ae", + "redeemScript": "0020292b052351a3c6f80d51bae642cc8821182893dbeedabc8f5d0e9677476026c1" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e686900", + "path": "0/0/20/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7", + "path": "0/0/20/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c", + "path": "0/0/20/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e6869002103b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7210295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c53ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.litecoin.halfsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.litecoin.halfsigned.json new file mode 100644 index 0000000000..3f80a18d44 --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.litecoin.halfsigned.json @@ -0,0 +1,186 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006b4830450221008d7a270e793fd336fca10b66129c64c00815dd9d3c4546b12d699ab33520073002207d6c5dd46f5c9c65c08fd295e9b4e7db453779928c765128039634b08ed467a1012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "3045022100e9f193db9830d516af5598045b9404d096e89c7a192f77ad220fa3a487308b0002205dea2fd75f700b3beec6e77a4bdd21a6ac9b9a355c1ea958a6864305e203d9fe01" + } + ] + }, + { + "type": "p2shP2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "a9140e26b25d0ceae6f9cff3edaccaa0d1a70da7600f87", + "value": "1000" + }, + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "02a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f035", + "path": "0/0/11/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b", + "path": "0/0/11/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a99", + "path": "0/0/11/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f0352102c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b2102234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a9953ae", + "redeemScript": "002046d4800a1393330196085399ed4a1700b4c2fd38c52188c3a3a91f721f3c9600", + "partialSig": [ + { + "pubkey": "02a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f035", + "signature": "3045022100a4bfba654b915a5ca895c5339ce5edab81bd67b65c805997cad9762840d3c7060220021f8ffb89ebea032cb339ecbd8494c84f955ed894963992b4f31e91e8df0a7401" + } + ] + }, + { + "type": "p2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "0020db0427f1822b4777670e6673c28af02f6da1d5afe71a71f5c9064470b229fafd", + "value": "1000" + }, + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce3", + "path": "0/0/21/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a7911", + "path": "0/0/21/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb418", + "path": "0/0/21/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522103519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce32103a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a79112102e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb41853ae", + "partialSig": [ + { + "pubkey": "03519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce3", + "signature": "304402204db56669993c9644b84a54892958b7c15d4897829d19a673f7a337bdfab834c202203c6aca862b8ab1a2a3b570689836f09a191b97bcee9ddd8e19271e7a53f6a80401" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006b483045022100ae347b03f4709e3d255f6dbfde83db5ffecd51574db382e366ad1c5357465025022050bad0e6959c77bebaa90e298de536f401278064dbc0f14c2dea64281e82ee12012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff04e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 1, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3045022100e5bcb83e8f66f30bf6ea886184ef607557cb166ca1d3de3253c293e1865c775202205577e3e2acd1fd7ea93b1e624169db1d52e97030680bc062095cf7ac667d4eca01" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c0", + "path": "0/0/10/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec8", + "path": "0/0/10/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb9654297554", + "path": "0/0/10/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "5221032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c02103545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec82102a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb965429755453ae", + "redeemScript": "0020292b052351a3c6f80d51bae642cc8821182893dbeedabc8f5d0e9677476026c1" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e686900", + "path": "0/0/20/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7", + "path": "0/0/20/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c", + "path": "0/0/20/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e6869002103b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7210295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c53ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.litecoin.unsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.litecoin.unsigned.json new file mode 100644 index 0000000000..daf5e041a7 --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.litecoin.unsigned.json @@ -0,0 +1,162 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "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", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006b4830450221008d7a270e793fd336fca10b66129c64c00815dd9d3c4546b12d699ab33520073002207d6c5dd46f5c9c65c08fd295e9b4e7db453779928c765128039634b08ed467a1012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff01e80300000000000017a914567cd7b44f9f3a07c3138f37bf984b60fbbaf2438700000000", + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae" + }, + { + "type": "p2shP2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "a9140e26b25d0ceae6f9cff3edaccaa0d1a70da7600f87", + "value": "1000" + }, + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "02a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f035", + "path": "0/0/11/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b", + "path": "0/0/11/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a99", + "path": "0/0/11/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102a585f3fab49b5ef95346d932221a221bd55ec191f15533e0f270b3582574f0352102c4f7866cfd5996bc1068a96313ca15e9ccb5e984583a07340dddae7af014605b2102234531bc9119a36a7946e95aeb74915dd087c7b44f517b8eeeb73f89a0ff1a9953ae", + "redeemScript": "002046d4800a1393330196085399ed4a1700b4c2fd38c52188c3a3a91f721f3c9600" + }, + { + "type": "p2wsh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "0020db0427f1822b4777670e6673c28af02f6da1d5afe71a71f5c9064470b229fafd", + "value": "1000" + }, + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce3", + "path": "0/0/21/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a7911", + "path": "0/0/21/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb418", + "path": "0/0/21/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522103519e572ab468560d1f8d0f87699d098308e166905f5dcae4390f060faa8f1ce32103a05bc26391221d685e18daaff36a1e95c34f99451253c16cd42650db278a79112102e80a08885c353676b820cdc290e6d81a0ac95a03e8abbf4bb7aeebfb70feb41853ae" + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "nonWitnessUtxo": "01000000010101010101010101010101010101010101010101010101010101010101010101000000006b483045022100ae347b03f4709e3d255f6dbfde83db5ffecd51574db382e366ad1c5357465025022050bad0e6959c77bebaa90e298de536f401278064dbc0f14c2dea64281e82ee12012103bd0c34b2373e387575cddb103ec03916c31044f2508e261fe3597a2680ecbd1fffffffff04e8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace8030000000000001976a914883ad8d1affdf8a73cbdaf7237b5c5ab9ec2c32688ace80300000000000017a91420b37094d82a513451ff0ccd9db23aba05bc5ef38700000000", + "sighashType": 1 + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c0", + "path": "0/0/10/1", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec8", + "path": "0/0/10/1", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "02a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb9654297554", + "path": "0/0/10/1", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "5221032e11f98552685fe907f31f9e7260c906e8de700f0c3c9f2f1410623dcdfd19c02103545757ce35cf1edfb13b9e2c4a2f9ce00ab8c36bee12bca2337ce583fd28aec82102a3cfb6665fe44f7520b6a1b4de1a4d46643b385eb1fd1e0ab70efb965429755453ae", + "redeemScript": "0020292b052351a3c6f80d51bae642cc8821182893dbeedabc8f5d0e9677476026c1" + }, + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e686900", + "path": "0/0/20/2", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "03b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7", + "path": "0/0/20/2", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c", + "path": "0/0/20/2", + "masterFingerprint": "f65cd869" + } + ], + "witnessScript": "522102d6d1140bd2c0f914cb30c992aa2fcd283f471a9281ceec9ec6c5bc448e6869002103b855a8758eda6863648445a9d3cfb95388aca174621ad98f099a1c4f9326fec7210295f7b527d2ea537a4d42830410abf4340731bf9b20e54972001933b9aa23536c53ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.zcash.fullsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.zcash.fullsigned.json new file mode 100644 index 0000000000..c4ee67621e --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.zcash.fullsigned.json @@ -0,0 +1,86 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "cHNidP8BAI8EAACAhSAviQIBB9zNCDF0dfUWO8QpQQ9z3Z/LZm/Irya0Dt4Kb9gTWgAAAAAA/////9zG/5Z7pfP2yyTWKwtgO9K9CnmeqYqTLnAuKoKMdoA/AQAAAAD/////AYQDAAAAAAAAF6kU2QlHRATBJKPQTD+/9h+qSc9DxYuHAAAAAAAAAAAAAAAAAAAAAAAAAE8BBIiyHgAAAAAAAAAAADqSLinwyOsNsqYEhMvctjH2sQfJyq4//c8+fS7B9rzQAxIUhxXzYdq2haZp1CQx5dbT+XNATaucn9G5ULJ5rXY0BMwYrghPAQSIsh4AAAAAAAAAAABtHWVtPd2RwZTARWWjYDcCohAWztFKJl84mC1ideZ7ZAPTusIxOnxrIcuxGxSw0QNB+SLApAOovYyH8NyCDzWvbgT2XNhpTwEEiLIeAAAAAAAAAAAAywT9Y6s02Q/mRmuIDioCzPioYzdDEpka+JEbGqq0QzQDNu8ij/6bjv/7oFLDLTNGYN0fg2bPj+RK5apnK2tikJUE8u8DiQABASDoAwAAAAAAABepFFZ817RPnzoHwxOPN7+YS2D7uvJDhyICA+FSTX9vxXqz6su2WbeHEGeApHXR20g5UsIxC36aOJdbSDBFAiEA8l0JX7fFIXphnq7n0jeMaonq7+IaCkkWGyXZcMl1l+QCIGwvnm1sL3/AX21b6v/D/UpaG00ASf1wJVHvUBnH3faaASICA/b0B2S9XWPyAKJ3iIOs916W8VCVyZgmPAhycNDJfn5/SDBFAiEAkWXvn88VxZjdoK4DsAzGWAuhgPHNtxoH3B9UqDNMLk8CIBM5f78LkenZ/50WfxWuRaTjQ24Unoeq/jKB62Ued7Q2AQEDBAEAAAABBGlSIQP29AdkvV1j8gCid4iDrPdelvFQlcmYJjwIcnDQyX5+fyEDX/t6vHAVngRp9LmJptXheFopBBaf8FCy9Gj+XT1du/IhA+FSTX9vxXqz6su2WbeHEGeApHXR20g5UsIxC36aOJdbU64iBgNf+3q8cBWeBGn0uYmm1eF4WikEFp/wULL0aP5dPV278hTMGK4IAAAAAAAAAAABAAAAAAAAACIGA+FSTX9vxXqz6su2WbeHEGeApHXR20g5UsIxC36aOJdbFPZc2GkAAAAAAAAAAAEAAAAAAAAAIgYD9vQHZL1dY/IAoneIg6z3XpbxUJXJmCY8CHJw0Ml+fn8U8u8DiQAAAAAAAAAAAQAAAAAAAAAAAQEg6AMAAAAAAAAXqRQgs3CU2CpRNFH/DM2dsjq6Bbxe84ciAgM27yKP/puO//ugUsMtM0Zg3R+DZs+P5Erlqmcra2KQlUcwRAIgWeyxOotIYmnQMgWSTaHh35lPjBfHJU7uuRkTJ+3NwyACIAlB/co5zrBlVD8thq3Jiz59SIZzMgkf+SC+HMAg49L1AQEDBAEAAAABBCMhAzbvIo/+m47/+6BSwy0zRmDdH4Nmz4/kSuWqZytrYpCVrAABAGlSIQLMTQ+kEcrCREhvjrLAjgNf90EPRgo1nKf4gQmRvTtCCSEC1y/Q0NkCk0NK1fyhYPJ44DxhRJeqTkJc9FTiwTMPlqshA0TYhBNt9VAgKGX/vGIYx/nIj+bOOclFeYGQuts4p1LxU64iAgLMTQ+kEcrCREhvjrLAjgNf90EPRgo1nKf4gQmRvTtCCRTy7wOJAAAAAAAAAAAAAAAAAAAAACICAtcv0NDZApNDStX8oWDyeOA8YUSXqk5CXPRU4sEzD5arFMwYrggAAAAAAAAAAAAAAAAAAAAAIgIDRNiEE231UCAoZf+8YhjH+ciP5s45yUV5gZC62zinUvEU9lzYaQAAAAAAAAAAAAAAAAAAAAAA", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "a914567cd7b44f9f3a07c3138f37bf984b60fbbaf24387", + "value": "1000" + }, + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "30450221009165ef9fcf15c598dda0ae03b00cc6580ba180f1cdb71a07dc1f54a8334c2e4f022013397fbf0b91e9d9ff9d167f15ae45a4e3436e149e87aafe3281eb651e77b43601" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "signature": "3045022100f25d095fb7c5217a619eaee7d2378c6a89eaefe21a0a49161b25d970c97597e402206c2f9e6d6c2f7fc05f6d5beaffc3fd4a5a1b4d0049fd702551ef5019c7ddf69a01" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "witnessUtxo": { + "script": "a91420b37094d82a513451ff0ccd9db23aba05bc5ef387", + "value": "1000" + }, + "sighashType": 1, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3044022059ecb13a8b486269d03205924da1e1df994f8c17c7254eeeb9191327edcdc32002200941fdca39ceb065543f2d86adc98b3e7d48867332091ff920be1cc020e3d2f501" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.zcash.halfsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.zcash.halfsigned.json new file mode 100644 index 0000000000..076450f4fc --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.zcash.halfsigned.json @@ -0,0 +1,82 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "cHNidP8BAI8EAACAhSAviQIBB9zNCDF0dfUWO8QpQQ9z3Z/LZm/Irya0Dt4Kb9gTWgAAAAAA/////9zG/5Z7pfP2yyTWKwtgO9K9CnmeqYqTLnAuKoKMdoA/AQAAAAD/////AYQDAAAAAAAAF6kU2QlHRATBJKPQTD+/9h+qSc9DxYuHAAAAAAAAAAAAAAAAAAAAAAAAAE8BBIiyHgAAAAAAAAAAADqSLinwyOsNsqYEhMvctjH2sQfJyq4//c8+fS7B9rzQAxIUhxXzYdq2haZp1CQx5dbT+XNATaucn9G5ULJ5rXY0BMwYrghPAQSIsh4AAAAAAAAAAABtHWVtPd2RwZTARWWjYDcCohAWztFKJl84mC1ideZ7ZAPTusIxOnxrIcuxGxSw0QNB+SLApAOovYyH8NyCDzWvbgT2XNhpTwEEiLIeAAAAAAAAAAAAywT9Y6s02Q/mRmuIDioCzPioYzdDEpka+JEbGqq0QzQDNu8ij/6bjv/7oFLDLTNGYN0fg2bPj+RK5apnK2tikJUE8u8DiQABASDoAwAAAAAAABepFFZ817RPnzoHwxOPN7+YS2D7uvJDhyICA/b0B2S9XWPyAKJ3iIOs916W8VCVyZgmPAhycNDJfn5/SDBFAiEAkWXvn88VxZjdoK4DsAzGWAuhgPHNtxoH3B9UqDNMLk8CIBM5f78LkenZ/50WfxWuRaTjQ24Unoeq/jKB62Ued7Q2AQEDBAEAAAABBGlSIQP29AdkvV1j8gCid4iDrPdelvFQlcmYJjwIcnDQyX5+fyEDX/t6vHAVngRp9LmJptXheFopBBaf8FCy9Gj+XT1du/IhA+FSTX9vxXqz6su2WbeHEGeApHXR20g5UsIxC36aOJdbU64iBgNf+3q8cBWeBGn0uYmm1eF4WikEFp/wULL0aP5dPV278hTMGK4IAAAAAAAAAAABAAAAAAAAACIGA+FSTX9vxXqz6su2WbeHEGeApHXR20g5UsIxC36aOJdbFPZc2GkAAAAAAAAAAAEAAAAAAAAAIgYD9vQHZL1dY/IAoneIg6z3XpbxUJXJmCY8CHJw0Ml+fn8U8u8DiQAAAAAAAAAAAQAAAAAAAAAAAQEg6AMAAAAAAAAXqRQgs3CU2CpRNFH/DM2dsjq6Bbxe84ciAgM27yKP/puO//ugUsMtM0Zg3R+DZs+P5Erlqmcra2KQlUcwRAIgWeyxOotIYmnQMgWSTaHh35lPjBfHJU7uuRkTJ+3NwyACIAlB/co5zrBlVD8thq3Jiz59SIZzMgkf+SC+HMAg49L1AQEDBAEAAAABBCMhAzbvIo/+m47/+6BSwy0zRmDdH4Nmz4/kSuWqZytrYpCVrAABAGlSIQLMTQ+kEcrCREhvjrLAjgNf90EPRgo1nKf4gQmRvTtCCSEC1y/Q0NkCk0NK1fyhYPJ44DxhRJeqTkJc9FTiwTMPlqshA0TYhBNt9VAgKGX/vGIYx/nIj+bOOclFeYGQuts4p1LxU64iAgLMTQ+kEcrCREhvjrLAjgNf90EPRgo1nKf4gQmRvTtCCRTy7wOJAAAAAAAAAAAAAAAAAAAAACICAtcv0NDZApNDStX8oWDyeOA8YUSXqk5CXPRU4sEzD5arFMwYrggAAAAAAAAAAAAAAAAAAAAAIgIDRNiEE231UCAoZf+8YhjH+ciP5s45yUV5gZC62zinUvEU9lzYaQAAAAAAAAAAAAAAAAAAAAAA", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "a914567cd7b44f9f3a07c3138f37bf984b60fbbaf24387", + "value": "1000" + }, + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae", + "partialSig": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "signature": "30450221009165ef9fcf15c598dda0ae03b00cc6580ba180f1cdb71a07dc1f54a8334c2e4f022013397fbf0b91e9d9ff9d167f15ae45a4e3436e149e87aafe3281eb651e77b43601" + } + ] + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "witnessUtxo": { + "script": "a91420b37094d82a513451ff0ccd9db23aba05bc5ef387", + "value": "1000" + }, + "sighashType": 1, + "partialSig": [ + { + "pubkey": "0336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095", + "signature": "3044022059ecb13a8b486269d03205924da1e1df994f8c17c7254eeeb9191327edcdc32002200941fdca39ceb065543f2d86adc98b3e7d48867332091ff920be1cc020e3d2f501" + } + ] + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.zcash.unsigned.json b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.zcash.unsigned.json new file mode 100644 index 0000000000..69085a6d11 --- /dev/null +++ b/modules/utxo-lib/test/bitgo/fixtures/psbt/psbt.zcash.unsigned.json @@ -0,0 +1,70 @@ +{ + "walletKeys": [ + "xprv9s21ZrQH143K45bDYc9c3aEaGiTK9aPtjgtHg6wBdkryBjix1KKXRCszxPcFPejLT9tdLgNe8E8AuQXK2fy8KhNPeLAZsGoX8w9KS2PkacL", + "xprv9s21ZrQH143K2eBLSVNk4zhjDzqzqM29aS9cjr4CcoNrKLYwLHtwgTURSk7RPV3cH9zNZQeR1zGw3MEwSjvARSfWEGpxfaBmduhW3TKsH5g", + "xprv9s21ZrQH143K39N9shF9hAsTwh1FvQuBk8UVsZVwr4XtpqF7stCu2LH358NLuqkkK6pu1Af7TJHr5FZERQoLLtnC7wkoM9sdFo1HuP7dWuv" + ], + "psbtBase64": "cHNidP8BAI8EAACAhSAviQIBB9zNCDF0dfUWO8QpQQ9z3Z/LZm/Irya0Dt4Kb9gTWgAAAAAA/////9zG/5Z7pfP2yyTWKwtgO9K9CnmeqYqTLnAuKoKMdoA/AQAAAAD/////AYQDAAAAAAAAF6kU2QlHRATBJKPQTD+/9h+qSc9DxYuHAAAAAAAAAAAAAAAAAAAAAAAAAE8BBIiyHgAAAAAAAAAAADqSLinwyOsNsqYEhMvctjH2sQfJyq4//c8+fS7B9rzQAxIUhxXzYdq2haZp1CQx5dbT+XNATaucn9G5ULJ5rXY0BMwYrghPAQSIsh4AAAAAAAAAAABtHWVtPd2RwZTARWWjYDcCohAWztFKJl84mC1ideZ7ZAPTusIxOnxrIcuxGxSw0QNB+SLApAOovYyH8NyCDzWvbgT2XNhpTwEEiLIeAAAAAAAAAAAAywT9Y6s02Q/mRmuIDioCzPioYzdDEpka+JEbGqq0QzQDNu8ij/6bjv/7oFLDLTNGYN0fg2bPj+RK5apnK2tikJUE8u8DiQABASDoAwAAAAAAABepFFZ817RPnzoHwxOPN7+YS2D7uvJDhwEDBAEAAAABBGlSIQP29AdkvV1j8gCid4iDrPdelvFQlcmYJjwIcnDQyX5+fyEDX/t6vHAVngRp9LmJptXheFopBBaf8FCy9Gj+XT1du/IhA+FSTX9vxXqz6su2WbeHEGeApHXR20g5UsIxC36aOJdbU64iBgNf+3q8cBWeBGn0uYmm1eF4WikEFp/wULL0aP5dPV278hTMGK4IAAAAAAAAAAABAAAAAAAAACIGA+FSTX9vxXqz6su2WbeHEGeApHXR20g5UsIxC36aOJdbFPZc2GkAAAAAAAAAAAEAAAAAAAAAIgYD9vQHZL1dY/IAoneIg6z3XpbxUJXJmCY8CHJw0Ml+fn8U8u8DiQAAAAAAAAAAAQAAAAAAAAAAAQEg6AMAAAAAAAAXqRQgs3CU2CpRNFH/DM2dsjq6Bbxe84cBAwQBAAAAAQQjIQM27yKP/puO//ugUsMtM0Zg3R+DZs+P5Erlqmcra2KQlawAAQBpUiECzE0PpBHKwkRIb46ywI4DX/dBD0YKNZyn+IEJkb07QgkhAtcv0NDZApNDStX8oWDyeOA8YUSXqk5CXPRU4sEzD5arIQNE2IQTbfVQIChl/7xiGMf5yI/mzjnJRXmBkLrbOKdS8VOuIgICzE0PpBHKwkRIb46ywI4DX/dBD0YKNZyn+IEJkb07QgkU8u8DiQAAAAAAAAAAAAAAAAAAAAAiAgLXL9DQ2QKTQ0rV/KFg8njgPGFEl6pOQlz0VOLBMw+WqxTMGK4IAAAAAAAAAAAAAAAAAAAAACICA0TYhBNt9VAgKGX/vGIYx/nIj+bOOclFeYGQuts4p1LxFPZc2GkAAAAAAAAAAAAAAAAAAAAAAA==", + "inputs": [ + { + "type": "p2sh", + "unknownKeyVals": [], + "witnessUtxo": { + "script": "a914567cd7b44f9f3a07c3138f37bf984b60fbbaf24387", + "value": "1000" + }, + "sighashType": 1, + "bip32Derivation": [ + { + "pubkey": "03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f", + "path": "0/0/1/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2", + "path": "0/0/1/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b", + "path": "0/0/1/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522103f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f21035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf22103e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b53ae" + }, + { + "type": "p2shP2pk", + "unknownKeyVals": [], + "redeemScript": "210336ef228ffe9b8efffba052c32d334660dd1f8366cf8fe44ae5aa672b6b629095ac", + "witnessUtxo": { + "script": "a91420b37094d82a513451ff0ccd9db23aba05bc5ef387", + "value": "1000" + }, + "sighashType": 1 + } + ], + "outputs": [ + { + "unknownKeyVals": [], + "bip32Derivation": [ + { + "pubkey": "02cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b4209", + "path": "0/0/0/0", + "masterFingerprint": "f2ef0389" + }, + { + "pubkey": "02d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab", + "path": "0/0/0/0", + "masterFingerprint": "cc18ae08" + }, + { + "pubkey": "0344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f1", + "path": "0/0/0/0", + "masterFingerprint": "f65cd869" + } + ], + "redeemScript": "522102cc4d0fa411cac244486f8eb2c08e035ff7410f460a359ca7f8810991bd3b42092102d72fd0d0d90293434ad5fca160f278e03c614497aa4e425cf454e2c1330f96ab210344d884136df550202865ffbc6218c7f9c88fe6ce39c945798190badb38a752f153ae" + } + ] +} diff --git a/modules/utxo-lib/test/bitgo/psbt/SignVerifyPsbtAndTx.ts b/modules/utxo-lib/test/bitgo/psbt/SignVerifyPsbtAndTx.ts index 6234202a42..1e2818da87 100644 --- a/modules/utxo-lib/test/bitgo/psbt/SignVerifyPsbtAndTx.ts +++ b/modules/utxo-lib/test/bitgo/psbt/SignVerifyPsbtAndTx.ts @@ -6,8 +6,11 @@ import { getSignatureValidationArrayPsbt, getStrictSignatureCount, getStrictSignatureCounts, + PsbtInput, + PsbtOutput, RootWalletKeys, Triple, + UtxoPsbt, UtxoTransaction, } from '../../../src/bitgo'; import { BIP32Interface } from '@bitgo/secp256k1'; @@ -15,10 +18,10 @@ import { constructPsbt, constructTxnBuilder, getDefaultWalletKeys, - Input, + Input as TestUtilInput, InputScriptType, inputScriptTypes, - Output, + Output as TestUtilOutput, outputScriptTypes, TxnInput, txnInputScriptTypes, @@ -28,11 +31,12 @@ import { import { getNetworkList, getNetworkName, isMainnet, Network, networks } from '../../../src'; import { isSupportedScriptType } from '../../../src/bitgo/outputScripts'; import { SignatureTargetType } from './Psbt'; +import { getFixture } from '../../fixture.util'; const rootWalletKeys = getDefaultWalletKeys(); const signs = ['unsigned', 'halfsigned', 'fullsigned'] as const; -const neutratedRootWalletKeys = new RootWalletKeys( +const rootWalletKeysXpubs = new RootWalletKeys( rootWalletKeys.triple.map((bip32) => bip32.neutered()) as Triple, rootWalletKeys.derivationPrefixes ); @@ -57,14 +61,50 @@ function signCount(sign: SignatureTargetType) { return sign === 'unsigned' ? 0 : sign === 'halfsigned' ? 1 : 2; } -function runPsbt(network: Network, sign: SignatureTargetType, inputs: Input[], outputs: Output[]) { +// normalize buffers to hex +function toFixture(obj: unknown) { + if (obj === null || obj === undefined) { + return obj; + } + if (typeof obj === 'bigint') { + return obj.toString(); + } + if (Buffer.isBuffer(obj)) { + return obj.toString('hex'); + } + if (Array.isArray(obj)) { + return obj.map(toFixture); + } + if (typeof obj === 'object') { + return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, toFixture(value)])); + } + return obj; +} + +function getFixturePsbtInputs(psbt: UtxoPsbt, inputs: TestUtilInput[]) { + if (inputs.length !== psbt.data.inputs.length) { + throw new Error('inputs length mismatch'); + } + return psbt.data.inputs.map((input: PsbtInput, index: number) => + toFixture({ type: inputs[index].scriptType, ...input }) + ); +} + +function getFixturePsbtOutputs(psbt: UtxoPsbt) { + return psbt.data.outputs.map((output: PsbtOutput) => toFixture(output)); +} + +function runPsbt(network: Network, sign: SignatureTargetType, inputs: TestUtilInput[], outputs: TestUtilOutput[]) { const coin = getNetworkName(network); const signatureCount = signCount(sign); + const inputTypes = inputs.map((input) => input.scriptType); + + describe(`psbt build, sign and verify for ${coin} ${inputTypes.join('-')} ${sign}`, function () { + let psbt: UtxoPsbt; - describe(`psbt build, sign and verify for ${coin} ${sign}`, function () { it(`getSignatureValidationArray with globalXpub ${coin} ${sign}`, function () { - const psbt = constructPsbt(inputs, outputs, network, rootWalletKeys, sign); - addXpubsToPsbt(psbt, neutratedRootWalletKeys); + psbt = constructPsbt(inputs, outputs, network, rootWalletKeys, sign, { deterministic: true }); + addXpubsToPsbt(psbt, rootWalletKeysXpubs); psbt.data.inputs.forEach((input, inputIndex) => { const isP2shP2pk = inputs[inputIndex].scriptType === 'p2shP2pk'; const expectedSigValid = getSigValidArray(inputs[inputIndex].scriptType, sign); @@ -78,13 +118,24 @@ function runPsbt(network: Network, sign: SignatureTargetType, inputs: Input[], o }); }); + it('matches fixture', async function () { + const fixture = { + walletKeys: rootWalletKeys.triple.map((xpub) => xpub.toBase58()), + psbtBase64: psbt.toBase64(), + inputs: getFixturePsbtInputs(psbt, inputs), + outputs: getFixturePsbtOutputs(psbt), + }; + const filename = [`psbt`, coin, sign, 'json'].join('.'); + assert.deepStrictEqual(fixture, await getFixture(`${__dirname}/../fixtures/psbt/${filename}`, fixture)); + }); + it(`getSignatureValidationArray with rootNodes ${coin} ${sign}`, function () { const psbt = constructPsbt(inputs, outputs, network, rootWalletKeys, sign); - addXpubsToPsbt(psbt, neutratedRootWalletKeys); + addXpubsToPsbt(psbt, rootWalletKeysXpubs); psbt.data.inputs.forEach((input, inputIndex) => { const isP2shP2pk = inputs[inputIndex].scriptType === 'p2shP2pk'; const expectedSigValid = getSigValidArray(inputs[inputIndex].scriptType, sign); - psbt.getSignatureValidationArray(inputIndex, { rootNodes: neutratedRootWalletKeys.triple }).forEach((sv, i) => { + psbt.getSignatureValidationArray(inputIndex, { rootNodes: rootWalletKeysXpubs.triple }).forEach((sv, i) => { if (isP2shP2pk && sign !== 'unsigned' && i === 0) { assert.strictEqual(sv, true); } else { @@ -96,7 +147,7 @@ function runPsbt(network: Network, sign: SignatureTargetType, inputs: Input[], o it(`getSignatureValidationArrayPsbt ${coin} ${sign}`, function () { const psbt = constructPsbt(inputs, outputs, network, rootWalletKeys, sign); - const sigValidations = getSignatureValidationArrayPsbt(psbt, neutratedRootWalletKeys); + const sigValidations = getSignatureValidationArrayPsbt(psbt, rootWalletKeysXpubs); psbt.data.inputs.forEach((input, inputIndex) => { const expectedSigValid = getSigValidArray(inputs[inputIndex].scriptType, sign); const sigValid = sigValidations.find((sv) => sv[0] === inputIndex); diff --git a/modules/utxo-lib/test/fixture.util.ts b/modules/utxo-lib/test/fixture.util.ts index f9773d207b..67574e9468 100644 --- a/modules/utxo-lib/test/fixture.util.ts +++ b/modules/utxo-lib/test/fixture.util.ts @@ -1,6 +1,6 @@ import * as assert from 'assert'; +import * as fs from 'fs'; import * as mpath from 'path'; -import * as fs from 'fs-extra'; function bufferAsHex(v: unknown): unknown { // You would think that you could use `Buffer.isBuffer(v)` here but you would be mistaken @@ -21,16 +21,16 @@ export async function readFixture(path: string, defaultValue: T): Promise path = path.replace('bitcoinTestnet4', 'bitcoinPublicSignet'); try { - await fs.access(mpath.dirname(path)); + await fs.promises.access(mpath.dirname(path)); } catch (e) { - await fs.mkdirp(mpath.dirname(path)); + await fs.promises.mkdir(mpath.dirname(path), { recursive: true }); } try { - return JSON.parse(await fs.readFile(path, 'utf8')) as T; + return JSON.parse(await fs.promises.readFile(path, 'utf8')) as T; } catch (e) { if (e.code === 'ENOENT') { - await fs.writeFile(path, toPrettyJSON(defaultValue)); + await fs.promises.writeFile(path, toPrettyJSON(defaultValue)); throw new Error(`wrote defaults, please check contents and re-run tests`); } @@ -46,3 +46,96 @@ export async function readFixture(path: string, defaultValue: T): Promise export function assertEqualJSON(a: T, b: T): void { assert.deepStrictEqual(JSON.parse(toPrettyJSON(a)), JSON.parse(toPrettyJSON(b))); } + +type FixtureEncoding = 'json' | 'hex' | 'txt'; + +function isNodeJsError(e: unknown): e is NodeJS.ErrnoException { + return e instanceof Error && typeof (e as NodeJS.ErrnoException).code === 'string'; +} + +function fixtureEncoding(path: string): FixtureEncoding { + if (path.endsWith('.json')) { + return 'json'; + } + if (path.endsWith('.hex')) { + return 'hex'; + } + if (path.endsWith('.txt')) { + return 'txt'; + } + throw new Error(`unknown fixture encoding for ${path}`); +} + +function decodeFixture(raw: string, encoding: FixtureEncoding): unknown { + switch (encoding) { + case 'json': + return JSON.parse(raw); + case 'hex': + return Buffer.from(raw, 'hex'); + case 'txt': + return raw; + } +} + +function encodeFixture(value: unknown, encoding: FixtureEncoding): string { + switch (encoding) { + case 'json': + return JSON.stringify(value, null, 2) + '\n'; + case 'hex': + if (!Buffer.isBuffer(value)) { + throw new Error(`expected Buffer, got ${typeof value}`); + } + return value.toString('hex'); + case 'txt': + if (typeof value !== 'string') { + throw new Error(`expected string, got ${typeof value}`); + } + return value; + } +} + +/** + * Return fixture described in `path`. + * + * If file does not exist and `defaultValue` is provided, writes defaultValue to `path` and throws an error. + * + * @param path + * @param defaultValue + * @return T - fixture content + */ +export async function getFixture(path: string, defaultValue?: T | (() => Promise)): Promise { + try { + await fs.promises.stat(mpath.dirname(path)); + } catch (e) { + if (isNodeJsError(e) && e.code === 'ENOENT') { + throw new Error(`fixture directory ${mpath.dirname(path)} not found, please create it first`); + } + throw e; + } + + const encoding = fixtureEncoding(path); + + try { + return decodeFixture(await fs.promises.readFile(path, 'utf8'), encoding) as T; + } catch (e) { + if (isNodeJsError(e) && e.code === 'ENOENT') { + if (process.env.WRITE_FIXTURES === '0') { + throw new Error(`fixture ${path} not found, WRITE_FIXTURES=0`); + } + if (defaultValue === undefined) { + throw new Error(`fixture ${path} not found and no default value given`); + } + if (typeof defaultValue === 'function') { + defaultValue = await (defaultValue as () => Promise)(); + } + await fs.promises.writeFile(path, encodeFixture(defaultValue, encoding)); + throw new Error(`wrote default value for ${path}, please inspect and restart test`); + } + + throw e; + } +} + +export function jsonNormalize(v: T): T { + return JSON.parse(JSON.stringify(v)) as T; +} diff --git a/modules/utxo-staking/package.json b/modules/utxo-staking/package.json index a1a9a8a7e4..bdf844da92 100644 --- a/modules/utxo-staking/package.json +++ b/modules/utxo-staking/package.json @@ -43,8 +43,8 @@ }, "type": "commonjs", "dependencies": { - "@babylonlabs-io/babylon-proto-ts": "1.0.0", - "@bitgo/babylonlabs-io-btc-staking-ts": "^2.4.1", + "@babylonlabs-io/babylon-proto-ts": "1.7.2", + "@bitgo/babylonlabs-io-btc-staking-ts": "^3.0.0", "@bitgo/utxo-core": "^1.20.4", "@bitgo/utxo-lib": "^11.11.1", "@bitgo/wasm-miniscript": "2.0.0-beta.7", diff --git a/modules/utxo-staking/src/babylon/delegationMessage.ts b/modules/utxo-staking/src/babylon/delegationMessage.ts index fd3fc40e1c..89368d5388 100644 --- a/modules/utxo-staking/src/babylon/delegationMessage.ts +++ b/modules/utxo-staking/src/babylon/delegationMessage.ts @@ -174,6 +174,14 @@ export function getBtcProviderForECKey( throw new Error(`unexpected signing step: ${options.action.name}`); } }, + + /** + * This function is only used by btc-staking-ts to create a staking expansion registration + * transaction, which we do not currently support. + */ + async getTransactionHex(txid: string): Promise { + throw new Error(`Unsupported operation getTransactionHex (txid=${txid})`); + }, }; } type Result = { @@ -252,6 +260,22 @@ export function toStakingTransaction(tx: TransactionLike): bitcoinjslib.Transact return bitcoinjslib.Transaction.fromHex(tx.toHex()); } +/** + * As of babylonlabs-io/btc-staking-ts v1.5.7, the BTC delegation message creation functions support two message types: + * - MsgCreateBTCDelegation + * - MsgBtcStakeExpand + * + * BitGo still only supports MsgCreateBTCDelegation, so we need to check the message type here. + * + * @param msg - the message to check + * @return `true` if the message is of type MsgCreateBTCDelegation + */ +function isMsgBtcStakeExpand( + msg: babylonProtobuf.btcstakingtx.MsgCreateBTCDelegation | babylonProtobuf.btcstakingtx.MsgBtcStakeExpand +) { + return 'previousStakingTxHash' in msg; +} + /* * This is mostly lifted from * https://github.com/babylonlabs-io/btc-staking-ts/blob/v0.4.0-rc.2/src/staking/manager.ts#L100-L172 @@ -270,7 +294,7 @@ export async function createDelegationMessageWithTransaction( throw new Error('Invalid Babylon address'); } // Create delegation message without including inclusion proof - return manager.createBtcDelegationMsg( + const msg = await manager.createBtcDelegationMsg( channel, staking, { @@ -283,6 +307,14 @@ export async function createDelegationMessageWithTransaction( staking.stakerInfo, staking.params ); + + // It shouldn't be possible for us to create a MsgBtcStakeExpand here because that only gets created when + // we pass channel = delegation:expand into createBtcDelegationMsg, which we cannot do. + if (isMsgBtcStakeExpand(msg.value)) { + throw new Error('MsgBtcStakeExpand is not supported'); + } + + return { ...msg, value: msg.value as babylonProtobuf.btcstakingtx.MsgCreateBTCDelegation }; } export async function createUnsignedPreStakeRegistrationBabylonTransactionWithBtcProvider( diff --git a/scripts/vendor-github-repo.ts b/scripts/vendor-github-repo.ts index b82754de94..e31b1e23e6 100644 --- a/scripts/vendor-github-repo.ts +++ b/scripts/vendor-github-repo.ts @@ -1,4 +1,4 @@ -import { execa, ResultPromise } from 'execa'; +import execa, { ExecaChildProcess } from 'execa'; import fs from 'fs/promises'; import tmp from 'tmp'; import yargs from 'yargs'; @@ -91,7 +91,7 @@ async function fetchArchive(lib: GithubSource, outfile: string): Promise { await fs.writeFile(outfile, Buffer.from(await result.arrayBuffer())); } -function pipe(cmd: ResultPromise): ResultPromise { +function pipe(cmd: ExecaChildProcess): ExecaChildProcess { cmd.stdout?.pipe(process.stdout); cmd.stderr?.pipe(process.stderr); return cmd; @@ -191,6 +191,30 @@ const vendorConfigs: VendorConfig[] = [ end: '8d84d9b02af73d7c216d87aceca3dec0baabfecf', }, }, + { + org: 'babylonlabs-io', + repo: 'btc-staking-ts', + tag: 'v2.5.7', + targetDir: 'modules/babylonlabs-io-btc-staking-ts', + removeFiles: [ + '.eslintrc.json', + '.github/', + '.husky/', + '.npmrc', + '.nvmrc', + '.prettierignore', + '.prettierrc.json', + 'docs/', + 'tests/', + '.releaserc.json', + '.commitlint.config.cjs', + 'README.md', + ], + cherryPick: { + start: '161a937c4303d8273922ebfd04640d2391aca246', + end: '8d84d9b02af73d7c216d87aceca3dec0baabfecf', + }, + }, ]; function getMatches(name: string, version: string | undefined): VendorConfig[] { diff --git a/yarn.lock b/yarn.lock index f0041d058e..8cf443687f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -909,6 +909,16 @@ dependencies: "@bufbuild/protobuf" "^2.2.0" +"@babylonlabs-io/babylon-proto-ts@1.7.2": + version "1.7.2" + resolved "https://registry.npmjs.org/@babylonlabs-io/babylon-proto-ts/-/babylon-proto-ts-1.7.2.tgz#7acbd0b38c7512216a10dc754d9354187f23de51" + integrity sha512-10WVqrXA9nIE8Pipmg0Y+ywT3ODonNoM1h3Hmu2oxc8F7IQBokx76WyLvVoY1Y7NxVuLm0uV9iEKZbp+YWwf9Q== + dependencies: + "@bufbuild/protobuf" "^2.2.0" + "@cosmjs/proto-signing" "^0.33.1" + "@cosmjs/stargate" "^0.33.1" + "@cosmjs/tendermint-rpc" "^0.33.1" + "@bitcoin-js/tiny-secp256k1-asmjs@2.2.3": version "2.2.3" resolved "https://registry.npmjs.org/@bitcoin-js/tiny-secp256k1-asmjs/-/tiny-secp256k1-asmjs-2.2.3.tgz" @@ -1168,6 +1178,16 @@ "@cosmjs/math" "^0.29.5" "@cosmjs/utils" "^0.29.5" +"@cosmjs/amino@^0.33.1": + version "0.33.1" + resolved "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.33.1.tgz#0d4957b2e755af8392627c0c0f72bee129dcdcf3" + integrity sha512-WfWiBf2EbIWpwKG9AOcsIIkR717SY+JdlXM/SL/bI66BdrhniAF+/ZNis9Vo9HF6lP2UU5XrSmFA4snAvEgdrg== + dependencies: + "@cosmjs/crypto" "^0.33.1" + "@cosmjs/encoding" "^0.33.1" + "@cosmjs/math" "^0.33.1" + "@cosmjs/utils" "^0.33.1" + "@cosmjs/crypto@^0.29.5": version "0.29.5" resolved "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.29.5.tgz" @@ -1194,6 +1214,19 @@ elliptic "^6.5.4" libsodium-wrappers "^0.7.6" +"@cosmjs/crypto@^0.33.1": + version "0.33.1" + resolved "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.33.1.tgz#761b1623e4abe8af4cbf7ca92639561314f04c3b" + integrity sha512-U4kGIj/SNBzlb2FGgA0sMR0MapVgJUg8N+oIAiN5+vl4GZ3aefmoL1RDyTrFS/7HrB+M+MtHsxC0tvEu4ic/zA== + dependencies: + "@cosmjs/encoding" "^0.33.1" + "@cosmjs/math" "^0.33.1" + "@cosmjs/utils" "^0.33.1" + "@noble/hashes" "^1" + bn.js "^5.2.0" + elliptic "^6.6.1" + libsodium-wrappers-sumo "^0.7.11" + "@cosmjs/encoding@^0.29.5": version "0.29.5" resolved "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.29.5.tgz" @@ -1212,7 +1245,7 @@ bech32 "^1.1.4" readonly-date "^1.0.0" -"@cosmjs/encoding@^0.33.0": +"@cosmjs/encoding@^0.33.0", "@cosmjs/encoding@^0.33.1": version "0.33.1" resolved "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.33.1.tgz" integrity sha512-nuNxf29fUcQE14+1p//VVQDwd1iau5lhaW/7uMz7V2AH3GJbFJoJVaKvVyZvdFk+Cnu+s3wCqgq4gJkhRCJfKw== @@ -1229,6 +1262,14 @@ "@cosmjs/stream" "^0.29.5" xstream "^11.14.0" +"@cosmjs/json-rpc@^0.33.1": + version "0.33.1" + resolved "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.33.1.tgz#a5b8459605750fa7d38c05aa6009a92010c0d042" + integrity sha512-T6VtWzecpmuTuMRGZWuBYHsMF/aznWCYUt/cGMWNSz7DBPipVd0w774PKpxXzpEbyt5sr61NiuLXc+Az15S/Cw== + dependencies: + "@cosmjs/stream" "^0.33.1" + xstream "^11.14.0" + "@cosmjs/math@^0.29.5": version "0.29.5" resolved "https://registry.npmjs.org/@cosmjs/math/-/math-0.29.5.tgz" @@ -1243,6 +1284,13 @@ dependencies: bn.js "^5.2.0" +"@cosmjs/math@^0.33.1": + version "0.33.1" + resolved "https://registry.npmjs.org/@cosmjs/math/-/math-0.33.1.tgz#04ae4cfdb05f04f1b13e908f9551ca85b13ba4d4" + integrity sha512-ytGkWdKFCPiiBU5eqjHNd59djPpIsOjbr2CkNjlnI1Zmdj+HDkSoD9MUGpz9/RJvRir5IvsXqdE05x8EtoQkJA== + dependencies: + bn.js "^5.2.0" + "@cosmjs/proto-signing@^0.29.5": version "0.29.5" resolved "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.29.5.tgz" @@ -1256,6 +1304,18 @@ cosmjs-types "^0.5.2" long "^4.0.0" +"@cosmjs/proto-signing@^0.33.1": + version "0.33.1" + resolved "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.33.1.tgz#b084eb86410486cff30da7de34a636421db90ca8" + integrity sha512-Sv4W+MxX+0LVnd+2rU4Fw1HRsmMwSVSYULj7pRkij3wnPwUlTVoJjmKFgKz13ooIlfzPrz/dnNjGp/xnmXChFQ== + dependencies: + "@cosmjs/amino" "^0.33.1" + "@cosmjs/crypto" "^0.33.1" + "@cosmjs/encoding" "^0.33.1" + "@cosmjs/math" "^0.33.1" + "@cosmjs/utils" "^0.33.1" + cosmjs-types "^0.9.0" + "@cosmjs/socket@^0.29.5": version "0.29.5" resolved "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.29.5.tgz" @@ -1266,6 +1326,16 @@ ws "^7" xstream "^11.14.0" +"@cosmjs/socket@^0.33.1": + version "0.33.1" + resolved "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.33.1.tgz#2402487e7c70c8a5c801bd3189a58a09da786513" + integrity sha512-KzAeorten6Vn20sMiM6NNWfgc7jbyVo4Zmxev1FXa5EaoLCZy48cmT3hJxUJQvJP/lAy8wPGEjZ/u4rmF11x9A== + dependencies: + "@cosmjs/stream" "^0.33.1" + isomorphic-ws "^4.0.1" + ws "^7" + xstream "^11.14.0" + "@cosmjs/stargate@^0.29.5": version "0.29.5" resolved "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.29.5.tgz" @@ -1284,6 +1354,20 @@ protobufjs "~6.11.3" xstream "^11.14.0" +"@cosmjs/stargate@^0.33.1": + version "0.33.1" + resolved "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.33.1.tgz#13972f710942ac728474051be4f9754814ccfb52" + integrity sha512-CnJ1zpSiaZgkvhk+9aTp5IPmgWn2uo+cNEBN8VuD9sD6BA0V4DMjqe251cNFLiMhkGtiE5I/WXFERbLPww3k8g== + dependencies: + "@cosmjs/amino" "^0.33.1" + "@cosmjs/encoding" "^0.33.1" + "@cosmjs/math" "^0.33.1" + "@cosmjs/proto-signing" "^0.33.1" + "@cosmjs/stream" "^0.33.1" + "@cosmjs/tendermint-rpc" "^0.33.1" + "@cosmjs/utils" "^0.33.1" + cosmjs-types "^0.9.0" + "@cosmjs/stream@^0.29.5": version "0.29.5" resolved "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.29.5.tgz" @@ -1291,6 +1375,13 @@ dependencies: xstream "^11.14.0" +"@cosmjs/stream@^0.33.1": + version "0.33.1" + resolved "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.33.1.tgz#2e928eb68c52253e64ab56a3047cd8039b66abde" + integrity sha512-bMUvEENjeQPSTx+YRzVsWT1uFIdHRcf4brsc14SOoRQ/j5rOJM/aHfsf/BmdSAnYbdOQ3CMKj/8nGAQ7xUdn7w== + dependencies: + xstream "^11.14.0" + "@cosmjs/tendermint-rpc@^0.29.5": version "0.29.5" resolved "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.29.5.tgz" @@ -1307,6 +1398,22 @@ readonly-date "^1.0.0" xstream "^11.14.0" +"@cosmjs/tendermint-rpc@^0.33.1": + version "0.33.1" + resolved "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.33.1.tgz#5ab5b0b63e585badaa5827aef7e9e3d18695630a" + integrity sha512-22klDFq2MWnf//C8+rZ5/dYatr6jeGT+BmVbutXYfAK9fmODbtFcumyvB6uWaEORWfNukl8YK1OLuaWezoQvxA== + dependencies: + "@cosmjs/crypto" "^0.33.1" + "@cosmjs/encoding" "^0.33.1" + "@cosmjs/json-rpc" "^0.33.1" + "@cosmjs/math" "^0.33.1" + "@cosmjs/socket" "^0.33.1" + "@cosmjs/stream" "^0.33.1" + "@cosmjs/utils" "^0.33.1" + axios "^1.6.0" + readonly-date "^1.0.0" + xstream "^11.14.0" + "@cosmjs/utils@^0.29.5": version "0.29.5" resolved "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.29.5.tgz" @@ -1317,6 +1424,11 @@ resolved "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.30.1.tgz" integrity sha512-KvvX58MGMWh7xA+N+deCfunkA/ZNDvFLw4YbOmX3f/XBIkqrVY7qlotfy2aNb1kgp6h4B6Yc8YawJPDTfvWX7g== +"@cosmjs/utils@^0.33.1": + version "0.33.1" + resolved "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.33.1.tgz#8882cd26172cb5b0b692c179407d6c3904493fed" + integrity sha512-UnLHDY6KMmC+UXf3Ufyh+onE19xzEXjT4VZ504Acmk4PXxqyvG4cCPprlKUFnGUX7f0z8Or9MAOHXBx41uHBcg== + "@csstools/postcss-cascade-layers@^1.1.1": version "1.1.1" resolved "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz" @@ -7211,7 +7323,7 @@ aws4@^1.8.0: resolved "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz" integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== -axios@0.25.0, axios@0.27.2, axios@1.7.4, axios@^0.21.2, axios@^0.26.1, axios@^1.12.0, axios@^1.8.3: +axios@0.25.0, axios@0.27.2, axios@1.7.4, axios@^0.21.2, axios@^0.26.1, axios@^1.12.0, axios@^1.6.0, axios@^1.8.3: version "1.12.1" resolved "https://registry.npmjs.org/axios/-/axios-1.12.1.tgz#0747b39c5b615f81f93f2c138e6d82a71426937f" integrity sha512-Kn4kbSXpkFHCGE6rBFNwIv0GQs4AvDT80jlveJDKFxjbTYMUeB4QtsdPCv6H8Cm19Je7IU6VFtRl2zWZI0rudQ== @@ -8990,6 +9102,11 @@ cosmjs-types@^0.6.1: long "^4.0.0" protobufjs "~6.11.2" +cosmjs-types@^0.9.0: + version "0.9.0" + resolved "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.9.0.tgz#c3bc482d28c7dfa25d1445093fdb2d9da1f6cfcc" + integrity sha512-MN/yUe6mkJwHnCFfsNPeCfXVhyxHYW6c/xDUzrSbBycYzw++XvWDMJArXp2pLdgD6FQ8DW79vkPjeNKVrXaHeQ== + crc-32@^1.2.0: version "1.2.2" resolved "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz" @@ -13994,7 +14111,7 @@ libsodium-sumo@^0.7.15: resolved "https://registry.npmjs.org/libsodium-sumo/-/libsodium-sumo-0.7.15.tgz" integrity sha512-5tPmqPmq8T8Nikpm1Nqj0hBHvsLFCXvdhBFV7SGOitQPZAA6jso8XoL0r4L7vmfKXr486fiQInvErHtEvizFMw== -libsodium-wrappers-sumo@^0.7.9: +libsodium-wrappers-sumo@^0.7.11, libsodium-wrappers-sumo@^0.7.9: version "0.7.15" resolved "https://registry.npmjs.org/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.15.tgz" integrity sha512-aSWY8wKDZh5TC7rMvEdTHoyppVq/1dTSAeAR7H6pzd6QRT3vQWcT5pGwCotLcpPEOLXX6VvqihSPkpEhYAjANA==