util/address: make address encoding more modular

This allow library clients to plug their own encoding parameters in a
backwards compatible manner.
This commit is contained in:
Marko Bencun 2021-11-14 13:09:52 +01:00
parent ed40f3d3a6
commit ad83f6ae00
No known key found for this signature in database
GPG Key ID: 804538928C37EAE8
1 changed files with 170 additions and 90 deletions

View File

@ -405,6 +405,148 @@ 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 {
let mut hash_engine = PubkeyHash::engine();
pk.write_into(&mut hash_engine)
.expect("engines don't error");
Payload::PubkeyHash(PubkeyHash::from_engine(hash_engine))
}
/// 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(ScriptHash::hash(&script[..])))
}
/// Create a witness pay to public key payload from a public key
pub fn p2wpkh(pk: &ecdsa::PublicKey) -> Result<Payload, 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(Payload::WitnessProgram {
version: WitnessVersion::V0,
program: WPubkeyHash::from_engine(hash_engine)[..].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> {
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(Payload::ScriptHash(ScriptHash::hash(
builder.into_script().as_bytes(),
)))
}
/// Create a witness pay to script hash payload.
pub fn p2wsh(script: &script::Script) -> Payload {
Payload::WitnessProgram {
version: WitnessVersion::V0,
program: WScriptHash::hash(&script[..])[..].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(&WScriptHash::hash(&script[..])[..])
.into_script();
Payload::ScriptHash(ScriptHash::hash(&ws[..]))
}
/// 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 +565,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 +577,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 +590,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 +603,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 +613,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 +621,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 +634,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 +649,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 +754,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)
}
}
} }
} }