1use alloc::string::{String, ToString};
4use alloc::vec::Vec;
5
6use transparent::address::TransparentAddress;
7use zcash_address::{
8 unified::{self, Container, Encoding, Typecode},
9 ConversionError, ToAddress, TryFromAddress, ZcashAddress,
10};
11use zcash_protocol::consensus::{self, NetworkType};
12
13#[cfg(feature = "sapling")]
14use sapling::PaymentAddress;
15use zcash_protocol::{PoolType, ShieldedProtocol};
16
17#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct UnifiedAddress {
20 #[cfg(feature = "orchard")]
21 orchard: Option<orchard::Address>,
22 #[cfg(feature = "sapling")]
23 sapling: Option<PaymentAddress>,
24 transparent: Option<TransparentAddress>,
25 unknown: Vec<(u32, Vec<u8>)>,
26}
27
28impl TryFrom<unified::Address> for UnifiedAddress {
29 type Error = &'static str;
30
31 fn try_from(ua: unified::Address) -> Result<Self, Self::Error> {
32 #[cfg(feature = "orchard")]
33 let mut orchard = None;
34 #[cfg(feature = "sapling")]
35 let mut sapling = None;
36 let mut transparent = None;
37
38 let mut unknown: Vec<(u32, Vec<u8>)> = vec![];
39
40 for item in ua.items_as_parsed() {
43 match item {
44 unified::Receiver::Orchard(data) => {
45 #[cfg(feature = "orchard")]
46 {
47 orchard = Some(
48 Option::from(orchard::Address::from_raw_address_bytes(data))
49 .ok_or("Invalid Orchard receiver in Unified Address")?,
50 );
51 }
52 #[cfg(not(feature = "orchard"))]
53 {
54 unknown.push((unified::Typecode::Orchard.into(), data.to_vec()));
55 }
56 }
57
58 unified::Receiver::Sapling(data) => {
59 #[cfg(feature = "sapling")]
60 {
61 sapling = Some(
62 PaymentAddress::from_bytes(data)
63 .ok_or("Invalid Sapling receiver in Unified Address")?,
64 );
65 }
66 #[cfg(not(feature = "sapling"))]
67 {
68 unknown.push((unified::Typecode::Sapling.into(), data.to_vec()));
69 }
70 }
71
72 unified::Receiver::P2pkh(data) => {
73 transparent = Some(TransparentAddress::PublicKeyHash(*data));
74 }
75
76 unified::Receiver::P2sh(data) => {
77 transparent = Some(TransparentAddress::ScriptHash(*data));
78 }
79
80 unified::Receiver::Unknown { typecode, data } => {
81 unknown.push((*typecode, data.clone()));
82 }
83 }
84 }
85
86 Ok(Self {
87 #[cfg(feature = "orchard")]
88 orchard,
89 #[cfg(feature = "sapling")]
90 sapling,
91 transparent,
92 unknown,
93 })
94 }
95}
96
97impl UnifiedAddress {
98 pub fn from_receivers(
103 #[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
104 #[cfg(feature = "sapling")] sapling: Option<PaymentAddress>,
105 transparent: Option<TransparentAddress>,
106 ) -> Option<Self> {
108 #[cfg(feature = "orchard")]
109 let has_orchard = orchard.is_some();
110 #[cfg(not(feature = "orchard"))]
111 let has_orchard = false;
112
113 #[cfg(feature = "sapling")]
114 let has_sapling = sapling.is_some();
115 #[cfg(not(feature = "sapling"))]
116 let has_sapling = false;
117
118 if has_orchard || has_sapling {
119 Some(Self {
120 #[cfg(feature = "orchard")]
121 orchard,
122 #[cfg(feature = "sapling")]
123 sapling,
124 transparent,
125 unknown: vec![],
126 })
127 } else {
128 None
130 }
131 }
132
133 pub fn has_orchard(&self) -> bool {
137 #[cfg(not(feature = "orchard"))]
138 return false;
139 #[cfg(feature = "orchard")]
140 return self.orchard.is_some();
141 }
142
143 #[cfg(feature = "orchard")]
145 pub fn orchard(&self) -> Option<&orchard::Address> {
146 self.orchard.as_ref()
147 }
148
149 pub fn has_sapling(&self) -> bool {
151 #[cfg(not(feature = "sapling"))]
152 return false;
153
154 #[cfg(feature = "sapling")]
155 return self.sapling.is_some();
156 }
157
158 #[cfg(feature = "sapling")]
160 pub fn sapling(&self) -> Option<&PaymentAddress> {
161 self.sapling.as_ref()
162 }
163
164 pub fn has_transparent(&self) -> bool {
166 self.transparent.is_some()
167 }
168
169 pub fn transparent(&self) -> Option<&TransparentAddress> {
171 self.transparent.as_ref()
172 }
173
174 pub fn unknown(&self) -> &[(u32, Vec<u8>)] {
176 &self.unknown
177 }
178
179 fn to_address(&self, net: NetworkType) -> ZcashAddress {
180 let items = self
181 .unknown
182 .iter()
183 .map(|(typecode, data)| unified::Receiver::Unknown {
184 typecode: *typecode,
185 data: data.clone(),
186 });
187
188 #[cfg(feature = "orchard")]
189 let items = items.chain(
190 self.orchard
191 .as_ref()
192 .map(|addr| addr.to_raw_address_bytes())
193 .map(unified::Receiver::Orchard),
194 );
195
196 #[cfg(feature = "sapling")]
197 let items = items.chain(
198 self.sapling
199 .as_ref()
200 .map(|pa| pa.to_bytes())
201 .map(unified::Receiver::Sapling),
202 );
203
204 let items = items.chain(self.transparent.as_ref().map(|taddr| match taddr {
205 TransparentAddress::PublicKeyHash(data) => unified::Receiver::P2pkh(*data),
206 TransparentAddress::ScriptHash(data) => unified::Receiver::P2sh(*data),
207 }));
208
209 let ua = unified::Address::try_from_items(items.collect())
210 .expect("UnifiedAddress should only be constructed safely");
211 ZcashAddress::from_unified(net, ua)
212 }
213
214 pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
216 self.to_address(params.network_type()).to_string()
217 }
218
219 pub fn receiver_types(&self) -> Vec<Typecode> {
221 let result = core::iter::empty();
222 #[cfg(feature = "orchard")]
223 let result = result.chain(self.orchard.map(|_| Typecode::Orchard));
224 #[cfg(feature = "sapling")]
225 let result = result.chain(self.sapling.map(|_| Typecode::Sapling));
226 let result = result.chain(self.transparent.map(|taddr| match taddr {
227 TransparentAddress::PublicKeyHash(_) => Typecode::P2pkh,
228 TransparentAddress::ScriptHash(_) => Typecode::P2sh,
229 }));
230 let result = result.chain(
231 self.unknown()
232 .iter()
233 .map(|(typecode, _)| Typecode::Unknown(*typecode)),
234 );
235 result.collect()
236 }
237}
238
239pub enum Receiver {
245 #[cfg(feature = "orchard")]
246 Orchard(orchard::Address),
247 #[cfg(feature = "sapling")]
248 Sapling(PaymentAddress),
249 Transparent(TransparentAddress),
250}
251
252impl Receiver {
253 pub fn to_zcash_address(&self, net: NetworkType) -> ZcashAddress {
259 match self {
260 #[cfg(feature = "orchard")]
261 Receiver::Orchard(addr) => {
262 let receiver = unified::Receiver::Orchard(addr.to_raw_address_bytes());
263 let ua = unified::Address::try_from_items(vec![receiver])
264 .expect("A unified address may contain a single Orchard receiver.");
265 ZcashAddress::from_unified(net, ua)
266 }
267 #[cfg(feature = "sapling")]
268 Receiver::Sapling(addr) => ZcashAddress::from_sapling(net, addr.to_bytes()),
269 Receiver::Transparent(TransparentAddress::PublicKeyHash(data)) => {
270 ZcashAddress::from_transparent_p2pkh(net, *data)
271 }
272 Receiver::Transparent(TransparentAddress::ScriptHash(data)) => {
273 ZcashAddress::from_transparent_p2sh(net, *data)
274 }
275 }
276 }
277
278 pub fn corresponds(&self, addr: &ZcashAddress) -> bool {
281 addr.matches_receiver(&match self {
282 #[cfg(feature = "orchard")]
283 Receiver::Orchard(addr) => unified::Receiver::Orchard(addr.to_raw_address_bytes()),
284 #[cfg(feature = "sapling")]
285 Receiver::Sapling(addr) => unified::Receiver::Sapling(addr.to_bytes()),
286 Receiver::Transparent(TransparentAddress::PublicKeyHash(data)) => {
287 unified::Receiver::P2pkh(*data)
288 }
289 Receiver::Transparent(TransparentAddress::ScriptHash(data)) => {
290 unified::Receiver::P2sh(*data)
291 }
292 })
293 }
294}
295
296#[derive(Debug, PartialEq, Eq, Clone)]
298pub enum Address {
299 #[cfg(feature = "sapling")]
301 Sapling(PaymentAddress),
302
303 Transparent(TransparentAddress),
305
306 Unified(UnifiedAddress),
310
311 Tex([u8; 20]),
315}
316
317#[cfg(feature = "sapling")]
318impl From<PaymentAddress> for Address {
319 fn from(addr: PaymentAddress) -> Self {
320 Address::Sapling(addr)
321 }
322}
323
324impl From<TransparentAddress> for Address {
325 fn from(addr: TransparentAddress) -> Self {
326 Address::Transparent(addr)
327 }
328}
329
330impl From<UnifiedAddress> for Address {
331 fn from(addr: UnifiedAddress) -> Self {
332 Address::Unified(addr)
333 }
334}
335
336impl TryFromAddress for Address {
337 type Error = &'static str;
338
339 #[cfg(feature = "sapling")]
340 fn try_from_sapling(
341 _net: NetworkType,
342 data: [u8; 43],
343 ) -> Result<Self, ConversionError<Self::Error>> {
344 let pa = PaymentAddress::from_bytes(&data).ok_or("Invalid Sapling payment address")?;
345 Ok(pa.into())
346 }
347
348 fn try_from_unified(
349 _net: NetworkType,
350 ua: zcash_address::unified::Address,
351 ) -> Result<Self, ConversionError<Self::Error>> {
352 UnifiedAddress::try_from(ua)
353 .map_err(ConversionError::User)
354 .map(Address::from)
355 }
356
357 fn try_from_transparent_p2pkh(
358 _net: NetworkType,
359 data: [u8; 20],
360 ) -> Result<Self, ConversionError<Self::Error>> {
361 Ok(TransparentAddress::PublicKeyHash(data).into())
362 }
363
364 fn try_from_transparent_p2sh(
365 _net: NetworkType,
366 data: [u8; 20],
367 ) -> Result<Self, ConversionError<Self::Error>> {
368 Ok(TransparentAddress::ScriptHash(data).into())
369 }
370
371 fn try_from_tex(
372 _net: NetworkType,
373 data: [u8; 20],
374 ) -> Result<Self, ConversionError<Self::Error>> {
375 Ok(Address::Tex(data))
376 }
377}
378
379impl Address {
380 pub fn decode<P: consensus::Parameters>(params: &P, s: &str) -> Option<Self> {
385 Self::try_from_zcash_address(params, s.parse::<ZcashAddress>().ok()?).ok()
386 }
387
388 pub fn try_from_zcash_address<P: consensus::Parameters>(
390 params: &P,
391 zaddr: ZcashAddress,
392 ) -> Result<Self, ConversionError<&'static str>> {
393 zaddr.convert_if_network(params.network_type())
394 }
395
396 pub fn to_zcash_address<P: consensus::Parameters>(&self, params: &P) -> ZcashAddress {
398 let net = params.network_type();
399
400 match self {
401 #[cfg(feature = "sapling")]
402 Address::Sapling(pa) => ZcashAddress::from_sapling(net, pa.to_bytes()),
403 Address::Transparent(addr) => match addr {
404 TransparentAddress::PublicKeyHash(data) => {
405 ZcashAddress::from_transparent_p2pkh(net, *data)
406 }
407 TransparentAddress::ScriptHash(data) => {
408 ZcashAddress::from_transparent_p2sh(net, *data)
409 }
410 },
411 Address::Unified(ua) => ua.to_address(net),
412 Address::Tex(data) => ZcashAddress::from_tex(net, *data),
413 }
414 }
415
416 pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
418 self.to_zcash_address(params).to_string()
419 }
420
421 pub fn can_receive_as(&self, pool_type: PoolType) -> bool {
423 match self {
424 #[cfg(feature = "sapling")]
425 Address::Sapling(_) => {
426 matches!(pool_type, PoolType::Shielded(ShieldedProtocol::Sapling))
427 }
428 Address::Transparent(_) | Address::Tex(_) => {
429 matches!(pool_type, PoolType::Transparent)
430 }
431 Address::Unified(ua) => match pool_type {
432 PoolType::Transparent => ua.has_transparent(),
433 PoolType::Shielded(ShieldedProtocol::Sapling) => ua.has_sapling(),
434 PoolType::Shielded(ShieldedProtocol::Orchard) => ua.has_orchard(),
435 },
436 }
437 }
438
439 pub fn to_transparent_address(&self) -> Option<TransparentAddress> {
442 match self {
443 #[cfg(feature = "sapling")]
444 Address::Sapling(_) => None,
445 Address::Transparent(addr) => Some(*addr),
446 Address::Unified(ua) => ua.transparent().copied(),
447 Address::Tex(addr_bytes) => Some(TransparentAddress::PublicKeyHash(*addr_bytes)),
448 }
449 }
450
451 #[cfg(feature = "sapling")]
454 pub fn to_sapling_address(&self) -> Option<PaymentAddress> {
455 match self {
456 Address::Sapling(addr) => Some(*addr),
457 Address::Transparent(_) => None,
458 Address::Unified(ua) => ua.sapling().copied(),
459 Address::Tex(_) => None,
460 }
461 }
462}
463
464#[cfg(all(
465 any(
466 feature = "orchard",
467 feature = "sapling",
468 feature = "transparent-inputs"
469 ),
470 any(test, feature = "test-dependencies")
471))]
472pub mod testing {
473 use proptest::prelude::*;
474 use zcash_protocol::consensus::Network;
475
476 use crate::keys::{testing::arb_unified_spending_key, UnifiedAddressRequest};
477
478 use super::{Address, UnifiedAddress};
479
480 #[cfg(feature = "sapling")]
481 use sapling::testing::arb_payment_address;
482 use transparent::address::testing::arb_transparent_addr;
483
484 pub fn arb_unified_addr(
485 params: Network,
486 request: UnifiedAddressRequest,
487 ) -> impl Strategy<Value = UnifiedAddress> {
488 arb_unified_spending_key(params).prop_map(move |k| k.default_address(request).0)
489 }
490
491 #[cfg(feature = "sapling")]
492 pub fn arb_addr(request: UnifiedAddressRequest) -> impl Strategy<Value = Address> {
493 prop_oneof![
494 arb_payment_address().prop_map(Address::Sapling),
495 arb_transparent_addr().prop_map(Address::Transparent),
496 arb_unified_addr(Network::TestNetwork, request).prop_map(Address::Unified),
497 proptest::array::uniform20(any::<u8>()).prop_map(Address::Tex),
498 ]
499 }
500
501 #[cfg(not(feature = "sapling"))]
502 pub fn arb_addr(request: UnifiedAddressRequest) -> impl Strategy<Value = Address> {
503 return prop_oneof![
504 arb_transparent_addr().prop_map(Address::Transparent),
505 arb_unified_addr(Network::TestNetwork, request).prop_map(Address::Unified),
506 proptest::array::uniform20(any::<u8>()).prop_map(Address::Tex),
507 ];
508 }
509}
510
511#[cfg(test)]
512mod tests {
513 use zcash_address::test_vectors;
514 use zcash_protocol::consensus::MAIN_NETWORK;
515
516 use super::{Address, UnifiedAddress};
517
518 #[cfg(feature = "sapling")]
519 use crate::keys::sapling;
520
521 #[cfg(any(feature = "orchard", feature = "sapling"))]
522 use zip32::AccountId;
523
524 #[test]
525 #[cfg(any(feature = "orchard", feature = "sapling"))]
526 fn ua_round_trip() {
527 #[cfg(feature = "orchard")]
528 let orchard = {
529 let sk =
530 orchard::keys::SpendingKey::from_zip32_seed(&[0; 32], 0, AccountId::ZERO).unwrap();
531 let fvk = orchard::keys::FullViewingKey::from(&sk);
532 Some(fvk.address_at(0u32, orchard::keys::Scope::External))
533 };
534
535 #[cfg(feature = "sapling")]
536 let sapling = {
537 let extsk = sapling::spending_key(&[0; 32], 0, AccountId::ZERO);
538 let dfvk = extsk.to_diversifiable_full_viewing_key();
539 Some(dfvk.default_address().1)
540 };
541
542 let transparent = None;
543
544 #[cfg(all(feature = "orchard", feature = "sapling"))]
545 let ua = UnifiedAddress::from_receivers(orchard, sapling, transparent).unwrap();
546
547 #[cfg(all(not(feature = "orchard"), feature = "sapling"))]
548 let ua = UnifiedAddress::from_receivers(sapling, transparent).unwrap();
549
550 #[cfg(all(feature = "orchard", not(feature = "sapling")))]
551 let ua = UnifiedAddress::from_receivers(orchard, transparent).unwrap();
552
553 let addr = Address::Unified(ua);
554 let addr_str = addr.encode(&MAIN_NETWORK);
555 assert_eq!(Address::decode(&MAIN_NETWORK, &addr_str), Some(addr));
556 }
557
558 #[test]
559 #[cfg(not(any(feature = "orchard", feature = "sapling")))]
560 fn ua_round_trip() {
561 let transparent = None;
562 assert_eq!(UnifiedAddress::from_receivers(transparent), None)
563 }
564
565 #[test]
566 fn ua_parsing() {
567 for tv in test_vectors::UNIFIED {
568 match Address::decode(&MAIN_NETWORK, tv.unified_addr) {
569 Some(Address::Unified(ua)) => {
570 assert_eq!(
571 ua.has_transparent(),
572 tv.p2pkh_bytes.is_some() || tv.p2sh_bytes.is_some()
573 );
574 #[cfg(feature = "sapling")]
575 assert_eq!(ua.has_sapling(), tv.sapling_raw_addr.is_some());
576 #[cfg(feature = "orchard")]
577 assert_eq!(ua.has_orchard(), tv.orchard_raw_addr.is_some());
578 }
579 Some(_) => {
580 panic!(
581 "{} did not decode to a unified address value.",
582 tv.unified_addr
583 );
584 }
585 None => {
586 panic!(
587 "Failed to decode unified address from test vector: {}",
588 tv.unified_addr
589 );
590 }
591 }
592 }
593 }
594}