From 7cd12f4ee29533b7bc5517f9014197af5226a058 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 5 Aug 2021 09:51:48 +0800 Subject: [PATCH] unified::ivk: Parse unified incoming viewing keys. --- components/zcash_address/src/kind/unified.rs | 1 + .../zcash_address/src/kind/unified/ivk.rs | 151 ++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 components/zcash_address/src/kind/unified/ivk.rs diff --git a/components/zcash_address/src/kind/unified.rs b/components/zcash_address/src/kind/unified.rs index 46e7b8bcf..213a834f3 100644 --- a/components/zcash_address/src/kind/unified.rs +++ b/components/zcash_address/src/kind/unified.rs @@ -6,6 +6,7 @@ use std::fmt; use zcash_encoding::CompactSize; pub(crate) mod address; +pub(crate) mod ivk; pub use address::Address; diff --git a/components/zcash_address/src/kind/unified/ivk.rs b/components/zcash_address/src/kind/unified/ivk.rs new file mode 100644 index 000000000..5fed0d8ae --- /dev/null +++ b/components/zcash_address/src/kind/unified/ivk.rs @@ -0,0 +1,151 @@ +use std::cmp; +use std::convert::{TryFrom, TryInto}; + +use crate::kind; + +use super::{ + private::{SealedContainer, SealedItem}, + Encoding, ParseError, Container, Typecode, +}; + +/// The set of known IVKs for Unified IVKs. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum Ivk { + /// The raw encoding of an Orchard Incoming Viewing Key. + /// + /// `(dk, ivk)` each 32 bytes. + Orchard([u8; 64]), + + /// Data contained within the Sapling component of a Unified Incoming Viewing Key. + /// + /// In order to ensure that Unified Addresses can always be derived from UIVKs, we + /// store more data here than was specified to be part of a Sapling IVK. Specifically, + /// we store the same data here as we do for Orchard. + /// + /// `(dk, ivk)` each 32 bytes. + Sapling([u8; 64]), + + /// The extended public key for the BIP 44 account corresponding to the transparent + /// address subtree from which transparent addresses are derived. + /// + /// Transparent addresses don't have "viewing keys" - the addresses themselves serve + /// that purpose. However, we want the ability to derive diversified Unified Addresses + /// from Unified Viewing Keys, and to not break the unlinkability property when they + /// include transparent receivers. To achieve this, we treat the last hardened node in + /// the BIP 44 derivation path as the "transparent viewing key"; all addresses derived + /// from this node use non-hardened derivation, and can thus be derived just from this + /// extended public key. + P2pkh([u8; 78]), + + /// The raw data of a P2SH address. + /// + /// # Security + /// + /// P2SH addresses are hashes of scripts, and as such have no generic HD mechanism for + /// us to derive independent-but-linked P2SH addresses. As such, if someone constructs + /// a UIVK containing a P2SH address, and then derives diversified UAs from it, those + /// UAs will be trivially linkable as they will share the same P2SH address. + P2sh(kind::p2sh::Data), + + Unknown { + typecode: u32, + data: Vec, + }, +} + +impl cmp::Ord for Ivk { + fn cmp(&self, other: &Self) -> cmp::Ordering { + match self.typecode().cmp(&other.typecode()) { + cmp::Ordering::Equal => self.data().cmp(other.data()), + res => res, + } + } +} + +impl cmp::PartialOrd for Ivk { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl TryFrom<(u32, &[u8])> for Ivk { + type Error = ParseError; + + fn try_from((typecode, data): (u32, &[u8])) -> Result { + match typecode.try_into()? { + Typecode::P2pkh => data.try_into().map(Ivk::P2pkh), + Typecode::P2sh => data.try_into().map(Ivk::P2sh), + Typecode::Sapling => data.try_into().map(Ivk::Sapling), + Typecode::Orchard => data.try_into().map(Ivk::Orchard), + Typecode::Unknown(_) => Ok(Ivk::Unknown { + typecode, + data: data.to_vec(), + }), + } + .map_err(|e| { + ParseError::InvalidEncoding(format!("Invalid ivk for typecode {}: {:?}", typecode, e)) + }) + } +} + +impl SealedItem for Ivk { + fn typecode(&self) -> Typecode { + match self { + Ivk::P2pkh(_) => Typecode::P2pkh, + Ivk::P2sh(_) => Typecode::P2sh, + Ivk::Sapling(_) => Typecode::Sapling, + Ivk::Orchard(_) => Typecode::Orchard, + Ivk::Unknown { typecode, .. } => Typecode::Unknown(*typecode), + } + } + + fn data(&self) -> &[u8] { + match self { + Ivk::P2pkh(data) => data, + Ivk::P2sh(data) => data, + Ivk::Sapling(data) => data, + Ivk::Orchard(data) => data, + Ivk::Unknown { data, .. } => data, + } + } +} + +/// A Unified Incoming Viewing Key. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Uivk(pub(crate) Vec); + +impl Container for Uivk { + type Item = Ivk; + + /// Returns the IVKs contained within this UIVK, in the order they were + /// parsed from the string encoding. + /// + /// This API is for advanced usage; in most cases you should use `Uivk::items`. + fn items_as_parsed(&self) -> &[Ivk] { + &self.0 + } +} +impl Encoding for Uivk {} + +impl SealedContainer for Uivk { + /// The HRP for a Bech32m-encoded mainnet Unified IVK. + /// + /// Defined in [ZIP 316][zip-0316]. + /// + /// [zip-0316]: https://zips.z.cash/zip-0316 + const MAINNET: &'static str = "uivk"; + + /// The HRP for a Bech32m-encoded testnet Unified IVK. + /// + /// Defined in [ZIP 316][zip-0316]. + /// + /// [zip-0316]: https://zips.z.cash/zip-0316 + const TESTNET: &'static str = "uivktest"; + + /// The HRP for a Bech32m-encoded regtest Unified IVK. + const REGTEST: &'static str = "uivkregtest"; + + fn from_inner(ivks: Vec) -> Self { + Self(ivks) + } +}