1pub mod error;
30
31use core::fmt;
32use core::marker::PhantomData;
33use core::str::FromStr;
34
35use hashes::Hash;
36use secp256k1::XOnlyPublicKey;
37
38use crate::blockdata::constants::MAX_SCRIPT_ELEMENT_SIZE;
39use crate::blockdata::script::{self, Script, ScriptBuf, ScriptHash};
40use crate::crypto::key::{PubkeyHash, PublicKey};
41use crate::dogecoin::constants::{
42 PUBKEY_ADDRESS_PREFIX_MAINNET, PUBKEY_ADDRESS_PREFIX_REGTEST, PUBKEY_ADDRESS_PREFIX_TESTNET,
43 SCRIPT_ADDRESS_PREFIX_MAINNET, SCRIPT_ADDRESS_PREFIX_REGTEST, SCRIPT_ADDRESS_PREFIX_TESTNET,
44};
45use crate::dogecoin::Network;
46
47#[rustfmt::skip] #[doc(inline)]
49pub use self::{
50 error::{
51 FromScriptError, InvalidBase58PayloadLengthError, InvalidLegacyPrefixError, LegacyAddressTooLongError,
52 NetworkValidationError, ParseError, P2shError,
53 },
54};
55
56pub use crate::address::{AddressType, NetworkChecked, NetworkUnchecked, NetworkValidation};
58
59#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
64enum AddressInner {
65 P2pkh { hash: PubkeyHash, network: Network },
66 P2sh { hash: ScriptHash, network: Network },
67}
68
69impl fmt::Display for AddressInner {
71 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
72 use AddressInner::*;
73 match self {
74 P2pkh { hash, network } => {
75 let mut prefixed = [0; 21];
76 prefixed[0] = match network {
77 Network::Dogecoin => PUBKEY_ADDRESS_PREFIX_MAINNET,
78 Network::Testnet => PUBKEY_ADDRESS_PREFIX_TESTNET,
79 Network::Regtest => PUBKEY_ADDRESS_PREFIX_REGTEST,
80 };
81 prefixed[1..].copy_from_slice(&hash[..]);
82 base58::encode_check_to_fmt(fmt, &prefixed[..])
83 }
84 P2sh { hash, network } => {
85 let mut prefixed = [0; 21];
86 prefixed[0] = match network {
87 Network::Dogecoin => SCRIPT_ADDRESS_PREFIX_MAINNET,
88 Network::Testnet => SCRIPT_ADDRESS_PREFIX_TESTNET,
89 Network::Regtest => SCRIPT_ADDRESS_PREFIX_REGTEST,
90 };
91 prefixed[1..].copy_from_slice(&hash[..]);
92 base58::encode_check_to_fmt(fmt, &prefixed[..])
93 }
94 }
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
103#[non_exhaustive]
104pub enum AddressData {
105 P2pkh {
107 pubkey_hash: PubkeyHash,
109 },
110 P2sh {
112 script_hash: ScriptHash,
114 },
115}
116
117#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
196#[repr(transparent)]
199pub struct Address<V = NetworkChecked>(AddressInner, PhantomData<V>)
200where
201 V: NetworkValidation;
202
203#[cfg(feature = "serde")]
204struct DisplayUnchecked<'a, N: NetworkValidation>(&'a Address<N>);
205
206#[cfg(feature = "serde")]
207impl<N: NetworkValidation> fmt::Display for DisplayUnchecked<'_, N> {
208 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
209 fmt::Display::fmt(&self.0 .0, fmt)
210 }
211}
212
213#[cfg(feature = "serde")]
214crate::serde_utils::serde_string_deserialize_impl!(Address<NetworkUnchecked>, "a Dogecoin address");
215
216#[cfg(feature = "serde")]
217impl<N: NetworkValidation> serde::Serialize for Address<N> {
218 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
219 where
220 S: serde::Serializer,
221 {
222 serializer.collect_str(&DisplayUnchecked(self))
223 }
224}
225
226impl<V: NetworkValidation> Address<V> {
229 pub fn as_unchecked(&self) -> &Address<NetworkUnchecked> {
231 unsafe { &*(self as *const Address<V> as *const Address<NetworkUnchecked>) }
232 }
233
234 pub fn into_unchecked(self) -> Address<NetworkUnchecked> {
236 Address(self.0, PhantomData)
237 }
238}
239
240impl Address {
242 #[inline]
246 pub fn p2pkh(pk: impl Into<PubkeyHash>, network: impl Into<Network>) -> Address {
247 let hash = pk.into();
248 Self(AddressInner::P2pkh { hash, network: network.into() }, PhantomData)
249 }
250
251 #[inline]
256 pub fn p2sh(script: &Script, network: impl Into<Network>) -> Result<Address, P2shError> {
257 if script.len() > MAX_SCRIPT_ELEMENT_SIZE {
258 return Err(P2shError::ExcessiveScriptSize);
259 }
260 let hash = script.script_hash();
261 Ok(Address::p2sh_from_hash(hash, network))
262 }
263
264 pub fn p2sh_from_hash(hash: ScriptHash, network: impl Into<Network>) -> Address {
271 Self(AddressInner::P2sh { hash, network: network.into() }, PhantomData)
272 }
273
274 #[inline]
280 pub fn address_type(&self) -> Option<AddressType> {
281 match self.0 {
282 AddressInner::P2pkh { .. } => Some(AddressType::P2pkh),
283 AddressInner::P2sh { .. } => Some(AddressType::P2sh),
284 }
285 }
286
287 pub fn to_address_data(&self) -> AddressData {
289 use AddressData::*;
290
291 match self.0 {
292 AddressInner::P2pkh { hash, network: _ } => P2pkh { pubkey_hash: hash },
293 AddressInner::P2sh { hash, network: _ } => P2sh { script_hash: hash },
294 }
295 }
296
297 pub fn pubkey_hash(&self) -> Option<PubkeyHash> {
299 use AddressInner::*;
300
301 match self.0 {
302 P2pkh { ref hash, network: _ } => Some(*hash),
303 _ => None,
304 }
305 }
306
307 pub fn script_hash(&self) -> Option<ScriptHash> {
309 use AddressInner::*;
310
311 match self.0 {
312 P2sh { ref hash, network: _ } => Some(*hash),
313 _ => None,
314 }
315 }
316
317 pub fn from_script(
319 script: &Script,
320 network: impl Into<Network>,
321 ) -> Result<Address, FromScriptError> {
322 let network = network.into();
323 if script.is_p2pkh() {
324 let bytes = script.as_bytes()[3..23].try_into().expect("statically 20B long");
325 let hash = PubkeyHash::from_byte_array(bytes);
326 Ok(Address::p2pkh(hash, network))
327 } else if script.is_p2sh() {
328 let bytes = script.as_bytes()[2..22].try_into().expect("statically 20B long");
329 let hash = ScriptHash::from_byte_array(bytes);
330 Ok(Address::p2sh_from_hash(hash, network))
331 } else {
332 Err(FromScriptError::UnrecognizedScript)
333 }
334 }
335
336 pub fn script_pubkey(&self) -> ScriptBuf {
338 use AddressInner::*;
339 match self.0 {
340 P2pkh { ref hash, network: _ } => ScriptBuf::new_p2pkh(hash),
341 P2sh { ref hash, network: _ } => ScriptBuf::new_p2sh(hash),
342 }
343 }
344
345 pub fn is_related_to_pubkey(&self, pubkey: &PublicKey) -> bool {
351 let pubkey_hash = pubkey.pubkey_hash();
352 let payload = self.payload_as_bytes();
353 let xonly_pubkey = XOnlyPublicKey::from(pubkey.inner);
354
355 (*pubkey_hash.as_byte_array() == *payload) || (xonly_pubkey.serialize() == *payload)
356 }
357
358 pub fn matches_script_pubkey(&self, script: &Script) -> bool {
361 use AddressInner::*;
362 match self.0 {
363 P2pkh { ref hash, network: _ } if script.is_p2pkh() => {
364 &script.as_bytes()[3..23] == <PubkeyHash as AsRef<[u8; 20]>>::as_ref(hash)
365 }
366 P2sh { ref hash, network: _ } if script.is_p2sh() => {
367 &script.as_bytes()[2..22] == <ScriptHash as AsRef<[u8; 20]>>::as_ref(hash)
368 }
369 P2pkh { .. } | P2sh { .. } => false,
370 }
371 }
372
373 fn payload_as_bytes(&self) -> &[u8] {
381 use AddressInner::*;
382 match self.0 {
383 P2sh { ref hash, network: _ } => hash.as_ref(),
384 P2pkh { ref hash, network: _ } => hash.as_ref(),
385 }
386 }
387}
388
389impl Address<NetworkUnchecked> {
391 pub fn assume_checked_ref(&self) -> &Address {
395 unsafe { &*(self as *const Address<NetworkUnchecked> as *const Address) }
396 }
397
398 pub fn is_valid_for_network(&self, n: Network) -> bool {
427 use AddressInner::*;
428 match self.0 {
429 P2pkh { hash: _, ref network } => *network == n,
430 P2sh { hash: _, network: Network::Dogecoin } => n == Network::Dogecoin,
431 P2sh { hash: _, network: Network::Testnet } => {
432 n == Network::Testnet || n == Network::Regtest
433 }
434 P2sh { hash: _, network: Network::Regtest } => {
435 n == Network::Testnet || n == Network::Regtest
436 }
437 }
438 }
439
440 #[inline]
483 pub fn require_network(self, required: Network) -> Result<Address, ParseError> {
484 if self.is_valid_for_network(required) {
485 Ok(self.assume_checked())
486 } else {
487 Err(NetworkValidationError { required, address: self }.into())
488 }
489 }
490
491 #[inline]
498 pub fn assume_checked(self) -> Address {
499 Address(self.0, PhantomData)
500 }
501}
502
503impl From<Address> for script::ScriptBuf {
504 fn from(a: Address) -> Self {
505 a.script_pubkey()
506 }
507}
508
509impl fmt::Display for Address {
512 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
513 fmt::Display::fmt(&self.0, fmt)
514 }
515}
516
517impl<V: NetworkValidation> fmt::Debug for Address<V> {
518 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
519 if V::IS_CHECKED {
520 fmt::Display::fmt(&self.0, f)
521 } else {
522 write!(f, "Address<NetworkUnchecked>(")?;
523 fmt::Display::fmt(&self.0, f)?;
524 write!(f, ")")
525 }
526 }
527}
528
529impl FromStr for Address<NetworkUnchecked> {
531 type Err = ParseError;
532
533 fn from_str(s: &str) -> Result<Address<NetworkUnchecked>, ParseError> {
534 if s.len() > 50 {
535 return Err(LegacyAddressTooLongError { length: s.len() }.into());
536 }
537 let data = base58::decode_check(s)?;
538 if data.len() != 21 {
539 return Err(InvalidBase58PayloadLengthError { length: s.len() }.into());
540 }
541
542 let (prefix, data) = data.split_first().expect("length checked above");
543 let data: [u8; 20] = data.try_into().expect("length checked above");
544
545 let inner = match *prefix {
546 PUBKEY_ADDRESS_PREFIX_MAINNET => {
547 let hash = PubkeyHash::from_byte_array(data);
548 AddressInner::P2pkh { hash, network: Network::Dogecoin }
549 }
550 PUBKEY_ADDRESS_PREFIX_TESTNET => {
551 let hash = PubkeyHash::from_byte_array(data);
552 AddressInner::P2pkh { hash, network: Network::Testnet }
553 }
554 PUBKEY_ADDRESS_PREFIX_REGTEST => {
555 let hash = PubkeyHash::from_byte_array(data);
556 AddressInner::P2pkh { hash, network: Network::Regtest }
557 }
558 SCRIPT_ADDRESS_PREFIX_MAINNET => {
559 let hash = ScriptHash::from_byte_array(data);
560 AddressInner::P2sh { hash, network: Network::Dogecoin }
561 }
562 SCRIPT_ADDRESS_PREFIX_TESTNET => {
567 let hash = ScriptHash::from_byte_array(data);
568 AddressInner::P2sh { hash, network: Network::Testnet }
569 }
570 invalid => return Err(InvalidLegacyPrefixError { invalid }.into()),
571 };
572
573 Ok(Address(inner, PhantomData))
574 }
575}
576
577#[cfg(test)]
578mod tests {
579 use super::*;
580 use crate::dogecoin::Network::{Dogecoin, Testnet};
581
582 fn roundtrips(addr: &Address, network: Network) {
583 assert_eq!(
584 Address::from_str(&addr.to_string()).unwrap().assume_checked(),
585 *addr,
586 "string round-trip failed for {}",
587 addr,
588 );
589 assert_eq!(
590 Address::from_script(&addr.script_pubkey(), network)
591 .expect("failed to create inner address from script_pubkey"),
592 *addr,
593 "script round-trip failed for {}",
594 addr,
595 );
596
597 #[cfg(feature = "serde")]
598 {
599 let ser = serde_json::to_string(addr).expect("failed to serialize address");
600 let back: Address<NetworkUnchecked> =
601 serde_json::from_str(&ser).expect("failed to deserialize address");
602 assert_eq!(back.assume_checked(), *addr, "serde round-trip failed for {}", addr)
603 }
604 }
605
606 #[test]
607 fn test_p2pkh_address_58() {
608 let hash = "162c5ea71c0b23f5b9022ef047c4a86470a5b070".parse::<PubkeyHash>().unwrap();
609 let addr = Address::p2pkh(hash, Dogecoin);
610
611 assert_eq!(
612 addr.script_pubkey(),
613 ScriptBuf::from_hex("76a914162c5ea71c0b23f5b9022ef047c4a86470a5b07088ac").unwrap()
614 );
615 assert_eq!(&addr.to_string(), "D7ALZLo7BL5vM9Vb4vAqvqwX9fpQ4wKRiy");
616 assert_eq!(addr.address_type(), Some(AddressType::P2pkh));
617 roundtrips(&addr, Dogecoin);
618 }
619
620 #[test]
621 fn test_p2pkh_from_key() {
622 let key = "048d5141948c1702e8c95f438815794b87f706a8d4cd2bffad1dc1570971032c9b6042a0431ded2478b5c9cf2d81c124a5e57347a3c63ef0e7716cf54d613ba183".parse::<PublicKey>().unwrap();
623 let addr = Address::p2pkh(key, Dogecoin);
624 assert_eq!(&addr.to_string(), "DUSamFaUtRQ78DVidoeY3J8keYkQXdinrt");
625
626 let key = "03df154ebfcf29d29cc10d5c2565018bce2d9edbab267c31d2caf44a63056cf99f"
627 .parse::<PublicKey>()
628 .unwrap();
629 let addr = Address::p2pkh(key, Testnet);
630 assert_eq!(&addr.to_string(), "neRuCZsfnZaJN8FmxxVZDSZbcGATnzGByf");
631 assert_eq!(addr.address_type(), Some(AddressType::P2pkh));
632 roundtrips(&addr, Testnet);
633 }
634
635 #[test]
636 fn test_p2sh_address_58() {
637 let hash = "162c5ea71c0b23f5b9022ef047c4a86470a5b070".parse::<ScriptHash>().unwrap();
638 let addr = Address::p2sh_from_hash(hash, Dogecoin);
639
640 assert_eq!(
641 addr.script_pubkey(),
642 ScriptBuf::from_hex("a914162c5ea71c0b23f5b9022ef047c4a86470a5b07087").unwrap(),
643 );
644 assert_eq!(&addr.to_string(), "9tTWgUQoVtNuogNtsZWJ3qmE7dkrPTrVAj");
645 assert_eq!(addr.address_type(), Some(AddressType::P2sh));
646 roundtrips(&addr, Dogecoin);
647 }
648
649 #[test]
650 fn test_p2sh_parse() {
651 let script = ScriptBuf::from_hex("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae").unwrap();
652 let addr = Address::p2sh(&script, Testnet).unwrap();
653 assert_eq!(&addr.to_string(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr");
654 assert_eq!(addr.address_type(), Some(AddressType::P2sh));
655 roundtrips(&addr, Testnet);
656 }
657
658 #[test]
659 fn test_p2sh_parse_for_large_script() {
660 let script = ScriptBuf::from_hex("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123").unwrap();
661 assert_eq!(Address::p2sh(&script, Testnet), Err(P2shError::ExcessiveScriptSize));
662 }
663
664 #[test]
665 fn test_address_debug() {
666 #[derive(Debug)]
669 #[allow(unused)]
670 struct Test<V: NetworkValidation> {
671 address: Address<V>,
672 }
673
674 let addr_str = "n48pquU8ieq7gidgJJ4vWD2jbsErmZvrwe";
675 let unchecked = Address::from_str(addr_str).unwrap();
676
677 assert_eq!(
678 format!("{:?}", Test { address: unchecked.clone() }),
679 format!("Test {{ address: Address<NetworkUnchecked>({}) }}", addr_str)
680 );
681
682 assert_eq!(
683 format!("{:?}", Test { address: unchecked.assume_checked() }),
684 format!("Test {{ address: {} }}", addr_str)
685 );
686 }
687
688 #[test]
689 fn test_address_type() {
690 let addresses = [
691 ("DMKhUaRmnxJXfDxyFguMnMjVdgvnNipFzt", Some(AddressType::P2pkh)),
692 ("A1yb6viUzAcUWftRHT6GpnCwvhXHg4CV1x", Some(AddressType::P2sh)),
693 ];
694 for (address, expected_type) in &addresses {
695 let addr =
696 Address::from_str(address).unwrap().require_network(Dogecoin).expect("mainnet");
697 assert_eq!(&addr.address_type(), expected_type);
698 }
699 }
700
701 #[test]
702 #[cfg(feature = "serde")]
703 fn test_json_serialize() {
704 use serde_json;
705
706 let addr =
707 Address::from_str("D7ALZLo7BL5vM9Vb4vAqvqwX9fpQ4wKRiy").unwrap().assume_checked();
708 let json = serde_json::to_value(&addr).unwrap();
709 assert_eq!(
710 json,
711 serde_json::Value::String("D7ALZLo7BL5vM9Vb4vAqvqwX9fpQ4wKRiy".to_owned())
712 );
713 let into: Address = serde_json::from_value::<Address<_>>(json).unwrap().assume_checked();
714 assert_eq!(addr.to_string(), into.to_string());
715 assert_eq!(
716 into.script_pubkey(),
717 ScriptBuf::from_hex("76a914162c5ea71c0b23f5b9022ef047c4a86470a5b07088ac").unwrap()
718 );
719
720 let addr =
721 Address::from_str("9tTWgUQoVtNuogNtsZWJ3qmE7dkrPTrVAj").unwrap().assume_checked();
722 let json = serde_json::to_value(&addr).unwrap();
723 assert_eq!(
724 json,
725 serde_json::Value::String("9tTWgUQoVtNuogNtsZWJ3qmE7dkrPTrVAj".to_owned())
726 );
727 let into: Address = serde_json::from_value::<Address<_>>(json).unwrap().assume_checked();
728 assert_eq!(addr.to_string(), into.to_string());
729 assert_eq!(
730 into.script_pubkey(),
731 ScriptBuf::from_hex("a914162c5ea71c0b23f5b9022ef047c4a86470a5b07087").unwrap()
732 );
733 }
734
735 #[test]
736 fn test_is_related_to_pubkey_p2pkh() {
737 let address_string = "neRuCZsfnZaJN8FmxxVZDSZbcGATnzGByf";
738 let address = Address::from_str(address_string)
739 .expect("address")
740 .require_network(Testnet)
741 .expect("testnet");
742
743 let pubkey_string = "03df154ebfcf29d29cc10d5c2565018bce2d9edbab267c31d2caf44a63056cf99f";
744 let pubkey = PublicKey::from_str(pubkey_string).expect("pubkey");
745
746 let result = address.is_related_to_pubkey(&pubkey);
747 assert!(result);
748
749 let unused_pubkey = PublicKey::from_str(
750 "02ba604e6ad9d3864eda8dc41c62668514ef7d5417d3b6db46e45cc4533bff001c",
751 )
752 .expect("pubkey");
753 assert!(!address.is_related_to_pubkey(&unused_pubkey))
754 }
755
756 #[test]
757 fn test_is_related_to_pubkey_p2pkh_uncompressed_key() {
758 let address_string = "DUSamFaUtRQ78DVidoeY3J8keYkQXdinrt";
759 let address = Address::from_str(address_string)
760 .expect("address")
761 .require_network(Dogecoin)
762 .expect("mainnet");
763
764 let pubkey_string = "048d5141948c1702e8c95f438815794b87f706a8d4cd2bffad1dc1570971032c9b6042a0431ded2478b5c9cf2d81c124a5e57347a3c63ef0e7716cf54d613ba183";
765 let pubkey = PublicKey::from_str(pubkey_string).expect("pubkey");
766
767 let result = address.is_related_to_pubkey(&pubkey);
768 assert!(result);
769
770 let unused_pubkey = PublicKey::from_str(
771 "02ba604e6ad9d3864eda8dc41c62668514ef7d5417d3b6db46e45cc4533bff001c",
772 )
773 .expect("pubkey");
774 assert!(!address.is_related_to_pubkey(&unused_pubkey))
775 }
776}