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:
commit
36f3d230b8
|
@ -42,7 +42,7 @@ use core::str::FromStr;
|
||||||
use secp256k1::{Secp256k1, Verification};
|
use secp256k1::{Secp256k1, Verification};
|
||||||
use bech32;
|
use bech32;
|
||||||
use hashes::Hash;
|
use hashes::Hash;
|
||||||
use hash_types::{PubkeyHash, WPubkeyHash, ScriptHash, WScriptHash};
|
use hash_types::{PubkeyHash, ScriptHash};
|
||||||
use blockdata::{script, opcodes};
|
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 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;
|
use network::constants::Network;
|
||||||
|
@ -405,6 +405,127 @@ impl Payload {
|
||||||
} => script::Script::new_witness_program(version, prog)
|
} => 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)]
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
@ -423,12 +544,9 @@ impl Address {
|
||||||
/// This is the preferred non-witness type address.
|
/// This is the preferred non-witness type address.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn p2pkh(pk: &ecdsa::PublicKey, network: Network) -> Address {
|
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 {
|
Address {
|
||||||
network: network,
|
network: network,
|
||||||
payload: Payload::PubkeyHash(PubkeyHash::from_engine(hash_engine)),
|
payload: Payload::p2pkh(pk),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,12 +556,9 @@ impl Address {
|
||||||
/// these days.
|
/// these days.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn p2sh(script: &script::Script, network: Network) -> Result<Address, Error> {
|
pub fn p2sh(script: &script::Script, network: Network) -> Result<Address, Error> {
|
||||||
if script.len() > MAX_SCRIPT_ELEMENT_SIZE{
|
|
||||||
return Err(Error::ExcessiveScriptSize);
|
|
||||||
}
|
|
||||||
Ok(Address {
|
Ok(Address {
|
||||||
network: network,
|
network: network,
|
||||||
payload: Payload::ScriptHash(ScriptHash::hash(&script[..])),
|
payload: Payload::p2sh(script)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,19 +569,9 @@ impl Address {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will only return an error if an uncompressed public key is provided.
|
/// Will only return an error if an uncompressed public key is provided.
|
||||||
pub fn p2wpkh(pk: &ecdsa::PublicKey, network: Network) -> Result<Address, Error> {
|
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 {
|
Ok(Address {
|
||||||
network: network,
|
network: network,
|
||||||
payload: Payload::WitnessProgram {
|
payload: Payload::p2wpkh(pk)?,
|
||||||
version: WitnessVersion::V0,
|
|
||||||
program: WPubkeyHash::from_engine(hash_engine)[..].to_vec(),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,20 +582,9 @@ impl Address {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Will only return an Error if an uncompressed public key is provided.
|
/// Will only return an Error if an uncompressed public key is provided.
|
||||||
pub fn p2shwpkh(pk: &ecdsa::PublicKey, network: Network) -> Result<Address, Error> {
|
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 {
|
Ok(Address {
|
||||||
network: network,
|
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 {
|
pub fn p2wsh(script: &script::Script, network: Network) -> Address {
|
||||||
Address {
|
Address {
|
||||||
network: network,
|
network: network,
|
||||||
payload: Payload::WitnessProgram {
|
payload: Payload::p2wsh(script),
|
||||||
version: WitnessVersion::V0,
|
|
||||||
program: WScriptHash::hash(&script[..])[..].to_vec(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -509,14 +600,9 @@ impl Address {
|
||||||
///
|
///
|
||||||
/// This is a segwit address type that looks familiar (as p2sh) to legacy clients.
|
/// This is a segwit address type that looks familiar (as p2sh) to legacy clients.
|
||||||
pub fn p2shwsh(script: &script::Script, network: Network) -> Address {
|
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 {
|
Address {
|
||||||
network: network,
|
network: network,
|
||||||
payload: Payload::ScriptHash(ScriptHash::hash(&ws[..])),
|
payload: Payload::p2shwsh(script),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -527,13 +613,9 @@ impl Address {
|
||||||
merkle_root: Option<TapBranchHash>,
|
merkle_root: Option<TapBranchHash>,
|
||||||
network: Network
|
network: Network
|
||||||
) -> Address {
|
) -> Address {
|
||||||
let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root);
|
|
||||||
Address {
|
Address {
|
||||||
network: network,
|
network: network,
|
||||||
payload: Payload::WitnessProgram {
|
payload: Payload::p2tr(secp, internal_key, merkle_root),
|
||||||
version: WitnessVersion::V1,
|
|
||||||
program: output_key.into_inner().serialize().to_vec()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -546,10 +628,7 @@ impl Address {
|
||||||
) -> Address {
|
) -> Address {
|
||||||
Address {
|
Address {
|
||||||
network: network,
|
network: network,
|
||||||
payload: Payload::WitnessProgram {
|
payload: Payload::p2tr_tweaked(output_key),
|
||||||
version: WitnessVersion::V1,
|
|
||||||
program: output_key.as_inner().serialize().to_vec()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -654,46 +733,26 @@ impl Address {
|
||||||
// be used in QR codes, see [`Address::to_qr_uri`].
|
// be used in QR codes, see [`Address::to_qr_uri`].
|
||||||
impl fmt::Display for Address {
|
impl fmt::Display for Address {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self.payload {
|
let p2pkh_prefix = match self.network {
|
||||||
Payload::PubkeyHash(ref hash) => {
|
|
||||||
let mut prefixed = [0; 21];
|
|
||||||
prefixed[0] = match self.network {
|
|
||||||
Network::Bitcoin => PUBKEY_ADDRESS_PREFIX_MAIN,
|
Network::Bitcoin => PUBKEY_ADDRESS_PREFIX_MAIN,
|
||||||
Network::Testnet | Network::Signet | Network::Regtest => PUBKEY_ADDRESS_PREFIX_TEST,
|
Network::Testnet | Network::Signet | Network::Regtest => PUBKEY_ADDRESS_PREFIX_TEST,
|
||||||
};
|
};
|
||||||
prefixed[1..].copy_from_slice(&hash[..]);
|
let p2sh_prefix = match self.network {
|
||||||
base58::check_encode_slice_to_fmt(fmt, &prefixed[..])
|
|
||||||
}
|
|
||||||
Payload::ScriptHash(ref hash) => {
|
|
||||||
let mut prefixed = [0; 21];
|
|
||||||
prefixed[0] = match self.network {
|
|
||||||
Network::Bitcoin => SCRIPT_ADDRESS_PREFIX_MAIN,
|
Network::Bitcoin => SCRIPT_ADDRESS_PREFIX_MAIN,
|
||||||
Network::Testnet | Network::Signet | Network::Regtest => SCRIPT_ADDRESS_PREFIX_TEST,
|
Network::Testnet | Network::Signet | Network::Regtest => SCRIPT_ADDRESS_PREFIX_TEST,
|
||||||
};
|
};
|
||||||
prefixed[1..].copy_from_slice(&hash[..]);
|
let bech32_hrp = match self.network {
|
||||||
base58::check_encode_slice_to_fmt(fmt, &prefixed[..])
|
|
||||||
}
|
|
||||||
Payload::WitnessProgram {
|
|
||||||
version,
|
|
||||||
program: ref prog,
|
|
||||||
} => {
|
|
||||||
let hrp = match self.network {
|
|
||||||
Network::Bitcoin => "bc",
|
Network::Bitcoin => "bc",
|
||||||
Network::Testnet | Network::Signet => "tb",
|
Network::Testnet | Network::Signet => "tb",
|
||||||
Network::Regtest => "bcrt",
|
Network::Regtest => "bcrt",
|
||||||
};
|
};
|
||||||
let mut upper_writer;
|
let encoding = AddressEncoding {
|
||||||
let writer = if fmt.alternate() {
|
payload: &self.payload,
|
||||||
upper_writer = UpperWriter(fmt);
|
p2pkh_prefix,
|
||||||
&mut upper_writer as &mut dyn fmt::Write
|
p2sh_prefix,
|
||||||
} else {
|
bech32_hrp,
|
||||||
fmt as &mut dyn fmt::Write
|
|
||||||
};
|
};
|
||||||
let mut bech32_writer = bech32::Bech32Writer::new(hrp, version.bech32_variant(), writer)?;
|
encoding.fmt(fmt)
|
||||||
bech32::WriteBase32::write_u5(&mut bech32_writer, version.into())?;
|
|
||||||
bech32::ToBase32::write_base32(&prog, &mut bech32_writer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue