From ab1d73b959d976d15bc0ac702beb5908c3f89e1e Mon Sep 17 00:00:00 2001 From: AmosOO7 Date: Fri, 15 May 2026 12:53:57 +0100 Subject: [PATCH 1/3] feat(wallet): derive wallet_name_from_descriptor from public descriptor checksums - Update `wallet_name_from_descriptor` to compute descriptor checksum explicitly - Remove reliance on `to_string()` implicitly containing `#checksum` - Clarify that wallet name is defined by public descriptor checksums - Add regression test for equivalent xpub/xprv wallet names --- src/wallet/mod.rs | 57 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 1cdd1cc7b..e2dd6b792 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -66,9 +66,9 @@ pub(crate) mod utils; use crate::collections::{BTreeMap, HashMap, HashSet}; use crate::descriptor::{ - check_wallet_descriptor, error::Error as DescriptorError, policy::BuildSatisfaction, - DerivedDescriptor, DescriptorMeta, ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, - Policy, XKeyUtils, + check_wallet_descriptor, checksum::calc_checksum, error::Error as DescriptorError, + policy::BuildSatisfaction, DerivedDescriptor, DescriptorMeta, ExtendedDescriptor, + ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils, }; use crate::psbt::PsbtUtils; use crate::types::*; @@ -2784,9 +2784,10 @@ impl AsRef> for Wallet { } } -/// Deterministically generate a unique name given the descriptors defining the [`Wallet`]. +/// Generate a wallet name from the checksums of the wallet's public descriptors. /// -/// Compatible with [`wallet_name_from_descriptor`]. +/// The name is derived from the checksums of the provided descriptor(s), ensuring +/// it's based on public information only. pub fn wallet_name_from_descriptor( descriptor: T, change_descriptor: Option, @@ -2796,18 +2797,14 @@ pub fn wallet_name_from_descriptor( where T: IntoWalletDescriptor, { - // TODO: check descriptors contains only public keys - let descriptor = descriptor - .into_wallet_descriptor(secp, network_kind)? - .0 - .to_string(); - let mut wallet_name = descriptor.split_once('#').unwrap().1.to_string(); + // Wallet name is defined by the checksums of the wallet's public descriptors. + let (descriptor, _keymap) = descriptor.into_wallet_descriptor(secp, network_kind)?; + let mut wallet_name = calc_checksum(&descriptor.to_string())?; + if let Some(change_descriptor) = change_descriptor { - let change_descriptor = change_descriptor - .into_wallet_descriptor(secp, network_kind)? - .0 - .to_string(); - wallet_name.push_str(change_descriptor.split_once('#').unwrap().1); + let (change_descriptor, _change_keymap) = + change_descriptor.into_wallet_descriptor(secp, network_kind)?; + wallet_name.push_str(&calc_checksum(&change_descriptor.to_string())?); } Ok(wallet_name) @@ -3064,4 +3061,32 @@ mod test { let wallet = params.network(Network::Testnet).create_wallet_no_persist(); assert!(wallet.is_err()); } + #[test] + fn test_wallet_name_from_descriptor_public_key_check() { + let secp = SecpCtx::new(); + + // Test with a public descriptor + let public_descriptor = "wpkh([31a30ffd/84'/1'/0']tpubDCG4yNzDpNYw5ZMuR2usfbPKcnaKjFGwgyussBdhjy2mXmLWnzkwUTZBQPrQxPVcfwh6uFPN4Q7Jk2DPRFb2c4xbrStpqCbKzLkGhvcJvSx/1/*)#vn4aqs37"; + let public_result = + wallet_name_from_descriptor(public_descriptor, None, NetworkKind::Test, &secp); + assert!(public_result.is_ok()); + + // Test with equivalent private descriptor (should produce same name) + let private_descriptor = "wpkh(tprv8ZgxMBicQKsPctT28ZYaU77s1UFjHv7o7cafmDntdggZ2dFtNn38RYMzJiDVMBqnqBFDP8rHxsiVRudhyrqi6mgPc4gekgxChgnkTSxHAZ5/84'/1'/0'/1/*)#7z7rgndh"; + let private_result = + wallet_name_from_descriptor(private_descriptor, None, NetworkKind::Test, &secp); + assert!(private_result.is_ok()); + assert_eq!(public_result.unwrap(), private_result.unwrap()); // Same wallet name + + // Test with change descriptor + let change_descriptor = "wpkh([76011771/84'/1'/0']tpubDC3fWoucXCvSyfh6YbyHu1mSQdFjCz5Ejx62eUnRkKdr9bsHGgLEjAaCRNNuaeLjCttfz8sXgshqzawtgWvtozE84rH9BvQn2PUyMCiU1fT/1/*)#jgrerlc3"; + let result_with_change = wallet_name_from_descriptor( + public_descriptor, + Some(change_descriptor), + NetworkKind::Test, + &secp, + ); + assert!(result_with_change.is_ok()); + // Wallet name should be main_checksum + change_checksum + } } From e2927163206ce8cc5b6490bfb945b16cda6f825d Mon Sep 17 00:00:00 2001 From: AmosOO7 Date: Fri, 15 May 2026 14:26:38 +0100 Subject: [PATCH 2/3] Add: Extract public_result.unwrap() into a variable to avoid consuming it twice, enabling comparison between checksums in test --- src/wallet/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index e2dd6b792..b6664254f 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -3070,13 +3070,15 @@ mod test { let public_result = wallet_name_from_descriptor(public_descriptor, None, NetworkKind::Test, &secp); assert!(public_result.is_ok()); + let public_name = public_result.unwrap(); + assert_eq!(public_name, "vn4aqs37"); // Checksum of the public descriptor // Test with equivalent private descriptor (should produce same name) let private_descriptor = "wpkh(tprv8ZgxMBicQKsPctT28ZYaU77s1UFjHv7o7cafmDntdggZ2dFtNn38RYMzJiDVMBqnqBFDP8rHxsiVRudhyrqi6mgPc4gekgxChgnkTSxHAZ5/84'/1'/0'/1/*)#7z7rgndh"; let private_result = wallet_name_from_descriptor(private_descriptor, None, NetworkKind::Test, &secp); assert!(private_result.is_ok()); - assert_eq!(public_result.unwrap(), private_result.unwrap()); // Same wallet name + assert_eq!(public_name, private_result.unwrap()); // Same wallet name // Test with change descriptor let change_descriptor = "wpkh([76011771/84'/1'/0']tpubDC3fWoucXCvSyfh6YbyHu1mSQdFjCz5Ejx62eUnRkKdr9bsHGgLEjAaCRNNuaeLjCttfz8sXgshqzawtgWvtozE84rH9BvQn2PUyMCiU1fT/1/*)#jgrerlc3"; From 21a0d4d99c1ca9eecdc3a6253055ca9956f42452 Mon Sep 17 00:00:00 2001 From: AmosOO7 Date: Fri, 15 May 2026 15:05:47 +0100 Subject: [PATCH 3/3] Update documentation to make the `wallet_name_from_descriptor`function clear to understand --- src/wallet/mod.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index b6664254f..f53041677 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -2784,10 +2784,16 @@ impl AsRef> for Wallet { } } -/// Generate a wallet name from the checksums of the wallet's public descriptors. +/// Generate a deterministic wallet name from descriptor checksums. /// -/// The name is derived from the checksums of the provided descriptor(s), ensuring -/// it's based on public information only. +/// The wallet name is computed from the parsed public descriptor: +/// - the checksum of the parsed public descriptor +/// - if present, the checksum of the parsed change descriptor is appended +/// +/// This is based on the public descriptor form, so equivalent xprv and xpub +/// inputs produce the same wallet name. +/// +/// Returns an error if descriptor parsing fails or if checksum computation fails. pub fn wallet_name_from_descriptor( descriptor: T, change_descriptor: Option,