Merge rust-bitcoin/rust-bitcoin#643: util/address: make address encoding more modular

506e03fa4d util/address: use hash functions of PublicKey/Script (Marko Bencun)
f826316c25 util/address: avoid .expect/panic (Marko Bencun)
ad83f6ae00 util/address: make address encoding more modular (Marko Bencun)

Pull request description:

  This allow library clients to plug their own encoding parameters in a
  backwards compatible manner.

Top commit has no ACKs.

Tree-SHA512: ae2ececbdfe4984fd62c975f4956686d79f6f5a6e65c34b55daa76fe785b8483ed7f35208d36b8bee545c7edd39ac878277a0fb8ea8c64a1943081e15c818bff
This commit is contained in:
sanket1729 2021-12-15 20:17:17 +05:30
commit 36f3d230b8
No known key found for this signature in database
GPG Key ID: 648FFB183E0870A2
1 changed files with 150 additions and 91 deletions

View File

@ -42,7 +42,7 @@ use core::str::FromStr;
use secp256k1::{Secp256k1, Verification};
use bech32;
use hashes::Hash;
use hash_types::{PubkeyHash, WPubkeyHash, ScriptHash, WScriptHash};
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};
use network::constants::Network;
@ -405,6 +405,127 @@ impl Payload {
} => script::Script::new_witness_program(version, prog)
}
}
/// Creates a pay to (compressed) public key hash payload from a public key
#[inline]
pub fn p2pkh(pk: &ecdsa::PublicKey) -> Payload {
Payload::PubkeyHash(pk.pubkey_hash())
}
/// Creates a pay to script hash P2SH payload from a script
#[inline]
pub fn p2sh(script: &script::Script) -> Result<Payload, Error> {
if script.len() > MAX_SCRIPT_ELEMENT_SIZE {
return Err(Error::ExcessiveScriptSize);
}
Ok(Payload::ScriptHash(script.script_hash()))
}
/// Create a witness pay to public key payload from a public key
pub fn p2wpkh(pk: &ecdsa::PublicKey) -> Result<Payload, Error> {
Ok(Payload::WitnessProgram {
version: WitnessVersion::V0,
program: pk.wpubkey_hash().ok_or(Error::UncompressedPubkey)?.to_vec(),
})
}
/// Create a pay to script payload that embeds a witness pay to public key
pub fn p2shwpkh(pk: &ecdsa::PublicKey) -> Result<Payload, Error> {
let builder = script::Builder::new()
.push_int(0)
.push_slice(&pk.wpubkey_hash().ok_or(Error::UncompressedPubkey)?);
Ok(Payload::ScriptHash(builder.into_script().script_hash()))
}
/// Create a witness pay to script hash payload.
pub fn p2wsh(script: &script::Script) -> Payload {
Payload::WitnessProgram {
version: WitnessVersion::V0,
program: script.wscript_hash().to_vec(),
}
}
/// Create a pay to script payload that embeds a witness pay to script hash address
pub fn p2shwsh(script: &script::Script) -> Payload {
let ws = script::Builder::new()
.push_int(0)
.push_slice(&script.wscript_hash())
.into_script();
Payload::ScriptHash(ws.script_hash())
}
/// Create a pay to taproot payload from untweaked key
pub fn p2tr<C: Verification>(
secp: &Secp256k1<C>,
internal_key: UntweakedPublicKey,
merkle_root: Option<TapBranchHash>,
) -> Payload {
let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root);
Payload::WitnessProgram {
version: WitnessVersion::V1,
program: output_key.into_inner().serialize().to_vec(),
}
}
/// Create a pay to taproot payload from a pre-tweaked output key.
///
/// This method is not recommended for use and [Payload::p2tr()] should be used where possible.
pub fn p2tr_tweaked(output_key: TweakedPublicKey) -> Payload {
Payload::WitnessProgram {
version: WitnessVersion::V1,
program: output_key.as_inner().serialize().to_vec(),
}
}
}
/// A utility struct to encode an address payload with the given parameters.
/// This is a low-level utility struct. Consider using `Address` instead.
pub struct AddressEncoding<'a> {
/// The address payload to encode.
pub payload: &'a Payload,
/// base58 version byte for p2pkh payloads (e.g. 0x00 for "1..." addresses).
pub p2pkh_prefix: u8,
/// base58 version byte for p2sh payloads (e.g. 0x05 for "3..." addresses).
pub p2sh_prefix: u8,
/// hrp used in bech32 addresss (e.g. "bc" for "bc1..." addresses).
pub bech32_hrp: &'a str,
}
impl<'a> fmt::Display for AddressEncoding<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self.payload {
Payload::PubkeyHash(hash) => {
let mut prefixed = [0; 21];
prefixed[0] = self.p2pkh_prefix;
prefixed[1..].copy_from_slice(&hash[..]);
base58::check_encode_slice_to_fmt(fmt, &prefixed[..])
}
Payload::ScriptHash(hash) => {
let mut prefixed = [0; 21];
prefixed[0] = self.p2sh_prefix;
prefixed[1..].copy_from_slice(&hash[..]);
base58::check_encode_slice_to_fmt(fmt, &prefixed[..])
}
Payload::WitnessProgram {
version,
program: prog,
} => {
let mut upper_writer;
let writer = if fmt.alternate() {
upper_writer = UpperWriter(fmt);
&mut upper_writer as &mut dyn fmt::Write
} else {
fmt as &mut dyn fmt::Write
};
let mut bech32_writer =
bech32::Bech32Writer::new(self.bech32_hrp, version.bech32_variant(), writer)?;
bech32::WriteBase32::write_u5(&mut bech32_writer, (*version).into())?;
bech32::ToBase32::write_base32(&prog, &mut bech32_writer)
}
}
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -423,12 +544,9 @@ impl Address {
/// This is the preferred non-witness type address.
#[inline]
pub fn p2pkh(pk: &ecdsa::PublicKey, network: Network) -> Address {
let mut hash_engine = PubkeyHash::engine();
pk.write_into(&mut hash_engine).expect("engines don't error");
Address {
network: network,
payload: Payload::PubkeyHash(PubkeyHash::from_engine(hash_engine)),
payload: Payload::p2pkh(pk),
}
}
@ -438,12 +556,9 @@ impl Address {
/// these days.
#[inline]
pub fn p2sh(script: &script::Script, network: Network) -> Result<Address, Error> {
if script.len() > MAX_SCRIPT_ELEMENT_SIZE{
return Err(Error::ExcessiveScriptSize);
}
Ok(Address {
network: network,
payload: Payload::ScriptHash(ScriptHash::hash(&script[..])),
payload: Payload::p2sh(script)?,
})
}
@ -454,19 +569,9 @@ impl Address {
/// # Errors
/// Will only return an error if an uncompressed public key is provided.
pub fn p2wpkh(pk: &ecdsa::PublicKey, network: Network) -> Result<Address, Error> {
if !pk.compressed {
return Err(Error::UncompressedPubkey);
}
let mut hash_engine = WPubkeyHash::engine();
pk.write_into(&mut hash_engine).expect("engines don't error");
Ok(Address {
network: network,
payload: Payload::WitnessProgram {
version: WitnessVersion::V0,
program: WPubkeyHash::from_engine(hash_engine)[..].to_vec(),
},
payload: Payload::p2wpkh(pk)?,
})
}
@ -477,20 +582,9 @@ impl Address {
/// # Errors
/// Will only return an Error if an uncompressed public key is provided.
pub fn p2shwpkh(pk: &ecdsa::PublicKey, network: Network) -> Result<Address, Error> {
if !pk.compressed {
return Err(Error::UncompressedPubkey);
}
let mut hash_engine = WPubkeyHash::engine();
pk.write_into(&mut hash_engine).expect("engines don't error");
let builder = script::Builder::new()
.push_int(0)
.push_slice(&WPubkeyHash::from_engine(hash_engine)[..]);
Ok(Address {
network: network,
payload: Payload::ScriptHash(ScriptHash::hash(builder.into_script().as_bytes())),
payload: Payload::p2shwpkh(pk)?,
})
}
@ -498,10 +592,7 @@ impl Address {
pub fn p2wsh(script: &script::Script, network: Network) -> Address {
Address {
network: network,
payload: Payload::WitnessProgram {
version: WitnessVersion::V0,
program: WScriptHash::hash(&script[..])[..].to_vec(),
},
payload: Payload::p2wsh(script),
}
}
@ -509,14 +600,9 @@ impl Address {
///
/// This is a segwit address type that looks familiar (as p2sh) to legacy clients.
pub fn p2shwsh(script: &script::Script, network: Network) -> Address {
let ws = script::Builder::new()
.push_int(0)
.push_slice(&WScriptHash::hash(&script[..])[..])
.into_script();
Address {
network: network,
payload: Payload::ScriptHash(ScriptHash::hash(&ws[..])),
payload: Payload::p2shwsh(script),
}
}
@ -527,13 +613,9 @@ impl Address {
merkle_root: Option<TapBranchHash>,
network: Network
) -> Address {
let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root);
Address {
network: network,
payload: Payload::WitnessProgram {
version: WitnessVersion::V1,
program: output_key.into_inner().serialize().to_vec()
}
payload: Payload::p2tr(secp, internal_key, merkle_root),
}
}
@ -546,10 +628,7 @@ impl Address {
) -> Address {
Address {
network: network,
payload: Payload::WitnessProgram {
version: WitnessVersion::V1,
program: output_key.as_inner().serialize().to_vec()
}
payload: Payload::p2tr_tweaked(output_key),
}
}
@ -654,46 +733,26 @@ impl Address {
// be used in QR codes, see [`Address::to_qr_uri`].
impl fmt::Display for Address {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self.payload {
Payload::PubkeyHash(ref hash) => {
let mut prefixed = [0; 21];
prefixed[0] = match self.network {
let p2pkh_prefix = match self.network {
Network::Bitcoin => PUBKEY_ADDRESS_PREFIX_MAIN,
Network::Testnet | Network::Signet | Network::Regtest => PUBKEY_ADDRESS_PREFIX_TEST,
};
prefixed[1..].copy_from_slice(&hash[..]);
base58::check_encode_slice_to_fmt(fmt, &prefixed[..])
}
Payload::ScriptHash(ref hash) => {
let mut prefixed = [0; 21];
prefixed[0] = match self.network {
let p2sh_prefix = match self.network {
Network::Bitcoin => SCRIPT_ADDRESS_PREFIX_MAIN,
Network::Testnet | Network::Signet | Network::Regtest => SCRIPT_ADDRESS_PREFIX_TEST,
};
prefixed[1..].copy_from_slice(&hash[..]);
base58::check_encode_slice_to_fmt(fmt, &prefixed[..])
}
Payload::WitnessProgram {
version,
program: ref prog,
} => {
let hrp = match self.network {
let bech32_hrp = match self.network {
Network::Bitcoin => "bc",
Network::Testnet | Network::Signet => "tb",
Network::Regtest => "bcrt",
};
let mut upper_writer;
let writer = if fmt.alternate() {
upper_writer = UpperWriter(fmt);
&mut upper_writer as &mut dyn fmt::Write
} else {
fmt as &mut dyn fmt::Write
let encoding = AddressEncoding {
payload: &self.payload,
p2pkh_prefix,
p2sh_prefix,
bech32_hrp,
};
let mut bech32_writer = bech32::Bech32Writer::new(hrp, version.bech32_variant(), writer)?;
bech32::WriteBase32::write_u5(&mut bech32_writer, version.into())?;
bech32::ToBase32::write_base32(&prog, &mut bech32_writer)
}
}
encoding.fmt(fmt)
}
}