1use alloc::{
4 boxed::Box,
5 string::{String, ToString},
6 vec::Vec,
7};
8use core::{convert::Infallible, fmt};
9
10use transparent::address::TransparentAddress;
11use zcash_address::{
12 ConversionError, ToAddress, TryFromAddress, ZcashAddress,
13 unified::{
14 self, Container, Encoding, MetadataItem, MetadataTypecode, Revision, Typecode, Uitem,
15 },
16};
17use zcash_protocol::{
18 PoolType, ShieldedPool,
19 consensus::{self, NetworkType},
20};
21
22use crate::encoding::{UnifiedDecodingError, UnifiedEncodingError};
23
24#[cfg(feature = "sapling")]
25use sapling::PaymentAddress;
26
27#[derive(Debug, PartialEq, Eq)]
29#[non_exhaustive]
30pub enum PruneError {
31 MustUnderstandMetadata(MetadataTypecode),
34 NotRepresentable(unified::ParseError),
37}
38
39impl fmt::Display for PruneError {
40 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
41 match self {
42 PruneError::MustUnderstandMetadata(typecode) => write!(
43 f,
44 "Cannot remove MUST-understand metadata item with typecode {:#04X}",
45 u32::from(*typecode)
46 ),
47 PruneError::NotRepresentable(cause) => {
48 write!(f, "The pruned unified address has no encoding: {cause}")
49 }
50 }
51 }
52}
53
54#[cfg(feature = "std")]
55impl std::error::Error for PruneError {}
56
57#[derive(Clone, Debug, PartialEq, Eq)]
59pub struct UnifiedAddress {
60 #[cfg(feature = "orchard")]
61 orchard: Option<orchard::Address>,
62 #[cfg(feature = "sapling")]
63 sapling: Option<PaymentAddress>,
64 transparent: Option<TransparentAddress>,
65 unknown: Vec<(u32, Vec<u8>)>,
66 expiry_height: Option<consensus::BlockHeight>,
67 expiry_time: Option<u64>,
68 unknown_metadata: Vec<(u32, Vec<u8>)>,
69}
70
71impl TryFrom<unified::Address> for UnifiedAddress {
72 type Error = &'static str;
73
74 fn try_from(ua: unified::Address) -> Result<Self, Self::Error> {
75 Self::from_container(ua).map_err(|typecode| match typecode {
76 Typecode::ORCHARD => "Invalid Orchard receiver in Unified Address",
77 _ => "Invalid Sapling receiver in Unified Address",
78 })
79 }
80}
81
82impl UnifiedAddress {
83 fn from_container(ua: unified::Address) -> Result<Self, Typecode> {
88 #[cfg(feature = "orchard")]
89 let mut orchard = None;
90 #[cfg(feature = "sapling")]
91 let mut sapling = None;
92 let mut transparent = None;
93
94 let mut unknown: Vec<(u32, Vec<u8>)> = vec![];
95 let mut expiry_height = None;
96 let mut expiry_time = None;
97 let mut unknown_metadata: Vec<(u32, Vec<u8>)> = vec![];
98
99 for item in ua.items_as_parsed() {
102 match item {
103 Uitem::Data(unified::Receiver::Orchard(data)) => {
104 #[cfg(feature = "orchard")]
105 {
106 orchard = Some(
107 Option::from(orchard::Address::from_raw_address_bytes(data))
108 .ok_or(Typecode::ORCHARD)?,
109 );
110 }
111 #[cfg(not(feature = "orchard"))]
112 {
113 unknown.push((Typecode::ORCHARD.into(), data.to_vec()));
114 }
115 }
116
117 Uitem::Data(unified::Receiver::Sapling(data)) => {
118 #[cfg(feature = "sapling")]
119 {
120 sapling = Some(PaymentAddress::from_bytes(data).ok_or(Typecode::SAPLING)?);
121 }
122 #[cfg(not(feature = "sapling"))]
123 {
124 unknown.push((Typecode::SAPLING.into(), data.to_vec()));
125 }
126 }
127
128 Uitem::Data(unified::Receiver::P2pkh(data)) => {
129 transparent = Some(TransparentAddress::PublicKeyHash(*data));
130 }
131
132 Uitem::Data(unified::Receiver::P2sh(data)) => {
133 transparent = Some(TransparentAddress::ScriptHash(*data));
134 }
135
136 Uitem::Data(unified::Receiver::Unknown { typecode, data }) => {
137 unknown.push((*typecode, data.clone()));
138 }
139
140 Uitem::Metadata(MetadataItem::ExpiryHeight(h)) => {
141 expiry_height = Some(consensus::BlockHeight::from_u32(*h));
142 }
143
144 Uitem::Metadata(MetadataItem::ExpiryTime(t)) => {
145 expiry_time = Some(*t);
146 }
147
148 Uitem::Metadata(MetadataItem::Unknown { typecode, data }) => {
149 unknown_metadata.push((*typecode, data.clone()));
150 }
151 }
152 }
153
154 Ok(Self {
155 #[cfg(feature = "orchard")]
156 orchard,
157 #[cfg(feature = "sapling")]
158 sapling,
159 transparent,
160 unknown,
161 expiry_height,
162 expiry_time,
163 unknown_metadata,
164 })
165 }
166
167 pub fn from_receivers(
178 #[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
179 #[cfg(feature = "sapling")] sapling: Option<PaymentAddress>,
180 transparent: Option<TransparentAddress>,
181 expiry_height: Option<consensus::BlockHeight>,
182 expiry_time: Option<u64>,
183 ) -> Option<Self> {
184 #[cfg(feature = "orchard")]
185 let has_orchard = orchard.is_some();
186 #[cfg(not(feature = "orchard"))]
187 let has_orchard = false;
188
189 #[cfg(feature = "sapling")]
190 let has_sapling = sapling.is_some();
191 #[cfg(not(feature = "sapling"))]
192 let has_sapling = false;
193
194 if has_orchard || has_sapling || transparent.is_some() {
195 Some(Self {
196 #[cfg(feature = "orchard")]
197 orchard,
198 #[cfg(feature = "sapling")]
199 sapling,
200 transparent,
201 unknown: vec![],
202 expiry_height,
203 expiry_time,
204 unknown_metadata: vec![],
205 })
206 } else {
207 None
208 }
209 }
210
211 pub fn has_orchard(&self) -> bool {
215 #[cfg(not(feature = "orchard"))]
216 return false;
217 #[cfg(feature = "orchard")]
218 return self.orchard.is_some();
219 }
220
221 #[cfg(feature = "orchard")]
223 pub fn orchard(&self) -> Option<&orchard::Address> {
224 self.orchard.as_ref()
225 }
226
227 pub fn has_sapling(&self) -> bool {
229 #[cfg(not(feature = "sapling"))]
230 return false;
231
232 #[cfg(feature = "sapling")]
233 return self.sapling.is_some();
234 }
235
236 #[cfg(feature = "sapling")]
238 pub fn sapling(&self) -> Option<&PaymentAddress> {
239 self.sapling.as_ref()
240 }
241
242 pub fn has_transparent(&self) -> bool {
244 self.transparent.is_some()
245 }
246
247 pub fn transparent(&self) -> Option<&TransparentAddress> {
249 self.transparent.as_ref()
250 }
251
252 pub fn unknown(&self) -> &[(u32, Vec<u8>)] {
254 &self.unknown
255 }
256
257 pub fn expiry_height(&self) -> Option<consensus::BlockHeight> {
259 self.expiry_height
260 }
261
262 pub fn expiry_time(&self) -> Option<u64> {
264 self.expiry_time
265 }
266
267 pub fn prune_retaining(&self, item_types: &[Typecode]) -> Result<Option<Self>, PruneError> {
287 let is_requested = |typecode: u32| {
288 item_types
289 .iter()
290 .any(|requested| requested.typecode_value() == typecode)
291 };
292
293 let must_understand_omitted = self
294 .expiry_height
295 .map(|_| MetadataTypecode::ExpiryHeight)
296 .into_iter()
297 .chain(self.expiry_time.map(|_| MetadataTypecode::ExpiryTime))
298 .chain(
299 self.unknown_metadata
300 .iter()
301 .map(|(typecode, _)| MetadataTypecode::Unknown(*typecode)),
302 )
303 .find(|typecode| typecode.is_must_understand() && !is_requested(u32::from(*typecode)));
304 if let Some(typecode) = must_understand_omitted {
305 return Err(PruneError::MustUnderstandMetadata(typecode));
306 }
307
308 let pruned = Self {
309 #[cfg(feature = "orchard")]
310 orchard: self
311 .orchard
312 .filter(|_| is_requested(Typecode::ORCHARD.into())),
313 #[cfg(feature = "sapling")]
314 sapling: self
315 .sapling
316 .filter(|_| is_requested(Typecode::SAPLING.into())),
317 transparent: self.transparent.filter(|taddr| {
318 is_requested(
319 match taddr {
320 TransparentAddress::PublicKeyHash(_) => Typecode::P2PKH,
321 TransparentAddress::ScriptHash(_) => Typecode::P2SH,
322 }
323 .into(),
324 )
325 }),
326 unknown: self
327 .unknown
328 .iter()
329 .filter(|(typecode, _)| is_requested(*typecode))
330 .cloned()
331 .collect(),
332 expiry_height: self
333 .expiry_height
334 .filter(|_| is_requested(MetadataTypecode::ExpiryHeight.into())),
335 expiry_time: self
336 .expiry_time
337 .filter(|_| is_requested(MetadataTypecode::ExpiryTime.into())),
338 unknown_metadata: self
339 .unknown_metadata
340 .iter()
341 .filter(|(typecode, _)| is_requested(*typecode))
342 .cloned()
343 .collect(),
344 };
345
346 let has_receiver = pruned.has_orchard()
347 || pruned.has_sapling()
348 || pruned.has_transparent()
349 || !pruned.unknown.is_empty();
350 if !has_receiver {
351 return Ok(None);
352 }
353
354 pruned
356 .to_container(Revision::R2)
357 .map_err(PruneError::NotRepresentable)?;
358 Ok(Some(pruned))
359 }
360
361 pub fn decode<P: consensus::Parameters>(
374 params: &P,
375 address: &str,
376 ) -> Result<Self, UnifiedDecodingError> {
377 let (actual, _revision, ua) =
378 unified::Address::decode(address).map_err(UnifiedDecodingError::Parse)?;
379 let expected = params.network_type();
380 if actual != expected {
381 return Err(UnifiedDecodingError::NetworkMismatch { expected, actual });
382 }
383 Self::from_container(ua).map_err(UnifiedDecodingError::InvalidItem)
384 }
385
386 pub fn to_zcash_address(&self, net: NetworkType) -> ZcashAddress {
397 self.to_zcash_address_revision(net, Revision::R0)
398 .or_else(|_| self.to_zcash_address_revision(net, Revision::R2))
399 .expect("Revision 2 can encode every UnifiedAddress")
404 }
405
406 pub fn to_zcash_address_revision(
421 &self,
422 net: NetworkType,
423 revision: Revision,
424 ) -> Result<ZcashAddress, UnifiedEncodingError> {
425 self.to_container(revision)
426 .map(|ua| ZcashAddress::from_unified(net, ua))
427 .map_err(|cause| UnifiedEncodingError::NotRepresentable { revision, cause })
428 }
429
430 fn to_container(&self, revision: Revision) -> Result<unified::Address, unified::ParseError> {
432 let items: Vec<Uitem<unified::Receiver>> = core::iter::empty()
433 .chain(self.unknown.iter().map(|(typecode, data)| {
434 Uitem::Data(unified::Receiver::Unknown {
435 typecode: *typecode,
436 data: data.clone(),
437 })
438 }))
439 .chain({
440 #[cfg(feature = "orchard")]
441 {
442 self.orchard
443 .as_ref()
444 .map(|addr| {
445 Uitem::Data(unified::Receiver::Orchard(addr.to_raw_address_bytes()))
446 })
447 .into_iter()
448 }
449 #[cfg(not(feature = "orchard"))]
450 {
451 core::iter::empty()
452 }
453 })
454 .chain({
455 #[cfg(feature = "sapling")]
456 {
457 self.sapling
458 .as_ref()
459 .map(|pa| Uitem::Data(unified::Receiver::Sapling(pa.to_bytes())))
460 .into_iter()
461 }
462 #[cfg(not(feature = "sapling"))]
463 {
464 core::iter::empty()
465 }
466 })
467 .chain(self.transparent.as_ref().map(|taddr| match taddr {
468 TransparentAddress::PublicKeyHash(data) => {
469 Uitem::Data(unified::Receiver::P2pkh(*data))
470 }
471 TransparentAddress::ScriptHash(data) => Uitem::Data(unified::Receiver::P2sh(*data)),
472 }))
473 .chain(
474 self.expiry_height
475 .map(|h| Uitem::Metadata(MetadataItem::ExpiryHeight(h.into()))),
476 )
477 .chain(
478 self.expiry_time
479 .map(|t| Uitem::Metadata(MetadataItem::ExpiryTime(t))),
480 )
481 .chain(self.unknown_metadata.iter().map(|(typecode, data)| {
482 Uitem::Metadata(MetadataItem::Unknown {
483 typecode: *typecode,
484 data: data.clone(),
485 })
486 }))
487 .collect();
488
489 unified::Address::try_from_items(revision, items)
490 }
491
492 pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
502 self.to_zcash_address(params.network_type()).to_string()
503 }
504
505 pub fn encode_revision<P: consensus::Parameters>(
520 &self,
521 params: &P,
522 revision: Revision,
523 ) -> Result<String, UnifiedEncodingError> {
524 self.to_zcash_address_revision(params.network_type(), revision)
525 .map(|addr| addr.to_string())
526 }
527
528 pub fn receiver_types(&self) -> Vec<Typecode> {
530 let result = core::iter::empty();
531 #[cfg(feature = "orchard")]
532 let result = result.chain(self.orchard.map(|_| Typecode::ORCHARD));
533 #[cfg(feature = "sapling")]
534 let result = result.chain(self.sapling.map(|_| Typecode::SAPLING));
535 let result = result.chain(self.transparent.map(|taddr| match taddr {
536 TransparentAddress::PublicKeyHash(_) => Typecode::P2PKH,
537 TransparentAddress::ScriptHash(_) => Typecode::P2SH,
538 }));
539 let result = result.chain(
540 self.unknown()
541 .iter()
542 .filter_map(|(typecode, _)| Typecode::try_from(*typecode).ok()),
543 );
544 result.collect()
545 }
546
547 pub fn as_understood_receivers(&self) -> Vec<Receiver> {
549 let result = core::iter::empty();
550 #[cfg(feature = "orchard")]
551 let result = result.chain(self.orchard.map(Receiver::Orchard));
552 #[cfg(feature = "sapling")]
553 let result = result.chain(self.sapling.map(Receiver::Sapling));
554 let result = result.chain(self.transparent.map(Receiver::Transparent));
555 result.collect()
556 }
557}
558
559pub enum Receiver {
565 #[cfg(feature = "orchard")]
566 Orchard(orchard::Address),
567 #[cfg(feature = "sapling")]
568 Sapling(PaymentAddress),
569 Transparent(TransparentAddress),
570}
571
572impl Receiver {
573 #[cfg_attr(not(feature = "orchard"), allow(unused_variables))]
583 pub fn to_zcash_address_revision(
584 &self,
585 net: NetworkType,
586 ua_revision: Revision,
587 ) -> ZcashAddress {
588 match self {
589 #[cfg(feature = "orchard")]
590 Receiver::Orchard(addr) => {
591 let receiver = Uitem::Data(unified::Receiver::Orchard(addr.to_raw_address_bytes()));
592 let ua = unified::Address::try_from_items(ua_revision, vec![receiver]).expect(
593 "Every revision permits a unified address with a single Orchard receiver.",
594 );
595 ZcashAddress::from_unified(net, ua)
596 }
597 #[cfg(feature = "sapling")]
598 Receiver::Sapling(addr) => ZcashAddress::from_sapling(net, addr.to_bytes()),
599 Receiver::Transparent(TransparentAddress::PublicKeyHash(data)) => {
600 ZcashAddress::from_transparent_p2pkh(net, *data)
601 }
602 Receiver::Transparent(TransparentAddress::ScriptHash(data)) => {
603 ZcashAddress::from_transparent_p2sh(net, *data)
604 }
605 }
606 }
607
608 pub fn to_zcash_address(&self, net: NetworkType) -> ZcashAddress {
617 self.to_zcash_address_revision(net, Revision::R0)
618 }
619
620 pub fn corresponds(&self, addr: &ZcashAddress) -> bool {
623 addr.matches_receiver(&match self {
624 #[cfg(feature = "orchard")]
625 Receiver::Orchard(addr) => unified::Receiver::Orchard(addr.to_raw_address_bytes()),
626 #[cfg(feature = "sapling")]
627 Receiver::Sapling(addr) => unified::Receiver::Sapling(addr.to_bytes()),
628 Receiver::Transparent(TransparentAddress::PublicKeyHash(data)) => {
629 unified::Receiver::P2pkh(*data)
630 }
631 Receiver::Transparent(TransparentAddress::ScriptHash(data)) => {
632 unified::Receiver::P2sh(*data)
633 }
634 })
635 }
636}
637
638#[derive(Debug, PartialEq, Eq, Clone)]
640pub enum Address {
641 #[cfg(feature = "sapling")]
643 Sapling(Box<PaymentAddress>),
644
645 Transparent(TransparentAddress),
647
648 Unified(Box<UnifiedAddress>),
652
653 Tex([u8; 20]),
657}
658
659#[cfg(feature = "sapling")]
660impl From<PaymentAddress> for Address {
661 fn from(addr: PaymentAddress) -> Self {
662 Address::Sapling(Box::new(addr))
663 }
664}
665
666impl From<TransparentAddress> for Address {
667 fn from(addr: TransparentAddress) -> Self {
668 Address::Transparent(addr)
669 }
670}
671
672impl From<UnifiedAddress> for Address {
673 fn from(addr: UnifiedAddress) -> Self {
674 Address::Unified(Box::new(addr))
675 }
676}
677
678impl TryFromAddress for Address {
679 type Error = &'static str;
680
681 #[cfg(feature = "sapling")]
682 fn try_from_sapling(
683 _net: NetworkType,
684 data: [u8; 43],
685 ) -> Result<Self, ConversionError<Self::Error>> {
686 let pa = PaymentAddress::from_bytes(&data).ok_or("Invalid Sapling payment address")?;
687 Ok(pa.into())
688 }
689
690 fn try_from_unified(
691 _net: NetworkType,
692 ua: zcash_address::unified::Address,
693 ) -> Result<Self, ConversionError<Self::Error>> {
694 UnifiedAddress::try_from(ua)
695 .map_err(ConversionError::User)
696 .map(Address::from)
697 }
698
699 fn try_from_transparent_p2pkh(
700 _net: NetworkType,
701 data: [u8; 20],
702 ) -> Result<Self, ConversionError<Self::Error>> {
703 Ok(TransparentAddress::PublicKeyHash(data).into())
704 }
705
706 fn try_from_transparent_p2sh(
707 _net: NetworkType,
708 data: [u8; 20],
709 ) -> Result<Self, ConversionError<Self::Error>> {
710 Ok(TransparentAddress::ScriptHash(data).into())
711 }
712
713 fn try_from_tex(
714 _net: NetworkType,
715 data: [u8; 20],
716 ) -> Result<Self, ConversionError<Self::Error>> {
717 Ok(Address::Tex(data))
718 }
719}
720
721impl Address {
722 pub fn decode<P: consensus::Parameters>(params: &P, s: &str) -> Option<Self> {
728 Self::try_from_zcash_address(params, s.parse::<ZcashAddress>().ok()?).ok()
729 }
730
731 pub fn try_from_zcash_address<P: consensus::Parameters>(
733 params: &P,
734 zaddr: ZcashAddress,
735 ) -> Result<Self, ConversionError<&'static str>> {
736 zaddr.convert_if_network(params.network_type())
737 }
738
739 pub fn to_zcash_address_revision<P: consensus::Parameters>(
752 &self,
753 params: &P,
754 ua_revision: Revision,
755 ) -> Result<ZcashAddress, UnifiedEncodingError> {
756 self.to_zcash_address_with(params.network_type(), |ua, net| {
757 ua.to_zcash_address_revision(net, ua_revision)
758 })
759 }
760
761 fn to_zcash_address_with<E>(
764 &self,
765 net: NetworkType,
766 encode_unified: impl FnOnce(&UnifiedAddress, NetworkType) -> Result<ZcashAddress, E>,
767 ) -> Result<ZcashAddress, E> {
768 Ok(match self {
769 #[cfg(feature = "sapling")]
770 Address::Sapling(pa) => ZcashAddress::from_sapling(net, pa.to_bytes()),
771 Address::Transparent(addr) => match addr {
772 TransparentAddress::PublicKeyHash(data) => {
773 ZcashAddress::from_transparent_p2pkh(net, *data)
774 }
775 TransparentAddress::ScriptHash(data) => {
776 ZcashAddress::from_transparent_p2sh(net, *data)
777 }
778 },
779 Address::Unified(ua) => encode_unified(ua, net)?,
780 Address::Tex(data) => ZcashAddress::from_tex(net, *data),
781 })
782 }
783
784 pub fn to_zcash_address<P: consensus::Parameters>(&self, params: &P) -> ZcashAddress {
792 let result: Result<_, Infallible> =
793 self.to_zcash_address_with(params.network_type(), |ua, net| {
794 Ok(ua.to_zcash_address(net))
795 });
796 match result {
797 Ok(addr) => addr,
798 Err(e) => match e {},
799 }
800 }
801
802 pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
810 self.to_zcash_address(params).to_string()
811 }
812
813 pub fn encode_revision<P: consensus::Parameters>(
826 &self,
827 params: &P,
828 ua_revision: Revision,
829 ) -> Result<String, UnifiedEncodingError> {
830 self.to_zcash_address_revision(params, ua_revision)
831 .map(|addr| addr.to_string())
832 }
833
834 pub fn can_receive_as(&self, pool_type: PoolType) -> bool {
836 match self {
837 #[cfg(feature = "sapling")]
838 Address::Sapling(_) => {
839 matches!(pool_type, PoolType::Shielded(ShieldedPool::Sapling))
840 }
841 Address::Transparent(_) | Address::Tex(_) => {
842 matches!(pool_type, PoolType::Transparent)
843 }
844 Address::Unified(ua) => match pool_type {
845 PoolType::Transparent => ua.has_transparent(),
846 PoolType::Shielded(ShieldedPool::Sapling) => ua.has_sapling(),
847 PoolType::Shielded(ShieldedPool::Orchard | ShieldedPool::Ironwood) => {
849 ua.has_orchard()
850 }
851 },
852 }
853 }
854
855 pub fn to_transparent_address(&self) -> Option<TransparentAddress> {
858 match self {
859 #[cfg(feature = "sapling")]
860 Address::Sapling(_) => None,
861 Address::Transparent(addr) => Some(*addr),
862 Address::Unified(ua) => ua.transparent().copied(),
863 Address::Tex(addr_bytes) => Some(TransparentAddress::PublicKeyHash(*addr_bytes)),
864 }
865 }
866
867 #[cfg(feature = "sapling")]
870 pub fn to_sapling_address(&self) -> Option<PaymentAddress> {
871 match self {
872 Address::Sapling(addr) => Some(**addr),
873 Address::Transparent(_) => None,
874 Address::Unified(ua) => ua.sapling().copied(),
875 Address::Tex(_) => None,
876 }
877 }
878
879 pub fn as_understood_unified_receivers(&self) -> Vec<Receiver> {
891 match self {
892 #[cfg(feature = "sapling")]
893 Address::Sapling(addr) => vec![Receiver::Sapling(**addr)],
894 Address::Transparent(addr) => vec![Receiver::Transparent(*addr)],
895 Address::Unified(ua) => ua.as_understood_receivers(),
896 Address::Tex(addr) => vec![Receiver::Transparent(TransparentAddress::PublicKeyHash(
897 *addr,
898 ))],
899 }
900 }
901}
902
903#[cfg(all(
904 any(
905 feature = "orchard",
906 feature = "sapling",
907 feature = "transparent-inputs"
908 ),
909 any(test, feature = "test-dependencies")
910))]
911pub mod testing {
912 use proptest::prelude::*;
913 use zcash_protocol::consensus::Network;
914
915 use crate::keys::{UnifiedAddressRequest, testing::arb_unified_spending_key};
916
917 use super::{Address, UnifiedAddress};
918
919 #[cfg(feature = "sapling")]
920 use sapling::testing::arb_payment_address;
921 use transparent::address::testing::arb_transparent_addr;
922
923 pub fn arb_unified_addr(
924 params: Network,
925 request: UnifiedAddressRequest,
926 ) -> impl Strategy<Value = UnifiedAddress> {
927 arb_unified_spending_key(params).prop_map(move |k| k.default_address(request).0)
928 }
929
930 #[cfg(feature = "sapling")]
931 pub fn arb_addr(request: UnifiedAddressRequest) -> impl Strategy<Value = Address> {
932 prop_oneof![
933 arb_payment_address().prop_map(Address::from),
934 arb_transparent_addr().prop_map(Address::Transparent),
935 arb_unified_addr(Network::TestNetwork, request).prop_map(Address::from),
936 proptest::array::uniform20(any::<u8>()).prop_map(Address::Tex),
937 ]
938 }
939
940 #[cfg(not(feature = "sapling"))]
941 pub fn arb_addr(request: UnifiedAddressRequest) -> impl Strategy<Value = Address> {
942 prop_oneof![
943 arb_transparent_addr().prop_map(Address::Transparent),
944 arb_unified_addr(Network::TestNetwork, request).prop_map(Address::from),
945 proptest::array::uniform20(any::<u8>()).prop_map(Address::Tex),
946 ]
947 }
948}
949
950#[cfg(test)]
951mod tests {
952 use assert_matches::assert_matches;
953 use transparent::address::TransparentAddress;
954 use zcash_address::{
955 test_vectors,
956 unified::{self, Encoding, MetadataItem, MetadataTypecode, ParseError, Revision, Uitem},
957 };
958 use zcash_protocol::consensus::{BlockHeight, MAIN_NETWORK, NetworkType, TEST_NETWORK};
959
960 use super::{Address, PruneError, UnifiedAddress};
961 use crate::encoding::{UnifiedDecodingError, UnifiedEncodingError};
962
963 #[cfg(feature = "sapling")]
964 use crate::keys::sapling;
965
966 use zcash_address::unified::Typecode;
967
968 #[cfg(any(feature = "orchard", feature = "sapling"))]
969 use zip32::AccountId;
970
971 #[test]
972 #[cfg(any(feature = "orchard", feature = "sapling"))]
973 fn ua_round_trip() {
974 #[cfg(feature = "orchard")]
975 let orchard = {
976 let sk =
977 orchard::keys::SpendingKey::from_zip32_seed(&[0; 32], 0, AccountId::ZERO).unwrap();
978 let fvk = orchard::keys::FullViewingKey::from(&sk);
979 Some(fvk.address_at(0u32, orchard::keys::Scope::External))
980 };
981
982 #[cfg(feature = "sapling")]
983 let sapling = {
984 let extsk = sapling::spending_key(&[0; 32], 0, AccountId::ZERO)
985 .expect("the derivation path yields a valid key");
986 let dfvk = extsk.to_diversifiable_full_viewing_key();
987 Some(dfvk.default_address().1)
988 };
989
990 for transparent in [None, Some(TransparentAddress::PublicKeyHash([0; 20]))] {
992 #[cfg(all(feature = "orchard", feature = "sapling"))]
993 let ua =
994 UnifiedAddress::from_receivers(orchard, sapling, transparent, None, None).unwrap();
995
996 #[cfg(all(not(feature = "orchard"), feature = "sapling"))]
997 let ua = UnifiedAddress::from_receivers(sapling, transparent, None, None).unwrap();
998
999 #[cfg(all(feature = "orchard", not(feature = "sapling")))]
1000 let ua = UnifiedAddress::from_receivers(orchard, transparent, None, None).unwrap();
1001
1002 let addr = Address::from(ua);
1003 assert_eq!(
1004 Address::decode(&MAIN_NETWORK, &addr.encode(&MAIN_NETWORK)),
1005 Some(addr.clone())
1006 );
1007 for revision in [Revision::R0, Revision::R2] {
1008 let addr_str = addr.encode_revision(&MAIN_NETWORK, revision).expect(
1009 "a shielded address without metadata has an encoding at every revision",
1010 );
1011 assert_eq!(
1012 Address::decode(&MAIN_NETWORK, &addr_str),
1013 Some(addr.clone())
1014 );
1015 }
1016 }
1017 }
1018
1019 #[test]
1020 #[cfg(not(any(feature = "orchard", feature = "sapling")))]
1021 fn ua_round_trip() {
1022 let transparent = None;
1023 assert_eq!(
1024 UnifiedAddress::from_receivers(transparent, None, None),
1025 None
1026 )
1027 }
1028
1029 #[test]
1030 fn ua_parsing() {
1031 for tv in test_vectors::UNIFIED {
1032 match Address::decode(&MAIN_NETWORK, tv.unified_addr) {
1033 Some(Address::Unified(ua)) => {
1034 assert_eq!(
1035 ua.has_transparent(),
1036 tv.p2pkh_bytes.is_some() || tv.p2sh_bytes.is_some()
1037 );
1038 #[cfg(feature = "sapling")]
1039 assert_eq!(ua.has_sapling(), tv.sapling_raw_addr.is_some());
1040 #[cfg(feature = "orchard")]
1041 assert_eq!(ua.has_orchard(), tv.orchard_raw_addr.is_some());
1042 }
1043 Some(_) => {
1044 panic!(
1045 "{} did not decode to a unified address value.",
1046 tv.unified_addr
1047 );
1048 }
1049 None => {
1050 panic!(
1051 "Failed to decode unified address from test vector: {}",
1052 tv.unified_addr
1053 );
1054 }
1055 }
1056 }
1057 }
1058
1059 #[test]
1060 fn r0_test_vectors_reencode_identically() {
1061 for tv in test_vectors::UNIFIED {
1062 let ua = UnifiedAddress::decode(&MAIN_NETWORK, tv.unified_addr)
1063 .expect("test vector is a valid unified address");
1064 assert_eq!(
1065 ua.encode_revision(&MAIN_NETWORK, Revision::R0).as_deref(),
1066 Ok(tv.unified_addr),
1067 );
1068 assert_eq!(ua.encode(&MAIN_NETWORK), tv.unified_addr,);
1070 }
1071 }
1072
1073 const UNASSIGNED_DATA_TYPECODE: u32 = 0x04;
1075 const SHOULD_UNDERSTAND_METADATA_TYPECODE: u32 = 0xC0;
1077 const UNKNOWN_ITEM_DATA: [u8; 4] = [0xAB; 4];
1079 const UNKNOWN_RECEIVER_DATA: [u8; 32] = [0xCD; 32];
1082 const P2PKH_HASH: [u8; 20] = [0x11; 20];
1084 const EXPIRY_HEIGHT: u32 = 3_000_000;
1086
1087 fn address_with_unknown_items(receiver_data: &[u8]) -> UnifiedAddress {
1090 let container = unified::Address::try_from_items(
1091 Revision::R2,
1092 vec![
1093 Uitem::Data(unified::Receiver::P2pkh(P2PKH_HASH)),
1094 Uitem::Data(unified::Receiver::Unknown {
1095 typecode: UNASSIGNED_DATA_TYPECODE,
1096 data: receiver_data.to_vec(),
1097 }),
1098 Uitem::Metadata(MetadataItem::Unknown {
1099 typecode: SHOULD_UNDERSTAND_METADATA_TYPECODE,
1100 data: UNKNOWN_ITEM_DATA.to_vec(),
1101 }),
1102 ],
1103 )
1104 .expect("the items form a valid Revision 2 address");
1105 UnifiedAddress::try_from(container).expect("the receivers are valid")
1106 }
1107
1108 fn unassigned_data_typecode() -> Typecode {
1109 Typecode::try_from(UNASSIGNED_DATA_TYPECODE).unwrap()
1110 }
1111
1112 fn should_understand_metadata_typecode() -> Typecode {
1113 Typecode::try_from(SHOULD_UNDERSTAND_METADATA_TYPECODE).unwrap()
1114 }
1115
1116 #[test]
1117 fn prune_retaining_removes_unrequested_items() {
1118 let ua = address_with_unknown_items(&UNKNOWN_RECEIVER_DATA);
1119
1120 let pruned = ua
1121 .prune_retaining(&[unassigned_data_typecode()])
1122 .unwrap()
1123 .expect("the unknown receiver remains");
1124 assert!(!pruned.has_transparent());
1125 assert_eq!(
1126 pruned.unknown(),
1127 &[(UNASSIGNED_DATA_TYPECODE, UNKNOWN_RECEIVER_DATA.to_vec())]
1128 );
1129 assert_eq!(
1131 UnifiedAddress::decode(&MAIN_NETWORK, &pruned.encode(&MAIN_NETWORK)),
1132 Ok(pruned.clone())
1133 );
1134
1135 let pruned = ua
1136 .prune_retaining(&[Typecode::P2PKH])
1137 .unwrap()
1138 .expect("the transparent receiver remains");
1139 assert_eq!(
1140 pruned.transparent(),
1141 Some(&TransparentAddress::PublicKeyHash(P2PKH_HASH))
1142 );
1143 assert!(pruned.unknown().is_empty());
1144 }
1145
1146 #[test]
1147 fn prune_retaining_to_unencodable_address_is_error() {
1148 let ua = address_with_unknown_items(&UNKNOWN_ITEM_DATA);
1149 assert_matches!(
1150 ua.prune_retaining(&[unassigned_data_typecode()]),
1151 Err(PruneError::NotRepresentable(
1152 ParseError::InvalidEncodedLength(_)
1153 ))
1154 );
1155 }
1156
1157 #[test]
1158 fn prune_retaining_to_every_item_type_is_identity() {
1159 let ua = address_with_unknown_items(&UNKNOWN_RECEIVER_DATA);
1160 assert_eq!(
1161 ua.prune_retaining(&[
1162 Typecode::P2PKH,
1163 unassigned_data_typecode(),
1164 should_understand_metadata_typecode(),
1165 ]),
1166 Ok(Some(ua))
1167 );
1168 }
1169
1170 #[test]
1171 fn prune_retaining_without_receivers_is_none() {
1172 let ua = address_with_unknown_items(&UNKNOWN_RECEIVER_DATA);
1173 assert_eq!(ua.prune_retaining(&[]), Ok(None));
1174 assert_eq!(
1175 ua.prune_retaining(&[should_understand_metadata_typecode()]),
1176 Ok(None)
1177 );
1178 }
1179
1180 #[test]
1181 fn prune_retaining_retains_must_understand_metadata() {
1182 let ua = UnifiedAddress::from_receivers(
1183 #[cfg(feature = "orchard")]
1184 None,
1185 #[cfg(feature = "sapling")]
1186 None,
1187 Some(TransparentAddress::PublicKeyHash(P2PKH_HASH)),
1188 Some(BlockHeight::from_u32(EXPIRY_HEIGHT)),
1189 None,
1190 )
1191 .expect("a transparent-only unified address is valid");
1192
1193 assert_eq!(
1194 ua.prune_retaining(&[Typecode::P2PKH]),
1195 Err(PruneError::MustUnderstandMetadata(
1196 MetadataTypecode::ExpiryHeight
1197 ))
1198 );
1199 assert_eq!(
1201 ua.prune_retaining(&[]),
1202 Err(PruneError::MustUnderstandMetadata(
1203 MetadataTypecode::ExpiryHeight
1204 ))
1205 );
1206 assert_eq!(
1207 ua.prune_retaining(&[Typecode::P2PKH, MetadataTypecode::ExpiryHeight.into()]),
1208 Ok(Some(ua))
1209 );
1210 }
1211
1212 #[test]
1213 #[cfg(feature = "orchard")]
1214 fn prune_retaining_removes_transparent_receiver() {
1215 let sk = orchard::keys::SpendingKey::from_zip32_seed(&[0; 32], 0, AccountId::ZERO).unwrap();
1216 let orchard = orchard::keys::FullViewingKey::from(&sk)
1217 .address_at(0u32, orchard::keys::Scope::External);
1218 let ua = UnifiedAddress::from_receivers(
1219 Some(orchard),
1220 #[cfg(feature = "sapling")]
1221 None,
1222 Some(TransparentAddress::PublicKeyHash(P2PKH_HASH)),
1223 None,
1224 None,
1225 )
1226 .unwrap();
1227
1228 let pruned = ua
1229 .prune_retaining(&[Typecode::ORCHARD])
1230 .unwrap()
1231 .expect("the Orchard receiver remains");
1232 assert_eq!(pruned.orchard(), Some(&orchard));
1233 assert!(!pruned.has_transparent());
1234 assert_eq!(
1235 UnifiedAddress::decode(&MAIN_NETWORK, &pruned.encode(&MAIN_NETWORK)),
1236 Ok(pruned)
1237 );
1238 }
1239
1240 #[test]
1241 fn decode_reports_structured_errors() {
1242 let tv = &test_vectors::UNIFIED[0];
1243 assert_eq!(
1244 UnifiedAddress::decode(&TEST_NETWORK, tv.unified_addr),
1245 Err(UnifiedDecodingError::NetworkMismatch {
1246 expected: NetworkType::Test,
1247 actual: NetworkType::Main,
1248 })
1249 );
1250
1251 assert_matches!(
1252 UnifiedAddress::decode(&MAIN_NETWORK, "not an address"),
1253 Err(UnifiedDecodingError::Parse(_))
1254 );
1255
1256 const INVALID_ORCHARD_RECEIVER: [u8; 43] = [0xff; 43];
1258 let invalid = unified::Address::try_from_items(
1259 Revision::R0,
1260 vec![Uitem::Data(unified::Receiver::Orchard(
1261 INVALID_ORCHARD_RECEIVER,
1262 ))],
1263 )
1264 .unwrap()
1265 .encode(&NetworkType::Main);
1266 #[cfg(feature = "orchard")]
1267 assert_eq!(
1268 UnifiedAddress::decode(&MAIN_NETWORK, &invalid),
1269 Err(UnifiedDecodingError::InvalidItem(Typecode::ORCHARD))
1270 );
1271 #[cfg(not(feature = "orchard"))]
1272 assert!(UnifiedAddress::decode(&MAIN_NETWORK, &invalid).is_ok());
1273 }
1274
1275 #[test]
1276 fn transparent_only_address_has_no_r0_encoding() {
1277 let ua = UnifiedAddress::from_receivers(
1278 #[cfg(feature = "orchard")]
1279 None,
1280 #[cfg(feature = "sapling")]
1281 None,
1282 Some(TransparentAddress::PublicKeyHash([0; 20])),
1283 None,
1284 None,
1285 )
1286 .expect("a transparent-only unified address is valid");
1287
1288 assert_eq!(
1289 ua.encode_revision(&MAIN_NETWORK, Revision::R0),
1290 Err(UnifiedEncodingError::NotRepresentable {
1291 revision: Revision::R0,
1292 cause: ParseError::OnlyTransparent,
1293 })
1294 );
1295 let encoded = ua
1296 .encode_revision(&MAIN_NETWORK, Revision::R2)
1297 .expect("every address has a Revision 2 encoding");
1298 assert_eq!(ua.encode(&MAIN_NETWORK), encoded);
1300 assert_eq!(UnifiedAddress::decode(&MAIN_NETWORK, &encoded), Ok(ua));
1301 }
1302}