Skip to content

Commit 2e6c5a4

Browse files
OttoAllmendingerllm-git
andcommitted
feat(wasm-utxo): refactor key handling with XprvTriple
Improves wallet key management by adding XprvTriple struct to replace loose vector handling of extended private keys. This provides: - Type safety with fixed array instead of Vec - Named accessor methods for keys (user, backup, bitgo) - Conversion utility to RootWalletKeys - Simplifies test fixtures with direct key access Issue: BTC-2652 Co-authored-by: llm-git <[email protected]>
1 parent 49829c8 commit 2e6c5a4

File tree

3 files changed

+70
-59
lines changed

3 files changed

+70
-59
lines changed

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

Lines changed: 56 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
//! .expect("Failed to decode PSBT");
1717
//!
1818
//! // Parse wallet keys (xprv)
19-
//! let xprvs = parse_wallet_keys(&fixture)
19+
//! let xprvs = fixture.get_wallet_xprvs()
2020
//! .expect("Failed to parse wallet keys");
2121
//!
2222
//! // Access fixture data
@@ -38,10 +38,50 @@
3838
//! }
3939
//! ```
4040
41+
use std::str::FromStr;
42+
43+
use crate::{bitcoin::bip32::Xpriv, fixed_script_wallet::RootWalletKeys};
44+
use miniscript::bitcoin::bip32::Xpub;
4145
use serde::{Deserialize, Serialize};
4246

4347
use crate::Network;
4448

49+
#[derive(Debug, Clone, PartialEq, Eq)]
50+
pub struct XprvTriple([Xpriv; 3]);
51+
52+
impl XprvTriple {
53+
pub fn new(xprvs: [Xpriv; 3]) -> Self {
54+
Self(xprvs)
55+
}
56+
57+
pub fn from_strings(strings: Vec<String>) -> Result<Self, Box<dyn std::error::Error>> {
58+
let xprvs = strings
59+
.iter()
60+
.map(|s| Xpriv::from_str(s).map_err(|e| Box::new(e) as Box<dyn std::error::Error>))
61+
.collect::<Result<Vec<_>, _>>()?;
62+
Ok(Self::new(
63+
xprvs.try_into().expect("Expected exactly 3 xprvs"),
64+
))
65+
}
66+
67+
pub fn user_key(&self) -> &Xpriv {
68+
&self.0[0]
69+
}
70+
71+
pub fn backup_key(&self) -> &Xpriv {
72+
&self.0[1]
73+
}
74+
75+
pub fn bitgo_key(&self) -> &Xpriv {
76+
&self.0[2]
77+
}
78+
79+
pub fn to_root_wallet_keys(&self) -> RootWalletKeys {
80+
let secp = crate::bitcoin::secp256k1::Secp256k1::new();
81+
RootWalletKeys::new(self.0.map(|x| Xpub::from_priv(&secp, &x)))
82+
}
83+
}
84+
4585
// Basic helper types (no dependencies on other types in this file)
4686

4787
#[derive(Debug, Clone, Deserialize, Serialize)]
@@ -511,23 +551,18 @@ impl PsbtStages {
511551
tx_format,
512552
)
513553
.expect("Failed to load fullsigned fixture");
514-
let wallet_keys_unsigned =
515-
parse_wallet_keys(&unsigned).expect("Failed to parse wallet keys");
516-
let wallet_keys_halfsigned =
517-
parse_wallet_keys(&halfsigned).expect("Failed to parse wallet keys");
518-
let wallet_keys_fullsigned =
519-
parse_wallet_keys(&fullsigned).expect("Failed to parse wallet keys");
554+
let wallet_keys_unsigned = unsigned
555+
.get_wallet_xprvs()
556+
.expect("Failed to parse wallet keys");
557+
let wallet_keys_halfsigned = halfsigned
558+
.get_wallet_xprvs()
559+
.expect("Failed to parse wallet keys");
560+
let wallet_keys_fullsigned = fullsigned
561+
.get_wallet_xprvs()
562+
.expect("Failed to parse wallet keys");
520563
assert_eq!(wallet_keys_unsigned, wallet_keys_halfsigned);
521564
assert_eq!(wallet_keys_unsigned, wallet_keys_fullsigned);
522-
let secp = crate::bitcoin::secp256k1::Secp256k1::new();
523-
let wallet_keys = crate::fixed_script_wallet::RootWalletKeys::new(
524-
wallet_keys_unsigned
525-
.iter()
526-
.map(|x| crate::bitcoin::bip32::Xpub::from_priv(&secp, x))
527-
.collect::<Vec<_>>()
528-
.try_into()
529-
.expect("Failed to convert to XpubTriple"),
530-
);
565+
let wallet_keys = wallet_keys_unsigned.to_root_wallet_keys();
531566

