1use alloc::vec::Vec;
2use core::convert::TryInto;
3
4use zcash_protocol::PoolType;
5
6use super::{DataTypecode, ParseError, Uitem, private::SealedItem};
7use crate::{constants, unified::Revision};
8
9#[derive(Clone, Debug, PartialEq, Eq, Hash)]
13pub enum Receiver {
14 Orchard([u8; 43]),
15 Sapling([u8; 43]),
16 P2pkh([u8; 20]),
17 P2sh([u8; 20]),
18 Unknown { typecode: u32, data: Vec<u8> },
19}
20
21impl SealedItem for Receiver {
22 fn parse(typecode: DataTypecode, addr: &[u8]) -> Result<Self, ParseError> {
23 match typecode {
24 DataTypecode::P2pkh => addr.try_into().map(Receiver::P2pkh),
25 DataTypecode::P2sh => addr.try_into().map(Receiver::P2sh),
26 DataTypecode::Sapling => addr.try_into().map(Receiver::Sapling),
27 DataTypecode::Orchard => addr.try_into().map(Receiver::Orchard),
28 DataTypecode::Unknown(tc) => Ok(Receiver::Unknown {
30 typecode: tc,
31 data: addr.to_vec(),
32 }),
33 }
34 .map_err(|e| {
35 ParseError::InvalidEncoding(format!(
36 "Invalid address for typecode {}: {e}",
37 u32::from(typecode)
38 ))
39 })
40 }
41
42 fn typecode(&self) -> DataTypecode {
43 match self {
44 Receiver::P2pkh(_) => DataTypecode::P2pkh,
45 Receiver::P2sh(_) => DataTypecode::P2sh,
46 Receiver::Sapling(_) => DataTypecode::Sapling,
47 Receiver::Orchard(_) => DataTypecode::Orchard,
48 Receiver::Unknown { typecode, .. } => DataTypecode::Unknown(*typecode),
49 }
50 }
51
52 fn data(&self) -> &[u8] {
53 match self {
54 Receiver::P2pkh(data) => data,
55 Receiver::P2sh(data) => data,
56 Receiver::Sapling(data) => data,
57 Receiver::Orchard(data) => data,
58 Receiver::Unknown { data, .. } => data,
59 }
60 }
61}
62
63#[derive(Clone, Debug, PartialEq, Eq, Hash)]
117pub struct Address {
118 pub(crate) revision: Revision,
119 pub(crate) items: Vec<Uitem<Receiver>>,
120}
121
122impl Address {
123 pub fn has_receiver_of_type(&self, pool_type: PoolType) -> bool {
125 self.items.iter().any(|item| match item {
126 Uitem::Data(Receiver::Orchard(_)) => pool_type == PoolType::ORCHARD,
127 Uitem::Data(Receiver::Sapling(_)) => pool_type == PoolType::SAPLING,
128 Uitem::Data(Receiver::P2pkh(_) | Receiver::P2sh(_)) => {
129 pool_type == PoolType::TRANSPARENT
130 }
131 Uitem::Data(Receiver::Unknown { .. }) => false,
132 Uitem::Metadata(_) => false,
133 })
134 }
135
136 pub fn contains_receiver(&self, receiver: &Receiver) -> bool {
138 self.items.iter().any(|item| match item {
139 Uitem::Data(r) => r == receiver,
140 Uitem::Metadata(_) => false,
141 })
142 }
143
144 pub fn can_receive_memo(&self) -> bool {
146 self.items.iter().any(|item| {
147 matches!(
148 item,
149 Uitem::Data(Receiver::Sapling(_)) | Uitem::Data(Receiver::Orchard(_))
150 )
151 })
152 }
153}
154
155impl super::private::SealedContainer for Address {
156 const MAINNET: &'static str = constants::mainnet::HRP_UNIFIED_ADDRESS;
157 const TESTNET: &'static str = constants::testnet::HRP_UNIFIED_ADDRESS;
158 const REGTEST: &'static str = constants::regtest::HRP_UNIFIED_ADDRESS;
159
160 const MAINNET_R2: &'static str = constants::mainnet::HRP_UNIFIED_ADDRESS_R2;
161 const TESTNET_R2: &'static str = constants::testnet::HRP_UNIFIED_ADDRESS_R2;
162 const REGTEST_R2: &'static str = constants::regtest::HRP_UNIFIED_ADDRESS_R2;
163
164 const MAINNET_R2_TI: &'static str = constants::mainnet::HRP_UNIFIED_ADDRESS_R2_TI;
165 const TESTNET_R2_TI: &'static str = constants::testnet::HRP_UNIFIED_ADDRESS_R2_TI;
166 const REGTEST_R2_TI: &'static str = constants::regtest::HRP_UNIFIED_ADDRESS_R2_TI;
167
168 const IS_ADDRESS: bool = true;
169
170 fn from_inner(revision: Revision, items: Vec<Uitem<Receiver>>) -> Self {
171 Self { revision, items }
172 }
173}
174
175impl super::Encoding for Address {}
176impl super::Container for Address {
177 type Item = Receiver;
178
179 fn revision(&self) -> Revision {
180 self.revision
181 }
182
183 fn items_as_parsed(&self) -> &[Uitem<Receiver>] {
184 &self.items
185 }
186}
187
188#[cfg(any(test, feature = "test-dependencies"))]
189pub mod testing {
190 use alloc::collections::BTreeSet;
191 use alloc::vec::Vec;
192
193 use crate::unified::Revision;
194 use proptest::{
195 array::{uniform11, uniform20, uniform32},
196 collection::vec,
197 prelude::*,
198 sample::select,
199 strategy::Strategy,
200 };
201
202 use super::{Address, Receiver};
203 use crate::unified::{DataTypecode, MetadataItem, Uitem};
204
205 prop_compose! {
206 fn uniform43()(a in uniform11(0u8..), b in uniform32(0u8..)) -> [u8; 43] {
207 let mut c = [0; 43];
208 c[..11].copy_from_slice(&a);
209 c[11..].copy_from_slice(&b);
210 c
211 }
212 }
213
214 pub fn arb_transparent_typecode() -> impl Strategy<Value = DataTypecode> {
216 select(vec![DataTypecode::P2pkh, DataTypecode::P2sh])
217 }
218
219 pub fn arb_shielded_typecode() -> impl Strategy<Value = DataTypecode> {
222 prop_oneof![
223 Just(DataTypecode::Sapling),
224 Just(DataTypecode::Orchard),
225 ((<u32>::from(DataTypecode::Orchard) + 1)..0xC0u32).prop_map(DataTypecode::Unknown)
226 ]
227 }
228
229 pub fn arb_typecodes() -> impl Strategy<Value = BTreeSet<DataTypecode>> {
232 prop::option::of(arb_transparent_typecode()).prop_flat_map(|transparent| {
233 prop::collection::hash_set(arb_shielded_typecode(), 1..4)
234 .prop_map(move |xs| xs.into_iter().chain(transparent).collect())
235 })
236 }
237
238 pub fn arb_unified_address_for_typecodes(
244 typecodes: BTreeSet<DataTypecode>,
245 ) -> impl Strategy<Value = Vec<Uitem<Receiver>>> {
246 typecodes
247 .into_iter()
248 .map(|tc| match tc {
249 DataTypecode::P2pkh => uniform20(0u8..).prop_map(Receiver::P2pkh).boxed(),
250 DataTypecode::P2sh => uniform20(0u8..).prop_map(Receiver::P2sh).boxed(),
251 DataTypecode::Sapling => uniform43().prop_map(Receiver::Sapling).boxed(),
252 DataTypecode::Orchard => uniform43().prop_map(Receiver::Orchard).boxed(),
253 DataTypecode::Unknown(typecode) => vec(any::<u8>(), 32..256)
254 .prop_map(move |data| Receiver::Unknown { typecode, data })
255 .boxed(),
256 })
257 .map(|s| s.prop_map(Uitem::Data))
258 .collect::<Vec<_>>()
259 }
260
261 pub fn arb_unified_address() -> impl Strategy<Value = Address> {
263 arb_typecodes()
264 .prop_flat_map(arb_unified_address_for_typecodes)
265 .prop_map(|items| Address {
266 revision: Revision::R0,
267 items,
268 })
269 }
270
271 pub fn arb_metadata_items() -> impl Strategy<Value = Vec<Uitem<Receiver>>> {
273 (
274 prop::option::of(
275 any::<u32>().prop_map(|h| Uitem::Metadata(MetadataItem::ExpiryHeight(h))),
276 ),
277 prop::option::of(
278 any::<u64>().prop_map(|t| Uitem::Metadata(MetadataItem::ExpiryTime(t))),
279 ),
280 )
281 .prop_map(|(h, t)| h.into_iter().chain(t).collect())
282 }
283
284 pub fn arb_known_shielded_typecode() -> impl Strategy<Value = DataTypecode> {
288 select(vec![DataTypecode::Sapling, DataTypecode::Orchard])
289 }
290
291 pub fn arb_r2_shielded_address() -> impl Strategy<Value = Address> {
294 (
295 prop::collection::btree_set(arb_known_shielded_typecode(), 1..2),
296 arb_metadata_items(),
297 )
298 .prop_flat_map(|(shielded_tcs, metadata)| {
299 arb_unified_address_for_typecodes(shielded_tcs).prop_map(move |mut items| {
300 items.extend(metadata.clone());
301 Address {
302 revision: Revision::R2,
303 items,
304 }
305 })
306 })
307 }
308
309 pub fn arb_r2_transparent_including_address() -> impl Strategy<Value = Address> {
312 (
313 prop::collection::hash_set(arb_shielded_typecode(), 1..3),
314 arb_transparent_typecode(),
315 arb_metadata_items(),
316 )
317 .prop_flat_map(|(shielded_tcs, transparent_tc, metadata)| {
318 let typecodes: BTreeSet<DataTypecode> = shielded_tcs
319 .into_iter()
320 .chain(Some(transparent_tc))
321 .collect();
322 arb_unified_address_for_typecodes(typecodes).prop_map(move |mut items| {
323 items.extend(metadata.clone());
324 Address {
325 revision: Revision::R2,
326 items,
327 }
328 })
329 })
330 }
331}
332
333#[cfg(feature = "test-dependencies")]
334pub mod test_vectors;
335
336#[cfg(test)]
337mod tests {
338 use alloc::borrow::ToOwned;
339 use alloc::vec::Vec;
340
341 use crate::unified::Revision;
342 use assert_matches::assert_matches;
343 use zcash_encoding::MAX_COMPACT_SIZE;
344 use zcash_protocol::consensus::NetworkType;
345
346 use crate::{
347 kind::unified::{Container, Encoding, MetadataItem, Uitem, private::SealedContainer},
348 unified::address::testing::{
349 arb_r2_shielded_address, arb_r2_transparent_including_address, arb_unified_address,
350 },
351 };
352
353 use proptest::{prelude::*, sample::select};
354
355 use super::{Address, ParseError, Receiver};
356 use crate::unified::Typecode;
357
358 proptest! {
359 #[test]
360 fn ua_roundtrip(
361 network in select(vec![NetworkType::Main, NetworkType::Test, NetworkType::Regtest]),
362 ua in arb_unified_address(),
363 ) {
364 let encoded = ua.encode(&network);
365 let decoded = Address::decode(&encoded);
366 let decoded = decoded.map(|(net, _rev, addr)| (net, addr));
367 prop_assert_eq!(&decoded, &Ok((network, ua)));
368 let reencoded = decoded.unwrap().1.encode(&network);
369 prop_assert_eq!(reencoded, encoded);
370 }
371
372 #[test]
373 fn r2_shielded_ua_roundtrip(
374 network in select(vec![NetworkType::Main, NetworkType::Test, NetworkType::Regtest]),
375 ua in arb_r2_shielded_address(),
376 ) {
377 let encoded = ua.encode(&network);
378 let decoded = Address::decode(&encoded);
379 let decoded = decoded.map(|(net, _rev, addr)| (net, addr));
380 prop_assert_eq!(&decoded, &Ok((network, ua)));
381 let reencoded = decoded.unwrap().1.encode(&network);
382 prop_assert_eq!(reencoded, encoded);
383 }
384
385 #[test]
386 fn r2_transparent_including_ua_roundtrip(
387 network in select(vec![NetworkType::Main, NetworkType::Test, NetworkType::Regtest]),
388 ua in arb_r2_transparent_including_address(),
389 ) {
390 let encoded = ua.encode(&network);
391 let decoded = Address::decode(&encoded);
392 let decoded = decoded.map(|(net, _rev, addr)| (net, addr));
393 prop_assert_eq!(&decoded, &Ok((network, ua)));
394 let reencoded = decoded.unwrap().1.encode(&network);
395 prop_assert_eq!(reencoded, encoded);
396 }
397 }
398
399 #[test]
400 fn padding() {
401 let _ua = Address {
403 revision: Revision::R0,
404 items: vec![Uitem::Data(Receiver::Orchard([1; 43]))],
405 };
406
407 let invalid_padding = [
409 0xe6, 0x59, 0xd1, 0xed, 0xf7, 0x4b, 0xe3, 0x5e, 0x5a, 0x54, 0x0e, 0x41, 0x5d, 0x2f,
410 0x0c, 0x0d, 0x33, 0x42, 0xbd, 0xbe, 0x9f, 0x82, 0x62, 0x01, 0xc1, 0x1b, 0xd4, 0x1e,
411 0x42, 0x47, 0x86, 0x23, 0x05, 0x4b, 0x98, 0xd7, 0x76, 0x86, 0xa5, 0xe3, 0x1b, 0xd3,
412 0x03, 0xca, 0x24, 0x44, 0x8e, 0x72, 0xc1, 0x4a, 0xc6, 0xbf, 0x3f, 0x2b, 0xce, 0xa7,
413 0x7b, 0x28, 0x69, 0xc9, 0x84,
414 ];
415 assert_eq!(
416 Address::parse_internal(Address::MAINNET, &invalid_padding[..], Revision::R0),
417 Err(ParseError::InvalidEncoding(
418 "Invalid padding bytes".to_owned()
419 ))
420 );
421
422 let truncated_padding = [
424 0x9a, 0x56, 0x12, 0xa3, 0x43, 0x45, 0xe0, 0x82, 0x6c, 0xac, 0x24, 0x8b, 0x3b, 0x45,
425 0x72, 0x9a, 0x53, 0xd5, 0xf8, 0xda, 0xec, 0x07, 0x7c, 0xba, 0x9f, 0xa8, 0xd2, 0x97,
426 0x5b, 0xda, 0x73, 0x1b, 0xd2, 0xd1, 0x32, 0x6b, 0x7b, 0x36, 0xdd, 0x57, 0x84, 0x2a,
427 0xa0, 0x21, 0x23, 0x89, 0x73, 0x85, 0xe1, 0x4b, 0x3e, 0x95, 0xb7, 0xd4, 0x67, 0xbc,
428 0x4b, 0x31, 0xee, 0x5a,
429 ];
430 assert_eq!(
431 Address::parse_internal(Address::MAINNET, &truncated_padding[..], Revision::R0),
432 Err(ParseError::InvalidEncoding(
433 "Invalid padding bytes".to_owned()
434 ))
435 );
436 }
437
438 #[test]
439 fn truncated() {
440 let truncated_sapling_data = [
442 0xaa, 0xb0, 0x6e, 0x7b, 0x26, 0x7a, 0x22, 0x17, 0x39, 0xfa, 0x07, 0x69, 0xe9, 0x32,
443 0x2b, 0xac, 0x8c, 0x9e, 0x5e, 0x8a, 0xd9, 0x24, 0x06, 0x5a, 0x13, 0x79, 0x3a, 0x8d,
444 0xb4, 0x52, 0xfa, 0x18, 0x4e, 0x33, 0x4d, 0x8c, 0x17, 0x77, 0x4d, 0x63, 0x69, 0x34,
445 0x22, 0x70, 0x3a, 0xea, 0x30, 0x82, 0x5a, 0x6b, 0x37, 0xd1, 0x0d, 0xbe, 0x20, 0xab,
446 0x82, 0x86, 0x98, 0x34, 0x6a, 0xd8, 0x45, 0x40, 0xd0, 0x25, 0x60, 0xbf, 0x1e, 0xb6,
447 0xeb, 0x06, 0x85, 0x70, 0x4c, 0x42, 0xbc, 0x19, 0x14, 0xef, 0x7a, 0x05, 0xa0, 0x71,
448 0xb2, 0x63, 0x80, 0xbb, 0xdc, 0x12, 0x08, 0x48, 0x28, 0x8f, 0x1c, 0x9e, 0xc3, 0x42,
449 0xc6, 0x5e, 0x68, 0xa2, 0x78, 0x6c, 0x9e,
450 ];
451 assert_matches!(
452 Address::parse_internal(Address::MAINNET, &truncated_sapling_data[..], Revision::R0),
453 Err(ParseError::InvalidEncoding(_))
454 );
455
456 let truncated_after_sapling_typecode = [
458 0x87, 0x7a, 0xdf, 0x79, 0x6b, 0xe3, 0xb3, 0x40, 0xef, 0xe4, 0x5d, 0xc2, 0x91, 0xa2,
459 0x81, 0xfc, 0x7d, 0x76, 0xbb, 0xb0, 0x58, 0x98, 0x53, 0x59, 0xd3, 0x3f, 0xbc, 0x4b,
460 0x86, 0x59, 0x66, 0x62, 0x75, 0x92, 0xba, 0xcc, 0x31, 0x1e, 0x60, 0x02, 0x3b, 0xd8,
461 0x4c, 0xdf, 0x36, 0xa1, 0xac, 0x82, 0x57, 0xed, 0x0c, 0x98, 0x49, 0x8f, 0x49, 0x7e,
462 0xe6, 0x70, 0x36, 0x5b, 0x7b, 0x9e,
463 ];
464 assert_matches!(
465 Address::parse_internal(
466 Address::MAINNET,
467 &truncated_after_sapling_typecode[..],
468 Revision::R0
469 ),
470 Err(ParseError::InvalidEncoding(_))
471 );
472 }
473
474 #[test]
475 fn duplicate_typecode() {
476 let ua = Address {
477 revision: Revision::R0,
478 items: vec![
479 Uitem::Data(Receiver::Sapling([1; 43])),
480 Uitem::Data(Receiver::Sapling([2; 43])),
481 ],
482 };
483 let encoded = ua.to_jumbled_bytes(Address::MAINNET);
484 assert_eq!(
485 Address::parse_internal(Address::MAINNET, &encoded[..], Revision::R0),
486 Err(ParseError::DuplicateTypecode(Typecode::Data(
487 super::super::DataTypecode::Sapling
488 )))
489 );
490 }
491
492 #[test]
493 fn p2pkh_and_p2sh() {
494 let ua = Address {
495 revision: Revision::R0,
496 items: vec![
497 Uitem::Data(Receiver::P2pkh([0; 20])),
498 Uitem::Data(Receiver::P2sh([0; 20])),
499 ],
500 };
501 let encoded = ua.to_jumbled_bytes(Address::MAINNET);
502 assert_eq!(
503 Address::parse_internal(Address::MAINNET, &encoded[..], Revision::R0),
504 Err(ParseError::BothP2phkAndP2sh)
505 );
506 }
507
508 #[test]
509 fn addresses_out_of_order() {
510 let ua = Address {
511 revision: Revision::R0,
512 items: vec![
513 Uitem::Data(Receiver::Sapling([0; 43])),
514 Uitem::Data(Receiver::P2pkh([0; 20])),
515 ],
516 };
517 let encoded = ua.to_jumbled_bytes(Address::MAINNET);
518 assert_eq!(
519 Address::parse_internal(Address::MAINNET, &encoded[..], Revision::R0),
520 Err(ParseError::InvalidTypecodeOrder)
521 );
522 }
523
524 #[test]
525 fn only_transparent() {
526 let encoded = [
528 0xf0, 0x9e, 0x9d, 0x6e, 0xf5, 0xa6, 0xac, 0x16, 0x50, 0xf0, 0xdb, 0xe1, 0x2c, 0xa5,
529 0x36, 0x22, 0xa2, 0x04, 0x89, 0x86, 0xe9, 0x6a, 0x9b, 0xf3, 0xff, 0x6d, 0x2f, 0xe6,
530 0xea, 0xdb, 0xc5, 0x20, 0x62, 0xf9, 0x6f, 0xa9, 0x86, 0xcc,
531 ];
532
533 assert_matches!(
534 Address::parse_internal(Address::MAINNET, &encoded[..], Revision::R0),
535 Err(ParseError::InvalidEncoding(_))
536 );
537 }
538
539 #[test]
540 fn receivers_are_sorted() {
541 let ua = Address {
542 revision: Revision::R0,
543 items: vec![
544 Uitem::Data(Receiver::P2pkh([0; 20])),
545 Uitem::Data(Receiver::Orchard([0; 43])),
546 Uitem::Data(Receiver::Unknown {
547 typecode: 0xff,
548 data: vec![],
549 }),
550 Uitem::Data(Receiver::Sapling([0; 43])),
551 ],
552 };
553
554 assert_eq!(
555 ua.items(),
556 vec![
557 Receiver::Orchard([0; 43]),
558 Receiver::Sapling([0; 43]),
559 Receiver::P2pkh([0; 20]),
560 Receiver::Unknown {
561 typecode: 0xff,
562 data: vec![],
563 },
564 ]
565 )
566 }
567
568 #[test]
569 fn address_receiver_queries() {
570 use zcash_protocol::PoolType;
571
572 let ua = Address {
574 revision: Revision::R0,
575 items: vec![
576 Uitem::Data(Receiver::Orchard([0; 43])),
577 Uitem::Data(Receiver::Sapling([1; 43])),
578 Uitem::Data(Receiver::P2pkh([2; 20])),
579 ],
580 };
581
582 assert!(ua.has_receiver_of_type(PoolType::ORCHARD));
584 assert!(ua.has_receiver_of_type(PoolType::SAPLING));
585 assert!(ua.has_receiver_of_type(PoolType::TRANSPARENT));
586
587 assert!(ua.can_receive_memo());
589
590 assert!(ua.contains_receiver(&Receiver::Orchard([0; 43])));
592 assert!(!ua.contains_receiver(&Receiver::Orchard([1; 43]))); let transparent_only = Address {
596 revision: Revision::R0,
597 items: vec![Uitem::Data(Receiver::P2pkh([0; 20]))],
598 };
599 assert!(!transparent_only.can_receive_memo());
600 assert!(!transparent_only.has_receiver_of_type(PoolType::ORCHARD));
601 assert!(!transparent_only.has_receiver_of_type(PoolType::SAPLING));
602 assert!(transparent_only.has_receiver_of_type(PoolType::TRANSPARENT));
603
604 let with_unknown = Address {
606 revision: Revision::R0,
607 items: vec![
608 Uitem::Data(Receiver::Orchard([0; 43])),
609 Uitem::Data(Receiver::Unknown {
610 typecode: 0xAA,
611 data: vec![0; 32],
612 }),
613 ],
614 };
615 assert!(!with_unknown.has_receiver_of_type(PoolType::SAPLING));
616 assert!(!with_unknown.has_receiver_of_type(PoolType::TRANSPARENT));
618 }
619
620 #[test]
621 fn r2_address_with_expiry() {
622 let items = vec![
624 Uitem::Data(Receiver::Orchard([7; 43])),
625 Uitem::Metadata(MetadataItem::ExpiryHeight(1_000_000)),
626 ];
627 let ua = Address::try_from_items(Revision::R2, items).unwrap();
628 assert_eq!(ua.revision(), Revision::R2);
629
630 let encoded = ua.encode(&NetworkType::Main);
632 assert!(encoded.starts_with("zu1")); let (net, rev, decoded) = Address::decode(&encoded).unwrap();
634 assert_eq!(net, NetworkType::Main);
635 assert_eq!(rev, Revision::R2);
636 assert_eq!(decoded, ua);
637
638 let meta: Vec<_> = decoded.metadata_items();
640 assert_eq!(meta.len(), 1);
641 assert_eq!(*meta[0], MetadataItem::ExpiryHeight(1_000_000));
642 }
643
644 #[test]
645 fn r2_tu_address_with_transparent() {
646 let items = vec![
648 Uitem::Data(Receiver::P2pkh([0; 20])),
649 Uitem::Data(Receiver::Orchard([0; 43])),
650 Uitem::Metadata(MetadataItem::ExpiryHeight(1_000_000)),
651 ];
652 let ua = Address::try_from_items(Revision::R2, items).unwrap();
653
654 let encoded = ua.encode(&NetworkType::Main);
655 assert!(encoded.starts_with("tu1")); let (net, rev, decoded) = Address::decode(&encoded).unwrap();
659 assert_eq!(net, NetworkType::Main);
660 assert_eq!(rev, Revision::R2);
661 assert_eq!(decoded, ua);
662 }
663
664 #[test]
665 fn zu_address_rejects_transparent() {
666 use crate::kind::unified::private::SealedContainer;
669
670 let ua = Address {
671 revision: Revision::R2,
672 items: vec![
673 Uitem::Data(Receiver::P2pkh([0; 20])),
674 Uitem::Data(Receiver::Orchard([0; 43])),
675 Uitem::Metadata(MetadataItem::ExpiryHeight(1_000_000)),
676 ],
677 };
678 let encoded = ua.to_jumbled_bytes(Address::MAINNET_R2);
679 assert_eq!(
680 Address::parse_internal(Address::MAINNET_R2, &encoded[..], Revision::R2),
681 Err(ParseError::TransparentReceiverInR2Address)
682 );
683 }
684
685 #[test]
686 fn tu_transparent_only_roundtrip() {
687 let items = vec![
689 Uitem::Data(Receiver::P2pkh([1; 20])),
690 Uitem::Metadata(MetadataItem::ExpiryHeight(500_000)),
691 ];
692 let ua = Address::try_from_items(Revision::R2, items).unwrap();
693
694 let encoded = ua.encode(&NetworkType::Main);
695 assert!(encoded.starts_with("tu1")); let (net, rev, decoded) = Address::decode(&encoded).unwrap();
699 assert_eq!(net, NetworkType::Main);
700 assert_eq!(rev, Revision::R2);
701 assert_eq!(decoded, ua);
702 }
703
704 #[test]
705 fn r2_rejects_metadata_only_container() {
706 let items = vec![Uitem::Metadata(MetadataItem::ExpiryHeight(100))];
708 assert_eq!(
709 Address::try_from_items(Revision::R2, items),
710 Err(ParseError::NoDataItems)
711 );
712 }
713
714 #[test]
715 fn r0_rejects_metadata_only_container() {
716 const SHOULD_UNDERSTAND_METADATA_TYPECODE: u32 = 0xC0;
722
723 let items = vec![Uitem::Metadata(MetadataItem::Unknown {
724 typecode: SHOULD_UNDERSTAND_METADATA_TYPECODE,
725 data: vec![],
726 })];
727 assert_eq!(
728 Address::try_from_items(Revision::R0, items),
729 Err(ParseError::NoDataItems)
730 );
731 }
732
733 #[test]
734 fn zu_address_allows_unrecognised_only_receiver() {
735 const EXPERIMENTAL_TYPECODE: u32 = 0xFFFA;
741
742 let items = vec![Uitem::Data(Receiver::Unknown {
743 typecode: EXPERIMENTAL_TYPECODE,
744 data: vec![0; 32],
745 })];
746 let ua = Address::try_from_items(Revision::R2, items).unwrap();
747
748 let encoded = ua.encode(&NetworkType::Main);
749 assert!(
750 encoded.starts_with("zu1"),
751 "expected a zu address: {encoded}"
752 );
753
754 let (net, rev, decoded) = Address::decode(&encoded).unwrap();
755 assert_eq!(net, NetworkType::Main);
756 assert_eq!(rev, Revision::R2);
757 assert_eq!(decoded, ua);
758 }
759
760 #[test]
761 fn rejects_typecode_above_compact_size_bound() {
762 let oversized_typecode = MAX_COMPACT_SIZE + 1;
765 let items = vec![Uitem::Data(Receiver::Unknown {
766 typecode: oversized_typecode,
767 data: vec![0; 32],
768 })];
769 assert_eq!(
770 Address::try_from_items(Revision::R2, items),
771 Err(ParseError::InvalidTypecodeValue(u64::from(
772 oversized_typecode
773 )))
774 );
775 }
776
777 #[test]
778 fn r0_rejects_must_understand_metadata() {
779 use crate::kind::unified::private::SealedContainer;
782
783 let ua = Address {
784 revision: Revision::R0,
785 items: vec![
786 Uitem::Data(Receiver::Orchard([1; 43])),
787 Uitem::Metadata(MetadataItem::ExpiryHeight(100)),
788 ],
789 };
790 let encoded = ua.to_jumbled_bytes(Address::MAINNET);
791 assert_matches!(
793 Address::parse_internal(Address::MAINNET, &encoded[..], Revision::R0),
794 Err(ParseError::NotUnderstood(0xE0))
795 );
796 }
797
798 #[test]
799 fn r2_rejects_unknown_must_understand_metadata() {
800 use crate::kind::unified::private::SealedContainer;
802
803 let ua = Address {
804 revision: Revision::R2,
805 items: vec![
806 Uitem::Data(Receiver::Orchard([1; 43])),
807 Uitem::Metadata(MetadataItem::Unknown {
808 typecode: 0xE5,
809 data: vec![0; 4],
810 }),
811 ],
812 };
813 let encoded = ua.to_jumbled_bytes(Address::MAINNET_R2);
814 assert_matches!(
815 Address::parse_internal(Address::MAINNET_R2, &encoded[..], Revision::R2),
816 Err(ParseError::NotUnderstood(0xE5))
817 );
818 }
819
820 #[test]
821 fn r0_construction_rejects_must_understand_metadata() {
822 use crate::kind::unified::MetadataTypecode;
825
826 const EXPIRY_HEIGHT: u32 = 100;
827 const EXPIRY_TIME: u64 = 1_700_000_000;
828
829 for (meta, typecode) in [
830 (
831 MetadataItem::ExpiryHeight(EXPIRY_HEIGHT),
832 MetadataTypecode::ExpiryHeight,
833 ),
834 (
835 MetadataItem::ExpiryTime(EXPIRY_TIME),
836 MetadataTypecode::ExpiryTime,
837 ),
838 ] {
839 let items = vec![
840 Uitem::Data(Receiver::Orchard([1; 43])),
841 Uitem::Metadata(meta.clone()),
842 ];
843 assert_eq!(
844 Address::try_from_items(Revision::R0, items.clone()),
845 Err(ParseError::NotDefinedInRevision {
846 typecode: Typecode::Metadata(typecode),
847 revision: Revision::R0,
848 })
849 );
850 assert!(Address::try_from_items(Revision::R2, items).is_ok());
851 }
852 }
853
854 #[test]
855 fn construction_rejects_unknown_must_understand_metadata() {
856 const UNKNOWN_MUST_UNDERSTAND_TYPECODE: u32 = 0xE5;
858
859 for revision in [Revision::R0, Revision::R2] {
860 let items = vec![
861 Uitem::Data(Receiver::Orchard([1; 43])),
862 Uitem::Metadata(MetadataItem::Unknown {
863 typecode: UNKNOWN_MUST_UNDERSTAND_TYPECODE,
864 data: vec![0; 4],
865 }),
866 ];
867 assert_eq!(
868 Address::try_from_items(revision, items),
869 Err(ParseError::NotUnderstood(UNKNOWN_MUST_UNDERSTAND_TYPECODE))
870 );
871 }
872 }
873
874 #[test]
875 fn construction_rejects_encodings_outside_f4jumble_range() {
876 const EXPERIMENTAL_TYPECODE: u32 = 0xFFFA;
879
880 let too_short = vec![Uitem::Data(Receiver::Unknown {
883 typecode: EXPERIMENTAL_TYPECODE,
884 data: vec![],
885 })];
886 assert_matches!(
887 Address::try_from_items(Revision::R2, too_short),
888 Err(ParseError::InvalidEncodedLength(length))
889 if length < *f4jumble::VALID_LENGTH.start()
890 );
891
892 let too_long = vec![Uitem::Data(Receiver::Unknown {
893 typecode: EXPERIMENTAL_TYPECODE,
894 data: vec![0; *f4jumble::VALID_LENGTH.end()],
895 })];
896 assert_matches!(
897 Address::try_from_items(Revision::R2, too_long),
898 Err(ParseError::InvalidEncodedLength(length))
899 if length > *f4jumble::VALID_LENGTH.end()
900 );
901 }
902
903 #[test]
904 fn transparent_only_r2_address_round_trips() {
905 let ua = Address::try_from_items(Revision::R2, vec![Uitem::Data(Receiver::P2pkh([1; 20]))])
907 .expect("a P2PKH receiver alone is a valid Revision 2 address");
908 let encoded = ua.encode(&NetworkType::Main);
909 assert!(
910 encoded.starts_with("tu1"),
911 "expected a tu address: {encoded}"
912 );
913 assert_eq!(
914 Address::decode(&encoded),
915 Ok((NetworkType::Main, Revision::R2, ua))
916 );
917 }
918}