Skip to content

Commit d157957

Browse files
OttoAllmendingerllm-git
andcommitted
feat(wasm-utxo): add BIP32Interface support for wallet keys
Add ability to parse and use wallet keys from BIP32Interface objects instead of requiring xpub strings. This provides a more flexible API for consuming code that already has BIP32 key objects. The implementation supports both property access and toBase58() method to extract the keys. Issue: BTC-2652 Co-authored-by: llm-git <[email protected]>
1 parent 6d57816 commit d157957

File tree

8 files changed

+492
-67
lines changed

8 files changed

+492
-67
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use std::str::FromStr;
2+
3+
use crate::bitcoin::bip32::Xpub;
4+
use crate::error::WasmMiniscriptError;
5+
use crate::try_from_js_value::{get_buffer_field, get_field, get_nested_field};
6+
use wasm_bindgen::JsValue;
7+
8+
fn try_xpub_from_bip32_properties(bip32_key: &JsValue) -> Result<Xpub, WasmMiniscriptError> {
9+
// Extract properties using helper functions
10+
let version: u32 = get_nested_field(bip32_key, "network.bip32.public")?;
11+
let depth: u8 = get_field(bip32_key, "depth")?;
12+
let parent_fingerprint: u32 = get_field(bip32_key, "parentFingerprint")?;
13+
let index: u32 = get_field(bip32_key, "index")?;
14+
let chain_code_bytes: [u8; 32] = get_buffer_field(bip32_key, "chainCode")?;
15+
let public_key_bytes: [u8; 33] = get_buffer_field(bip32_key, "publicKey")?;
16+
17+
// Build BIP32 serialization (78 bytes total)
18+
let mut data = Vec::with_capacity(78);
19+
data.extend_from_slice(&version.to_be_bytes()); // 4 bytes: version
20+
data.push(depth); // 1 byte: depth
21+
data.extend_from_slice(&parent_fingerprint.to_be_bytes()); // 4 bytes: parent fingerprint
22+
data.extend_from_slice(&index.to_be_bytes()); // 4 bytes: index
23+
data.extend_from_slice(&chain_code_bytes); // 32 bytes: chain code
24+
data.extend_from_slice(&public_key_bytes); // 33 bytes: public key
25+
26+
// Use the Xpub::decode method which properly handles network detection and constructs the Xpub
27+
Xpub::decode(&data)
28+
.map_err(|e| WasmMiniscriptError::new(&format!("Failed to decode xpub: {}", e)))
29+
}
30+
31+
fn xpub_from_base58_method(bip32_key: &JsValue) -> Result<Xpub, WasmMiniscriptError> {
32+
// Fallback: Call toBase58() method on BIP32Interface
33+
let to_base58 = js_sys::Reflect::get(bip32_key, &JsValue::from_str("toBase58"))
34+
.map_err(|_| WasmMiniscriptError::new("Failed to get 'toBase58' method"))?;
35+
36+
if !to_base58.is_function() {
37+
return Err(WasmMiniscriptError::new("'toBase58' is not a function"));
38+
}
39+
40+
let to_base58_fn = js_sys::Function::from(to_base58);
41+
let xpub_str = to_base58_fn
42+
.call0(bip32_key)
43+
.map_err(|_| WasmMiniscriptError::new("Failed to call 'toBase58'"))?;
44+
45+
let xpub_string = xpub_str
46+
.as_string()
47+
.ok_or_else(|| WasmMiniscriptError::new("'toBase58' did not return a string"))?;
48+
49+
Xpub::from_str(&xpub_string)
50+
.map_err(|e| WasmMiniscriptError::new(&format!("Failed to parse xpub: {}", e)))
51+
}
52+
53+
pub fn xpub_from_bip32interface(bip32_key: &JsValue) -> Result<Xpub, WasmMiniscriptError> {
54+
// Try to construct from properties first, fall back to toBase58() if that fails
55+
try_xpub_from_bip32_properties(bip32_key).or_else(|_| xpub_from_base58_method(bip32_key))
56+
}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// This module contains code for the BitGo Fixed Script Wallets.
22
/// These are not based on descriptors.
3+
mod bip32interface;
34
mod wallet_keys;
45

56
pub mod wallet_scripts;
@@ -30,8 +31,8 @@ impl FixedScriptWallet {
3031
let chain = Chain::try_from(chain)
3132
.map_err(|e| WasmMiniscriptError::new(&format!("Invalid chain: {}", e)))?;
3233

33-
let xpubs = xpub_triple_from_jsvalue(&keys)?;
34-
let scripts = WalletScripts::from_xpubs(&xpubs, chain, index);
34+
let wallet_keys = RootWalletKeys::from_jsvalue(&keys)?;
35+
let scripts = WalletScripts::from_wallet_keys(&wallet_keys, chain, index);
3536
Ok(scripts.output_script().to_bytes())
3637
}
3738

@@ -43,10 +44,10 @@ impl FixedScriptWallet {
4344
network: JsValue,
4445
) -> Result<String, WasmMiniscriptError> {
4546
let network = Network::try_from_js_value(&network)?;
46-
let xpubs = xpub_triple_from_jsvalue(&keys)?;
47+
let wallet_keys = RootWalletKeys::from_jsvalue(&keys)?;
4748
let chain = Chain::try_from(chain)
4849
.map_err(|e| WasmMiniscriptError::new(&format!("Invalid chain: {}", e)))?;
49-
let scripts = WalletScripts::from_xpubs(&xpubs, chain, index);
50+
let scripts = WalletScripts::from_wallet_keys(&wallet_keys, chain, index);
5051
let script = scripts.output_script();
5152
let address = crate::address::utxolib_compat::from_output_script_with_network(
5253
&script,

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use super::wallet_scripts::{Chain, WalletScripts};
77
use crate::bitcoin::bip32::{DerivationPath, Fingerprint, Xpriv, Xpub};
88
use crate::bitcoin::psbt::{Input as PsbtInput, Output as PsbtOutput, Psbt};
99
use crate::bitcoin::{Transaction, TxIn, TxOut};
10+
use crate::RootWalletKeys;
1011
use std::collections::BTreeMap;
1112
use std::str::FromStr;
1213

@@ -31,7 +32,8 @@ pub fn get_test_wallet_keys(seed: &str) -> XpubTriple {
3132
/// Create a PSBT output for an external wallet (different keys)
3233
pub fn create_external_output(seed: &str) -> PsbtOutput {
3334
let xpubs = get_test_wallet_keys(seed);
34-
let _scripts = WalletScripts::from_xpubs(&xpubs, Chain::P2wshExternal, 0);
35+
let _scripts =
36+
WalletScripts::from_wallet_keys(&RootWalletKeys::new(xpubs), Chain::P2wshExternal, 0);
3537
PsbtOutput {
3638
bip32_derivation: BTreeMap::new(),
3739
// witness_script: scripts.witness_script,

0 commit comments

Comments
 (0)