532567
Ok(Self {
533568
network,
@@ -614,6 +649,11 @@ impl PsbtFixture {
614649
Ok(psbt)
615650
}
616651

652+
/// Parse wallet keys from fixture (xprv strings)
653+
pub fn get_wallet_xprvs(&self) -> Result<XprvTriple, Box<dyn std::error::Error>> {
654+
XprvTriple::from_strings(self.wallet_keys.clone())
655+
}
656+
617657
pub fn find_input_with_script_type(
618658
&self,
619659
script_type: ScriptType,
@@ -807,19 +847,6 @@ pub fn decode_psbt_from_fixture(
807847
Ok(psbt)
808848
}
809849

810-
/// Parse wallet keys from fixture (xprv strings)
811-
pub fn parse_wallet_keys(
812-
fixture: &PsbtFixture,
813-
) -> Result<Vec<crate::bitcoin::bip32::Xpriv>, Box<dyn std::error::Error>> {
814-
use std::str::FromStr;
815-
816-
fixture
817-
.wallet_keys
818-
.iter()
819-
.map(|key_str| crate::bitcoin::bip32::Xpriv::from_str(key_str).map_err(|e| e.into()))
820-
.collect()
821-
}
822-
823850
// Helper functions for validation
824851

825852
/// Compares a generated hex string with an expected hex string
@@ -1382,10 +1409,6 @@ mod tests {
13821409
let psbt = decode_psbt_from_fixture(&fixture).expect("Failed to decode PSBT");
13831410
assert_eq!(psbt.inputs.len(), 7);
13841411
assert_eq!(psbt.outputs.len(), 5);
1385-
1386-
// Parse wallet keys
1387-
let xprvs = parse_wallet_keys(&fixture).expect("Failed to parse wallet keys");
1388-
assert_eq!(xprvs.len(), 3);
13891412
}
13901413

13911414
#[test]

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ pub fn to_pub_triple(xpubs: &XpubTriple) -> PubTriple {
3232
.expect("could not convert vec to array")
3333
}
3434

35+
pub fn derivation_path(prefix: &DerivationPath, chain: u32, index: u32) -> DerivationPath {
36+
prefix
37+
.child(ChildNumber::Normal { index: chain })
38+
.child(ChildNumber::Normal { index })
39+
}
40+
3541
#[derive(Debug, Clone)]
3642
pub struct RootWalletKeys {
3743
xpubs: XpubTriple,
@@ -68,10 +74,7 @@ impl RootWalletKeys {
6874
let paths: Vec<DerivationPath> = self
6975
.derivation_prefixes
7076
.iter()
71-
.map(|p| {
72-
p.child(ChildNumber::Normal { index: chain })
73-
.child(ChildNumber::Normal { index })
74-
})
77+
.map(|p| derivation_path(p, chain, index))
7578
.collect::<Vec<_>>();
7679

7780
let ctx = Secp256k1::new();

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

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ impl ScriptP2shP2pk {
3131

3232
#[cfg(test)]
3333
mod tests {
34+
use miniscript::bitcoin::bip32::Xpub;
35+
3436
use super::*;
3537
use crate::bitcoin::secp256k1::Secp256k1;
36-
use crate::fixed_script_wallet::test_utils::fixtures::{
37-
load_psbt_fixture, parse_wallet_keys, SignatureState,
38-
};
38+
use crate::fixed_script_wallet::test_utils::fixtures::{load_psbt_fixture, SignatureState};
3939

4040
#[test]
4141
fn test_p2sh_p2pk_script_generation_from_fixture() {
@@ -63,26 +63,11 @@ mod tests {
6363
.expect("No partial signature found");
6464

6565
// Parse the wallet keys
66-
let xprvs = parse_wallet_keys(&fixture).expect("Failed to parse wallet keys");
66+
let xprvs = fixture
67+
.get_wallet_xprvs()
68+
.expect("Failed to parse wallet keys");
6769
let secp = Secp256k1::new();
68-
69-
// Find which key matches the expected pubkey
70-
let mut matching_key = None;
71-
for xprv in &xprvs {
72-
let xpub = crate::bitcoin::bip32::Xpub::from_priv(&secp, xprv);
73-
// Convert secp256k1::PublicKey to bitcoin::PublicKey
74-
let bitcoin_pubkey = crate::bitcoin::PublicKey::new(xpub.public_key);
75-
let compressed_pubkey = CompressedPublicKey::try_from(bitcoin_pubkey)
76-
.expect("Failed to convert to compressed pubkey");
77-
let pubkey_hex = hex::encode(compressed_pubkey.to_bytes());
78-
79-
if pubkey_hex == *expected_pubkey {
80-
matching_key = Some(compressed_pubkey);
81-
break;
82-
}
83-
}
84-
85-
let pubkey = matching_key.expect("Could not find matching pubkey in wallet keys");
70+
let pubkey = Xpub::from_priv(&secp, xprvs.user_key()).to_pub();
8671

8772
// Build the p2sh-p2pk script
8873
let script = ScriptP2shP2pk::new(pubkey);

0 commit comments

Comments
 (0)