Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions packages/wasm-utxo/js/fixedScriptWallet/BitGoPsbt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,14 @@ import { type BIP32Arg, BIP32 } from "../bip32.js";
import { type ECPairArg, ECPair } from "../ecpair.js";
import type { UtxolibName } from "../utxolibCompat.js";
import type { CoinName } from "../coinName.js";
import type { InputScriptType } from "./scriptType.js";

export type { InputScriptType };

export type NetworkName = UtxolibName | CoinName;

export type ScriptId = { chain: number; index: number };

export type InputScriptType =
| "p2shP2pk"
| "p2sh"
| "p2shP2wsh"
| "p2wsh"
| "p2trLegacy"
| "p2trMusig2ScriptPath"
| "p2trMusig2KeyPath";

export type OutPoint = {
txid: string;
vout: number;
Expand Down
36 changes: 35 additions & 1 deletion packages/wasm-utxo/js/fixedScriptWallet/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { FixedScriptWalletNamespace } from "../wasm/wasm_utxo.js";
import type { CoinName } from "../coinName.js";

export { RootWalletKeys, type WalletKeysArg, type IWalletKeys } from "./RootWalletKeys.js";
export { ReplayProtection, type ReplayProtectionArg } from "./ReplayProtection.js";
export { outputScript, address } from "./address.js";
export { Dimensions } from "./Dimensions.js";
export { type OutputScriptType, type InputScriptType, type ScriptType } from "./scriptType.js";

// Bitcoin-like PSBT (for all non-Zcash networks)
export {
BitGoPsbt,
type NetworkName,
type ScriptId,
type InputScriptType,
type ParsedInput,
type ParsedOutput,
type ParsedTransaction,
Expand All @@ -26,3 +29,34 @@ export {
type ZcashNetworkName,
type CreateEmptyZcashOptions,
} from "./ZcashBitGoPsbt.js";

import type { ScriptType } from "./scriptType.js";

/**
* Check if a network supports a given fixed-script wallet script type
*
* @param coin - Coin name (e.g., "btc", "ltc", "doge")
* @param scriptType - Output script type or input script type to check
* @returns `true` if the network supports the script type, `false` otherwise
*
* @example
* ```typescript
* // Bitcoin supports all script types
* supportsScriptType("btc", "p2tr"); // true
*
* // Litecoin supports segwit but not taproot
* supportsScriptType("ltc", "p2wsh"); // true
* supportsScriptType("ltc", "p2tr"); // false
*
* // Dogecoin only supports legacy scripts
* supportsScriptType("doge", "p2sh"); // true
* supportsScriptType("doge", "p2wsh"); // false
*
* // Also works with input script types
* supportsScriptType("btc", "p2trMusig2KeyPath"); // true
* supportsScriptType("doge", "p2trLegacy"); // false
* ```
*/
export function supportsScriptType(coin: CoinName, scriptType: ScriptType): boolean {
return FixedScriptWalletNamespace.supports_script_type(coin, scriptType);
}
32 changes: 32 additions & 0 deletions packages/wasm-utxo/js/fixedScriptWallet/scriptType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Fixed-script wallet output script types (2-of-3 multisig)
*
* This type represents the abstract script type, independent of chain (external/internal).
* Use this for checking network support or when you need the script type without derivation info.
*/
export type OutputScriptType =
| "p2sh"
| "p2shP2wsh"
| "p2wsh"
| "p2tr" // alias for p2trLegacy
| "p2trLegacy"
| "p2trMusig2";

/**
* Input script types for fixed-script wallets
*
* These are more specific than output types and include single-sig and taproot variants.
*/
export type InputScriptType =
| "p2shP2pk"
| "p2sh"
| "p2shP2wsh"
| "p2wsh"
| "p2trLegacy"
| "p2trMusig2ScriptPath"
| "p2trMusig2KeyPath";

/**
* Union of all script types that can be checked for network support
*/
export type ScriptType = OutputScriptType | InputScriptType;
10 changes: 10 additions & 0 deletions packages/wasm-utxo/src/address/networks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use super::{
ZCASH_TEST,
};
use crate::bitcoin::Script;
use crate::fixed_script_wallet::wallet_scripts::OutputScriptType;
use crate::networks::Network;
use miniscript::bitcoin::WitnessVersion;

Expand Down Expand Up @@ -120,6 +121,15 @@ impl OutputScriptSupport {
}
Ok(())
}

/// Check if the network supports a given fixed-script wallet script type
pub fn supports_script_type(&self, script_type: OutputScriptType) -> bool {
match script_type {
OutputScriptType::P2sh => true, // all networks support legacy scripts
OutputScriptType::P2shP2wsh | OutputScriptType::P2wsh => self.segwit,
OutputScriptType::P2trLegacy | OutputScriptType::P2trMusig2 => self.taproot,
}
}
}

