Skip to content

Commit 7db5759

Browse files
Merge pull request #88 from BitGo/BTC-2916.impl-supportsScriptType
feat(wasm-utxo): add OutputScriptType enum and supportsScriptType function
2 parents 0c79108 + 2c28d11 commit 7db5759

11 files changed

Lines changed: 405 additions & 205 deletions

File tree

packages/wasm-utxo/js/fixedScriptWallet/BitGoPsbt.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,14 @@ import { type BIP32Arg, BIP32 } from "../bip32.js";
55
import { type ECPairArg, ECPair } from "../ecpair.js";
66
import type { UtxolibName } from "../utxolibCompat.js";
77
import type { CoinName } from "../coinName.js";
8+
import type { InputScriptType } from "./scriptType.js";
9+
10+
export type { InputScriptType };
811

912
export type NetworkName = UtxolibName | CoinName;
1013

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

13-
export type InputScriptType =
14-
| "p2shP2pk"
15-
| "p2sh"
16-
| "p2shP2wsh"
17-
| "p2wsh"
18-
| "p2trLegacy"
19-
| "p2trMusig2ScriptPath"
20-
| "p2trMusig2KeyPath";
21-
2216
export type OutPoint = {
2317
txid: string;
2418
vout: number;

packages/wasm-utxo/js/fixedScriptWallet/index.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1+
import { FixedScriptWalletNamespace } from "../wasm/wasm_utxo.js";
2+
import type { CoinName } from "../coinName.js";
3+
14
export { RootWalletKeys, type WalletKeysArg, type IWalletKeys } from "./RootWalletKeys.js";
25
export { ReplayProtection, type ReplayProtectionArg } from "./ReplayProtection.js";
36
export { outputScript, address } from "./address.js";
47
export { Dimensions } from "./Dimensions.js";
8+
export { type OutputScriptType, type InputScriptType, type ScriptType } from "./scriptType.js";
59

610
// Bitcoin-like PSBT (for all non-Zcash networks)
711
export {
812
BitGoPsbt,
913
type NetworkName,
1014
type ScriptId,
11-
type InputScriptType,
1215
type ParsedInput,
1316
type ParsedOutput,
1417
type ParsedTransaction,
@@ -26,3 +29,34 @@ export {
2629
type ZcashNetworkName,
2730
type CreateEmptyZcashOptions,
2831
} from "./ZcashBitGoPsbt.js";
32+
33+
import type { ScriptType } from "./scriptType.js";
34+
35+
/**
36+
* Check if a network supports a given fixed-script wallet script type
37+
*
38+
* @param coin - Coin name (e.g., "btc", "ltc", "doge")
39+
* @param scriptType - Output script type or input script type to check
40+
* @returns `true` if the network supports the script type, `false` otherwise
41+
*
42+
* @example
43+
* ```typescript
44+
* // Bitcoin supports all script types
45+
* supportsScriptType("btc", "p2tr"); // true
46+
*
47+
* // Litecoin supports segwit but not taproot
48+
* supportsScriptType("ltc", "p2wsh"); // true
49+
* supportsScriptType("ltc", "p2tr"); // false
50+
*
51+
* // Dogecoin only supports legacy scripts
52+
* supportsScriptType("doge", "p2sh"); // true
53+
* supportsScriptType("doge", "p2wsh"); // false
54+
*
55+
* // Also works with input script types
56+
* supportsScriptType("btc", "p2trMusig2KeyPath"); // true
57+
* supportsScriptType("doge", "p2trLegacy"); // false
58+
* ```
59+
*/
60+
export function supportsScriptType(coin: CoinName, scriptType: ScriptType): boolean {
61+
return FixedScriptWalletNamespace.supports_script_type(coin, scriptType);
62+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Fixed-script wallet output script types (2-of-3 multisig)
3+
*
4+
* This type represents the abstract script type, independent of chain (external/internal).
5+
* Use this for checking network support or when you need the script type without derivation info.
6+
*/
7+
export type OutputScriptType =
8+
| "p2sh"
9+
| "p2shP2wsh"
10+
| "p2wsh"
11+
| "p2tr" // alias for p2trLegacy
12+
| "p2trLegacy"
13+
| "p2trMusig2";
14+
15+
/**
16+
* Input script types for fixed-script wallets
17+
*
18+
* These are more specific than output types and include single-sig and taproot variants.
19+
*/
20+
export type InputScriptType =
21+
| "p2shP2pk"
22+
| "p2sh"
23+
| "p2shP2wsh"
24+
| "p2wsh"
25+
| "p2trLegacy"
26+
| "p2trMusig2ScriptPath"
27+
| "p2trMusig2KeyPath";
28+
29+
/**
30+
* Union of all script types that can be checked for network support
31+
*/
32+
export type ScriptType = OutputScriptType | InputScriptType;

packages/wasm-utxo/src/address/networks.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use super::{
1313
ZCASH_TEST,
1414
};
1515
use crate::bitcoin::Script;
16+
use crate::fixed_script_wallet::wallet_scripts::OutputScriptType;
1617
use crate::networks::Network;
1718
use miniscript::bitcoin::WitnessVersion;
1819

@@ -120,6 +121,15 @@ impl OutputScriptSupport {
120121
}
121122
Ok(())
122123
}
124+
125+
/// Check if the network supports a given fixed-script wallet script type
126+
pub fn supports_script_type(&self, script_type: OutputScriptType) -> bool {
127+
match script_type {
128+
OutputScriptType::P2sh => true, // all networks support legacy scripts
129+
OutputScriptType::P2shP2wsh | OutputScriptType::P2wsh => self.segwit,
130+
OutputScriptType::P2trLegacy | OutputScriptType::P2trMusig2 => self.taproot,
131+
}
132+
}
123133
}
124134

125135
impl Network {

packages/wasm-utxo/src/fixed_script_wallet/bitgo_psbt/mod.rs

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -184,16 +184,13 @@ fn get_default_sighash_type(
184184
network: Network,
185185
chain: crate::fixed_script_wallet::wallet_scripts::Chain,
186186
) -> miniscript::bitcoin::psbt::PsbtSighashType {
187-
use crate::fixed_script_wallet::wallet_scripts::Chain;
187+
use crate::fixed_script_wallet::wallet_scripts::OutputScriptType;
188188
use miniscript::bitcoin::sighash::{EcdsaSighashType, TapSighashType};
189189

190190
// For taproot, always use Default
191191
if matches!(
192-
chain,
193-
Chain::P2trInternal
194-
| Chain::P2trExternal
195-
| Chain::P2trMusig2Internal
196-
| Chain::P2trMusig2External
192+
chain.script_type,
193+
OutputScriptType::P2trLegacy | OutputScriptType::P2trMusig2
197194
) {
198195
return TapSighashType::Default.into();
199196
}
@@ -758,7 +755,7 @@ impl BitGoPsbt {
758755
options: WalletInputOptions,
759756
) -> Result<usize, String> {
760757
use crate::fixed_script_wallet::to_pub_triple;
761-
use crate::fixed_script_wallet::wallet_scripts::{Chain, WalletScripts};
758+
use crate::fixed_script_wallet::wallet_scripts::{Chain, OutputScriptType, WalletScripts};
762759
use miniscript::bitcoin::psbt::Input;
763760
use miniscript::bitcoin::taproot::{LeafVersion, TapLeafHash};
764761
use miniscript::bitcoin::{transaction::Sequence, Amount, OutPoint, TxIn, TxOut};
@@ -799,18 +796,8 @@ impl BitGoPsbt {
799796
// Create the PSBT input
800797
let mut psbt_input = Input::default();
801798

802-
// Determine if segwit based on chain type
803-
let is_segwit = matches!(
804-
chain_enum,
805-
Chain::P2shP2wshExternal
806-
| Chain::P2shP2wshInternal
807-
| Chain::P2wshExternal
808-
| Chain::P2wshInternal
809-
| Chain::P2trInternal
810-
| Chain::P2trExternal
811-
| Chain::P2trMusig2Internal
812-
| Chain::P2trMusig2External
813-
);
799+
// Determine if segwit based on chain type (all types except P2sh are segwit)
800+
let is_segwit = chain_enum.script_type != OutputScriptType::P2sh;
814801

815802
if let (false, Some(tx_bytes)) = (is_segwit, options.prev_tx) {
816803
// Non-segwit with prev_tx: use non_witness_utxo

packages/wasm-utxo/src/fixed_script_wallet/bitgo_psbt/psbt_wallet_input.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ use miniscript::bitcoin::secp256k1::{self, PublicKey};
44
use miniscript::bitcoin::{OutPoint, ScriptBuf, TapLeafHash, XOnlyPublicKey};
55

66
use crate::bitcoin::bip32::KeySource;
7-
use crate::fixed_script_wallet::{Chain, ReplayProtection, RootWalletKeys, WalletScripts};
7+
use crate::fixed_script_wallet::{
8+
Chain, OutputScriptType, ReplayProtection, RootWalletKeys, WalletScripts,
9+
};
810
use crate::Network;
911

1012
pub type Bip32DerivationMap = std::collections::BTreeMap<PublicKey, KeySource>;
@@ -655,12 +657,12 @@ pub enum InputScriptType {
655657
impl InputScriptType {
656658
pub fn from_script_id(script_id: ScriptId, psbt_input: &Input) -> Result<Self, String> {
657659
let chain = Chain::try_from(script_id.chain).map_err(|e| e.to_string())?;
658-
match chain {
659-
Chain::P2shExternal | Chain::P2shInternal => Ok(InputScriptType::P2sh),
660-
Chain::P2shP2wshExternal | Chain::P2shP2wshInternal => Ok(InputScriptType::P2shP2wsh),
661-
Chain::P2wshExternal | Chain::P2wshInternal => Ok(InputScriptType::P2wsh),
662-
Chain::P2trInternal | Chain::P2trExternal => Ok(InputScriptType::P2trLegacy),
663-
Chain::P2trMusig2Internal | Chain::P2trMusig2External => {
660+
match chain.script_type {
661+
OutputScriptType::P2sh => Ok(InputScriptType::P2sh),
662+
OutputScriptType::P2shP2wsh => Ok(InputScriptType::P2shP2wsh),
663+
OutputScriptType::P2wsh => Ok(InputScriptType::P2wsh),
664+
OutputScriptType::P2trLegacy => Ok(InputScriptType::P2trLegacy),
665+
OutputScriptType::P2trMusig2 => {
664666
// check if tap_script_sigs or tap_scripts are set
665667
if !psbt_input.tap_script_sigs.is_empty() || !psbt_input.tap_scripts.is_empty() {
666668
Ok(InputScriptType::P2trMusig2ScriptPath)

packages/wasm-utxo/src/fixed_script_wallet/test_utils/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ pub mod fixtures;
44
pub mod psbt_compare;
55

66
use super::wallet_keys::XpubTriple;
7-
use super::wallet_scripts::{Chain, WalletScripts};
7+
use super::wallet_scripts::{Chain, OutputScriptType, Scope, WalletScripts};
88
use crate::bitcoin::bip32::{DerivationPath, Fingerprint, Xpriv, Xpub};
99
use crate::bitcoin::psbt::{Input as PsbtInput, Output as PsbtOutput, Psbt};
1010
use crate::bitcoin::{Transaction, TxIn, TxOut};
@@ -38,7 +38,7 @@ pub fn create_external_output(seed: &str) -> PsbtOutput {
3838
let xpubs = get_test_wallet_keys(seed);
3939
let _scripts = WalletScripts::from_wallet_keys(
4040
&RootWalletKeys::new(xpubs),
41-
Chain::P2wshExternal,
41+
Chain::new(OutputScriptType::P2wsh, Scope::External),
4242
0,
4343
&Network::Bitcoin.output_script_support(),
4444
)

packages/wasm-utxo/src/fixed_script_wallet/wallet_scripts/checkmultisig.rs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,12 @@ mod tests {
100100
use crate::bitcoin::blockdata::script::Builder;
101101
use crate::fixed_script_wallet::wallet_keys::tests::get_test_wallet_keys;
102102
use crate::fixed_script_wallet::wallet_keys::to_pub_triple;
103-
use crate::fixed_script_wallet::wallet_scripts::Chain;
104103

105104
#[test]
106105
fn test_parse_multisig_script_2_of_3_valid() {
107106
// Get test keys
108107
let wallet_keys = get_test_wallet_keys("test_parse");
109-
let derived_keys = wallet_keys
110-
.derive_for_chain_and_index(Chain::P2shExternal as u32, 0)
111-
.unwrap();
108+
let derived_keys = wallet_keys.derive_for_chain_and_index(0, 0).unwrap();
112109
let pub_triple = to_pub_triple(&derived_keys);
113110

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

134129
// Build script from keys
@@ -168,9 +163,7 @@ mod tests {
168163
fn test_parse_multisig_script_2_of_3_wrong_quorum() {
169164
// Create a valid key for testing
170165
let wallet_keys = get_test_wallet_keys("test_wrong_quorum");
171-
let derived_keys = wallet_keys
172-
.derive_for_chain_and_index(Chain::P2shExternal as u32, 0)
173-
.unwrap();
166+
let derived_keys = wallet_keys.derive_for_chain_and_index(0, 0).unwrap();
174167
let pub_triple = to_pub_triple(&derived_keys);
175168

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

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

228217
// Build script without OP_CHECKMULTISIG

0 commit comments

Comments
 (0)