Merge rust-bitcoin/rust-bitcoin#839: feat: Add Address.is_related_to_*_key()

51fef76129 feat: Add Address.is_related_to_pubkey() (Andrew Ahlers)

Pull request description:

  ## Motivation

  This is addressing the second half of this comment: https://github.com/rust-bitcoin/rust-bitcoin/pull/684#issuecomment-1012136845

  > but would accept a PR (or two PRs) that returns Result<bool, UnsupportedAddress> and a method to check if a PublicKey is associated with an address.

  (The first half was addressed [here](https://github.com/rust-bitcoin/rust-bitcoin/pull/819))

  These changes will help build out and improve message signature verification. We don't necessarily need to add it to this crate but it allows for easy verification with something such as:
  1. recovering a pubkey
  2. checking if that pubkey relates to the given address

  ## Possible Improvements

  - There is likely a better name than `is_related_to_secp256k1_key()`
  - This could drop the `secp256k1` part of the name and take in a Pubkey enum that also supports Schnorr pubkeys and then this could be used for taproot addresses as well. This felt like a much larger change that will likely get turned down. Verifying taproot is simple enough and if absolutely desired, similar functions can be added for schnorr keys (tweaked and untweaked)

ACKs for top commit:
  Kixunil:
    ACK 51fef76129 for merging after TR
  apoelstra:
    ACK 51fef76129

Tree-SHA512: c9ab8c0f101fb4c647713e7f500656617025d8741676e8eb8a3132009dde9937d50cf9ac3d8055feb14452324a292397e46639cbaca71cac77af4b06dc42d09d
This commit is contained in:
Andrew Poelstra 2022-03-28 17:29:22 +00:00
commit 0d5565e131
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
1 changed files with 132 additions and 2 deletions

View File

@ -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};
@ -714,6 +714,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
@ -857,6 +888,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;
@ -1260,4 +1299,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);
}
}