1use alloc::string::{String, ToString};
4use alloc::vec::Vec;
5use core::cmp;
6use core::convert::{TryFrom, TryInto};
7use core::fmt;
8use core::num::TryFromIntError;
9
10#[cfg(feature = "std")]
11use std::error::Error;
12
13use bech32::{Bech32m, Checksum, Hrp, primitives::decode::CheckedHrpstring};
14
15use zcash_protocol::consensus::NetworkType;
16
17pub(crate) mod address;
18pub(crate) mod fvk;
19pub(crate) mod ivk;
20
21pub use address::{Address, Receiver};
22pub use fvk::{Fvk, Ufvk};
23pub use ivk::{Ivk, Uivk};
24pub use zcash_protocol::address::Revision;
25
26#[cfg(feature = "test-dependencies")]
27pub use address::testing;
28
29const PADDING_LEN: usize = 16;
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub enum DataTypecode {
38 P2pkh,
42 P2sh,
52 Sapling,
56 Orchard,
60 Unknown(u32),
62}
63
64impl DataTypecode {
65 pub fn is_transparent(&self) -> bool {
66 matches!(self, DataTypecode::P2pkh | DataTypecode::P2sh)
67 }
68
69 pub fn preference_order(a: &Self, b: &Self) -> cmp::Ordering {
70 use DataTypecode::*;
71 match (a, b) {
72 (Orchard, Orchard) | (Sapling, Sapling) | (P2sh, P2sh) | (P2pkh, P2pkh) => {
73 cmp::Ordering::Equal
74 }
75
76 (Unknown(a), Unknown(b)) => b.cmp(a),
77
78 (Orchard, _) => cmp::Ordering::Less,
79 (_, Orchard) => cmp::Ordering::Greater,
80
81 (Sapling, _) => cmp::Ordering::Less,
82 (_, Sapling) => cmp::Ordering::Greater,
83
84 (P2sh, _) => cmp::Ordering::Less,
85 (_, P2sh) => cmp::Ordering::Greater,
86
87 (P2pkh, _) => cmp::Ordering::Less,
88 (_, P2pkh) => cmp::Ordering::Greater,
89 }
90 }
91}
92
93impl From<DataTypecode> for u32 {
94 fn from(t: DataTypecode) -> Self {
95 match t {
96 DataTypecode::P2pkh => 0x00,
97 DataTypecode::P2sh => 0x01,
98 DataTypecode::Sapling => 0x02,
99 DataTypecode::Orchard => 0x03,
100 DataTypecode::Unknown(tc) => tc,
101 }
102 }
103}
104
105#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
107pub enum MetadataTypecode {
108 ExpiryHeight,
110 ExpiryTime,
112 Unknown(u32),
114}
115
116impl From<MetadataTypecode> for u32 {
117 fn from(t: MetadataTypecode) -> Self {
118 match t {
119 MetadataTypecode::ExpiryHeight => 0xE0,
120 MetadataTypecode::ExpiryTime => 0xE1,
121 MetadataTypecode::Unknown(tc) => tc,
122 }
123 }
124}
125
126#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
134pub enum Typecode {
135 Data(DataTypecode),
137 Metadata(MetadataTypecode),
139}
140
141impl Typecode {
143 pub const P2PKH: Typecode = Typecode::Data(DataTypecode::P2pkh);
145 pub const P2SH: Typecode = Typecode::Data(DataTypecode::P2sh);
147 pub const SAPLING: Typecode = Typecode::Data(DataTypecode::Sapling);
149 pub const ORCHARD: Typecode = Typecode::Data(DataTypecode::Orchard);
151}
152
153impl From<DataTypecode> for Typecode {
154 fn from(tc: DataTypecode) -> Self {
155 Typecode::Data(tc)
156 }
157}
158
159impl From<MetadataTypecode> for Typecode {
160 fn from(tc: MetadataTypecode) -> Self {
161 Typecode::Metadata(tc)
162 }
163}
164
165const MUST_UNDERSTAND_METADATA_MIN: u32 = 0xE0;
167const METADATA_TYPECODE_MAX: u32 = 0xFC;
169const METADATA_TYPECODE_MIN: u32 = 0xC0;
171
172impl Typecode {
173 pub fn typecode_value(&self) -> u32 {
175 match self {
176 Typecode::Data(tc) => u32::from(*tc),
177 Typecode::Metadata(tc) => u32::from(*tc),
178 }
179 }
180
181 pub fn preference_order(a: &Self, b: &Self) -> cmp::Ordering {
182 use DataTypecode::*;
183 match (a, b) {
184 (Typecode::Data(_), Typecode::Metadata(_)) => cmp::Ordering::Less,
186 (Typecode::Metadata(_), Typecode::Data(_)) => cmp::Ordering::Greater,
187
188 (Typecode::Metadata(a), Typecode::Metadata(b)) => u32::from(*a).cmp(&u32::from(*b)),
190
191 (Typecode::Data(a), Typecode::Data(b)) => match (a, b) {
193 (Orchard, Orchard) | (Sapling, Sapling) | (P2sh, P2sh) | (P2pkh, P2pkh) => {
194 cmp::Ordering::Equal
195 }
196
197 (Unknown(a), Unknown(b)) => b.cmp(a),
198
199 (Orchard, _) => cmp::Ordering::Less,
200 (_, Orchard) => cmp::Ordering::Greater,
201
202 (Sapling, _) => cmp::Ordering::Less,
203 (_, Sapling) => cmp::Ordering::Greater,
204
205 (P2sh, _) => cmp::Ordering::Less,
206 (_, P2sh) => cmp::Ordering::Greater,
207
208 (P2pkh, _) => cmp::Ordering::Less,
209 (_, P2pkh) => cmp::Ordering::Greater,
210 },
211 }
212 }
213
214 pub fn encoding_order(a: &Self, b: &Self) -> cmp::Ordering {
215 a.typecode_value().cmp(&b.typecode_value())
216 }
217
218 pub fn is_transparent_data(&self) -> bool {
219 match self {
220 Typecode::Data(tc) => tc.is_transparent(),
221 Typecode::Metadata(_) => false,
222 }
223 }
224}
225
226impl TryFrom<u32> for Typecode {
227 type Error = ParseError;
228
229 fn try_from(typecode: u32) -> Result<Self, Self::Error> {
230 match typecode {
231 0x00 => Ok(Typecode::Data(DataTypecode::P2pkh)),
232 0x01 => Ok(Typecode::Data(DataTypecode::P2sh)),
233 0x02 => Ok(Typecode::Data(DataTypecode::Sapling)),
234 0x03 => Ok(Typecode::Data(DataTypecode::Orchard)),
235 0x04..=0xBF => Ok(Typecode::Data(DataTypecode::Unknown(typecode))),
236 tc @ METADATA_TYPECODE_MIN..=METADATA_TYPECODE_MAX => {
237 match tc {
238 0xE0 => Ok(Typecode::Metadata(MetadataTypecode::ExpiryHeight)),
239 0xE1 => Ok(Typecode::Metadata(MetadataTypecode::ExpiryTime)),
240 _ => Ok(Typecode::Metadata(MetadataTypecode::Unknown(tc))),
243 }
244 }
245 0xFD..=0x02000000 => Ok(Typecode::Data(DataTypecode::Unknown(typecode))),
246 0x02000001..=u32::MAX => Err(ParseError::InvalidTypecodeValue(u64::from(typecode))),
247 }
248 }
249}
250
251impl From<Typecode> for u32 {
252 fn from(t: Typecode) -> Self {
253 t.typecode_value()
254 }
255}
256
257impl TryFrom<Typecode> for usize {
258 type Error = TryFromIntError;
259 fn try_from(t: Typecode) -> Result<Self, Self::Error> {
260 u32::from(t).try_into()
261 }
262}
263
264#[derive(Clone, Debug, PartialEq, Eq, Hash)]
266pub enum MetadataItem {
267 ExpiryHeight(u32),
269 ExpiryTime(u64),
271 Unknown { typecode: u32, data: Vec<u8> },
273}
274
275impl MetadataItem {
276 pub fn typecode(&self) -> MetadataTypecode {
278 match self {
279 MetadataItem::ExpiryHeight(_) => MetadataTypecode::ExpiryHeight,
280 MetadataItem::ExpiryTime(_) => MetadataTypecode::ExpiryTime,
281 MetadataItem::Unknown { typecode, .. } => MetadataTypecode::Unknown(*typecode),
282 }
283 }
284
285 pub fn data(&self) -> Vec<u8> {
287 match self {
288 MetadataItem::ExpiryHeight(h) => h.to_le_bytes().to_vec(),
289 MetadataItem::ExpiryTime(t) => t.to_le_bytes().to_vec(),
290 MetadataItem::Unknown { data, .. } => data.clone(),
291 }
292 }
293
294 pub fn combined_typecode(&self) -> Typecode {
296 Typecode::Metadata(self.typecode())
297 }
298}
299
300#[derive(Clone, Debug, PartialEq, Eq, Hash)]
302pub enum Uitem<T> {
303 Data(T),
305 Metadata(MetadataItem),
307}
308
309impl<T: private::SealedItem> Uitem<T> {
310 pub(crate) fn encoding_order(a: &Self, b: &Self) -> cmp::Ordering {
313 fn typecode_value<T: private::SealedItem>(item: &Uitem<T>) -> u32 {
314 match item {
315 Uitem::Data(d) => u32::from(d.typecode()),
316 Uitem::Metadata(m) => u32::from(m.typecode()),
317 }
318 }
319
320 typecode_value(a)
321 .cmp(&typecode_value(b))
322 .then_with(|| match (a, b) {
323 (Uitem::Data(a), Uitem::Data(b)) => a.data().cmp(b.data()),
324 (Uitem::Metadata(a), Uitem::Metadata(b)) => a.data().cmp(&b.data()),
325 _ => cmp::Ordering::Equal,
328 })
329 }
330}
331
332#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
335pub enum P2shItemKind {
336 FullViewing,
339 IncomingViewing,
342}
343
344#[non_exhaustive]
353#[derive(Clone, Copy, Debug, PartialEq, Eq)]
354pub enum P2shItemError {
355 Malformed,
358 TemplateEncoding,
360 PlaceholderCount {
363 placeholders: usize,
365 keys: usize,
367 },
368 Multipath,
371}
372
373impl fmt::Display for P2shItemError {
374 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375 match self {
376 P2shItemError::Malformed => {
377 write!(f, "malformed wallet policy container encoding")
378 }
379 P2shItemError::TemplateEncoding => {
380 write!(f, "descriptor template is not US-ASCII")
381 }
382 P2shItemError::PlaceholderCount { placeholders, keys } => {
383 write!(
384 f,
385 "template has {placeholders} key placeholders but {keys} key entries"
386 )
387 }
388 P2shItemError::Multipath => {
389 write!(
390 f,
391 "a key placeholder does not use the required multipath notation"
392 )
393 }
394 }
395 }
396}
397
398#[cfg(feature = "std")]
399impl Error for P2shItemError {}
400
401#[derive(Debug, PartialEq, Eq)]
403pub enum ParseError {
404 BothP2phkAndP2sh,
406 DuplicateTypecode(Typecode),
408 InvalidTypecodeValue(u64),
410 InvalidEncoding(String),
412 InvalidTypecodeOrder,
414 OnlyTransparent,
416 NotUnified,
418 UnknownPrefix(String),
420 NotUnderstood(u32),
423 TransparentReceiverInR2Address,
425 NoDataItems,
427 InvalidMetadataLength {
429 typecode: u32,
430 expected: usize,
431 actual: usize,
432 },
433 InvalidP2shItem(P2shItemError),
435}
436
437impl fmt::Display for ParseError {
438 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439 match self {
440 ParseError::BothP2phkAndP2sh => write!(f, "UA contains both P2PKH and P2SH items"),
441 ParseError::DuplicateTypecode(c) => write!(f, "Duplicate typecode {}", u32::from(*c)),
442 ParseError::InvalidTypecodeValue(v) => write!(f, "Typecode value out of range {v}"),
443 ParseError::InvalidEncoding(msg) => write!(f, "Invalid encoding: {msg}"),
444 ParseError::InvalidTypecodeOrder => write!(f, "Items are out of order."),
445 ParseError::OnlyTransparent => write!(f, "UA only contains transparent items"),
446 ParseError::NotUnified => write!(f, "Address is not Bech32m encoded"),
447 ParseError::UnknownPrefix(s) => {
448 write!(f, "Unrecognized Bech32m human-readable prefix: {s}")
449 }
450 ParseError::NotUnderstood(tc) => {
451 write!(
452 f,
453 "MUST-understand metadata typecode 0x{tc:02X} not recognized"
454 )
455 }
456 ParseError::TransparentReceiverInR2Address => {
457 write!(
458 f,
459 "Transparent receivers are not permitted in Revision 2 Unified Addresses"
460 )
461 }
462 ParseError::NoDataItems => {
463 write!(f, "Unified container has no data items")
464 }
465 ParseError::InvalidMetadataLength {
466 typecode,
467 expected,
468 actual,
469 } => {
470 write!(
471 f,
472 "Metadata typecode 0x{typecode:02X} has invalid length: expected {expected}, got {actual}"
473 )
474 }
475 ParseError::InvalidP2shItem(e) => {
476 write!(f, "Invalid P2SH viewing key item: {e}")
477 }
478 }
479 }
480}
481
482#[cfg(feature = "std")]
483impl Error for ParseError {}
484
485pub(crate) mod private {
486 use alloc::borrow::ToOwned;
487 use alloc::vec::Vec;
488 use core::cmp;
489 use core::convert::{TryFrom, TryInto};
490 use corez::io::Write;
491
492 use super::{
493 MUST_UNDERSTAND_METADATA_MIN, MetadataItem, MetadataTypecode, PADDING_LEN, ParseError,
494 Typecode, Uitem,
495 };
496 use zcash_encoding::CompactSize;
497 use zcash_protocol::address::Revision;
498 use zcash_protocol::consensus::NetworkType;
499
500 pub trait SealedItem: Clone {
502 fn parse(typecode: super::DataTypecode, data: &[u8]) -> Result<Self, ParseError>;
503 fn typecode(&self) -> super::DataTypecode;
504 fn data(&self) -> &[u8];
505
506 fn preference_order(a: &Self, b: &Self) -> cmp::Ordering {
507 match super::DataTypecode::preference_order(&a.typecode(), &b.typecode()) {
508 cmp::Ordering::Equal => a.data().cmp(b.data()),
509 res => res,
510 }
511 }
512
513 fn encoding_order(a: &Self, b: &Self) -> cmp::Ordering {
514 match u32::from(a.typecode()).cmp(&u32::from(b.typecode())) {
515 cmp::Ordering::Equal => a.data().cmp(b.data()),
516 res => res,
517 }
518 }
519
520 fn write_raw_encoding<W: Write>(&self, mut writer: W) -> corez::io::Result<()> {
521 let data = self.data();
522 CompactSize::write(
523 &mut writer,
524 <u32>::from(self.typecode())
525 .try_into()
526 .expect("a typecode fits in a usize"),
527 )?;
528 CompactSize::write(&mut writer, data.len())?;
529 writer.write_all(data)
530 }
531 }
532
533 fn write_metadata_raw_encoding<W: Write>(
535 item: &MetadataItem,
536 mut writer: W,
537 ) -> corez::io::Result<()> {
538 let tc_val: u32 = item.typecode().into();
539 let data = item.data();
540 CompactSize::write(
541 &mut writer,
542 tc_val.try_into().expect("a typecode fits in a usize"),
543 )?;
544 CompactSize::write(&mut writer, data.len())?;
545 writer.write_all(&data)
546 }
547
548 pub trait SealedContainer: super::Container + core::marker::Sized {
550 const MAINNET: &'static str;
551 const TESTNET: &'static str;
552 const REGTEST: &'static str;
553
554 const MAINNET_R2: &'static str;
555 const TESTNET_R2: &'static str;
556 const REGTEST_R2: &'static str;
557
558 const MAINNET_R2_TI: &'static str;
562 const TESTNET_R2_TI: &'static str;
563 const REGTEST_R2_TI: &'static str;
564
565 const IS_ADDRESS: bool;
567
568 fn from_inner(revision: Revision, items: Vec<Uitem<Self::Item>>) -> Self;
572
573 fn network_hrp(
574 network: &NetworkType,
575 revision: Revision,
576 has_transparent: bool,
577 ) -> &'static str {
578 match (network, revision) {
579 (NetworkType::Main, Revision::R0) => Self::MAINNET,
580 (NetworkType::Test, Revision::R0) => Self::TESTNET,
581 (NetworkType::Regtest, Revision::R0) => Self::REGTEST,
582 (NetworkType::Main, Revision::R2) => {
583 if Self::IS_ADDRESS && has_transparent {
584 Self::MAINNET_R2_TI
585 } else {
586 Self::MAINNET_R2
587 }
588 }
589 (NetworkType::Test, Revision::R2) => {
590 if Self::IS_ADDRESS && has_transparent {
591 Self::TESTNET_R2_TI
592 } else {
593 Self::TESTNET_R2
594 }
595 }
596 (NetworkType::Regtest, Revision::R2) => {
597 if Self::IS_ADDRESS && has_transparent {
598 Self::REGTEST_R2_TI
599 } else {
600 Self::REGTEST_R2
601 }
602 }
603 }
604 }
605
606 fn hrp_network(hrp: &str) -> Option<(NetworkType, Revision)> {
607 if hrp == Self::MAINNET {
608 Some((NetworkType::Main, Revision::R0))
609 } else if hrp == Self::TESTNET {
610 Some((NetworkType::Test, Revision::R0))
611 } else if hrp == Self::REGTEST {
612 Some((NetworkType::Regtest, Revision::R0))
613 } else if hrp == Self::MAINNET_R2 {
614 Some((NetworkType::Main, Revision::R2))
615 } else if hrp == Self::TESTNET_R2 {
616 Some((NetworkType::Test, Revision::R2))
617 } else if hrp == Self::REGTEST_R2 {
618 Some((NetworkType::Regtest, Revision::R2))
619 } else if hrp == Self::MAINNET_R2_TI {
620 Some((NetworkType::Main, Revision::R2))
621 } else if hrp == Self::TESTNET_R2_TI {
622 Some((NetworkType::Test, Revision::R2))
623 } else if hrp == Self::REGTEST_R2_TI {
624 Some((NetworkType::Regtest, Revision::R2))
625 } else {
626 None
627 }
628 }
629
630 fn write_raw_encoding<W: Write>(&self, mut writer: W) -> corez::io::Result<()> {
631 for item in self.items_as_parsed() {
632 match item {
633 Uitem::Data(data_item) => data_item.write_raw_encoding(&mut writer)?,
634 Uitem::Metadata(meta_item) => {
635 write_metadata_raw_encoding(meta_item, &mut writer)?;
636 }
637 }
638 }
639 Ok(())
640 }
641
642 fn to_jumbled_bytes(&self, hrp: &str) -> Vec<u8> {
644 assert!(hrp.len() <= PADDING_LEN);
645
646 let mut padded = Vec::new();
647 self.write_raw_encoding(&mut padded)
648 .expect("writing to a Vec cannot fail");
649
650 let mut padding = [0u8; PADDING_LEN];
651 padding[0..hrp.len()].copy_from_slice(hrp.as_bytes());
652 padded
653 .write_all(&padding)
654 .expect("writing to a Vec cannot fail");
655
656 f4jumble::f4jumble(&padded)
657 .unwrap_or_else(|e| panic!("f4jumble failed on {:?}: {}", padded, e))
658 }
659
660 fn parse_items<T: Into<Vec<u8>>>(
662 hrp: &str,
663 buf: T,
664 revision: Revision,
665 ) -> Result<Vec<Uitem<Self::Item>>, ParseError> {
666 fn read_raw_item(
667 mut cursor: &mut corez::io::Cursor<&[u8]>,
668 ) -> Result<(u32, Vec<u8>), ParseError> {
669 let typecode = CompactSize::read(&mut cursor)
670 .map(|v| u32::try_from(v).expect("CompactSize::read enforces MAX_SIZE limit"))
671 .map_err(|e| {
672 ParseError::InvalidEncoding(format!(
673 "Failed to deserialize CompactSize-encoded typecode {e}"
674 ))
675 })?;
676 let length = CompactSize::read(&mut cursor).map_err(|e| {
677 ParseError::InvalidEncoding(format!(
678 "Failed to deserialize CompactSize-encoded length {e}"
679 ))
680 })?;
681 let addr_end = cursor.position().checked_add(length).ok_or_else(|| {
682 ParseError::InvalidEncoding(format!(
683 "Length value {length} caused an overflow error"
684 ))
685 })?;
686 let buf = cursor.get_ref();
687 if (buf.len() as u64) < addr_end {
688 return Err(ParseError::InvalidEncoding(format!(
689 "Truncated: unable to read {length} bytes of item data"
690 )));
691 }
692 let data = buf[cursor.position() as usize..addr_end as usize].to_vec();
693 cursor.set_position(addr_end);
694 Ok((typecode, data))
695 }
696
697 let mut encoded = buf.into();
699 f4jumble::f4jumble_inv_mut(&mut encoded[..]).map_err(|e| {
700 ParseError::InvalidEncoding(format!("F4Jumble decoding failed: {e}"))
701 })?;
702
703 if hrp.len() > 16 {
705 return Err(ParseError::InvalidEncoding(
706 "Invalid human-readable part".to_owned(),
707 ));
708 }
709 let mut expected_padding = [0; PADDING_LEN];
710 expected_padding[0..hrp.len()].copy_from_slice(hrp.as_bytes());
711 let encoded = match encoded.split_at(encoded.len() - PADDING_LEN) {
712 (encoded, tail) if tail == expected_padding => Ok(encoded),
713 _ => Err(ParseError::InvalidEncoding(
714 "Invalid padding bytes".to_owned(),
715 )),
716 }?;
717
718 let mut cursor = corez::io::Cursor::new(encoded);
719 let mut result = vec![];
720 while cursor.position() < encoded.len().try_into().unwrap() {
721 let (tc_val, data) = read_raw_item(&mut cursor)?;
722
723 match Typecode::try_from(tc_val)? {
724 Typecode::Data(dtc) => {
725 let dtc = if revision == Revision::R0
730 && !Self::IS_ADDRESS
731 && dtc == super::DataTypecode::P2sh
732 {
733 super::DataTypecode::Unknown(u32::from(dtc))
734 } else {
735 dtc
736 };
737 result.push(Uitem::Data(Self::Item::parse(dtc, &data)?));
738 }
739 Typecode::Metadata(mtc) => {
740 result.push(Uitem::Metadata(parse_metadata_item(revision, mtc, data)?));
741 }
742 }
743 }
744 assert_eq!(cursor.position(), encoded.len().try_into().unwrap());
745
746 Ok(result)
747 }
748
749 fn try_from_items_internal(
752 revision: Revision,
753 items: Vec<Uitem<Self::Item>>,
754 ) -> Result<Self, ParseError> {
755 assert!(u32::from(Typecode::P2SH) == u32::from(Typecode::P2PKH) + 1);
756
757 let mut has_data_item = false;
758 let mut only_transparent = true;
759 let mut prev_code: Option<u32> = None;
760 for item in &items {
761 let t = match item {
762 Uitem::Data(d) => Typecode::Data(d.typecode()),
763 Uitem::Metadata(m) => m.combined_typecode(),
764 };
765 let t_code = Some(t.typecode_value());
766
767 if t_code < prev_code {
768 return Err(ParseError::InvalidTypecodeOrder);
769 } else if t_code == prev_code {
770 return Err(ParseError::DuplicateTypecode(t));
771 }
772
773 if let Uitem::Data(d) = item {
774 has_data_item = true;
775 let dt = d.typecode();
776 if dt == super::DataTypecode::P2sh
777 && prev_code == Some(u32::from(super::DataTypecode::P2pkh))
778 {
779 return Err(ParseError::BothP2phkAndP2sh);
780 }
781
782 if !dt.is_transparent() {
783 only_transparent = false;
784 }
785 }
786
787 prev_code = t_code;
788 }
789
790 if !has_data_item {
793 return Err(ParseError::NoDataItems);
794 }
795
796 if revision == Revision::R0 && only_transparent {
801 return Err(ParseError::OnlyTransparent);
802 }
803
804 Ok(Self::from_inner(revision, items))
805 }
806
807 fn parse_internal<T: Into<Vec<u8>>>(
808 hrp: &str,
809 buf: T,
810 revision: Revision,
811 ) -> Result<Self, ParseError> {
812 let result = Self::parse_items(hrp, buf, revision)
813 .and_then(|items| Self::try_from_items_internal(revision, items))?;
814
815 if revision == Revision::R2 && Self::IS_ADDRESS {
817 let is_ti_hrp = hrp == Self::MAINNET_R2_TI
818 || hrp == Self::TESTNET_R2_TI
819 || hrp == Self::REGTEST_R2_TI;
820
821 let has_transparent = result
822 .items_as_parsed()
823 .iter()
824 .any(|item| matches!(item, Uitem::Data(d) if d.typecode().is_transparent()));
825
826 if !is_ti_hrp && has_transparent {
832 return Err(ParseError::TransparentReceiverInR2Address);
833 }
834 }
835
836 Ok(result)
837 }
838 }
839
840 pub(crate) fn validate_p2sh_item(
854 kind: super::P2shItemKind,
855 data: &[u8],
856 ) -> Result<(), super::P2shItemError> {
857 use super::{P2shItemError, P2shItemKind};
858
859 const KEY_INFO_LEN: usize = 65;
862
863 let mut cursor = corez::io::Cursor::new(data);
864 let template_len = CompactSize::read(&mut cursor)
865 .ok()
866 .and_then(|n| usize::try_from(n).ok())
867 .ok_or(P2shItemError::Malformed)?;
868 let template_start = usize::try_from(cursor.position()).expect("cursor fits in usize");
869 let template_end = template_start
870 .checked_add(template_len)
871 .filter(|end| *end <= data.len())
872 .ok_or(P2shItemError::Malformed)?;
873 let template = &data[template_start..template_end];
874 cursor.set_position(template_end as u64);
875
876 let n_keys = CompactSize::read(&mut cursor)
877 .ok()
878 .and_then(|n| usize::try_from(n).ok())
879 .ok_or(P2shItemError::Malformed)?;
880 let keys_start = usize::try_from(cursor.position()).expect("cursor fits in usize");
881 if n_keys
882 .checked_mul(KEY_INFO_LEN)
883 .and_then(|len| keys_start.checked_add(len))
884 != Some(data.len())
885 {
886 return Err(P2shItemError::Malformed);
887 }
888
889 if !template.is_ascii() {
890 return Err(P2shItemError::TemplateEncoding);
891 }
892
893 let mut placeholders = 0usize;
896 let mut seen = alloc::vec![false; n_keys];
897 let mut i = 0;
898 while i < template.len() {
899 if template[i] != b'@' {
900 i += 1;
901 continue;
902 }
903 let digits_start = i + 1;
904 let digits_end = template[digits_start..]
905 .iter()
906 .position(|b| !b.is_ascii_digit())
907 .map(|n| digits_start + n)
908 .unwrap_or(template.len());
909 let index: usize = core::str::from_utf8(&template[digits_start..digits_end])
910 .ok()
911 .filter(|s| !s.is_empty())
912 .and_then(|s| s.parse().ok())
913 .ok_or(P2shItemError::Malformed)?;
914 if index >= n_keys || seen[index] {
915 return Err(P2shItemError::PlaceholderCount {
916 placeholders: placeholders + 1,
917 keys: n_keys,
918 });
919 }
920 seen[index] = true;
921 placeholders += 1;
922
923 let suffix = &template[digits_end..];
924 let multipath_ok = match kind {
925 P2shItemKind::FullViewing => suffix.starts_with(b"/**"),
926 P2shItemKind::IncomingViewing => {
927 suffix.starts_with(b"/*") && !suffix.starts_with(b"/**")
928 }
929 };
930 if !multipath_ok {
931 return Err(P2shItemError::Multipath);
932 }
933 i = digits_end;
934 }
935 if placeholders != n_keys {
936 return Err(P2shItemError::PlaceholderCount {
937 placeholders,
938 keys: n_keys,
939 });
940 }
941
942 Ok(())
943 }
944
945 fn parse_metadata_item(
948 revision: Revision,
949 typecode: MetadataTypecode,
950 data: Vec<u8>,
951 ) -> Result<MetadataItem, ParseError> {
952 let tc_val = u32::from(typecode);
953 if revision == Revision::R0 && tc_val >= MUST_UNDERSTAND_METADATA_MIN {
956 return Err(ParseError::NotUnderstood(tc_val));
957 }
958 match typecode {
959 MetadataTypecode::ExpiryHeight => {
960 let data: [u8; 4] =
961 data.as_slice()
962 .try_into()
963 .map_err(|_| ParseError::InvalidMetadataLength {
964 typecode: tc_val,
965 expected: 4,
966 actual: data.len(),
967 })?;
968 Ok(MetadataItem::ExpiryHeight(u32::from_le_bytes(data)))
969 }
970 MetadataTypecode::ExpiryTime => {
971 let data: [u8; 8] =
972 data.as_slice()
973 .try_into()
974 .map_err(|_| ParseError::InvalidMetadataLength {
975 typecode: tc_val,
976 expected: 8,
977 actual: data.len(),
978 })?;
979 Ok(MetadataItem::ExpiryTime(u64::from_le_bytes(data)))
980 }
981 MetadataTypecode::Unknown(tc) if tc >= MUST_UNDERSTAND_METADATA_MIN => {
982 Err(ParseError::NotUnderstood(tc))
985 }
986 MetadataTypecode::Unknown(tc) => Ok(MetadataItem::Unknown { typecode: tc, data }),
987 }
988 }
989}
990
991use private::SealedItem;
992
993#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
999pub enum Bech32mZip316 {}
1000impl Checksum for Bech32mZip316 {
1001 type MidstateRepr = <Bech32m as Checksum>::MidstateRepr;
1002 const CODE_LENGTH: usize = 4194368;
1004 const CHECKSUM_LENGTH: usize = Bech32m::CHECKSUM_LENGTH;
1005 const GENERATOR_SH: [u32; 5] = Bech32m::GENERATOR_SH;
1006 const TARGET_RESIDUE: u32 = Bech32m::TARGET_RESIDUE;
1007}
1008
1009pub trait Encoding: private::SealedContainer {
1011 fn try_from_items(
1016 revision: Revision,
1017 mut items: Vec<Uitem<Self::Item>>,
1018 ) -> Result<Self, ParseError> {
1019 items.sort_unstable_by(Uitem::encoding_order);
1020 Self::try_from_items_internal(revision, items)
1021 }
1022
1023 fn decode(s: &str) -> Result<(NetworkType, Revision, Self), ParseError> {
1027 if let Ok(parsed) = CheckedHrpstring::new::<Bech32mZip316>(s) {
1028 let hrp = parsed.hrp();
1029 let hrp = hrp.as_str();
1030 let (net, revision) =
1032 Self::hrp_network(hrp).ok_or_else(|| ParseError::UnknownPrefix(hrp.to_string()))?;
1033
1034 let data = parsed.byte_iter().collect::<Vec<_>>();
1035
1036 Self::parse_internal(hrp, data, revision).map(|value| (net, revision, value))
1037 } else {
1038 Err(ParseError::NotUnified)
1039 }
1040 }
1041
1042 fn encode(&self, network: &NetworkType) -> String {
1047 let has_transparent = Self::IS_ADDRESS
1048 && self
1049 .items_as_parsed()
1050 .iter()
1051 .any(|item| matches!(item, Uitem::Data(d) if d.typecode().is_transparent()));
1052 let hrp = Self::network_hrp(network, self.revision(), has_transparent);
1053 bech32::encode::<Bech32mZip316>(Hrp::parse_unchecked(hrp), &self.to_jumbled_bytes(hrp))
1054 .expect("F4Jumble ensures length is short enough by construction")
1055 }
1056}
1057
1058pub trait Container {
1060 type Item: Item;
1062
1063 fn revision(&self) -> Revision;
1065
1066 fn items(&self) -> Vec<Self::Item> {
1068 let mut items: Vec<_> = self
1069 .items_as_parsed()
1070 .iter()
1071 .filter_map(|item| match item {
1072 Uitem::Data(d) => Some(d.clone()),
1073 Uitem::Metadata(_) => None,
1074 })
1075 .collect();
1076 items.sort_unstable_by(Self::Item::preference_order);
1077 items
1078 }
1079
1080 fn items_as_parsed(&self) -> &[Uitem<Self::Item>];
1083
1084 fn metadata_items(&self) -> Vec<&MetadataItem> {
1086 self.items_as_parsed()
1087 .iter()
1088 .filter_map(|item| match item {
1089 Uitem::Metadata(m) => Some(m),
1090 Uitem::Data(_) => None,
1091 })
1092 .collect()
1093 }
1094}
1095
1096pub trait Item: SealedItem {
1098 fn typed_encoding(&self) -> Vec<u8> {
1104 let mut ret = vec![];
1105 self.write_raw_encoding(&mut ret)
1106 .expect("writing to a Vec cannot fail");
1107 ret
1108 }
1109}
1110
1111impl<T: SealedItem> Item for T {}