Skip to content

Commit

Permalink
refact(chain): Use hash of SPK at index 0 as Descriptor unique ID
Browse files Browse the repository at this point in the history
  • Loading branch information
notmandatory committed Jun 24, 2024
1 parent 6dab68d commit 3a9c3ad
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 7 deletions.
136 changes: 131 additions & 5 deletions crates/chain/src/descriptor_ext.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use crate::{
alloc::{string::ToString, vec::Vec},
alloc::vec::Vec,
miniscript::{Descriptor, DescriptorPublicKey},
};
use bitcoin::bip32;
use bitcoin::hashes::{hash_newtype, sha256, Hash};
use miniscript::descriptor::{ConversionError, DescriptorXKey, Wildcard};
use miniscript::{translate_hash_clone, TranslatePk, Translator};

hash_newtype! {
/// Represents the ID of a descriptor, defined as the sha256 hash of
Expand Down Expand Up @@ -35,10 +38,133 @@ impl DescriptorExt for Descriptor<DescriptorPublicKey> {
.to_sat()
}

// fn descriptor_id(&self) -> DescriptorId {
// let desc = self.to_string();
// let desc_without_checksum = desc.split('#').next().expect("Must be here");
// let descriptor_bytes = <Vec<u8>>::from(desc_without_checksum.as_bytes());
// DescriptorId(sha256::Hash::hash(&descriptor_bytes))
// }

/// Use the hash of the SPK at index 0 as the descriptor's unique ID.
fn descriptor_id(&self) -> DescriptorId {
let desc = self.to_string();
let desc_without_checksum = desc.split('#').next().expect("Must be here");
let descriptor_bytes = <Vec<u8>>::from(desc_without_checksum.as_bytes());
DescriptorId(sha256::Hash::hash(&descriptor_bytes))
let desc = self.clone();
struct Derivator {}
impl Translator<DescriptorPublicKey, DescriptorPublicKey, ConversionError> for Derivator {
fn pk(
&mut self,
pk: &DescriptorPublicKey,
) -> Result<DescriptorPublicKey, ConversionError> {
let pk = pk.clone();
match pk {
DescriptorPublicKey::XPub(xkey) => {
if xkey.wildcard == Wildcard::None {
//dbg!(&xkey);
let new_path_len = &xkey.derivation_path.len().saturating_sub(1);
let new_path = xkey.derivation_path[..*new_path_len].to_vec();
let new_path = bip32::DerivationPath::from(new_path);
let new_xkey = DescriptorXKey {
origin: xkey.origin,
xkey: xkey.xkey,
derivation_path: new_path,
wildcard: Wildcard::Unhardened,
};
//dbg!(&new_xkey);
Ok(DescriptorPublicKey::XPub(new_xkey))
} else {
Ok(DescriptorPublicKey::XPub(xkey))
}
}
pk => Ok(pk),
}
}

translate_hash_clone!(DescriptorPublicKey, DescriptorPublicKey, ConversionError);
}
let translated_desc = desc.translate_pk(&mut Derivator {}).unwrap();
let spk = translated_desc
.at_derivation_index(0)
.unwrap()
.script_pubkey();
let spk_bytes = <Vec<u8>>::from(spk.as_bytes());
DescriptorId(Hash::hash(&spk_bytes))
}
}

#[cfg(test)]
mod test {
use crate::DescriptorExt;
use bitcoin::bip32::{DerivationPath, Xpriv, Xpub};
use bitcoin::key::Secp256k1;
use core::str::FromStr;
use miniscript::{Descriptor, DescriptorPublicKey};

#[test]
fn test_descriptor_id_equivalents() {
let desc_a = test_desc("44'/0'/1'", "2/3", false);
let desc_b = test_desc("44'/0'/1'/2", "3", false);
assert!(equal_ids(desc_a, desc_b));

let desc_a = test_desc("44'/0'/1'", "2/3", true);
let desc_b = test_desc("44'/0'/1'/2", "3", true);
assert!(equal_ids(desc_a, desc_b));

let desc_a = test_desc("44'/0'/1'/2", "3/4", false);
let desc_b = test_desc("44'/0'/1'/2", "3", true);
assert!(equal_ids(desc_a, desc_b));

let desc_a = test_desc("44'/0'/1'/2", "3", false);
let desc_b = test_desc("44'/0'/1'/2", "0", false);
assert!(equal_ids(desc_a, desc_b));

// found/fixed in wallet psbt test_psbt_fee_rate_with_missing_txout()
let secp = Secp256k1::new();
let (desc_a, _keymap) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0)").unwrap();
let (desc_b, _keymap) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/1)").unwrap();
assert!(equal_ids(desc_a, desc_b));

// this test fails, no good solution allowing two non-overlapping fixed indexes
// let desc_a = test_desc("44'/0'/1'/2", "3/4", false);
// let desc_b = test_desc("44'/0'/1'/2", "3/5", false);
// assert!(!equal_ids(desc_a, desc_b));
}

fn test_desc(
parent_path: &str,
extended_path: &str,
add_wildcard: bool,
) -> Descriptor<DescriptorPublicKey> {
let secp = Secp256k1::new();

// Create master extended private key
let master_xprv = Xpriv::from_str("xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2").unwrap();

let parent_path = DerivationPath::from_str(parent_path).unwrap();
let extended_path = DerivationPath::from_str(extended_path).unwrap();

let derived_xprv = master_xprv
.derive_priv(&secp, &parent_path)
.expect("derived xprv");
let derived_xpub = Xpub::from_priv(&secp, &derived_xprv);
let descriptor = if add_wildcard {
format!(
"wpkh([{}/{}]{}/{}/*)",
&derived_xpub.parent_fingerprint, &parent_path, &derived_xpub, &extended_path
)
} else {
format!(
"wpkh([{}/{}]{}/{})",
&derived_xpub.parent_fingerprint, &parent_path, &derived_xpub, &extended_path
)
};
Descriptor::from_str(&descriptor).unwrap()
}

fn equal_ids(
desc_a: Descriptor<DescriptorPublicKey>,
desc_b: Descriptor<DescriptorPublicKey>,
) -> bool {
let desc_a_id = desc_a.descriptor_id();
let desc_b_id = desc_b.descriptor_id();
desc_a_id == desc_b_id
}
}
4 changes: 2 additions & 2 deletions crates/wallet/tests/psbt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ fn test_psbt_fee_rate_with_missing_txout() {
assert!(wpkh_psbt.fee_amount().is_none());
assert!(wpkh_psbt.fee_rate().is_none());

let desc = "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0)";
let change_desc = "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/1)";
let desc = "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0/*)";
let change_desc = "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/1/*)";
let (mut pkh_wallet, _) = get_funded_wallet_with_change(desc, change_desc);
let addr = pkh_wallet.peek_address(KeychainKind::External, 0);
let mut builder = pkh_wallet.build_tx();
Expand Down

0 comments on commit 3a9c3ad

Please sign in to comment.