diff --git a/src/util/address.rs b/src/util/address.rs index a1718332..e4e6e48d 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -39,9 +39,9 @@ use core::num::ParseIntError; use core::str::FromStr; #[cfg(feature = "std")] use std::error; -use secp256k1::{Secp256k1, Verification}; +use secp256k1::{Secp256k1, Verification, XOnlyPublicKey}; use bech32; -use hashes::Hash; +use hashes::{sha256, Hash, HashEngine}; use hash_types::{PubkeyHash, ScriptHash}; use blockdata::{script, opcodes}; use blockdata::constants::{PUBKEY_ADDRESS_PREFIX_MAIN, SCRIPT_ADDRESS_PREFIX_MAIN, PUBKEY_ADDRESS_PREFIX_TEST, SCRIPT_ADDRESS_PREFIX_TEST, MAX_SCRIPT_ELEMENT_SIZE}; @@ -727,6 +727,37 @@ impl Address { (Network::Testnet, _) | (Network::Regtest, _) | (Network::Signet, _) => true } } + + /// Returns true if the given pubkey is directly related to the address payload. + /// + /// This is determined by directly comparing the address payload with either the + /// hash of the given public key or the segwit redeem hash generated from the + /// given key. For taproot addresses, the supplied key is assumed to be tweaked + pub fn is_related_to_pubkey(&self, pubkey: &PublicKey) -> bool { + let pubkey_hash = pubkey.pubkey_hash(); + let payload = self.payload_as_bytes(); + let xonly_pubkey = XOnlyPublicKey::from(pubkey.inner); + + (*pubkey_hash == *payload) || (xonly_pubkey.serialize() == *payload) || (*segwit_redeem_hash(&pubkey_hash) == *payload) + } + + /// Returns true if the supplied xonly public key can be used to derive the address. + /// + /// This will only work for Taproot addresses. The Public Key is + /// assumed to have already been tweaked. + pub fn is_related_to_xonly_pubkey(&self, xonly_pubkey: &XOnlyPublicKey) -> bool { + let payload = self.payload_as_bytes(); + payload == xonly_pubkey.serialize() + } + + /// Return the address payload as a byte slice + fn payload_as_bytes(&self) -> &[u8] { + match &self.payload { + Payload::ScriptHash(hash) => hash, + Payload::PubkeyHash(hash) => hash, + Payload::WitnessProgram { program, .. } => program, + } + } } // Alternate formatting `{:#}` is used to return uppercase version of bech32 addresses which should @@ -870,6 +901,14 @@ impl fmt::Debug for Address { } } +/// Convert a byte array of a pubkey hash into a segwit redeem hash +fn segwit_redeem_hash(pubkey_hash: &[u8]) -> ::hashes::hash160::Hash { + let mut sha_engine = sha256::Hash::engine(); + sha_engine.input(&[0, 20]); + sha_engine.input(pubkey_hash); + ::hashes::hash160::Hash::from_engine(sha_engine) +} + #[cfg(test)] mod tests { use core::str::FromStr; @@ -1273,4 +1312,95 @@ mod tests { assert_eq!(address.address_type(), Some(AddressType::P2tr)); roundtrips(&address); } + + #[test] + fn test_is_related_to_pubkey_p2wpkh() { + let address_string = "bc1qhvd6suvqzjcu9pxjhrwhtrlj85ny3n2mqql5w4"; + let address = Address::from_str(address_string).expect("address"); + + let pubkey_string = "0347ff3dacd07a1f43805ec6808e801505a6e18245178609972a68afbc2777ff2b"; + let pubkey = PublicKey::from_str(pubkey_string).expect("pubkey"); + + let result = address.is_related_to_pubkey(&pubkey); + assert!(result); + + let unused_pubkey = PublicKey::from_str("02ba604e6ad9d3864eda8dc41c62668514ef7d5417d3b6db46e45cc4533bff001c").expect("pubkey"); + assert!(!address.is_related_to_pubkey(&unused_pubkey)) + } + + #[test] + fn test_is_related_to_pubkey_p2shwpkh() { + let address_string = "3EZQk4F8GURH5sqVMLTFisD17yNeKa7Dfs"; + let address = Address::from_str(address_string).expect("address"); + + let pubkey_string = "0347ff3dacd07a1f43805ec6808e801505a6e18245178609972a68afbc2777ff2b"; + let pubkey = PublicKey::from_str(pubkey_string).expect("pubkey"); + + let result = address.is_related_to_pubkey(&pubkey); + assert!(result); + + let unused_pubkey = PublicKey::from_str("02ba604e6ad9d3864eda8dc41c62668514ef7d5417d3b6db46e45cc4533bff001c").expect("pubkey"); + assert!(!address.is_related_to_pubkey(&unused_pubkey)) + } + + #[test] + fn test_is_related_to_pubkey_p2pkh() { + let address_string = "1J4LVanjHMu3JkXbVrahNuQCTGCRRgfWWx"; + let address = Address::from_str(address_string).expect("address"); + + let pubkey_string = "0347ff3dacd07a1f43805ec6808e801505a6e18245178609972a68afbc2777ff2b"; + let pubkey = PublicKey::from_str(pubkey_string).expect("pubkey"); + + let result = address.is_related_to_pubkey(&pubkey); + assert!(result); + + let unused_pubkey = PublicKey::from_str("02ba604e6ad9d3864eda8dc41c62668514ef7d5417d3b6db46e45cc4533bff001c").expect("pubkey"); + assert!(!address.is_related_to_pubkey(&unused_pubkey)) + } + + #[test] + fn test_is_related_to_pubkey_p2pkh_uncompressed_key() { + let address_string = "msvS7KzhReCDpQEJaV2hmGNvuQqVUDuC6p"; + let address = Address::from_str(address_string).expect("address"); + + let pubkey_string = "04e96e22004e3db93530de27ccddfdf1463975d2138ac018fc3e7ba1a2e5e0aad8e424d0b55e2436eb1d0dcd5cb2b8bcc6d53412c22f358de57803a6a655fbbd04"; + let pubkey = PublicKey::from_str(pubkey_string).expect("pubkey"); + + let result = address.is_related_to_pubkey(&pubkey); + assert!(result); + + let unused_pubkey = PublicKey::from_str("02ba604e6ad9d3864eda8dc41c62668514ef7d5417d3b6db46e45cc4533bff001c").expect("pubkey"); + assert!(!address.is_related_to_pubkey(&unused_pubkey)) + } + + #[test] + fn test_is_related_to_pubkey_p2tr(){ + let pubkey_string = "0347ff3dacd07a1f43805ec6808e801505a6e18245178609972a68afbc2777ff2b"; + let pubkey = PublicKey::from_str(pubkey_string).expect("pubkey"); + let xonly_pubkey = XOnlyPublicKey::from(pubkey.inner); + let tweaked_pubkey = TweakedPublicKey::dangerous_assume_tweaked(xonly_pubkey); + let address = Address::p2tr_tweaked(tweaked_pubkey, Network::Bitcoin); + + assert_eq!(address, Address::from_str("bc1pgllnmtxs0g058qz7c6qgaqq4qknwrqj9z7rqn9e2dzhmcfmhlu4sfadf5e").expect("address")); + + let result = address.is_related_to_pubkey(&pubkey); + assert!(result); + + let unused_pubkey = PublicKey::from_str("02ba604e6ad9d3864eda8dc41c62668514ef7d5417d3b6db46e45cc4533bff001c").expect("pubkey"); + assert!(!address.is_related_to_pubkey(&unused_pubkey)); + } + + #[test] + fn test_is_related_to_xonly_pubkey(){ + let pubkey_string = "0347ff3dacd07a1f43805ec6808e801505a6e18245178609972a68afbc2777ff2b"; + let pubkey = PublicKey::from_str(pubkey_string).expect("pubkey"); + let xonly_pubkey = XOnlyPublicKey::from(pubkey.inner); + let tweaked_pubkey = TweakedPublicKey::dangerous_assume_tweaked(xonly_pubkey); + let address = Address::p2tr_tweaked(tweaked_pubkey, Network::Bitcoin); + + assert_eq!(address, Address::from_str("bc1pgllnmtxs0g058qz7c6qgaqq4qknwrqj9z7rqn9e2dzhmcfmhlu4sfadf5e").expect("address")); + + let result = address.is_related_to_xonly_pubkey(&xonly_pubkey); + assert!(result); + } }