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;
20mod revision;
21
22pub use address::{Address, Receiver};
23pub use fvk::{Fvk, Ufvk};
24pub use ivk::{Ivk, Uivk};
25pub use revision::Revision;
26
27#[cfg(feature = "test-dependencies")]
28pub use address::testing;
29
30const PADDING_LEN: usize = 16;
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub enum DataTypecode {
39 P2pkh,
43 P2sh,
53 Sapling,
57 Orchard,
61 Unknown(u32),
63}
64
65impl DataTypecode {
66 pub fn is_transparent(&self) -> bool {
67 matches!(self, DataTypecode::P2pkh | DataTypecode::P2sh)
68 }
69
70 pub fn preference_order(a: &Self, b: &Self) -> cmp::Ordering {
71 use DataTypecode::*;
72 match (a, b) {
73 (Orchard, Orchard) | (Sapling, Sapling) | (P2sh, P2sh) | (P2pkh, P2pkh) => {
74 cmp::Ordering::Equal
75 }
76
77 (Unknown(a), Unknown(b)) => b.cmp(a),
78
79 (Orchard, _) => cmp::Ordering::Less,
80 (_, Orchard) => cmp::Ordering::Greater,
81
82 (Sapling, _) => cmp::Ordering::Less,
83 (_, Sapling) => cmp::Ordering::Greater,
84
85 (P2sh, _) => cmp::Ordering::Less,
86 (_, P2sh) => cmp::Ordering::Greater,
87
88 (P2pkh, _) => cmp::Ordering::Less,
89 (_, P2pkh) => cmp::Ordering::Greater,
90 }
91 }
92}
93
94impl From<DataTypecode> for u32 {
95 fn from(t: DataTypecode) -> Self {
96 match t {
97 DataTypecode::P2pkh => 0x00,
98 DataTypecode::P2sh => 0x01,
99 DataTypecode::Sapling => 0x02,
100 DataTypecode::Orchard => 0x03,
101 DataTypecode::Unknown(tc) => tc,
102 }
103 }
104}
105
106#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
108pub enum MetadataTypecode {
109 ExpiryHeight,
111 ExpiryTime,
113 Unknown(u32),
115}
116
117impl MetadataTypecode {
118 pub fn is_must_understand(&self) -> bool {
125 u32::from(*self) >= MUST_UNDERSTAND_METADATA_MIN
126 }
127}
128
129impl From<MetadataTypecode> for u32 {
130 fn from(t: MetadataTypecode) -> Self {
131 match t {
132 MetadataTypecode::ExpiryHeight => 0xE0,
133 MetadataTypecode::ExpiryTime => 0xE1,
134 MetadataTypecode::Unknown(tc) => tc,
135 }
136 }
137}
138
139#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
147pub enum Typecode {
148 Data(DataTypecode),
150 Metadata(MetadataTypecode),
152}
153
154impl Typecode {
156 pub const P2PKH: Typecode = Typecode::Data(DataTypecode::P2pkh);
158 pub const P2SH: Typecode = Typecode::Data(DataTypecode::P2sh);
160 pub const SAPLING: Typecode = Typecode::Data(DataTypecode::Sapling);
162 pub const ORCHARD: Typecode = Typecode::Data(DataTypecode::Orchard);
164}
165
166impl From<DataTypecode> for Typecode {
167 fn from(tc: DataTypecode) -> Self {
168 Typecode::Data(tc)
169 }
170}
171
172impl From<MetadataTypecode> for Typecode {
173 fn from(tc: MetadataTypecode) -> Self {
174 Typecode::Metadata(tc)
175 }
176}
177
178const MUST_UNDERSTAND_METADATA_MIN: u32 = 0xE0;
180const METADATA_TYPECODE_MAX: u32 = 0xFC;
182const METADATA_TYPECODE_MIN: u32 = 0xC0;
184
185impl Typecode {
186 pub fn typecode_value(&self) -> u32 {
188 match self {
189 Typecode::Data(tc) => u32::from(*tc),
190 Typecode::Metadata(tc) => u32::from(*tc),
191 }
192 }
193
194 pub fn preference_order(a: &Self, b: &Self) -> cmp::Ordering {
195 use DataTypecode::*;
196 match (a, b) {
197 (Typecode::Data(_), Typecode::Metadata(_)) => cmp::Ordering::Less,
199 (Typecode::Metadata(_), Typecode::Data(_)) => cmp::Ordering::Greater,
200
201 (Typecode::Metadata(a), Typecode::Metadata(b)) => u32::from(*a).cmp(&u32::from(*b)),
203
204 (Typecode::Data(a), Typecode::Data(b)) => match (a, b) {
206 (Orchard, Orchard) | (Sapling, Sapling) | (P2sh, P2sh) | (P2pkh, P2pkh) => {
207 cmp::Ordering::Equal
208 }
209
210 (Unknown(a), Unknown(b)) => b.cmp(a),
211
212 (Orchard, _) => cmp::Ordering::Less,
213 (_, Orchard) => cmp::Ordering::Greater,
214
215 (Sapling, _) => cmp::Ordering::Less,
216 (_, Sapling) => cmp::Ordering::Greater,
217
218 (P2sh, _) => cmp::Ordering::Less,
219 (_, P2sh) => cmp::Ordering::Greater,
220
221 (P2pkh, _) => cmp::Ordering::Less,
222 (_, P2pkh) => cmp::Ordering::Greater,
223 },
224 }
225 }
226
227 pub fn encoding_order(a: &Self, b: &Self) -> cmp::Ordering {
228 a.typecode_value().cmp(&b.typecode_value())
229 }
230
231 pub fn is_transparent_data(&self) -> bool {
232 match self {
233 Typecode::Data(tc) => tc.is_transparent(),
234 Typecode::Metadata(_) => false,
235 }
236 }
237}
238
239impl TryFrom<u32> for Typecode {
240 type Error = ParseError;
241
242 fn try_from(typecode: u32) -> Result<Self, Self::Error> {
243 match typecode {
244 0x00 => Ok(Typecode::Data(DataTypecode::P2pkh)),
245 0x01 => Ok(Typecode::Data(DataTypecode::P2sh)),
246 0x02 => Ok(Typecode::Data(DataTypecode::Sapling)),
247 0x03 => Ok(Typecode::Data(DataTypecode::Orchard)),
248 0x04..=0xBF => Ok(Typecode::Data(DataTypecode::Unknown(typecode))),
249 tc @ METADATA_TYPECODE_MIN..=METADATA_TYPECODE_MAX => {
250 match tc {
251 0xE0 => Ok(Typecode::Metadata(MetadataTypecode::ExpiryHeight)),
252 0xE1 => Ok(Typecode::Metadata(MetadataTypecode::ExpiryTime)),
253 _ => Ok(Typecode::Metadata(MetadataTypecode::Unknown(tc))),
256 }
257 }
258 0xFD..=0x02000000 => Ok(Typecode::Data(DataTypecode::Unknown(typecode))),
259 0x02000001..=u32::MAX => Err(ParseError::InvalidTypecodeValue(u64::from(typecode))),
260 }
261 }
262}
263
264impl From<Typecode> for u32 {
265 fn from(t: Typecode) -> Self {
266 t.typecode_value()
267 }
268}
269
270impl TryFrom<Typecode> for usize {
271 type Error = TryFromIntError;
272 fn try_from(t: Typecode) -> Result<Self, Self::Error> {
273 u32::from(t).try_into()
274 }
275}
276
277#[derive(Clone, Debug, PartialEq, Eq, Hash)]
279pub enum MetadataItem {
280 ExpiryHeight(u32),
282 ExpiryTime(u64),
284 Unknown { typecode: u32, data: Vec<u8> },
286}
287
288impl MetadataItem {
289 pub fn typecode(&self) -> MetadataTypecode {
291 match self {
292 MetadataItem::ExpiryHeight(_) => MetadataTypecode::ExpiryHeight,
293 MetadataItem::ExpiryTime(_) => MetadataTypecode::ExpiryTime,
294 MetadataItem::Unknown { typecode, .. } => MetadataTypecode::Unknown(*typecode),
295 }
296 }
297
298 pub fn data(&self) -> Vec<u8> {
300 match self {
301 MetadataItem::ExpiryHeight(h) => h.to_le_bytes().to_vec(),
302 MetadataItem::ExpiryTime(t) => t.to_le_bytes().to_vec(),
303 MetadataItem::Unknown { data, .. } => data.clone(),
304 }
305 }
306
307 pub fn combined_typecode(&self) -> Typecode {
309 Typecode::Metadata(self.typecode())
310 }
311}
312
313#[derive(Clone, Debug, PartialEq, Eq, Hash)]
315pub enum Uitem<T> {
316 Data(T),
318 Metadata(MetadataItem),
320}
321
322impl<T: private::SealedItem> Uitem<T> {
323 pub(crate) fn encoding_order(a: &Self, b: &Self) -> cmp::Ordering {
326 fn typecode_value<T: private::SealedItem>(item: &Uitem<T>) -> u32 {
327 match item {
328 Uitem::Data(d) => u32::from(d.typecode()),
329 Uitem::Metadata(m) => u32::from(m.typecode()),
330 }
331 }
332
333 typecode_value(a)
334 .cmp(&typecode_value(b))
335 .then_with(|| match (a, b) {
336 (Uitem::Data(a), Uitem::Data(b)) => a.data().cmp(b.data()),
337 (Uitem::Metadata(a), Uitem::Metadata(b)) => a.data().cmp(&b.data()),
338 _ => cmp::Ordering::Equal,
341 })
342 }
343}
344
345#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
348pub enum P2shItemKind {
349 FullViewing,
352 IncomingViewing,
355}
356
357#[non_exhaustive]
366#[derive(Clone, Copy, Debug, PartialEq, Eq)]
367pub enum P2shItemError {
368 Malformed,
371 TemplateEncoding,
373 PlaceholderCount {
376 placeholders: usize,
378 keys: usize,
380 },
381 Multipath,
384}
385
386impl fmt::Display for P2shItemError {
387 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388 match self {
389 P2shItemError::Malformed => {
390 write!(f, "malformed wallet policy container encoding")
391 }
392 P2shItemError::TemplateEncoding => {
393 write!(f, "descriptor template is not US-ASCII")
394 }
395 P2shItemError::PlaceholderCount { placeholders, keys } => {
396 write!(
397 f,
398 "template has {placeholders} key placeholders but {keys} key entries"
399 )
400 }
401 P2shItemError::Multipath => {
402 write!(
403 f,
404 "a key placeholder does not use the required multipath notation"
405 )
406 }
407 }
408 }
409}
410
411#[cfg(feature = "std")]
412impl Error for P2shItemError {}
413
414#[derive(Debug, PartialEq, Eq)]
416pub enum ParseError {
417 BothP2phkAndP2sh,
419 DuplicateTypecode(Typecode),
421 InvalidTypecodeValue(u64),
423 InvalidEncoding(String),
425 InvalidTypecodeOrder,
427 OnlyTransparent,
429 NotUnified,
431 UnknownPrefix(String),
433 NotUnderstood(u32),
436 TransparentReceiverInR2Address,
438 NoDataItems,
440 InvalidMetadataLength {
442 typecode: u32,
443 expected: usize,
444 actual: usize,
445 },
446 InvalidP2shItem(P2shItemError),
448 NotDefinedInRevision {
450 typecode: Typecode,
451 revision: Revision,
452 },
453 InvalidEncodedLength(usize),
456}
457
458impl fmt::Display for ParseError {
459 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460 match self {
461 ParseError::BothP2phkAndP2sh => write!(f, "UA contains both P2PKH and P2SH items"),
462 ParseError::DuplicateTypecode(c) => write!(f, "Duplicate typecode {}", u32::from(*c)),
463 ParseError::InvalidTypecodeValue(v) => write!(f, "Typecode value out of range {v}"),
464 ParseError::InvalidEncoding(msg) => write!(f, "Invalid encoding: {msg}"),
465 ParseError::InvalidTypecodeOrder => write!(f, "Items are out of order."),
466 ParseError::OnlyTransparent => write!(f, "UA only contains transparent items"),
467 ParseError::NotUnified => write!(f, "Address is not Bech32m encoded"),
468 ParseError::UnknownPrefix(s) => {
469 write!(f, "Unrecognized Bech32m human-readable prefix: {s}")
470 }
471 ParseError::NotUnderstood(tc) => {
472 write!(
473 f,
474 "MUST-understand metadata typecode 0x{tc:02X} not recognized"
475 )
476 }
477 ParseError::TransparentReceiverInR2Address => {
478 write!(
479 f,
480 "Transparent receivers are not permitted in Revision 2 Unified Addresses"
481 )
482 }
483 ParseError::NoDataItems => {
484 write!(f, "Unified container has no data items")
485 }
486 ParseError::InvalidMetadataLength {
487 typecode,
488 expected,
489 actual,
490 } => {
491 write!(
492 f,
493 "Metadata typecode 0x{typecode:02X} has invalid length: expected {expected}, got {actual}"
494 )
495 }
496 ParseError::InvalidP2shItem(e) => {
497 write!(f, "Invalid P2SH viewing key item: {e}")
498 }
499 ParseError::InvalidEncodedLength(length) => {
500 write!(
501 f,
502 "Padded unified encoding of {length} bytes is outside the range {}..={} that F4Jumble accepts",
503 f4jumble::VALID_LENGTH.start(),
504 f4jumble::VALID_LENGTH.end()
505 )
506 }
507 ParseError::NotDefinedInRevision { typecode, revision } => {
508 write!(
509 f,
510 "Typecode 0x{:02X} is not defined in unified container revision {revision:?}",
511 typecode.typecode_value()
512 )
513 }
514 }
515 }
516}
517
518#[cfg(feature = "std")]
519impl Error for ParseError {}
520
521pub(crate) mod private {
522 use alloc::borrow::ToOwned;
523 use alloc::vec::Vec;
524 use core::cmp;
525 use core::convert::{TryFrom, TryInto};
526 use corez::io::Write;
527
528 use super::{
529 MUST_UNDERSTAND_METADATA_MIN, MetadataItem, MetadataTypecode, PADDING_LEN, ParseError,
530 Typecode, Uitem,
531 };
532 use crate::unified::Revision;
533 use zcash_encoding::{CompactSize, MAX_COMPACT_SIZE};
534 use zcash_protocol::consensus::NetworkType;
535
536 pub trait SealedItem: Clone {
538 fn parse(typecode: super::DataTypecode, data: &[u8]) -> Result<Self, ParseError>;
539 fn typecode(&self) -> super::DataTypecode;
540 fn data(&self) -> &[u8];
541
542 fn preference_order(a: &Self, b: &Self) -> cmp::Ordering {
543 match super::DataTypecode::preference_order(&a.typecode(), &b.typecode()) {
544 cmp::Ordering::Equal => a.data().cmp(b.data()),
545 res => res,
546 }
547 }
548
549 fn encoding_order(a: &Self, b: &Self) -> cmp::Ordering {
550 match u32::from(a.typecode()).cmp(&u32::from(b.typecode())) {
551 cmp::Ordering::Equal => a.data().cmp(b.data()),
552 res => res,
553 }
554 }
555
556 fn write_raw_encoding<W: Write>(&self, mut writer: W) -> corez::io::Result<()> {
557 let data = self.data();
558 CompactSize::write(
559 &mut writer,
560 <u32>::from(self.typecode())
561 .try_into()
562 .expect("a typecode fits in a usize"),
563 )?;
564 CompactSize::write(&mut writer, data.len())?;
565 writer.write_all(data)
566 }
567 }
568
569 fn write_metadata_raw_encoding<W: Write>(
571 item: &MetadataItem,
572 mut writer: W,
573 ) -> corez::io::Result<()> {
574 let tc_val: u32 = item.typecode().into();
575 let data = item.data();
576 CompactSize::write(
577 &mut writer,
578 tc_val.try_into().expect("a typecode fits in a usize"),
579 )?;
580 CompactSize::write(&mut writer, data.len())?;
581 writer.write_all(&data)
582 }
583
584 pub trait SealedContainer: super::Container + core::marker::Sized {
586 const MAINNET: &'static str;
587 const TESTNET: &'static str;
588 const REGTEST: &'static str;
589
590 const MAINNET_R2: &'static str;
591 const TESTNET_R2: &'static str;
592 const REGTEST_R2: &'static str;
593
594 const MAINNET_R2_TI: &'static str;
598 const TESTNET_R2_TI: &'static str;
599 const REGTEST_R2_TI: &'static str;
600
601 const IS_ADDRESS: bool;
603
604 fn from_inner(revision: Revision, items: Vec<Uitem<Self::Item>>) -> Self;
608
609 fn network_hrp(
610 network: &NetworkType,
611 revision: Revision,
612 has_transparent: bool,
613 ) -> &'static str {
614 match (network, revision) {
615 (NetworkType::Main, Revision::R0) => Self::MAINNET,
616 (NetworkType::Test, Revision::R0) => Self::TESTNET,
617 (NetworkType::Regtest, Revision::R0) => Self::REGTEST,
618 (NetworkType::Main, Revision::R2) => {
619 if Self::IS_ADDRESS && has_transparent {
620 Self::MAINNET_R2_TI
621 } else {
622 Self::MAINNET_R2
623 }
624 }
625 (NetworkType::Test, Revision::R2) => {
626 if Self::IS_ADDRESS && has_transparent {
627 Self::TESTNET_R2_TI
628 } else {
629 Self::TESTNET_R2
630 }
631 }
632 (NetworkType::Regtest, Revision::R2) => {
633 if Self::IS_ADDRESS && has_transparent {
634 Self::REGTEST_R2_TI
635 } else {
636 Self::REGTEST_R2
637 }
638 }
639 }
640 }
641
642 fn hrp_network(hrp: &str) -> Option<(NetworkType, Revision)> {
643 if hrp == Self::MAINNET {
644 Some((NetworkType::Main, Revision::R0))
645 } else if hrp == Self::TESTNET {
646 Some((NetworkType::Test, Revision::R0))
647 } else if hrp == Self::REGTEST {
648 Some((NetworkType::Regtest, Revision::R0))
649 } else if hrp == Self::MAINNET_R2 {
650 Some((NetworkType::Main, Revision::R2))
651 } else if hrp == Self::TESTNET_R2 {
652 Some((NetworkType::Test, Revision::R2))
653 } else if hrp == Self::REGTEST_R2 {
654 Some((NetworkType::Regtest, Revision::R2))
655 } else if hrp == Self::MAINNET_R2_TI {
656 Some((NetworkType::Main, Revision::R2))
657 } else if hrp == Self::TESTNET_R2_TI {
658 Some((NetworkType::Test, Revision::R2))
659 } else if hrp == Self::REGTEST_R2_TI {
660 Some((NetworkType::Regtest, Revision::R2))
661 } else {
662 None
663 }
664 }
665
666 fn write_raw_encoding<W: Write>(&self, mut writer: W) -> corez::io::Result<()> {
667 for item in self.items_as_parsed() {
668 match item {
669 Uitem::Data(data_item) => data_item.write_raw_encoding(&mut writer)?,
670 Uitem::Metadata(meta_item) => {
671 write_metadata_raw_encoding(meta_item, &mut writer)?;
672 }
673 }
674 }
675 Ok(())
676 }
677
678 fn to_jumbled_bytes(&self, hrp: &str) -> Vec<u8> {
680 assert!(hrp.len() <= PADDING_LEN);
681
682 let mut padded = Vec::new();
683 self.write_raw_encoding(&mut padded)
684 .expect("writing to a Vec cannot fail");
685
686 let mut padding = [0u8; PADDING_LEN];
687 padding[0..hrp.len()].copy_from_slice(hrp.as_bytes());
688 padded
689 .write_all(&padding)
690 .expect("writing to a Vec cannot fail");
691
692 f4jumble::f4jumble(&padded)
695 .expect("the padded encoding of a unified container has a valid F4Jumble length")
696 }
697
698 fn parse_items<T: Into<Vec<u8>>>(
700 hrp: &str,
701 buf: T,
702 revision: Revision,
703 ) -> Result<Vec<Uitem<Self::Item>>, ParseError> {
704 fn read_raw_item(
705 mut cursor: &mut corez::io::Cursor<&[u8]>,
706 ) -> Result<(u32, Vec<u8>), ParseError> {
707 let typecode = CompactSize::read(&mut cursor)
708 .map(|v| u32::try_from(v).expect("CompactSize::read enforces MAX_SIZE limit"))
709 .map_err(|e| {
710 ParseError::InvalidEncoding(format!(
711 "Failed to deserialize CompactSize-encoded typecode {e}"
712 ))
713 })?;
714 let length = CompactSize::read(&mut cursor).map_err(|e| {
715 ParseError::InvalidEncoding(format!(
716 "Failed to deserialize CompactSize-encoded length {e}"
717 ))
718 })?;
719 let addr_end = cursor.position().checked_add(length).ok_or_else(|| {
720 ParseError::InvalidEncoding(format!(
721 "Length value {length} caused an overflow error"
722 ))
723 })?;
724 let buf = cursor.get_ref();
725 if (buf.len() as u64) < addr_end {
726 return Err(ParseError::InvalidEncoding(format!(
727 "Truncated: unable to read {length} bytes of item data"
728 )));
729 }
730 let data = buf[cursor.position() as usize..addr_end as usize].to_vec();
731 cursor.set_position(addr_end);
732 Ok((typecode, data))
733 }
734
735 let mut encoded = buf.into();
737 f4jumble::f4jumble_inv_mut(&mut encoded[..]).map_err(|e| {
738 ParseError::InvalidEncoding(format!("F4Jumble decoding failed: {e}"))
739 })?;
740
741 if hrp.len() > 16 {
743 return Err(ParseError::InvalidEncoding(
744 "Invalid human-readable part".to_owned(),
745 ));
746 }
747 let mut expected_padding = [0; PADDING_LEN];
748 expected_padding[0..hrp.len()].copy_from_slice(hrp.as_bytes());
749 let encoded = match encoded.split_at(encoded.len() - PADDING_LEN) {
750 (encoded, tail) if tail == expected_padding => Ok(encoded),
751 _ => Err(ParseError::InvalidEncoding(
752 "Invalid padding bytes".to_owned(),
753 )),
754 }?;
755
756 let mut cursor = corez::io::Cursor::new(encoded);
757 let mut result = vec![];
758 while cursor.position() < encoded.len().try_into().unwrap() {
759 let (tc_val, data) = read_raw_item(&mut cursor)?;
760
761 match Typecode::try_from(tc_val)? {
762 Typecode::Data(dtc) => {
763 let dtc = if revision == Revision::R0
768 && !Self::IS_ADDRESS
769 && dtc == super::DataTypecode::P2sh
770 {
771 super::DataTypecode::Unknown(u32::from(dtc))
772 } else {
773 dtc
774 };
775 result.push(Uitem::Data(Self::Item::parse(dtc, &data)?));
776 }
777 Typecode::Metadata(mtc) => {
778 result.push(Uitem::Metadata(parse_metadata_item(revision, mtc, data)?));
779 }
780 }
781 }
782 assert_eq!(cursor.position(), encoded.len().try_into().unwrap());
783
784 Ok(result)
785 }
786
787 fn try_from_items_internal(
790 revision: Revision,
791 items: Vec<Uitem<Self::Item>>,
792 ) -> Result<Self, ParseError> {
793 assert!(u32::from(Typecode::P2SH) == u32::from(Typecode::P2PKH) + 1);
794
795 let mut has_data_item = false;
796 let mut only_transparent = true;
797 let mut prev_code: Option<u32> = None;
798 for item in &items {
799 let t = match item {
800 Uitem::Data(d) => Typecode::Data(d.typecode()),
801 Uitem::Metadata(m) => m.combined_typecode(),
802 };
803 if t.typecode_value() > MAX_COMPACT_SIZE {
806 return Err(ParseError::InvalidTypecodeValue(u64::from(
807 t.typecode_value(),
808 )));
809 }
810
811 let t_code = Some(t.typecode_value());
812
813 match item {
814 Uitem::Data(d) => {
815 if revision == Revision::R0
817 && !Self::IS_ADDRESS
818 && d.typecode() == super::DataTypecode::P2sh
819 {
820 return Err(ParseError::NotDefinedInRevision {
821 typecode: t,
822 revision,
823 });
824 }
825 }
826 Uitem::Metadata(m) => match m.typecode() {
827 MetadataTypecode::Unknown(tc) if tc >= MUST_UNDERSTAND_METADATA_MIN => {
830 return Err(ParseError::NotUnderstood(tc));
831 }
832 _ if revision == Revision::R0
834 && t.typecode_value() >= MUST_UNDERSTAND_METADATA_MIN =>
835 {
836 return Err(ParseError::NotDefinedInRevision {
837 typecode: t,
838 revision,
839 });
840 }
841 _ => {}
842 },
843 }
844
845 if t_code < prev_code {
846 return Err(ParseError::InvalidTypecodeOrder);
847 } else if t_code == prev_code {
848 return Err(ParseError::DuplicateTypecode(t));
849 }
850
851 if let Uitem::Data(d) = item {
852 has_data_item = true;
853 let dt = d.typecode();
854 if dt == super::DataTypecode::P2sh
855 && prev_code == Some(u32::from(super::DataTypecode::P2pkh))
856 {
857 return Err(ParseError::BothP2phkAndP2sh);
858 }
859
860 if !dt.is_transparent() {
861 only_transparent = false;
862 }
863 }
864
865 prev_code = t_code;
866 }
867
868 if !has_data_item {
871 return Err(ParseError::NoDataItems);
872 }
873
874 if revision == Revision::R0 && only_transparent {
879 return Err(ParseError::OnlyTransparent);
880 }
881
882 let container = Self::from_inner(revision, items);
883
884 let mut raw = Vec::new();
887 container
888 .write_raw_encoding(&mut raw)
889 .expect("writing to a Vec cannot fail");
890 let padded_length = raw.len() + PADDING_LEN;
891 if !f4jumble::VALID_LENGTH.contains(&padded_length) {
892 return Err(ParseError::InvalidEncodedLength(padded_length));
893 }
894
895 Ok(container)
896 }
897
898 fn parse_internal<T: Into<Vec<u8>>>(
899 hrp: &str,
900 buf: T,
901 revision: Revision,
902 ) -> Result<Self, ParseError> {
903 let result = Self::parse_items(hrp, buf, revision)
904 .and_then(|items| Self::try_from_items_internal(revision, items))?;
905
906 if revision == Revision::R2 && Self::IS_ADDRESS {
908 let is_ti_hrp = hrp == Self::MAINNET_R2_TI
909 || hrp == Self::TESTNET_R2_TI
910 || hrp == Self::REGTEST_R2_TI;
911
912 let has_transparent = result
913 .items_as_parsed()
914 .iter()
915 .any(|item| matches!(item, Uitem::Data(d) if d.typecode().is_transparent()));
916
917 if !is_ti_hrp && has_transparent {
923 return Err(ParseError::TransparentReceiverInR2Address);
924 }
925 }
926
927 Ok(result)
928 }
929 }
930
931 pub(crate) fn validate_p2sh_item(
945 kind: super::P2shItemKind,
946 data: &[u8],
947 ) -> Result<(), super::P2shItemError> {
948 use super::{P2shItemError, P2shItemKind};
949
950 const KEY_INFO_LEN: usize = 65;
953
954 let mut cursor = corez::io::Cursor::new(data);
955 let template_len = CompactSize::read(&mut cursor)
956 .ok()
957 .and_then(|n| usize::try_from(n).ok())
958 .ok_or(P2shItemError::Malformed)?;
959 let template_start = usize::try_from(cursor.position()).expect("cursor fits in usize");
960 let template_end = template_start
961 .checked_add(template_len)
962 .filter(|end| *end <= data.len())
963 .ok_or(P2shItemError::Malformed)?;
964 let template = &data[template_start..template_end];
965 cursor.set_position(template_end as u64);
966
967 let n_keys = CompactSize::read(&mut cursor)
968 .ok()
969 .and_then(|n| usize::try_from(n).ok())
970 .ok_or(P2shItemError::Malformed)?;
971 let keys_start = usize::try_from(cursor.position()).expect("cursor fits in usize");
972 if n_keys
973 .checked_mul(KEY_INFO_LEN)
974 .and_then(|len| keys_start.checked_add(len))
975 != Some(data.len())
976 {
977 return Err(P2shItemError::Malformed);
978 }
979
980 if !template.is_ascii() {
981 return Err(P2shItemError::TemplateEncoding);
982 }
983
984 let mut placeholders = 0usize;
987 let mut seen = alloc::vec![false; n_keys];
988 let mut i = 0;
989 while i < template.len() {
990 if template[i] != b'@' {
991 i += 1;
992 continue;
993 }
994 let digits_start = i + 1;
995 let digits_end = template[digits_start..]
996 .iter()
997 .position(|b| !b.is_ascii_digit())
998 .map(|n| digits_start + n)
999 .unwrap_or(template.len());
1000 let index: usize = core::str::from_utf8(&template[digits_start..digits_end])
1001 .ok()
1002 .filter(|s| !s.is_empty())
1003 .and_then(|s| s.parse().ok())
1004 .ok_or(P2shItemError::Malformed)?;
1005 if index >= n_keys || seen[index] {
1006 return Err(P2shItemError::PlaceholderCount {
1007 placeholders: placeholders + 1,
1008 keys: n_keys,
1009 });
1010 }
1011 seen[index] = true;
1012 placeholders += 1;
1013
1014 let suffix = &template[digits_end..];
1015 let multipath_ok = match kind {
1016 P2shItemKind::FullViewing => suffix.starts_with(b"/**"),
1017 P2shItemKind::IncomingViewing => {
1018 suffix.starts_with(b"/*") && !suffix.starts_with(b"/**")
1019 }
1020 };
1021 if !multipath_ok {
1022 return Err(P2shItemError::Multipath);
1023 }
1024 i = digits_end;
1025 }
1026 if placeholders != n_keys {
1027 return Err(P2shItemError::PlaceholderCount {
1028 placeholders,
1029 keys: n_keys,
1030 });
1031 }
1032
1033 Ok(())
1034 }
1035
1036 fn parse_metadata_item(
1039 revision: Revision,
1040 typecode: MetadataTypecode,
1041 data: Vec<u8>,
1042 ) -> Result<MetadataItem, ParseError> {
1043 let tc_val = u32::from(typecode);
1044 if revision == Revision::R0 && tc_val >= MUST_UNDERSTAND_METADATA_MIN {
1047 return Err(ParseError::NotUnderstood(tc_val));
1048 }
1049 match typecode {
1050 MetadataTypecode::ExpiryHeight => {
1051 let data: [u8; 4] =
1052 data.as_slice()
1053 .try_into()
1054 .map_err(|_| ParseError::InvalidMetadataLength {
1055 typecode: tc_val,
1056 expected: 4,
1057 actual: data.len(),
1058 })?;
1059 Ok(MetadataItem::ExpiryHeight(u32::from_le_bytes(data)))
1060 }
1061 MetadataTypecode::ExpiryTime => {
1062 let data: [u8; 8] =
1063 data.as_slice()
1064 .try_into()
1065 .map_err(|_| ParseError::InvalidMetadataLength {
1066 typecode: tc_val,
1067 expected: 8,
1068 actual: data.len(),
1069 })?;
1070 Ok(MetadataItem::ExpiryTime(u64::from_le_bytes(data)))
1071 }
1072 MetadataTypecode::Unknown(tc) if tc >= MUST_UNDERSTAND_METADATA_MIN => {
1073 Err(ParseError::NotUnderstood(tc))
1076 }
1077 MetadataTypecode::Unknown(tc) => Ok(MetadataItem::Unknown { typecode: tc, data }),
1078 }
1079 }
1080}
1081
1082use private::SealedItem;
1083
1084#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1090pub enum Bech32mZip316 {}
1091impl Checksum for Bech32mZip316 {
1092 type MidstateRepr = <Bech32m as Checksum>::MidstateRepr;
1093 const CODE_LENGTH: usize = 4194368;
1095 const CHECKSUM_LENGTH: usize = Bech32m::CHECKSUM_LENGTH;
1096 const GENERATOR_SH: [u32; 5] = Bech32m::GENERATOR_SH;
1097 const TARGET_RESIDUE: u32 = Bech32m::TARGET_RESIDUE;
1098}
1099
1100pub trait Encoding: private::SealedContainer {
1102 fn try_from_items(
1107 revision: Revision,
1108 mut items: Vec<Uitem<Self::Item>>,
1109 ) -> Result<Self, ParseError> {
1110 items.sort_unstable_by(Uitem::encoding_order);
1111 Self::try_from_items_internal(revision, items)
1112 }
1113
1114 fn decode(s: &str) -> Result<(NetworkType, Revision, Self), ParseError> {
1118 if let Ok(parsed) = CheckedHrpstring::new::<Bech32mZip316>(s) {
1119 let hrp = parsed.hrp();
1120 let hrp = hrp.as_str();
1121 let (net, revision) =
1123 Self::hrp_network(hrp).ok_or_else(|| ParseError::UnknownPrefix(hrp.to_string()))?;
1124
1125 let data = parsed.byte_iter().collect::<Vec<_>>();
1126
1127 Self::parse_internal(hrp, data, revision).map(|value| (net, revision, value))
1128 } else {
1129 Err(ParseError::NotUnified)
1130 }
1131 }
1132
1133 fn encode(&self, network: &NetworkType) -> String {
1138 let has_transparent = Self::IS_ADDRESS
1139 && self
1140 .items_as_parsed()
1141 .iter()
1142 .any(|item| matches!(item, Uitem::Data(d) if d.typecode().is_transparent()));
1143 let hrp = Self::network_hrp(network, self.revision(), has_transparent);
1144 bech32::encode::<Bech32mZip316>(Hrp::parse_unchecked(hrp), &self.to_jumbled_bytes(hrp))
1145 .expect("F4Jumble ensures length is short enough by construction")
1146 }
1147}
1148
1149pub trait Container {
1151 type Item: Item;
1153
1154 fn revision(&self) -> Revision;
1156
1157 fn items(&self) -> Vec<Self::Item> {
1159 let mut items: Vec<_> = self
1160 .items_as_parsed()
1161 .iter()
1162 .filter_map(|item| match item {
1163 Uitem::Data(d) => Some(d.clone()),
1164 Uitem::Metadata(_) => None,
1165 })
1166 .collect();
1167 items.sort_unstable_by(Self::Item::preference_order);
1168 items
1169 }
1170
1171 fn items_as_parsed(&self) -> &[Uitem<Self::Item>];
1174
1175 fn metadata_items(&self) -> Vec<&MetadataItem> {
1177 self.items_as_parsed()
1178 .iter()
1179 .filter_map(|item| match item {
1180 Uitem::Metadata(m) => Some(m),
1181 Uitem::Data(_) => None,
1182 })
1183 .collect()
1184 }
1185}
1186
1187pub trait Item: SealedItem {
1189 fn typed_encoding(&self) -> Vec<u8> {
1195 let mut ret = vec![];
1196 self.write_raw_encoding(&mut ret)
1197 .expect("writing to a Vec cannot fail");
1198 ret
1199 }
1200}
1201
1202impl<T: SealedItem> Item for T {}
1203
1204#[cfg(test)]
1205mod tests {
1206 use super::{
1207 METADATA_TYPECODE_MAX, METADATA_TYPECODE_MIN, MUST_UNDERSTAND_METADATA_MIN,
1208 MetadataTypecode, Typecode,
1209 };
1210
1211 #[test]
1212 fn expiry_metadata_is_must_understand() {
1213 assert!(MetadataTypecode::ExpiryHeight.is_must_understand());
1214 assert!(MetadataTypecode::ExpiryTime.is_must_understand());
1215 }
1216
1217 #[test]
1218 fn must_understand_follows_typecode_range() {
1219 for value in METADATA_TYPECODE_MIN..=METADATA_TYPECODE_MAX {
1220 match Typecode::try_from(value) {
1221 Ok(Typecode::Metadata(tc)) => assert_eq!(
1222 tc.is_must_understand(),
1223 value >= MUST_UNDERSTAND_METADATA_MIN,
1224 "typecode {value:#04X}"
1225 ),
1226 other => panic!("typecode {value:#04X} parsed as {other:?}"),
1227 }
1228 }
1229 }
1230}