pub mod error;
use core::fmt;
use core::marker::PhantomData;
use core::str::FromStr;
use bech32::primitives::decode::UncheckedHrpstring;
use bech32::primitives::gf32::Fe32;
use bech32::primitives::hrp::Hrp;
use bech32::{Bech32m, ByteIterExt as _, Fe32IterExt as _};
use internals::array::ArrayExt;
use crate::constants::{
PUBKEY_ADDRESS_PREFIX_MAIN, PUBKEY_ADDRESS_PREFIX_REGTEST, PUBKEY_ADDRESS_PREFIX_TEST,
SCRIPT_ADDRESS_PREFIX_MAIN, SCRIPT_ADDRESS_PREFIX_MAIN_ALT, SCRIPT_ADDRESS_PREFIX_REGTEST,
SCRIPT_ADDRESS_PREFIX_REGTEST_ALT, SCRIPT_ADDRESS_PREFIX_TEST, SCRIPT_ADDRESS_PREFIX_TEST_ALT,
};
use crate::crypto::key::{PubkeyHash, WPubkeyHash};
use crate::network::{Network, NetworkKind, Params};
use crate::opcodes::Opcode;
use crate::prelude::{String, ToOwned};
use crate::script::witness_program::WitnessProgram;
use crate::script::witness_version::WitnessVersion;
use crate::script::{
self, RedeemScriptSizeError, Script, ScriptExt as _, ScriptHash, ScriptHashableTag,
ScriptPubKey, ScriptPubKeyBuf, ScriptPubKeyBufExt as _, ScriptPubKeyExt as _, WScriptHash,
WitnessScript, WitnessScriptExt as _, WitnessScriptSizeError,
};
#[rustfmt::skip] #[doc(inline)]
pub use self::error::{
Base58Error, Bech32Error, FromScriptError, InvalidBase58PayloadLengthError,
InvalidLegacyPrefixError, InvalidSegwitHrpError, LegacyAddressTooLongError,
NetworkValidationError, ParseError, ParseBech32Error, UnknownAddressTypeError,
UnknownHrpError,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum AddressType {
P2pkh,
P2sh,
P2wpkh,
P2wsh,
P2wsh512,
}
impl fmt::Display for AddressType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match *self {
Self::P2pkh => "p2pkh",
Self::P2sh => "p2sh",
Self::P2wpkh => "p2wpkh",
Self::P2wsh => "p2wsh",
Self::P2wsh512 => "p2wsh512",
})
}
}
impl FromStr for AddressType {
type Err = UnknownAddressTypeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"p2pkh" => Ok(Self::P2pkh),
"p2sh" => Ok(Self::P2sh),
"p2wpkh" => Ok(Self::P2wpkh),
"p2wsh" => Ok(Self::P2wsh),
"p2wsh512" => Ok(Self::P2wsh512),
_ => Err(UnknownAddressTypeError(s.to_owned())),
}
}
}
mod sealed {
pub trait NetworkValidation {}
impl NetworkValidation for super::NetworkChecked {}
impl NetworkValidation for super::NetworkUnchecked {}
pub trait NetworkValidationUnchecked {}
impl NetworkValidationUnchecked for super::NetworkUnchecked {}
}
pub trait NetworkValidation:
sealed::NetworkValidation + Sync + Send + Sized + Unpin + Copy
{
const IS_CHECKED: bool;
}
pub trait NetworkValidationUnchecked:
NetworkValidation + sealed::NetworkValidationUnchecked + Sync + Send + Sized + Unpin
{
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum NetworkChecked {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum NetworkUnchecked {}
impl NetworkValidation for NetworkChecked {
const IS_CHECKED: bool = true;
}
impl NetworkValidation for NetworkUnchecked {
const IS_CHECKED: bool = false;
}
impl NetworkValidationUnchecked for NetworkUnchecked {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum AddressInner {
P2pkh { hash: PubkeyHash, network: LegacyAddressNetwork },
P2sh { hash: ScriptHash, network: LegacyAddressNetwork },
Segwit { program: WitnessProgram, hrp: KnownHrp },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum LegacyAddressNetwork {
Mainnet,
Testnet,
Regtest,
}
impl LegacyAddressNetwork {
const fn network_kind(self) -> NetworkKind {
match self {
Self::Mainnet => NetworkKind::Main,
Self::Testnet | Self::Regtest => NetworkKind::Test,
}
}
const fn network(self) -> Network {
match self {
Self::Mainnet => Network::Tidecoin,
Self::Testnet => Network::Testnet,
Self::Regtest => Network::Regtest,
}
}
const fn p2pkh_prefix(self) -> u8 {
match self {
Self::Mainnet => PUBKEY_ADDRESS_PREFIX_MAIN,
Self::Testnet => PUBKEY_ADDRESS_PREFIX_TEST,
Self::Regtest => PUBKEY_ADDRESS_PREFIX_REGTEST,
}
}
const fn p2sh_prefix(self) -> u8 {
match self {
Self::Mainnet => SCRIPT_ADDRESS_PREFIX_MAIN,
Self::Testnet => SCRIPT_ADDRESS_PREFIX_TEST,
Self::Regtest => SCRIPT_ADDRESS_PREFIX_REGTEST_ALT,
}
}
}
impl From<Network> for LegacyAddressNetwork {
fn from(network: Network) -> Self {
match network {
Network::Tidecoin => Self::Mainnet,
Network::Testnet => Self::Testnet,
Network::Regtest => Self::Regtest,
}
}
}
impl From<NetworkKind> for LegacyAddressNetwork {
fn from(network: NetworkKind) -> Self {
match network {
NetworkKind::Main => Self::Mainnet,
NetworkKind::Test => Self::Testnet,
}
}
}
#[doc(hidden)]
pub trait IntoAddressNetwork {
#[doc(hidden)]
fn into_address_network(self) -> Network;
}
impl IntoAddressNetwork for Network {
fn into_address_network(self) -> Network {
self
}
}
impl IntoAddressNetwork for NetworkKind {
fn into_address_network(self) -> Network {
match self {
Self::Main => Network::Tidecoin,
Self::Test => Network::Testnet,
}
}
}
impl fmt::Display for AddressInner {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use AddressInner::*;
match self {
P2pkh { hash, network } => {
let mut prefixed = [0; 21];
prefixed[0] = network.p2pkh_prefix();
prefixed[1..].copy_from_slice(hash.as_byte_array());
base58::encode_check_to_fmt(fmt, &prefixed[..])
}
P2sh { hash, network } => {
let mut prefixed = [0; 21];
prefixed[0] = network.p2sh_prefix();
prefixed[1..].copy_from_slice(hash.as_byte_array());
base58::encode_check_to_fmt(fmt, &prefixed[..])
}
Segwit { program, hrp } => {
let hrp = hrp.to_hrp();
let version = Fe32::try_from(program.version().to_num())
.expect("version nums 0-16 are valid fe32 values");
let prog = program.program().as_ref();
let is_pq = matches!(
hrp,
h if h == KnownHrp::HRP_PQ_MAINNET
|| h == KnownHrp::HRP_PQ_TESTNET
|| h == KnownHrp::HRP_PQ_REGTEST
);
if is_pq {
encode_pq_bech32m(fmt, hrp, version, prog, fmt.alternate())
} else if fmt.alternate() {
bech32::segwit::encode_upper_to_fmt_unchecked(fmt, hrp, version, prog)
} else {
bech32::segwit::encode_lower_to_fmt_unchecked(fmt, hrp, version, prog)
}
}
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum KnownHrp {
Mainnet,
Testnets,
Regtest,
PqMainnet,
PqTestnets,
PqRegtest,
}
impl KnownHrp {
const HRP_MAINNET: Hrp = Hrp::parse_unchecked("tbc");
const HRP_TESTNET: Hrp = Hrp::parse_unchecked("ttbc");
const HRP_REGTEST: Hrp = Hrp::parse_unchecked("rtbc");
const HRP_PQ_MAINNET: Hrp = Hrp::parse_unchecked("q");
const HRP_PQ_TESTNET: Hrp = Hrp::parse_unchecked("tq");
const HRP_PQ_REGTEST: Hrp = Hrp::parse_unchecked("rq");
fn from_network(network: Network) -> Self {
match network {
Network::Tidecoin => Self::Mainnet,
Network::Testnet => Self::Testnets,
Network::Regtest => Self::Regtest,
}
}
fn from_network_pq(network: Network) -> Self {
match network {
Network::Tidecoin => Self::PqMainnet,
Network::Testnet => Self::PqTestnets,
Network::Regtest => Self::PqRegtest,
}
}
fn from_hrp(hrp: Hrp) -> Result<Self, UnknownHrpError> {
if hrp == Self::HRP_MAINNET {
Ok(Self::Mainnet)
} else if hrp == Self::HRP_TESTNET {
Ok(Self::Testnets)
} else if hrp == Self::HRP_REGTEST {
Ok(Self::Regtest)
} else if hrp == Self::HRP_PQ_MAINNET {
Ok(Self::PqMainnet)
} else if hrp == Self::HRP_PQ_TESTNET {
Ok(Self::PqTestnets)
} else if hrp == Self::HRP_PQ_REGTEST {
Ok(Self::PqRegtest)
} else {
Err(UnknownHrpError(hrp.to_lowercase()))
}
}
fn to_hrp(self) -> Hrp {
match self {
Self::Mainnet => Self::HRP_MAINNET,
Self::Testnets => Self::HRP_TESTNET,
Self::Regtest => Self::HRP_REGTEST,
Self::PqMainnet => Self::HRP_PQ_MAINNET,
Self::PqTestnets => Self::HRP_PQ_TESTNET,
Self::PqRegtest => Self::HRP_PQ_REGTEST,
}
}
}
impl From<Network> for KnownHrp {
fn from(n: Network) -> Self {
Self::from_network(n)
}
}
impl From<KnownHrp> for NetworkKind {
fn from(hrp: KnownHrp) -> Self {
match hrp {
KnownHrp::Mainnet | KnownHrp::PqMainnet => Self::Main,
KnownHrp::Testnets | KnownHrp::PqTestnets => Self::Test,
KnownHrp::Regtest | KnownHrp::PqRegtest => Self::Test,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum AddressData {
P2pkh {
pubkey_hash: PubkeyHash,
},
P2sh {
script_hash: ScriptHash,
},
Segwit {
witness_program: WitnessProgram,
},
}
internals::transparent_newtype! {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Address<V = NetworkChecked>(PhantomData<V>, AddressInner)
where
V: NetworkValidation;
impl<V> Address<V> {
fn from_inner_ref(inner: &_) -> &Self;
}
}
#[cfg(feature = "serde")]
struct DisplayUnchecked<'a, N: NetworkValidation>(&'a Address<N>);
#[cfg(feature = "serde")]
impl<N: NetworkValidation> fmt::Display for DisplayUnchecked<'_, N> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0.inner(), fmt)
}
}
#[cfg(feature = "serde")]
impl<'de, U: NetworkValidationUnchecked> serde::Deserialize<'de> for Address<U> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
use core::fmt::Formatter;
struct Visitor<U>(PhantomData<U>);
impl<U> serde::de::Visitor<'_> for Visitor<U>
where
U: NetworkValidationUnchecked + NetworkValidation,
Address<U>: FromStr,
{
type Value = Address<U>;
fn expecting(&self, f: &mut Formatter) -> core::fmt::Result {
f.write_str("A Tidecoin address")
}
fn visit_str<E>(self, v: &str) -> core::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
let address = v.parse::<Address<NetworkUnchecked>>().map_err(E::custom)?;
Ok(Address::from_inner(address.to_inner()))
}
}
deserializer.deserialize_str(Visitor(PhantomData::<U>))
}
}
#[cfg(feature = "serde")]
impl<V: NetworkValidation> serde::Serialize for Address<V> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_str(&DisplayUnchecked(self))
}
}
impl<V: NetworkValidation> Address<V> {
fn from_inner(inner: AddressInner) -> Self {
Self(PhantomData, inner)
}
fn to_inner(self) -> AddressInner {
self.1
}
fn inner(&self) -> &AddressInner {
&self.1
}
pub fn as_unchecked(&self) -> &Address<NetworkUnchecked> {
Address::from_inner_ref(self.inner())
}
pub fn to_unchecked(self) -> Address<NetworkUnchecked> {
Address::from_inner(self.to_inner())
}
pub fn network_kind(&self) -> NetworkKind {
use AddressInner::*;
match *self.inner() {
P2pkh { hash: _, ref network } => network.network_kind(),
P2sh { hash: _, ref network } => network.network_kind(),
Segwit { program: _, ref hrp } => NetworkKind::from(*hrp),
}
}
}
impl Address {
#[inline]
pub fn p2pkh(pk: impl Into<PubkeyHash>, network: impl IntoAddressNetwork) -> Self {
let hash = pk.into();
Self::from_inner(AddressInner::P2pkh {
hash,
network: LegacyAddressNetwork::from(network.into_address_network()),
})
}
#[inline]
pub fn p2sh<T: ScriptHashableTag>(
redeem_script: &Script<T>,
network: impl IntoAddressNetwork,
) -> Result<Self, RedeemScriptSizeError> {
let hash = redeem_script.script_hash()?;
Ok(Self::p2sh_from_hash(hash, network))
}
pub fn p2sh_from_hash(hash: ScriptHash, network: impl IntoAddressNetwork) -> Self {
Self::from_inner(AddressInner::P2sh {
hash,
network: LegacyAddressNetwork::from(network.into_address_network()),
})
}
pub fn p2wpkh(pubkey_hash: WPubkeyHash, hrp: impl Into<KnownHrp>) -> Self {
let program = WitnessProgram::p2wpkh(pubkey_hash);
Self::from_witness_program(program, hrp)
}
pub fn p2shwpkh(pubkey_hash: WPubkeyHash, network: impl IntoAddressNetwork) -> Self {
let builder = ScriptPubKey::builder().push_int_unchecked(0).push_slice(pubkey_hash);
let script_hash = builder.as_script().script_hash().expect("script is less than 520 bytes");
Self::p2sh_from_hash(script_hash, network)
}
pub fn p2wsh(
witness_script: &WitnessScript,
hrp: impl Into<KnownHrp>,
) -> Result<Self, WitnessScriptSizeError> {
let program = WitnessProgram::p2wsh(witness_script)?;
Ok(Self::from_witness_program(program, hrp))
}
pub fn p2wsh_from_hash(hash: WScriptHash, hrp: impl Into<KnownHrp>) -> Self {
let program = WitnessProgram::p2wsh_from_hash(hash);
Self::from_witness_program(program, hrp)
}
pub fn p2shwsh(
witness_script: &WitnessScript,
network: impl IntoAddressNetwork,
) -> Result<Self, WitnessScriptSizeError> {
let hash = witness_script.wscript_hash()?;
let builder =
ScriptPubKey::builder().push_int_unchecked(0).push_slice(hash.to_byte_array());
let script_hash = builder.as_script().script_hash().expect("script is less than 520 bytes");
Ok(Self::p2sh_from_hash(script_hash, network))
}
pub fn p2wsh512(script_hash: [u8; 64], network: Network) -> Self {
let program = WitnessProgram::p2wsh512(script_hash);
let hrp = KnownHrp::from_network_pq(network);
Self::from_witness_program(program, hrp)
}
pub fn from_witness_program(program: WitnessProgram, hrp: impl Into<KnownHrp>) -> Self {
let inner = AddressInner::Segwit { program, hrp: hrp.into() };
Self::from_inner(inner)
}
#[inline]
pub fn address_type(&self) -> Option<AddressType> {
match *self.inner() {
AddressInner::P2pkh { .. } => Some(AddressType::P2pkh),
AddressInner::P2sh { .. } => Some(AddressType::P2sh),
AddressInner::Segwit { ref program, hrp: _ } => {
if program.is_p2wpkh() {
Some(AddressType::P2wpkh)
} else if program.is_p2wsh() {
Some(AddressType::P2wsh)
} else if program.is_p2wsh512() {
Some(AddressType::P2wsh512)
} else {
None
}
}
}
}
pub fn to_address_data(self) -> AddressData {
use AddressData::*;
match *self.inner() {
AddressInner::P2pkh { hash, network: _ } => P2pkh { pubkey_hash: hash },
AddressInner::P2sh { hash, network: _ } => P2sh { script_hash: hash },
AddressInner::Segwit { program, hrp: _ } => Segwit { witness_program: program },
}
}
pub fn pubkey_hash(&self) -> Option<PubkeyHash> {
use AddressInner::*;
match *self.inner() {
P2pkh { ref hash, network: _ } => Some(*hash),
_ => None,
}
}
pub fn script_hash(&self) -> Option<ScriptHash> {
use AddressInner::*;
match *self.inner() {
P2sh { ref hash, network: _ } => Some(*hash),
_ => None,
}
}
pub fn witness_program(&self) -> Option<WitnessProgram> {
use AddressInner::*;
match *self.inner() {
Segwit { ref program, hrp: _ } => Some(*program),
_ => None,
}
}
pub fn is_spend_standard(&self) -> bool {
self.address_type().is_some()
}
pub fn from_script(
script: &ScriptPubKey,
params: impl AsRef<Params>,
) -> Result<Self, FromScriptError> {
let network = params.as_ref().network;
if script.is_p2pkh() {
let bytes = script.as_bytes()[3..23].try_into().expect("statically 20B long");
let hash = PubkeyHash::from_byte_array(bytes);
Ok(Self::p2pkh(hash, network))
} else if script.is_p2sh() {
let bytes = script.as_bytes()[2..22].try_into().expect("statically 20B long");
let hash = ScriptHash::from_byte_array(bytes);
Ok(Self::p2sh_from_hash(hash, network))
} else if script.is_witness_program() {
let opcode = script.first_opcode().expect("is_witness_program guarantees len > 4");
let version = WitnessVersion::try_from(opcode)?;
let program = WitnessProgram::new(version, &script.as_bytes()[2..])?;
let hrp = if program.is_p2wsh512() {
KnownHrp::from_network_pq(network)
} else {
KnownHrp::from_network(network)
};
Ok(Self::from_witness_program(program, hrp))
} else {
Err(FromScriptError::UnrecognizedScript)
}
}
pub fn script_pubkey(&self) -> ScriptPubKeyBuf {
use AddressInner::*;
match *self.inner() {
P2pkh { hash, network: _ } => ScriptPubKeyBuf::new_p2pkh(hash),
P2sh { hash, network: _ } => ScriptPubKeyBuf::new_p2sh(hash),
Segwit { ref program, hrp: _ } => {
let prog = program.program();
let version = program.version();
script::new_witness_program_unchecked(version, prog)
}
}
}
pub fn to_qr_uri(self) -> String {
format!("tidecoin:{:#}", self)
}
pub fn matches_script_pubkey(&self, script: &ScriptPubKey) -> bool {
use AddressInner::*;
match *self.inner() {
P2pkh { ref hash, network: _ } if script.is_p2pkh() => {
&script.as_bytes()[3..23] == <PubkeyHash as AsRef<[u8; 20]>>::as_ref(hash)
}
P2sh { ref hash, network: _ } if script.is_p2sh() => {
&script.as_bytes()[2..22] == <ScriptHash as AsRef<[u8; 20]>>::as_ref(hash)
}
Segwit { ref program, hrp: _ } if script.is_witness_program() => {
script.as_bytes()[0] == Opcode::from(program.version()).to_u8()
&& &script.as_bytes()[2..] == program.program().as_bytes()
}
P2pkh { .. } | P2sh { .. } | Segwit { .. } => false,
}
}
}
impl Address<NetworkUnchecked> {
pub fn assume_checked_ref(&self) -> &Address {
Address::from_inner_ref(self.inner())
}
pub fn is_valid_for_network(&self, n: Network) -> bool {
use AddressInner::*;
match *self.inner() {
P2pkh { hash: _, ref network } => network.network() == n,
P2sh { hash: _, ref network } => network.network() == n,
Segwit { program: _, ref hrp } => {
*hrp == KnownHrp::from_network(n) || *hrp == KnownHrp::from_network_pq(n)
}
}
}
#[inline]
pub fn require_network(self, required: Network) -> Result<Address, ParseError> {
if self.is_valid_for_network(required) {
Ok(self.assume_checked())
} else {
Err(NetworkValidationError { required, address: self }.into())
}
}
#[inline]
pub fn assume_checked(self) -> Address {
Address::from_inner(self.to_inner())
}
pub fn from_bech32_str(s: &str) -> Result<Self, Bech32Error> {
let lower = s.to_lowercase();
let is_pq = ["q1", "tq1", "rq1"].iter().any(|&p| lower.starts_with(p));
if is_pq {
Self::from_bech32_pq(s)
} else {
let (hrp, witness_version, data) = bech32::segwit::decode(s)
.map_err(|e| Bech32Error::ParseBech32(ParseBech32Error(e)))?;
let version = WitnessVersion::try_from(witness_version.to_u8())?;
let program = WitnessProgram::new(version, &data)?;
let hrp = KnownHrp::from_hrp(hrp)?;
validate_hrp_namespace(hrp, &program)?;
let inner = AddressInner::Segwit { program, hrp };
Ok(Self::from_inner(inner))
}
}
fn from_bech32_pq(s: &str) -> Result<Self, Bech32Error> {
use bech32::primitives::decode::SegwitHrpstringError;
let unchecked = UncheckedHrpstring::new(s)
.map_err(|e| pq_decode_err(SegwitHrpstringError::Unchecked(e)))?;
let mut checked = unchecked
.validate_and_remove_checksum::<Bech32m>()
.map_err(|e| pq_decode_err(SegwitHrpstringError::Checksum(e)))?;
let witness_version = checked
.remove_witness_version()
.ok_or_else(|| pq_decode_err(SegwitHrpstringError::NoData))?;
let version = WitnessVersion::try_from(witness_version.to_u8())?;
let data: alloc::vec::Vec<u8> = checked.byte_iter().collect();
let program = WitnessProgram::new(version, &data)?;
let hrp = KnownHrp::from_hrp(checked.hrp())?;
validate_hrp_namespace(hrp, &program)?;
let inner = AddressInner::Segwit { program, hrp };
Ok(Self::from_inner(inner))
}
pub fn from_base58_str(s: &str) -> Result<Self, Base58Error> {
if s.len() > 50 {
return Err(LegacyAddressTooLongError { length: s.len() }.into());
}
let data = base58::decode_check(s)?;
let data: &[u8; 21] = (&*data)
.try_into()
.map_err(|_| InvalidBase58PayloadLengthError { length: data.len() })?;
let (prefix, &data) = data.split_first();
let inner = match *prefix {
PUBKEY_ADDRESS_PREFIX_MAIN => {
let hash = PubkeyHash::from_byte_array(data);
AddressInner::P2pkh { hash, network: LegacyAddressNetwork::Mainnet }
}
PUBKEY_ADDRESS_PREFIX_TEST => {
let hash = PubkeyHash::from_byte_array(data);
AddressInner::P2pkh { hash, network: LegacyAddressNetwork::Testnet }
}
PUBKEY_ADDRESS_PREFIX_REGTEST => {
let hash = PubkeyHash::from_byte_array(data);
AddressInner::P2pkh { hash, network: LegacyAddressNetwork::Regtest }
}
SCRIPT_ADDRESS_PREFIX_MAIN => {
let hash = ScriptHash::from_byte_array(data);
AddressInner::P2sh { hash, network: LegacyAddressNetwork::Mainnet }
}
SCRIPT_ADDRESS_PREFIX_MAIN_ALT => {
let hash = ScriptHash::from_byte_array(data);
AddressInner::P2sh { hash, network: LegacyAddressNetwork::Mainnet }
}
SCRIPT_ADDRESS_PREFIX_TEST => {
let hash = ScriptHash::from_byte_array(data);
AddressInner::P2sh { hash, network: LegacyAddressNetwork::Testnet }
}
SCRIPT_ADDRESS_PREFIX_TEST_ALT => {
let hash = ScriptHash::from_byte_array(data);
AddressInner::P2sh { hash, network: LegacyAddressNetwork::Testnet }
}
SCRIPT_ADDRESS_PREFIX_REGTEST => {
let hash = ScriptHash::from_byte_array(data);
AddressInner::P2sh { hash, network: LegacyAddressNetwork::Regtest }
}
SCRIPT_ADDRESS_PREFIX_REGTEST_ALT => {
let hash = ScriptHash::from_byte_array(data);
AddressInner::P2sh { hash, network: LegacyAddressNetwork::Regtest }
}
invalid => return Err(InvalidLegacyPrefixError { invalid }.into()),
};
Ok(Self::from_inner(inner))
}
}
impl From<Address> for ScriptPubKeyBuf {
fn from(a: Address) -> Self {
a.script_pubkey()
}
}
impl fmt::Display for Address {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.inner(), fmt)
}
}
impl<V: NetworkValidation> fmt::Debug for Address<V> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if V::IS_CHECKED {
fmt::Display::fmt(&self.inner(), f)
} else {
write!(f, "Address<NetworkUnchecked>(")?;
fmt::Display::fmt(&self.inner(), f)?;
write!(f, ")")
}
}
}
impl<U: NetworkValidationUnchecked> FromStr for Address<U> {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, ParseError> {
let lower = s.to_lowercase();
if ["tbc1", "ttbc1", "rtbc1", "q1", "tq1", "rq1"]
.iter()
.any(|&prefix| lower.starts_with(prefix))
{
let address = Address::from_bech32_str(s)?;
Ok(Self::from_inner(address.to_inner()))
} else {
match Address::from_base58_str(s) {
Ok(address) => Ok(Self::from_inner(address.to_inner())),
Err(_) => {
let hrp = match s.rfind('1') {
Some(pos) => &s[..pos],
None => s,
};
Err(UnknownHrpError(hrp.to_owned()).into())
}
}
}
}
}
fn validate_hrp_namespace(hrp: KnownHrp, program: &WitnessProgram) -> Result<(), Bech32Error> {
let pq_hrp = matches!(hrp, KnownHrp::PqMainnet | KnownHrp::PqTestnets | KnownHrp::PqRegtest);
let pq_program = program.is_p2wsh512();
if pq_hrp == pq_program {
return Ok(());
}
let expected = if pq_hrp {
"p2wsh512 witness-v1 64-byte programs"
} else {
"legacy segwit witness-v0 programs"
};
Err(InvalidSegwitHrpError { hrp: hrp.to_hrp().to_lowercase(), expected }.into())
}
fn encode_pq_bech32m(
f: &mut fmt::Formatter,
hrp: Hrp,
version: Fe32,
program: &[u8],
uppercase: bool,
) -> fmt::Result {
let iter = program
.iter()
.copied()
.bytes_to_fes()
.with_checksum::<Bech32m>(&hrp)
.with_witness_version(version)
.chars();
for c in iter {
let c = if uppercase { c.to_ascii_uppercase() } else { c };
write!(f, "{}", c)?;
}
Ok(())
}
fn pq_decode_err(e: bech32::primitives::decode::SegwitHrpstringError) -> Bech32Error {
Bech32Error::ParseBech32(ParseBech32Error(bech32::segwit::DecodeError::from(e)))
}
#[cfg(test)]
mod tests {
use alloc::string::{String, ToString};
use core::str::FromStr;
use bech32::primitives::gf32::Fe32;
use bech32::primitives::iter::{ByteIterExt as _, Fe32IterExt as _};
use bech32::Bech32m;
use hashes::sha512;
use super::*;
use crate::network::Network::Tidecoin;
use crate::script::WitnessScriptBuf;
fn roundtrips(addr: &Address, network: Network) {
assert_eq!(
addr.to_string().parse::<Address<_>>().unwrap().assume_checked(),
*addr,
"string round-trip failed for {}",
addr,
);
assert_eq!(
Address::from_script(&addr.script_pubkey(), network)
.expect("failed to create inner address from script_pubkey"),
*addr,
"script round-trip failed for {}",
addr,
);
#[cfg(feature = "serde")]
{
let ser = serde_json::to_string(addr).expect("failed to serialize address");
let back: Address<NetworkUnchecked> =
serde_json::from_str(&ser).expect("failed to deserialize address");
assert_eq!(back.assume_checked(), *addr, "serde round-trip failed for {}", addr)
}
}
fn encode_pq_address(hrp: KnownHrp, version: u8, program: &[u8]) -> String {
let version_fe = Fe32::try_from(version).unwrap();
let hrp_value = hrp.to_hrp();
let chars = program
.iter()
.copied()
.bytes_to_fes()
.with_checksum::<Bech32m>(&hrp_value)
.with_witness_version(version_fe)
.chars();
let mut encoded = hrp_value.to_string();
encoded.push('1');
encoded.extend(chars);
encoded
}
#[test]
fn p2pkh_address_58() {
let hash = "162c5ea71c0b23f5b9022ef047c4a86470a5b070".parse::<PubkeyHash>().unwrap();
let addr = Address::p2pkh(hash, NetworkKind::Main);
assert_eq!(
addr.script_pubkey(),
ScriptPubKeyBuf::from_hex_no_length_prefix(
"76a914162c5ea71c0b23f5b9022ef047c4a86470a5b07088ac"
)
.unwrap()
);
assert_eq!(&addr.to_string(), "EKB9WfgyJsUYoSur9BAoPDkt3BbE6dYkYA");
assert_eq!(addr.address_type(), Some(AddressType::P2pkh));
roundtrips(&addr, Tidecoin);
}
#[test]
fn p2wpkh_and_p2shwpkh_from_hashes_roundtrip() {
let hash = WPubkeyHash::from_byte_array([0x11; 20]);
let native = Address::p2wpkh(hash, KnownHrp::Mainnet);
assert_eq!(native.address_type(), Some(AddressType::P2wpkh));
assert!(native.to_string().starts_with("tbc1q"));
roundtrips(&native, Tidecoin);
let wrapped = Address::p2shwpkh(hash, Network::Tidecoin);
assert_eq!(wrapped.address_type(), Some(AddressType::P2sh));
assert!(wrapped.to_string().starts_with('T'));
roundtrips(&wrapped, Tidecoin);
}
#[test]
fn p2wsh_and_p2wsh512_roundtrip() {
let witness_script = WitnessScriptBuf::from_hex_no_length_prefix("5121aa51ae").unwrap();
let p2wsh = Address::p2wsh(&witness_script, KnownHrp::Mainnet).unwrap();
assert_eq!(p2wsh.address_type(), Some(AddressType::P2wsh));
roundtrips(&p2wsh, Tidecoin);
let hash = sha512::Hash::hash(witness_script.as_bytes()).to_byte_array();
let p2wsh512 = Address::p2wsh512(hash, Network::Tidecoin);
assert_eq!(p2wsh512.address_type(), Some(AddressType::P2wsh512));
assert!(p2wsh512.to_string().starts_with("q1"));
roundtrips(&p2wsh512, Tidecoin);
}
#[test]
fn address_type_detection_is_tidecoin_specific() {
let witness_script = WitnessScriptBuf::from_hex_no_length_prefix("51").unwrap();
let hash = sha512::Hash::hash(witness_script.as_bytes()).to_byte_array();
let pq = Address::p2wsh512(hash, Network::Tidecoin).to_string();
let addresses = [
("EKB9WfgyJsUYoSur9BAoPDkt3BbE6dYkYA", Some(AddressType::P2pkh)),
("TBzT29EC2eJayKNcvbpzuEU4BKqQt4QGvz", Some(AddressType::P2sh)),
(
"tbc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxs0479ga",
Some(AddressType::P2wsh),
),
(&pq, Some(AddressType::P2wsh512)),
("tbc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqhqlyfv", None),
];
for (address, expected_type) in addresses {
let addr =
address.parse::<Address<_>>().unwrap().require_network(Network::Tidecoin).unwrap();
assert_eq!(addr.address_type(), expected_type);
}
}
#[test]
fn valid_and_invalid_address_types_parse() {
assert_eq!("p2wsh".parse::<AddressType>().unwrap(), AddressType::P2wsh);
assert_eq!(
"invalid".parse::<AddressType>(),
Err(UnknownAddressTypeError("invalid".to_owned()))
);
}
#[test]
fn regtest_legacy_decoding_reencodes_to_canonical_node_prefixes() {
let p2pkh =
Address::<NetworkUnchecked>::from_str("p76p8nAKz7uyGorcGP4ShgLAYimASoxSJq").unwrap();
assert!(p2pkh.is_valid_for_network(Network::Regtest));
assert!(!p2pkh.is_valid_for_network(Network::Testnet));
assert_eq!(p2pkh.assume_checked_ref().to_string(), "p76p8nAKz7uyGorcGP4ShgLAYimASoxSJq");
let noncanonical_p2sh =
Address::<NetworkUnchecked>::from_str("2Htyg1M1ptaatHp24Md8b8LbZtEA3eMfM4t").unwrap();
assert!(noncanonical_p2sh.is_valid_for_network(Network::Regtest));
assert!(!noncanonical_p2sh.is_valid_for_network(Network::Testnet));
assert_eq!(
noncanonical_p2sh.assume_checked_ref().to_string(),
"r9M4zPwPT2vox56WnmpC6KBDbwfg8X4QD8"
);
}
#[test]
fn pq_bech32_namespace_matches_node_rules() {
let witness_script = WitnessScriptBuf::from_hex_no_length_prefix("51").unwrap();
let hash = sha512::Hash::hash(witness_script.as_bytes()).to_byte_array();
let addr = Address::p2wsh512(hash, Network::Tidecoin);
assert!(addr.to_string().starts_with("q1"));
let parsed = addr
.to_string()
.parse::<Address<_>>()
.unwrap()
.require_network(Network::Tidecoin)
.unwrap();
assert_eq!(parsed.address_type(), Some(AddressType::P2wsh512));
let pq_v0 = encode_pq_address(KnownHrp::PqMainnet, 0, &[0x42; 20]);
assert!(Address::<NetworkUnchecked>::from_bech32_str(&pq_v0).is_err());
let legacy_v1 = encode_pq_address(KnownHrp::Mainnet, 1, &[0x11; 64]);
assert!(Address::<NetworkUnchecked>::from_bech32_str(&legacy_v1).is_err());
}
#[test]
fn pq_bech32_roundtrips_all_supported_networks() {
let witness_script = WitnessScriptBuf::from_hex_no_length_prefix("51").unwrap();
let hash = sha512::Hash::hash(witness_script.as_bytes()).to_byte_array();
for (network, prefix) in
[(Network::Tidecoin, "q1"), (Network::Testnet, "tq1"), (Network::Regtest, "rq1")]
{
let address = Address::p2wsh512(hash, network);
assert!(address.to_string().starts_with(prefix));
roundtrips(&address, network);
assert_eq!(Address::from_script(&address.script_pubkey(), network).unwrap(), address);
}
}
#[test]
fn bech32_checksum_errors_reject_legacy_and_pq_namespaces() {
let corrupt_last = |mut s: String| {
let replacement = if s.ends_with('q') { 'p' } else { 'q' };
s.pop();
s.push(replacement);
s
};
let legacy = "tbc1qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp";
assert!(matches!(
Address::<NetworkUnchecked>::from_bech32_str(&corrupt_last(legacy.to_owned())),
Err(Bech32Error::ParseBech32(_))
));
let pq = Address::p2wsh512([0x22; 64], Network::Tidecoin).to_string();
assert!(matches!(
Address::<NetworkUnchecked>::from_bech32_str(&corrupt_last(pq)),
Err(Bech32Error::ParseBech32(_))
));
}
}