impl Network {
Expand Down
25 changes: 6 additions & 19 deletions packages/wasm-utxo/src/fixed_script_wallet/bitgo_psbt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,16 +184,13 @@ fn get_default_sighash_type(
network: Network,
chain: crate::fixed_script_wallet::wallet_scripts::Chain,
) -> miniscript::bitcoin::psbt::PsbtSighashType {
use crate::fixed_script_wallet::wallet_scripts::Chain;
use crate::fixed_script_wallet::wallet_scripts::OutputScriptType;
use miniscript::bitcoin::sighash::{EcdsaSighashType, TapSighashType};

// For taproot, always use Default
if matches!(
chain,
Chain::P2trInternal
| Chain::P2trExternal
| Chain::P2trMusig2Internal
| Chain::P2trMusig2External
chain.script_type,
OutputScriptType::P2trLegacy | OutputScriptType::P2trMusig2
) {
return TapSighashType::Default.into();
}
Expand Down Expand Up @@ -758,7 +755,7 @@ impl BitGoPsbt {
options: WalletInputOptions,
) -> Result<usize, String> {
use crate::fixed_script_wallet::to_pub_triple;
use crate::fixed_script_wallet::wallet_scripts::{Chain, WalletScripts};
use crate::fixed_script_wallet::wallet_scripts::{Chain, OutputScriptType, WalletScripts};
use miniscript::bitcoin::psbt::Input;
use miniscript::bitcoin::taproot::{LeafVersion, TapLeafHash};
use miniscript::bitcoin::{transaction::Sequence, Amount, OutPoint, TxIn, TxOut};
Expand Down Expand Up @@ -799,18 +796,8 @@ impl BitGoPsbt {
// Create the PSBT input
let mut psbt_input = Input::default();

// Determine if segwit based on chain type
let is_segwit = matches!(
chain_enum,
Chain::P2shP2wshExternal
| Chain::P2shP2wshInternal
| Chain::P2wshExternal
| Chain::P2wshInternal
| Chain::P2trInternal
| Chain::P2trExternal
| Chain::P2trMusig2Internal
| Chain::P2trMusig2External
);
// Determine if segwit based on chain type (all types except P2sh are segwit)
let is_segwit = chain_enum.script_type != OutputScriptType::P2sh;

if let (false, Some(tx_bytes)) = (is_segwit, options.prev_tx) {
// Non-segwit with prev_tx: use non_witness_utxo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use miniscript::bitcoin::secp256k1::{self, PublicKey};
use miniscript::bitcoin::{OutPoint, ScriptBuf, TapLeafHash, XOnlyPublicKey};

use crate::bitcoin::bip32::KeySource;
use crate::fixed_script_wallet::{Chain, ReplayProtection, RootWalletKeys, WalletScripts};
use crate::fixed_script_wallet::{
Chain, OutputScriptType, ReplayProtection, RootWalletKeys, WalletScripts,
};
use crate::Network;

pub type Bip32DerivationMap = std::collections::BTreeMap<PublicKey, KeySource>;
Expand Down Expand Up @@ -655,12 +657,12 @@ pub enum InputScriptType {
impl InputScriptType {
pub fn from_script_id(script_id: ScriptId, psbt_input: &Input) -> Result<Self, String> {
let chain = Chain::try_from(script_id.chain).map_err(|e| e.to_string())?;
match chain {
Chain::P2shExternal | Chain::P2shInternal => Ok(InputScriptType::P2sh),
Chain::P2shP2wshExternal | Chain::P2shP2wshInternal => Ok(InputScriptType::P2shP2wsh),
Chain::P2wshExternal | Chain::P2wshInternal => Ok(InputScriptType::P2wsh),
Chain::P2trInternal | Chain::P2trExternal => Ok(InputScriptType::P2trLegacy),
Chain::P2trMusig2Internal | Chain::P2trMusig2External => {
match chain.script_type {
OutputScriptType::P2sh => Ok(InputScriptType::P2sh),
OutputScriptType::P2shP2wsh => Ok(InputScriptType::P2shP2wsh),
OutputScriptType::P2wsh => Ok(InputScriptType::P2wsh),
OutputScriptType::P2trLegacy => Ok(InputScriptType::P2trLegacy),
OutputScriptType::P2trMusig2 => {
// check if tap_script_sigs or tap_scripts are set
if !psbt_input.tap_script_sigs.is_empty() || !psbt_input.tap_scripts.is_empty() {
Ok(InputScriptType::P2trMusig2ScriptPath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub mod fixtures;
pub mod psbt_compare;

use super::wallet_keys::XpubTriple;
use super::wallet_scripts::{Chain, WalletScripts};
use super::wallet_scripts::{Chain, OutputScriptType, Scope, WalletScripts};
use crate::bitcoin::bip32::{DerivationPath, Fingerprint, Xpriv, Xpub};
use crate::bitcoin::psbt::{Input as PsbtInput, Output as PsbtOutput, Psbt};
use crate::bitcoin::{Transaction, TxIn, TxOut};
Expand Down Expand Up @@ -38,7 +38,7 @@ pub fn create_external_output(seed: &str) -> PsbtOutput {
let xpubs = get_test_wallet_keys(seed);
let _scripts = WalletScripts::from_wallet_keys(
&RootWalletKeys::new(xpubs),
Chain::P2wshExternal,
Chain::new(OutputScriptType::P2wsh, Scope::External),
0,
&Network::Bitcoin.output_script_support(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,12 @@ mod tests {
use crate::bitcoin::blockdata::script::Builder;
use crate::fixed_script_wallet::wallet_keys::tests::get_test_wallet_keys;
use crate::fixed_script_wallet::wallet_keys::to_pub_triple;
use crate::fixed_script_wallet::wallet_scripts::Chain;

#[test]
fn test_parse_multisig_script_2_of_3_valid() {
// Get test keys
let wallet_keys = get_test_wallet_keys("test_parse");
let derived_keys = wallet_keys
.derive_for_chain_and_index(Chain::P2shExternal as u32, 0)
.unwrap();
let derived_keys = wallet_keys.derive_for_chain_and_index(0, 0).unwrap();
let pub_triple = to_pub_triple(&derived_keys);

// Build a valid 2-of-3 multisig script
Expand All @@ -126,9 +123,7 @@ mod tests {
// Test multiple different key sets
for seed in ["seed1", "seed2", "seed3"] {
let wallet_keys = get_test_wallet_keys(seed);
let derived_keys = wallet_keys
.derive_for_chain_and_index(Chain::P2shExternal as u32, 42)
.unwrap();
let derived_keys = wallet_keys.derive_for_chain_and_index(0, 42).unwrap();
let original_keys = to_pub_triple(&derived_keys);

// Build script from keys
Expand Down Expand Up @@ -168,9 +163,7 @@ mod tests {
fn test_parse_multisig_script_2_of_3_wrong_quorum() {
// Create a valid key for testing
let wallet_keys = get_test_wallet_keys("test_wrong_quorum");
let derived_keys = wallet_keys
.derive_for_chain_and_index(Chain::P2shExternal as u32, 0)
.unwrap();
let derived_keys = wallet_keys.derive_for_chain_and_index(0, 0).unwrap();
let pub_triple = to_pub_triple(&derived_keys);

// Build script with wrong quorum (OP_1 instead of OP_2)
Expand All @@ -194,9 +187,7 @@ mod tests {
fn test_parse_multisig_script_2_of_3_wrong_total() {
// Create a valid key for testing
let wallet_keys = get_test_wallet_keys("test_wrong_total");
let derived_keys = wallet_keys
.derive_for_chain_and_index(Chain::P2shExternal as u32, 0)
.unwrap();
let derived_keys = wallet_keys.derive_for_chain_and_index(0, 0).unwrap();
let pub_triple = to_pub_triple(&derived_keys);

// Build script with wrong total (OP_4 instead of OP_3)
Expand All @@ -220,9 +211,7 @@ mod tests {
fn test_parse_multisig_script_2_of_3_missing_checkmultisig() {
// Create a valid key for testing
let wallet_keys = get_test_wallet_keys("test_missing_checkmultisig");
let derived_keys = wallet_keys
.derive_for_chain_and_index(Chain::P2shExternal as u32, 0)
.unwrap();
let derived_keys = wallet_keys.derive_for_chain_and_index(0, 0).unwrap();
let pub_triple = to_pub_triple(&derived_keys);

// Build script without OP_CHECKMULTISIG
Expand Down
Loading