Skip to content

Commit cf6e1f1

Browse files
committed
fixed hex to xprv to hex round-trip
1 parent d60a1ad commit cf6e1f1

File tree

1 file changed

+62
-54
lines changed

1 file changed

+62
-54
lines changed

client/src/rpc.rs

Lines changed: 62 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ pub struct WalletLoadRequest {
480480
const RPC_WALLET_NOT_LOADED: i32 = -18;
481481

482482
impl WalletManager {
483-
pub async fn import_wallet(&self, wallet: WalletExport) -> anyhow::Result<()> {
483+
pub async fn import_wallet(&self, mut wallet: WalletExport) -> anyhow::Result<()> {
484484
let wallet_path = self.data_dir.join(&wallet.label);
485485
if wallet_path.exists() {
486486
return Err(anyhow!(format!(
@@ -489,6 +489,37 @@ impl WalletManager {
489489
)));
490490
}
491491

492+
// If this is a hex-based descriptor, convert it to proper xprv format before storing
493+
if let Some(descriptor) = &wallet.descriptor {
494+
if descriptor.starts_with("tr([hex:") {
495+
if let Some(hex_secret) = &wallet.hex_secret {
496+
let (network, _) = self.fallback_network();
497+
let xpriv = Self::xpriv_from_hex_secret(network, hex_secret)?;
498+
let (external_desc, internal_desc) = Self::default_descriptors(xpriv);
499+
500+
// Create a temporary wallet to get the descriptor strings
501+
let temp_wallet = bdk::Wallet::create(external_desc, internal_desc)
502+
.network(network)
503+
.create_wallet_no_persist()?;
504+
505+
// Get proper xprv-based descriptor string
506+
let proper_descriptor = temp_wallet
507+
.public_descriptor(KeychainKind::External)
508+
.to_string_with_secret(
509+
&temp_wallet
510+
.get_signers(KeychainKind::External)
511+
.as_key_map(temp_wallet.secp_ctx()),
512+
);
513+
let proper_descriptor = Self::remove_checksum(proper_descriptor);
514+
515+
// Update the wallet to store the proper xprv descriptor
516+
wallet.descriptor = Some(proper_descriptor);
517+
} else {
518+
return Err(anyhow!("Hex-based descriptor found but no hex_secret in wallet"));
519+
}
520+
}
521+
}
522+
492523
fs::create_dir_all(&wallet_path)?;
493524
let wallet_export_path = wallet_path.join("wallet.json");
494525
let mut file = fs::File::create(wallet_export_path)?;
@@ -506,14 +537,20 @@ impl WalletManager {
506537
let wallet = fs::read_to_string(wallet_dir.join("wallet.json"))?;
507538
let mut export: WalletExport = serde_json::from_str(&wallet)?;
508539

509-
// If hex_secret is requested, we need to extract it from the wallet descriptor
540+
// If hex_secret is requested, use the stored hex_secret if available,
541+
// otherwise extract it from the xprv descriptor
510542
if hex_secret {
511-
// Parse the descriptor to extract the xprv and get the hex secret
512-
if let Some(descriptor) = &export.descriptor {
513-
if let Some(hex_secret_value) = self.extract_hex_secret_from_descriptor(descriptor)? {
514-
export.hex_secret = Some(hex_secret_value);
543+
if export.hex_secret.is_none() {
544+
// Only extract from descriptor if no hex_secret was stored
545+
if let Some(descriptor) = &export.descriptor {
546+
if let Some(hex_secret_value) = self.extract_hex_secret_from_descriptor(descriptor)? {
547+
export.hex_secret = Some(hex_secret_value);
548+
}
515549
}
516550
}
551+
} else {
552+
// If hex_secret is not requested, remove it from the export
553+
export.hex_secret = None;
517554
}
518555

519556
Ok(export)
@@ -644,7 +681,7 @@ impl WalletManager {
644681
let file = fs::File::open(wallet_dir.join("wallet.json"))?;
645682

646683
let (network, genesis_hash) = self.fallback_network();
647-
let export: WalletExport = serde_json::from_reader(file)?;
684+
let mut export: WalletExport = serde_json::from_reader(file)?;
648685

649686
let wallet_config = WalletConfig {
650687
start_block: export.blockheight,
@@ -656,50 +693,11 @@ impl WalletManager {
656693
let external_descriptor = export.descriptor().expect("expected a descriptor");
657694
let internal_descriptor = export.change_descriptor().expect("expected a change descriptor");
658695

659-
// Check if this is a hex-based descriptor and convert it
660-
let (external, internal) = if external_descriptor.starts_with("tr([hex:") {
661-
// Extract hex secret from descriptor
662-
if let Some(hex_secret) = export.hex_secret.as_ref() {
663-
// Convert hex secret to proper xprv and create descriptors
664-
let xpriv = Self::xpriv_from_hex_secret(network, hex_secret)?;
665-
let (external_desc, internal_desc) = Self::default_descriptors(xpriv);
666-
667-
// Create a temporary wallet to get the descriptor strings
668-
let temp_wallet = bdk::Wallet::create(external_desc, internal_desc)
669-
.network(network)
670-
.create_wallet_no_persist()?;
671-
672-
// Get descriptor strings using the same method as WalletExport
673-
let external_str = temp_wallet
674-
.public_descriptor(KeychainKind::External)
675-
.to_string_with_secret(
676-
&temp_wallet
677-
.get_signers(KeychainKind::External)
678-
.as_key_map(temp_wallet.secp_ctx()),
679-
);
680-
let internal_str = temp_wallet
681-
.public_descriptor(KeychainKind::Internal)
682-
.to_string_with_secret(
683-
&temp_wallet
684-
.get_signers(KeychainKind::Internal)
685-
.as_key_map(temp_wallet.secp_ctx()),
686-
);
687-
688-
// Remove checksums (same as WalletExport does)
689-
let external_str = Self::remove_checksum(external_str);
690-
let internal_str = Self::remove_checksum(internal_str);
691-
692-
(external_str, internal_str)
693-
} else {
694-
return Err(anyhow!("Hex-based descriptor found but no hex_secret in export"));
695-
}
696-
} else {
697-
(external_descriptor, internal_descriptor)
698-
};
699-
696+
// At this point, the descriptor should already be in proper xprv format
697+
// since import_wallet converts hex-based descriptors before storing
700698
WalletDescriptors {
701-
external,
702-
internal,
699+
external: external_descriptor,
700+
internal: internal_descriptor,
703701
}
704702
},
705703
};
@@ -739,7 +737,7 @@ impl WalletManager {
739737
}
740738

741739
fn xpriv_from_hex_secret(network: Network, hex_secret: &str) -> anyhow::Result<Xpriv> {
742-
use spaces_protocol::bitcoin::bip32::Xpriv;
740+
use spaces_protocol::bitcoin::bip32::{Xpriv, ChainCode, ChildNumber};
743741
use spaces_protocol::bitcoin::key::Secp256k1;
744742
use spaces_protocol::bitcoin::secp256k1::SecretKey;
745743

@@ -755,10 +753,20 @@ impl WalletManager {
755753
let secret_key = SecretKey::from_slice(&secret_bytes)
756754
.map_err(|e| anyhow!("Invalid secret key: {}", e))?;
757755

758-
// Create Xpriv from secret key
756+
// Create Xpriv directly from the private key bytes
757+
// We need to create a proper xprv structure with the exact private key
759758
let secp = Secp256k1::new();
760-
let xpriv = Xpriv::new_master(network, &secret_key.secret_bytes())
761-
.map_err(|e| anyhow!("Failed to create xpriv: {}", e))?;
759+
760+
// Create the xprv by manually constructing it with the exact private key
761+
// This ensures the private key bytes are preserved exactly
762+
let xpriv = Xpriv {
763+
network: network.into(),
764+
depth: 0,
765+
parent_fingerprint: Default::default(),
766+
child_number: ChildNumber::from_normal_idx(0)?,
767+
chain_code: [0u8; 32].into(), // Use zero chain code for direct private key
768+
private_key: secret_key,
769+
};
762770

763771
Ok(xpriv)
764772
}

0 commit comments

Comments
 (0)