`zcash_address`: Add support for ZIP 316, Revision 1
This commit is contained in:
parent
c0542c9589
commit
aea04d1ad2
|
@ -11,10 +11,12 @@ and this library adheres to Rust's notion of
|
|||
- `zcash_address::ZcashAddress::{can_receive_memo, can_receive_as, matches_receiver}`
|
||||
- `zcash_address::unified`:
|
||||
- `Address::{can_receive_memo, has_receiver_of_type, contains_receiver, receivers}`
|
||||
- `Container::revision`
|
||||
- `DataTypecode`
|
||||
- `MetadataTypecode`
|
||||
- `Item`
|
||||
- `MetadataItem`
|
||||
- `MetadataTypecode`
|
||||
- `Revision`
|
||||
- Module `zcash_address::testing` under the `test-dependencies` feature.
|
||||
- Module `zcash_address::unified::address::testing` under the
|
||||
`test-dependencies` feature.
|
||||
|
@ -23,6 +25,7 @@ and this library adheres to Rust's notion of
|
|||
- `zcash_address::unified`:
|
||||
- `Typecode` has changed. Instead of having a variant for each receiver type,
|
||||
it now has two variants, `Typecode::Data` and `Typecode::Metadata`.
|
||||
- `Encoding::try_from_items` now takes an additional `Revision` argument.
|
||||
|
||||
### Removed
|
||||
- `zcash_address::unified::Container::items` Preference order is only
|
||||
|
|
|
@ -182,7 +182,7 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::{
|
||||
kind::unified,
|
||||
unified::{Item, Receiver},
|
||||
unified::{Item, Receiver, Revision},
|
||||
Network,
|
||||
};
|
||||
|
||||
|
@ -234,21 +234,30 @@ mod tests {
|
|||
"u1qpatys4zruk99pg59gcscrt7y6akvl9vrhcfyhm9yxvxz7h87q6n8cgrzzpe9zru68uq39uhmlpp5uefxu0su5uqyqfe5zp3tycn0ecl",
|
||||
ZcashAddress {
|
||||
net: Network::Main,
|
||||
kind: AddressKind::Unified(unified::Address(vec![Item::Data(Receiver::Sapling([0; 43]))])),
|
||||
kind: AddressKind::Unified(unified::Address {
|
||||
revision: Revision::R0,
|
||||
receivers: vec![Item::Data(Receiver::Sapling([0; 43]))]
|
||||
}),
|
||||
},
|
||||
);
|
||||
encoding(
|
||||
"utest10c5kutapazdnf8ztl3pu43nkfsjx89fy3uuff8tsmxm6s86j37pe7uz94z5jhkl49pqe8yz75rlsaygexk6jpaxwx0esjr8wm5ut7d5s",
|
||||
ZcashAddress {
|
||||
net: Network::Test,
|
||||
kind: AddressKind::Unified(unified::Address(vec![Item::Data(Receiver::Sapling([0; 43]))])),
|
||||
kind: AddressKind::Unified(unified::Address {
|
||||
revision: Revision::R0,
|
||||
receivers: vec![Item::Data(Receiver::Sapling([0; 43]))]
|
||||
}),
|
||||
},
|
||||
);
|
||||
encoding(
|
||||
"uregtest15xk7vj4grjkay6mnfl93dhsflc2yeunhxwdh38rul0rq3dfhzzxgm5szjuvtqdha4t4p2q02ks0jgzrhjkrav70z9xlvq0plpcjkd5z3",
|
||||
ZcashAddress {
|
||||
net: Network::Regtest,
|
||||
kind: AddressKind::Unified(unified::Address(vec![Item::Data(Receiver::Sapling([0; 43]))])),
|
||||
kind: AddressKind::Unified(unified::Address {
|
||||
revision: Revision::R0,
|
||||
receivers: vec![Item::Data(Receiver::Sapling([0; 43]))]
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
@ -215,9 +215,15 @@ pub enum MetadataItem {
|
|||
|
||||
impl MetadataItem {
|
||||
/// Parse a metadata item for the specified metadata typecode from the provided bytes.
|
||||
pub fn parse(typecode: MetadataTypecode, data: &[u8]) -> Result<Self, ParseError> {
|
||||
match typecode {
|
||||
MetadataTypecode::ExpiryHeight => data
|
||||
pub fn parse(
|
||||
revision: Revision,
|
||||
typecode: MetadataTypecode,
|
||||
data: &[u8],
|
||||
) -> Result<Self, ParseError> {
|
||||
use MetadataTypecode::*;
|
||||
use Revision::*;
|
||||
match (revision, typecode) {
|
||||
(R1, ExpiryHeight) => data
|
||||
.try_into()
|
||||
.map(u32::from_le_bytes)
|
||||
.map(MetadataItem::ExpiryHeight)
|
||||
|
@ -226,7 +232,7 @@ impl MetadataItem {
|
|||
"Expiry height must be a 32-bit little-endian value.".to_string(),
|
||||
)
|
||||
}),
|
||||
MetadataTypecode::ExpiryTime => data
|
||||
(R1, ExpiryTime) => data
|
||||
.try_into()
|
||||
.map(u64::from_le_bytes)
|
||||
.map(MetadataItem::ExpiryTime)
|
||||
|
@ -235,11 +241,12 @@ impl MetadataItem {
|
|||
"Expiry time must be a 64-bit little-endian value.".to_string(),
|
||||
)
|
||||
}),
|
||||
MetadataTypecode::MustUnderstand(tc) => Err(ParseError::NotUnderstood(tc)),
|
||||
MetadataTypecode::Unknown(typecode) => Ok(MetadataItem::Unknown {
|
||||
(R0 | R1, MustUnderstand(tc)) => Err(ParseError::NotUnderstood(tc)),
|
||||
(R0 | R1, Unknown(typecode)) => Ok(MetadataItem::Unknown {
|
||||
typecode,
|
||||
data: data.to_vec(),
|
||||
}),
|
||||
(R0, ExpiryHeight | ExpiryTime) => Err(ParseError::NotUnderstood(typecode.into())),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -349,8 +356,15 @@ impl fmt::Display for ParseError {
|
|||
|
||||
impl Error for ParseError {}
|
||||
|
||||
/// The revision of the Unified Address standard that an address was parsed under.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Revision {
|
||||
R0,
|
||||
R1,
|
||||
}
|
||||
|
||||
pub(crate) mod private {
|
||||
use super::{DataTypecode, ParseError, Typecode, PADDING_LEN};
|
||||
use super::{DataTypecode, ParseError, Revision, Typecode, PADDING_LEN};
|
||||
use crate::{
|
||||
unified::{Item, MetadataItem},
|
||||
Network,
|
||||
|
@ -375,29 +389,47 @@ pub(crate) mod private {
|
|||
|
||||
/// A Unified Container containing addresses or viewing keys.
|
||||
pub trait SealedContainer: super::Container + std::marker::Sized {
|
||||
const MAINNET: &'static str;
|
||||
const TESTNET: &'static str;
|
||||
const REGTEST: &'static str;
|
||||
const MAINNET_R0: &'static str;
|
||||
const TESTNET_R0: &'static str;
|
||||
const REGTEST_R0: &'static str;
|
||||
|
||||
const MAINNET_R1: &'static str;
|
||||
const TESTNET_R1: &'static str;
|
||||
const REGTEST_R1: &'static str;
|
||||
|
||||
/// Implementations of this method should act as unchecked constructors
|
||||
/// of the container type; the caller is guaranteed to check the
|
||||
/// general invariants that apply to all unified containers.
|
||||
fn from_inner(items: Vec<Item<Self::DataItem>>) -> Self;
|
||||
fn from_inner(revision: Revision, items: Vec<Item<Self::DataItem>>) -> Self;
|
||||
|
||||
fn network_hrp(network: &Network) -> &'static str {
|
||||
match network {
|
||||
Network::Main => Self::MAINNET,
|
||||
Network::Test => Self::TESTNET,
|
||||
Network::Regtest => Self::REGTEST,
|
||||
fn network_hrp(revision: Revision, network: &Network) -> &'static str {
|
||||
match (revision, network) {
|
||||
(Revision::R0, Network::Main) => Self::MAINNET_R0,
|
||||
(Revision::R0, Network::Test) => Self::TESTNET_R0,
|
||||
(Revision::R0, Network::Regtest) => Self::REGTEST_R0,
|
||||
(Revision::R1, Network::Main) => Self::MAINNET_R1,
|
||||
(Revision::R1, Network::Test) => Self::TESTNET_R1,
|
||||
(Revision::R1, Network::Regtest) => Self::REGTEST_R1,
|
||||
}
|
||||
}
|
||||
|
||||
fn hrp_revision(hrp: &str) -> Option<Revision> {
|
||||
if hrp == Self::MAINNET_R0 || hrp == Self::TESTNET_R0 || hrp == Self::REGTEST_R0 {
|
||||
Some(Revision::R0)
|
||||
} else if hrp == Self::MAINNET_R1 || hrp == Self::TESTNET_R1 || hrp == Self::REGTEST_R1
|
||||
{
|
||||
Some(Revision::R1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn hrp_network(hrp: &str) -> Option<Network> {
|
||||
if hrp == Self::MAINNET {
|
||||
if hrp == Self::MAINNET_R0 || hrp == Self::MAINNET_R1 {
|
||||
Some(Network::Main)
|
||||
} else if hrp == Self::TESTNET {
|
||||
} else if hrp == Self::TESTNET_R0 || hrp == Self::TESTNET_R1 {
|
||||
Some(Network::Test)
|
||||
} else if hrp == Self::REGTEST {
|
||||
} else if hrp == Self::REGTEST_R0 || hrp == Self::REGTEST_R1 {
|
||||
Some(Network::Regtest)
|
||||
} else {
|
||||
None
|
||||
|
@ -434,11 +466,13 @@ pub(crate) mod private {
|
|||
}
|
||||
|
||||
/// Parse the items of the unified container.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn parse_items<T: Into<Vec<u8>>>(
|
||||
hrp: &str,
|
||||
buf: T,
|
||||
) -> Result<Vec<Item<Self::DataItem>>, ParseError> {
|
||||
fn read_receiver<R: SealedDataItem>(
|
||||
) -> Result<(Revision, Vec<Item<Self::DataItem>>), ParseError> {
|
||||
fn read_item<R: SealedDataItem>(
|
||||
revision: Revision,
|
||||
mut cursor: &mut std::io::Cursor<&[u8]>,
|
||||
) -> Result<Item<R>, ParseError> {
|
||||
let typecode = CompactSize::read(&mut cursor)
|
||||
|
@ -471,7 +505,9 @@ pub(crate) mod private {
|
|||
let data = &buf[cursor.position() as usize..addr_end as usize];
|
||||
let result = match Typecode::try_from(typecode)? {
|
||||
Typecode::Data(tc) => Item::Data(R::parse(tc, data)?),
|
||||
Typecode::Metadata(tc) => Item::Metadata(MetadataItem::parse(tc, data)?),
|
||||
Typecode::Metadata(tc) => {
|
||||
Item::Metadata(MetadataItem::parse(revision, tc, data)?)
|
||||
}
|
||||
};
|
||||
cursor.set_position(addr_end);
|
||||
Ok(result)
|
||||
|
@ -498,19 +534,25 @@ pub(crate) mod private {
|
|||
)),
|
||||
}?;
|
||||
|
||||
let revision = Self::hrp_revision(hrp)
|
||||
.ok_or_else(|| ParseError::UnknownPrefix(hrp.to_string()))?;
|
||||
|
||||
let mut cursor = std::io::Cursor::new(encoded);
|
||||
let mut result = vec![];
|
||||
while cursor.position() < encoded.len().try_into().unwrap() {
|
||||
result.push(read_receiver(&mut cursor)?);
|
||||
result.push(read_item(revision, &mut cursor)?);
|
||||
}
|
||||
assert_eq!(cursor.position(), encoded.len().try_into().unwrap());
|
||||
|
||||
Ok(result)
|
||||
Ok((revision, result))
|
||||
}
|
||||
|
||||
/// A private function that constructs a unified container with the
|
||||
/// specified items, which must be in ascending typecode order.
|
||||
fn try_from_items_internal(items: Vec<Item<Self::DataItem>>) -> Result<Self, ParseError> {
|
||||
fn try_from_items_internal(
|
||||
revision: Revision,
|
||||
items: Vec<Item<Self::DataItem>>,
|
||||
) -> Result<Self, ParseError> {
|
||||
assert!(u32::from(Typecode::P2SH) == u32::from(Typecode::P2PKH) + 1);
|
||||
|
||||
let mut only_transparent = true;
|
||||
|
@ -538,12 +580,13 @@ pub(crate) mod private {
|
|||
Err(ParseError::OnlyTransparent)
|
||||
} else {
|
||||
// All checks pass!
|
||||
Ok(Self::from_inner(items))
|
||||
Ok(Self::from_inner(revision, items))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_internal<T: Into<Vec<u8>>>(hrp: &str, buf: T) -> Result<Self, ParseError> {
|
||||
Self::parse_items(hrp, buf).and_then(Self::try_from_items_internal)
|
||||
Self::parse_items(hrp, buf)
|
||||
.and_then(|(revision, items)| Self::try_from_items_internal(revision, items))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -563,9 +606,12 @@ pub trait Encoding: private::SealedContainer {
|
|||
/// * the item list may not contain two items having the same typecode
|
||||
/// * the item list may not contain only transparent items (or no items)
|
||||
/// * the item list may not contain both P2PKH and P2SH items.
|
||||
fn try_from_items(mut items: Vec<Item<Self::DataItem>>) -> Result<Self, ParseError> {
|
||||
fn try_from_items(
|
||||
revision: Revision,
|
||||
mut items: Vec<Item<Self::DataItem>>,
|
||||
) -> Result<Self, ParseError> {
|
||||
items.sort_unstable_by(Item::encoding_order);
|
||||
Self::try_from_items_internal(items)
|
||||
Self::try_from_items_internal(revision, items)
|
||||
}
|
||||
|
||||
/// Decodes a unified container from its string representation, preserving
|
||||
|
@ -592,7 +638,7 @@ pub trait Encoding: private::SealedContainer {
|
|||
/// ordering of the contained items such that it correctly obeys round-trip
|
||||
/// serialization invariants.
|
||||
fn encode(&self, network: &Network) -> String {
|
||||
let hrp = Self::network_hrp(network);
|
||||
let hrp = Self::network_hrp(self.revision(), network);
|
||||
bech32::encode(
|
||||
hrp,
|
||||
self.to_jumbled_bytes(hrp).to_base32(),
|
||||
|
@ -609,4 +655,8 @@ pub trait Container {
|
|||
|
||||
/// Returns the items in encoding order.
|
||||
fn items_as_parsed(&self) -> &[Item<Self::DataItem>];
|
||||
|
||||
/// Returns the revision of the ZIP 316 standard that this unified container
|
||||
/// conforms to.
|
||||
fn revision(&self) -> Revision;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use zcash_protocol::{PoolType, ShieldedProtocol};
|
||||
|
||||
use super::{private::SealedDataItem, DataTypecode, Item, ParseError};
|
||||
use super::{private::SealedDataItem, DataTypecode, Item, ParseError, Revision};
|
||||
|
||||
use std::{cmp, convert::TryInto};
|
||||
|
||||
|
@ -69,7 +69,7 @@ impl SealedDataItem for Receiver {
|
|||
/// # use std::convert::Infallible;
|
||||
/// # use std::error::Error;
|
||||
/// use zcash_address::{
|
||||
/// unified::{self, Container, Encoding, Item},
|
||||
/// unified::{self, Container, Encoding, Item, Revision},
|
||||
/// ConversionError, TryFromRawAddress, ZcashAddress,
|
||||
/// };
|
||||
///
|
||||
|
@ -100,13 +100,16 @@ impl SealedDataItem for Receiver {
|
|||
/// let receivers: Vec<unified::Receiver> = ua.receivers();
|
||||
///
|
||||
/// // And we can create the UA from a list of receivers:
|
||||
/// let new_ua = unified::Address::try_from_items(receivers.into_iter().map(Item::Data).collect())?;
|
||||
/// let new_ua = unified::Address::try_from_items(Revision::R0, receivers.into_iter().map(Item::Data).collect())?;
|
||||
/// assert_eq!(new_ua, ua);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Address(pub(crate) Vec<Item<Receiver>>);
|
||||
pub struct Address {
|
||||
pub(crate) revision: Revision,
|
||||
pub(crate) receivers: Vec<Item<Receiver>>,
|
||||
}
|
||||
|
||||
impl Address {
|
||||
/// Returns the receiver items for this address, in order of decreasing preference.
|
||||
|
@ -115,7 +118,7 @@ impl Address {
|
|||
/// of a type that wallet supports from the result.
|
||||
pub fn receivers(&self) -> Vec<Receiver> {
|
||||
let mut result = self
|
||||
.0
|
||||
.receivers
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
Item::Data(r) => Some(r.clone()),
|
||||
|
@ -130,7 +133,7 @@ impl Address {
|
|||
impl Address {
|
||||
/// Returns whether this address has the ability to receive transfers of the given pool type.
|
||||
pub fn has_receiver_of_type(&self, pool_type: PoolType) -> bool {
|
||||
self.0.iter().any(|item| match item {
|
||||
self.receivers.iter().any(|item| match item {
|
||||
Item::Data(Receiver::Orchard(_)) => {
|
||||
pool_type == PoolType::Shielded(ShieldedProtocol::Orchard)
|
||||
}
|
||||
|
@ -147,14 +150,14 @@ impl Address {
|
|||
|
||||
/// Returns whether this address contains the given receiver.
|
||||
pub fn contains_receiver(&self, receiver: &Receiver) -> bool {
|
||||
self.0
|
||||
self.receivers
|
||||
.iter()
|
||||
.any(|item| matches!(item, Item::Data(r) if r == receiver))
|
||||
}
|
||||
|
||||
/// Returns whether this address can receive a memo.
|
||||
pub fn can_receive_memo(&self) -> bool {
|
||||
self.0.iter().any(|r| {
|
||||
self.receivers.iter().any(|r| {
|
||||
matches!(
|
||||
r,
|
||||
Item::Data(Receiver::Sapling(_)) | Item::Data(Receiver::Orchard(_))
|
||||
|
@ -164,25 +167,45 @@ impl Address {
|
|||
}
|
||||
|
||||
impl super::private::SealedContainer for Address {
|
||||
/// The HRP for a Bech32m-encoded mainnet Unified Address.
|
||||
/// The HRP for a Bech32m-encoded mainnet Revision 0 Unified Address.
|
||||
///
|
||||
/// Defined in [ZIP 316][zip-0316].
|
||||
///
|
||||
/// [zip-0316]: https://zips.z.cash/zip-0316
|
||||
const MAINNET: &'static str = "u";
|
||||
const MAINNET_R0: &'static str = "u";
|
||||
|
||||
/// The HRP for a Bech32m-encoded testnet Unified Address.
|
||||
/// The HRP for a Bech32m-encoded testnet Revision 0 Unified Address.
|
||||
///
|
||||
/// Defined in [ZIP 316][zip-0316].
|
||||
///
|
||||
/// [zip-0316]: https://zips.z.cash/zip-0316
|
||||
const TESTNET: &'static str = "utest";
|
||||
const TESTNET_R0: &'static str = "utest";
|
||||
|
||||
/// The HRP for a Bech32m-encoded regtest Unified Address.
|
||||
const REGTEST: &'static str = "uregtest";
|
||||
/// The HRP for a Bech32m-encoded regtest Revision 0 Unified Address.
|
||||
const REGTEST_R0: &'static str = "uregtest";
|
||||
|
||||
fn from_inner(receivers: Vec<Item<Self::DataItem>>) -> Self {
|
||||
Self(receivers)
|
||||
/// The HRP for a Bech32m-encoded mainnet Revision 1 Unified Address.
|
||||
///
|
||||
/// Defined in [ZIP 316][zip-0316].
|
||||
///
|
||||
/// [zip-0316]: https://zips.z.cash/zip-0316
|
||||
const MAINNET_R1: &'static str = "ur";
|
||||
|
||||
/// The HRP for a Bech32m-encoded testnet Revision 1 Unified Address.
|
||||
///
|
||||
/// Defined in [ZIP 316][zip-0316].
|
||||
///
|
||||
/// [zip-0316]: https://zips.z.cash/zip-0316
|
||||
const TESTNET_R1: &'static str = "urtest";
|
||||
|
||||
/// The HRP for a Bech32m-encoded regtest Revision 1 Unified Address.
|
||||
const REGTEST_R1: &'static str = "urregtest";
|
||||
|
||||
fn from_inner(revision: Revision, receivers: Vec<Item<Self::DataItem>>) -> Self {
|
||||
Self {
|
||||
revision,
|
||||
receivers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,7 +214,11 @@ impl super::Container for Address {
|
|||
type DataItem = Receiver;
|
||||
|
||||
fn items_as_parsed(&self) -> &[Item<Receiver>] {
|
||||
&self.0
|
||||
&self.receivers
|
||||
}
|
||||
|
||||
fn revision(&self) -> Revision {
|
||||
self.revision
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,7 +233,7 @@ pub mod testing {
|
|||
};
|
||||
|
||||
use super::{Address, Receiver};
|
||||
use crate::unified::{DataTypecode, Item};
|
||||
use crate::unified::{DataTypecode, Item, Revision};
|
||||
use zcash_encoding::MAX_COMPACT_SIZE;
|
||||
|
||||
prop_compose! {
|
||||
|
@ -271,9 +298,12 @@ pub mod testing {
|
|||
arb_typecodes()
|
||||
.prop_flat_map(arb_unified_address_receivers)
|
||||
.prop_map(|rs| {
|
||||
let mut items = rs.into_iter().map(Item::Data).collect::<Vec<_>>();
|
||||
items.sort_unstable_by(Item::encoding_order);
|
||||
Address(items)
|
||||
let mut receivers = rs.into_iter().map(Item::Data).collect::<Vec<_>>();
|
||||
receivers.sort_unstable_by(Item::encoding_order);
|
||||
Address {
|
||||
revision: Revision::R0,
|
||||
receivers,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -287,7 +317,7 @@ mod tests {
|
|||
|
||||
use crate::{
|
||||
kind::unified::{private::SealedContainer, Encoding},
|
||||
unified::{address::testing::arb_unified_address, Item, Typecode},
|
||||
unified::{address::testing::arb_unified_address, Item, Revision, Typecode},
|
||||
Network,
|
||||
};
|
||||
|
||||
|
@ -322,7 +352,7 @@ mod tests {
|
|||
0x7b, 0x28, 0x69, 0xc9, 0x84,
|
||||
];
|
||||
assert_eq!(
|
||||
Address::parse_internal(Address::MAINNET, &invalid_padding[..]),
|
||||
Address::parse_internal(Address::MAINNET_R0, &invalid_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -337,7 +367,7 @@ mod tests {
|
|||
0x4b, 0x31, 0xee, 0x5a,
|
||||
];
|
||||
assert_eq!(
|
||||
Address::parse_internal(Address::MAINNET, &truncated_padding[..]),
|
||||
Address::parse_internal(Address::MAINNET_R0, &truncated_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -362,7 +392,7 @@ mod tests {
|
|||
0xc6, 0x5e, 0x68, 0xa2, 0x78, 0x6c, 0x9e,
|
||||
];
|
||||
assert_matches!(
|
||||
Address::parse_internal(Address::MAINNET, &truncated_sapling_data[..]),
|
||||
Address::parse_internal(Address::MAINNET_R0, &truncated_sapling_data[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
|
||||
|
@ -375,7 +405,7 @@ mod tests {
|
|||
0xe6, 0x70, 0x36, 0x5b, 0x7b, 0x9e,
|
||||
];
|
||||
assert_matches!(
|
||||
Address::parse_internal(Address::MAINNET, &truncated_after_sapling_typecode[..]),
|
||||
Address::parse_internal(Address::MAINNET_R0, &truncated_after_sapling_typecode[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
}
|
||||
|
@ -384,13 +414,16 @@ mod tests {
|
|||
fn duplicate_typecode() {
|
||||
// Construct and serialize an invalid UA. This must be done using private
|
||||
// methods, as the public API does not permit construction of such invalid values.
|
||||
let ua = Address(vec![
|
||||
let ua = Address {
|
||||
revision: Revision::R0,
|
||||
receivers: vec![
|
||||
Item::Data(Receiver::Sapling([1; 43])),
|
||||
Item::Data(Receiver::Sapling([2; 43])),
|
||||
]);
|
||||
let encoded = ua.to_jumbled_bytes(Address::MAINNET);
|
||||
],
|
||||
};
|
||||
let encoded = ua.to_jumbled_bytes(Address::MAINNET_R0);
|
||||
assert_eq!(
|
||||
Address::parse_internal(Address::MAINNET, &encoded[..]),
|
||||
Address::parse_internal(Address::MAINNET_R0, &encoded[..]),
|
||||
Err(ParseError::DuplicateTypecode(Typecode::SAPLING))
|
||||
);
|
||||
}
|
||||
|
@ -399,14 +432,17 @@ mod tests {
|
|||
fn p2pkh_and_p2sh() {
|
||||
// Construct and serialize an invalid UA. This must be done using private
|
||||
// methods, as the public API does not permit construction of such invalid values.
|
||||
let ua = Address(vec![
|
||||
let ua = Address {
|
||||
revision: Revision::R0,
|
||||
receivers: vec![
|
||||
Item::Data(Receiver::P2pkh([0; 20])),
|
||||
Item::Data(Receiver::P2sh([0; 20])),
|
||||
]);
|
||||
let encoded = ua.to_jumbled_bytes(Address::MAINNET);
|
||||
],
|
||||
};
|
||||
let encoded = ua.to_jumbled_bytes(Address::MAINNET_R0);
|
||||
// ensure that decoding catches the error
|
||||
assert_eq!(
|
||||
Address::parse_internal(Address::MAINNET, &encoded[..]),
|
||||
Address::parse_internal(Address::MAINNET_R0, &encoded[..]),
|
||||
Err(ParseError::BothP2phkAndP2sh)
|
||||
);
|
||||
}
|
||||
|
@ -415,14 +451,17 @@ mod tests {
|
|||
fn addresses_out_of_order() {
|
||||
// Construct and serialize an invalid UA. This must be done using private
|
||||
// methods, as the public API does not permit construction of such invalid values.
|
||||
let ua = Address(vec![
|
||||
let ua = Address {
|
||||
revision: Revision::R0,
|
||||
receivers: vec![
|
||||
Item::Data(Receiver::Sapling([0; 43])),
|
||||
Item::Data(Receiver::P2pkh([0; 20])),
|
||||
]);
|
||||
let encoded = ua.to_jumbled_bytes(Address::MAINNET);
|
||||
],
|
||||
};
|
||||
let encoded = ua.to_jumbled_bytes(Address::MAINNET_R0);
|
||||
// ensure that decoding catches the error
|
||||
assert_eq!(
|
||||
Address::parse_internal(Address::MAINNET, &encoded[..]),
|
||||
Address::parse_internal(Address::MAINNET_R0, &encoded[..]),
|
||||
Err(ParseError::InvalidTypecodeOrder)
|
||||
);
|
||||
}
|
||||
|
@ -441,7 +480,7 @@ mod tests {
|
|||
// with only one of them we don't have sufficient data for F4Jumble (so we hit a
|
||||
// different error).
|
||||
assert_matches!(
|
||||
Address::parse_internal(Address::MAINNET, &encoded[..]),
|
||||
Address::parse_internal(Address::MAINNET_R0, &encoded[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
}
|
||||
|
@ -449,7 +488,9 @@ mod tests {
|
|||
#[test]
|
||||
fn receivers_are_sorted() {
|
||||
// Construct a UA with receivers in an unsorted order.
|
||||
let ua = Address(vec![
|
||||
let ua = Address {
|
||||
revision: Revision::R0,
|
||||
receivers: vec![
|
||||
Item::Data(Receiver::P2pkh([0; 20])),
|
||||
Item::Data(Receiver::Orchard([0; 43])),
|
||||
Item::Data(Receiver::Unknown {
|
||||
|
@ -457,7 +498,8 @@ mod tests {
|
|||
data: vec![],
|
||||
}),
|
||||
Item::Data(Receiver::Sapling([0; 43])),
|
||||
]);
|
||||
],
|
||||
};
|
||||
|
||||
// `Address::receivers` sorts the receivers in priority order.
|
||||
assert_eq!(
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::convert::TryInto;
|
|||
|
||||
use super::{
|
||||
private::{SealedContainer, SealedDataItem},
|
||||
Container, DataTypecode, Encoding, Item, ParseError,
|
||||
Container, DataTypecode, Encoding, Item, ParseError, Revision,
|
||||
};
|
||||
|
||||
/// The set of known FVKs for Unified FVKs.
|
||||
|
@ -79,7 +79,7 @@ impl SealedDataItem for Fvk {
|
|||
///
|
||||
/// ```
|
||||
/// # use std::error::Error;
|
||||
/// use zcash_address::unified::{self, Container, Encoding, Item};
|
||||
/// use zcash_address::unified::{self, Container, Encoding, Item, Revision};
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// # let ufvk_from_user = || "uview1cgrqnry478ckvpr0f580t6fsahp0a5mj2e9xl7hv2d2jd4ldzy449mwwk2l9yeuts85wjls6hjtghdsy5vhhvmjdw3jxl3cxhrg3vs296a3czazrycrr5cywjhwc5c3ztfyjdhmz0exvzzeyejamyp0cr9z8f9wj0953fzht0m4lenk94t70ruwgjxag2tvp63wn9ftzhtkh20gyre3w5s24f6wlgqxnjh40gd2lxe75sf3z8h5y2x0atpxcyf9t3em4h0evvsftluruqne6w4sm066sw0qe5y8qg423grple5fftxrqyy7xmqmatv7nzd7tcjadu8f7mqz4l83jsyxy4t8pkayytyk7nrp467ds85knekdkvnd7hqkfer8mnqd7pv";
|
||||
|
@ -91,44 +91,68 @@ impl SealedDataItem for Fvk {
|
|||
/// let fvks: &[Item<unified::Fvk>] = ufvk.items_as_parsed();
|
||||
///
|
||||
/// // And we can create the UFVK from a list of FVKs:
|
||||
/// let new_ufvk = unified::Ufvk::try_from_items(fvks.to_vec())?;
|
||||
/// let new_ufvk = unified::Ufvk::try_from_items(Revision::R0, fvks.to_vec())?;
|
||||
/// assert_eq!(new_ufvk, ufvk);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Ufvk(pub(crate) Vec<Item<Fvk>>);
|
||||
pub struct Ufvk {
|
||||
pub(crate) revision: Revision,
|
||||
pub(crate) fvks: Vec<Item<Fvk>>,
|
||||
}
|
||||
|
||||
impl Container for Ufvk {
|
||||
type DataItem = Fvk;
|
||||
|
||||
fn items_as_parsed(&self) -> &[Item<Fvk>] {
|
||||
&self.0
|
||||
&self.fvks
|
||||
}
|
||||
|
||||
fn revision(&self) -> Revision {
|
||||
self.revision
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoding for Ufvk {}
|
||||
|
||||
impl SealedContainer for Ufvk {
|
||||
/// The HRP for a Bech32m-encoded mainnet Unified FVK.
|
||||
/// The HRP for a Bech32m-encoded mainnet Revision 0 Unified FVK.
|
||||
///
|
||||
/// Defined in [ZIP 316][zip-0316].
|
||||
///
|
||||
/// [zip-0316]: https://zips.z.cash/zip-0316
|
||||
const MAINNET: &'static str = "uview";
|
||||
const MAINNET_R0: &'static str = "uview";
|
||||
|
||||
/// The HRP for a Bech32m-encoded testnet Unified FVK.
|
||||
/// The HRP for a Bech32m-encoded testnet Revision 0 Unified FVK.
|
||||
///
|
||||
/// Defined in [ZIP 316][zip-0316].
|
||||
///
|
||||
/// [zip-0316]: https://zips.z.cash/zip-0316
|
||||
const TESTNET: &'static str = "uviewtest";
|
||||
const TESTNET_R0: &'static str = "uviewtest";
|
||||
|
||||
/// The HRP for a Bech32m-encoded regtest Unified FVK.
|
||||
const REGTEST: &'static str = "uviewregtest";
|
||||
/// The HRP for a Bech32m-encoded regtest Revision 0 Unified FVK.
|
||||
const REGTEST_R0: &'static str = "uviewregtest";
|
||||
|
||||
fn from_inner(fvks: Vec<Item<Self::DataItem>>) -> Self {
|
||||
Self(fvks)
|
||||
/// The HRP for a Bech32m-encoded mainnet Revision 1 Unified FVK.
|
||||
///
|
||||
/// Defined in [ZIP 316][zip-0316].
|
||||
///
|
||||
/// [zip-0316]: https://zips.z.cash/zip-0316
|
||||
const MAINNET_R1: &'static str = "urview";
|
||||
|
||||
/// The HRP for a Bech32m-encoded testnet Revision 1 Unified FVK.
|
||||
///
|
||||
/// Defined in [ZIP 316][zip-0316].
|
||||
///
|
||||
/// [zip-0316]: https://zips.z.cash/zip-0316
|
||||
const TESTNET_R1: &'static str = "urviewtest";
|
||||
|
||||
/// The HRP for a Bech32m-encoded regtest Revision 1 Unified FVK.
|
||||
const REGTEST_R1: &'static str = "urviewregtest";
|
||||
|
||||
fn from_inner(revision: Revision, fvks: Vec<Item<Self::DataItem>>) -> Self {
|
||||
Self { revision, fvks }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,7 +165,7 @@ mod tests {
|
|||
use super::{Fvk, ParseError, Ufvk};
|
||||
use crate::{
|
||||
kind::unified::{private::SealedContainer, Encoding},
|
||||
unified::{Item, Typecode},
|
||||
unified::{Item, Revision, Typecode},
|
||||
Network,
|
||||
};
|
||||
|
||||
|
@ -199,9 +223,9 @@ mod tests {
|
|||
shielded in arb_shielded_fvk(),
|
||||
transparent in prop::option::of(arb_transparent_fvk()),
|
||||
) -> Ufvk {
|
||||
let mut items: Vec<_> = transparent.into_iter().chain(shielded).map(Item::Data).collect();
|
||||
items.sort_unstable_by(Item::encoding_order);
|
||||
Ufvk(items)
|
||||
let mut fvks: Vec<_> = transparent.into_iter().chain(shielded).map(Item::Data).collect();
|
||||
fvks.sort_unstable_by(Item::encoding_order);
|
||||
Ufvk { revision: Revision::R0, fvks }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,7 +257,7 @@ mod tests {
|
|||
0xdf, 0x63, 0xe7, 0xef, 0x65, 0x6b, 0x18, 0x23, 0xf7, 0x3e, 0x35, 0x7c, 0xf3, 0xc4,
|
||||
];
|
||||
assert_eq!(
|
||||
Ufvk::parse_internal(Ufvk::MAINNET, &invalid_padding[..]),
|
||||
Ufvk::parse_internal(Ufvk::MAINNET_R0, &invalid_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -251,7 +275,7 @@ mod tests {
|
|||
0x43, 0x8e, 0xc0, 0x3e, 0x9f, 0xf4, 0xf1, 0x80, 0x32, 0xcf, 0x2f, 0x7e, 0x7f, 0x91,
|
||||
];
|
||||
assert_eq!(
|
||||
Ufvk::parse_internal(Ufvk::MAINNET, &truncated_padding[..]),
|
||||
Ufvk::parse_internal(Ufvk::MAINNET_R0, &truncated_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -283,7 +307,7 @@ mod tests {
|
|||
0x8c, 0x7a, 0xbf, 0x7b, 0x9a, 0xdd, 0xee, 0x18, 0x2c, 0x2d, 0xc2, 0xfc,
|
||||
];
|
||||
assert_matches!(
|
||||
Ufvk::parse_internal(Ufvk::MAINNET, &truncated_sapling_data[..]),
|
||||
Ufvk::parse_internal(Ufvk::MAINNET_R0, &truncated_sapling_data[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
|
||||
|
@ -298,7 +322,7 @@ mod tests {
|
|||
0x54, 0xd1, 0x9e, 0xec, 0x8b, 0xef, 0x35, 0xb8, 0x44, 0xdd, 0xab, 0x9a, 0x8d,
|
||||
];
|
||||
assert_matches!(
|
||||
Ufvk::parse_internal(Ufvk::MAINNET, &truncated_after_sapling_typecode[..]),
|
||||
Ufvk::parse_internal(Ufvk::MAINNET_R0, &truncated_after_sapling_typecode[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
}
|
||||
|
@ -307,13 +331,16 @@ mod tests {
|
|||
fn duplicate_typecode() {
|
||||
// Construct and serialize an invalid Ufvk. This must be done using private
|
||||
// methods, as the public API does not permit construction of such invalid values.
|
||||
let ufvk = Ufvk(vec![
|
||||
let ufvk = Ufvk {
|
||||
revision: Revision::R0,
|
||||
fvks: vec![
|
||||
Item::Data(Fvk::Sapling([1; 128])),
|
||||
Item::Data(Fvk::Sapling([2; 128])),
|
||||
]);
|
||||
let encoded = ufvk.to_jumbled_bytes(Ufvk::MAINNET);
|
||||
],
|
||||
};
|
||||
let encoded = ufvk.to_jumbled_bytes(Ufvk::MAINNET_R0);
|
||||
assert_eq!(
|
||||
Ufvk::parse_internal(Ufvk::MAINNET, &encoded[..]),
|
||||
Ufvk::parse_internal(Ufvk::MAINNET_R0, &encoded[..]),
|
||||
Err(ParseError::DuplicateTypecode(Typecode::SAPLING))
|
||||
);
|
||||
}
|
||||
|
@ -331,7 +358,7 @@ mod tests {
|
|||
];
|
||||
|
||||
assert_eq!(
|
||||
Ufvk::parse_internal(Ufvk::MAINNET, &encoded[..]),
|
||||
Ufvk::parse_internal(Ufvk::MAINNET_R0, &encoded[..]),
|
||||
Err(ParseError::OnlyTransparent)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::convert::TryInto;
|
|||
|
||||
use super::{
|
||||
private::{SealedContainer, SealedDataItem},
|
||||
Container, DataTypecode, Encoding, Item, ParseError,
|
||||
Container, DataTypecode, Encoding, Item, ParseError, Revision,
|
||||
};
|
||||
|
||||
/// The set of known IVKs for Unified IVKs.
|
||||
|
@ -84,7 +84,7 @@ impl SealedDataItem for Ivk {
|
|||
///
|
||||
/// ```
|
||||
/// # use std::error::Error;
|
||||
/// use zcash_address::unified::{self, Container, Encoding, Item};
|
||||
/// use zcash_address::unified::{self, Container, Encoding, Item, Revision};
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// # let uivk_from_user = || "uivk1djetqg3fws7y7qu5tekynvcdhz69gsyq07ewvppmzxdqhpfzdgmx8urnkqzv7ylz78ez43ux266pqjhecd59fzhn7wpe6zarnzh804hjtkyad25ryqla5pnc8p5wdl3phj9fczhz64zprun3ux7y9jc08567xryumuz59rjmg4uuflpjqwnq0j0tzce0x74t4tv3gfjq7nczkawxy6y7hse733ae3vw7qfjd0ss0pytvezxp42p6rrpzeh6t2zrz7zpjk0xhngcm6gwdppxs58jkx56gsfflugehf5vjlmu7vj3393gj6u37wenavtqyhdvcdeaj86s6jczl4zq";
|
||||
|
@ -96,44 +96,68 @@ impl SealedDataItem for Ivk {
|
|||
/// let ivks: &[Item<unified::Ivk>] = uivk.items_as_parsed();
|
||||
///
|
||||
/// // And we can create the UIVK from a vector of IVKs:
|
||||
/// let new_uivk = unified::Uivk::try_from_items(ivks.to_vec())?;
|
||||
/// let new_uivk = unified::Uivk::try_from_items(Revision::R0, ivks.to_vec())?;
|
||||
/// assert_eq!(new_uivk, uivk);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Uivk(pub(crate) Vec<Item<Ivk>>);
|
||||
pub struct Uivk {
|
||||
pub(crate) revision: Revision,
|
||||
pub(crate) ivks: Vec<Item<Ivk>>,
|
||||
}
|
||||
|
||||
impl Container for Uivk {
|
||||
type DataItem = Ivk;
|
||||
|
||||
fn items_as_parsed(&self) -> &[Item<Ivk>] {
|
||||
&self.0
|
||||
&self.ivks
|
||||
}
|
||||
|
||||
fn revision(&self) -> Revision {
|
||||
self.revision
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoding for Uivk {}
|
||||
|
||||
impl SealedContainer for Uivk {
|
||||
/// The HRP for a Bech32m-encoded mainnet Unified IVK.
|
||||
/// The HRP for a Bech32m-encoded mainnet Revision 0 Unified IVK.
|
||||
///
|
||||
/// Defined in [ZIP 316][zip-0316].
|
||||
///
|
||||
/// [zip-0316]: https://zips.z.cash/zip-0316
|
||||
const MAINNET: &'static str = "uivk";
|
||||
const MAINNET_R0: &'static str = "uivk";
|
||||
|
||||
/// The HRP for a Bech32m-encoded testnet Unified IVK.
|
||||
/// The HRP for a Bech32m-encoded testnet Revision 0 Unified IVK.
|
||||
///
|
||||
/// Defined in [ZIP 316][zip-0316].
|
||||
///
|
||||
/// [zip-0316]: https://zips.z.cash/zip-0316
|
||||
const TESTNET: &'static str = "uivktest";
|
||||
const TESTNET_R0: &'static str = "uivktest";
|
||||
|
||||
/// The HRP for a Bech32m-encoded regtest Unified IVK.
|
||||
const REGTEST: &'static str = "uivkregtest";
|
||||
/// The HRP for a Bech32m-encoded regtest Revision 0 Unified IVK.
|
||||
const REGTEST_R0: &'static str = "uivkregtest";
|
||||
|
||||
fn from_inner(ivks: Vec<Item<Self::DataItem>>) -> Self {
|
||||
Self(ivks)
|
||||
/// The HRP for a Bech32m-encoded mainnet Revision 1 Unified IVK.
|
||||
///
|
||||
/// Defined in [ZIP 316][zip-0316].
|
||||
///
|
||||
/// [zip-0316]: https://zips.z.cash/zip-0316
|
||||
const MAINNET_R1: &'static str = "urivk";
|
||||
|
||||
/// The HRP for a Bech32m-encoded testnet Revision 1 Unified IVK.
|
||||
///
|
||||
/// Defined in [ZIP 316][zip-0316].
|
||||
///
|
||||
/// [zip-0316]: https://zips.z.cash/zip-0316
|
||||
const TESTNET_R1: &'static str = "urivktest";
|
||||
|
||||
/// The HRP for a Bech32m-encoded regtest Revision 1 Unified IVK.
|
||||
const REGTEST_R1: &'static str = "urivkregtest";
|
||||
|
||||
fn from_inner(revision: Revision, ivks: Vec<Item<Self::DataItem>>) -> Self {
|
||||
Self { revision, ivks }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,7 +174,7 @@ mod tests {
|
|||
use super::{Ivk, ParseError, Uivk};
|
||||
use crate::{
|
||||
kind::unified::{private::SealedContainer, Encoding},
|
||||
unified::{Item, Typecode},
|
||||
unified::{Item, Revision, Typecode},
|
||||
Network,
|
||||
};
|
||||
|
||||
|
@ -192,9 +216,9 @@ mod tests {
|
|||
shielded in arb_shielded_ivk(),
|
||||
transparent in prop::option::of(arb_transparent_ivk()),
|
||||
) -> Uivk {
|
||||
let mut items: Vec<_> = transparent.into_iter().chain(shielded).map(Item::Data).collect();
|
||||
items.sort_unstable_by(Item::encoding_order);
|
||||
Uivk(items)
|
||||
let mut ivks: Vec<_> = transparent.into_iter().chain(shielded).map(Item::Data).collect();
|
||||
ivks.sort_unstable_by(Item::encoding_order);
|
||||
Uivk { revision: Revision::R0, ivks }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,7 +248,7 @@ mod tests {
|
|||
0x83, 0xe8, 0x92, 0x18, 0x28, 0x70, 0x1e, 0x81, 0x76, 0x56, 0xb6, 0x15,
|
||||
];
|
||||
assert_eq!(
|
||||
Uivk::parse_internal(Uivk::MAINNET, &invalid_padding[..]),
|
||||
Uivk::parse_internal(Uivk::MAINNET_R0, &invalid_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -240,7 +264,7 @@ mod tests {
|
|||
0xf9, 0x65, 0x49, 0x14, 0xab, 0x7c, 0x55, 0x7b, 0x39, 0x47,
|
||||
];
|
||||
assert_eq!(
|
||||
Uivk::parse_internal(Uivk::MAINNET, &truncated_padding[..]),
|
||||
Uivk::parse_internal(Uivk::MAINNET_R0, &truncated_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -268,7 +292,7 @@ mod tests {
|
|||
0xf5, 0xd5, 0x8a, 0xb5, 0x1a,
|
||||
];
|
||||
assert_matches!(
|
||||
Uivk::parse_internal(Uivk::MAINNET, &truncated_sapling_data[..]),
|
||||
Uivk::parse_internal(Uivk::MAINNET_R0, &truncated_sapling_data[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
|
||||
|
@ -281,7 +305,7 @@ mod tests {
|
|||
0xd8, 0x21, 0x5e, 0x8, 0xa, 0x82, 0x95, 0x21, 0x74,
|
||||
];
|
||||
assert_matches!(
|
||||
Uivk::parse_internal(Uivk::MAINNET, &truncated_after_sapling_typecode[..]),
|
||||
Uivk::parse_internal(Uivk::MAINNET_R0, &truncated_after_sapling_typecode[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
}
|
||||
|
@ -289,10 +313,13 @@ mod tests {
|
|||
#[test]
|
||||
fn duplicate_typecode() {
|
||||
// Construct and serialize an invalid UIVK.
|
||||
let uivk = Uivk(vec![
|
||||
let uivk = Uivk {
|
||||
revision: Revision::R0,
|
||||
ivks: vec![
|
||||
Item::Data(Ivk::Sapling([1; 64])),
|
||||
Item::Data(Ivk::Sapling([2; 64])),
|
||||
]);
|
||||
],
|
||||
};
|
||||
let encoded = uivk.encode(&Network::Main);
|
||||
assert_eq!(
|
||||
Uivk::decode(&encoded),
|
||||
|
@ -313,7 +340,7 @@ mod tests {
|
|||
];
|
||||
|
||||
assert_eq!(
|
||||
Uivk::parse_internal(Uivk::MAINNET, &encoded[..]),
|
||||
Uivk::parse_internal(Uivk::MAINNET_R0, &encoded[..]),
|
||||
Err(ParseError::OnlyTransparent)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use {
|
|||
unified::{
|
||||
self,
|
||||
address::{test_vectors::TEST_VECTORS, Receiver},
|
||||
Item,
|
||||
Item, Revision,
|
||||
},
|
||||
Network, ToAddress, ZcashAddress,
|
||||
},
|
||||
|
@ -40,7 +40,13 @@ fn unified() {
|
|||
.map(Item::Data)
|
||||
.collect();
|
||||
|
||||
let expected_addr = ZcashAddress::from_unified(Network::Main, unified::Address(receivers));
|
||||
let expected_addr = ZcashAddress::from_unified(
|
||||
Network::Main,
|
||||
unified::Address {
|
||||
revision: Revision::R0,
|
||||
receivers,
|
||||
},
|
||||
);
|
||||
|
||||
// Test parsing
|
||||
let addr: ZcashAddress = tv.unified_addr.parse().unwrap();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Structs for handling supported address types.
|
||||
|
||||
use zcash_address::{
|
||||
unified::{self, Container, DataTypecode, Encoding, Item, Typecode},
|
||||
unified::{self, Container, DataTypecode, Encoding, Item, Revision, Typecode},
|
||||
ConversionError, ToAddress, TryFromRawAddress, ZcashAddress,
|
||||
};
|
||||
use zcash_primitives::legacy::TransparentAddress;
|
||||
|
@ -278,6 +278,11 @@ impl UnifiedAddress {
|
|||
.chain(self.expiry_time.map(unified::MetadataItem::ExpiryTime));
|
||||
|
||||
let ua = unified::Address::try_from_items(
|
||||
if self.expiry_height().is_some() || self.expiry_time().is_some() {
|
||||
Revision::R1
|
||||
} else {
|
||||
Revision::R0
|
||||
},
|
||||
data_items
|
||||
.map(Item::Data)
|
||||
.chain(meta_items.map(Item::Metadata))
|
||||
|
@ -337,7 +342,7 @@ impl Receiver {
|
|||
Receiver::Orchard(addr) => {
|
||||
let receiver =
|
||||
unified::Item::Data(unified::Receiver::Orchard(addr.to_raw_address_bytes()));
|
||||
let ua = unified::Address::try_from_items(vec![receiver])
|
||||
let ua = unified::Address::try_from_items(Revision::R0, vec![receiver])
|
||||
.expect("A unified address may contain a single Orchard receiver.");
|
||||
ZcashAddress::from_unified(net, ua)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{
|
|||
error,
|
||||
fmt::{self, Display},
|
||||
};
|
||||
use zcash_address::unified::{self, Container, Encoding, Item, MetadataItem, Typecode};
|
||||
use zcash_address::unified::{self, Container, Encoding, Item, MetadataItem, Revision, Typecode};
|
||||
use zcash_primitives::consensus::BlockHeight;
|
||||
use zcash_protocol::consensus;
|
||||
use zip32::{AccountId, DiversifierIndex};
|
||||
|
@ -855,6 +855,11 @@ impl UnifiedFullViewingKey {
|
|||
.chain(self.expiry_time.map(unified::MetadataItem::ExpiryTime));
|
||||
|
||||
zcash_address::unified::Ufvk::try_from_items(
|
||||
if self.expiry_height().is_some() || self.expiry_time().is_some() {
|
||||
Revision::R1
|
||||
} else {
|
||||
Revision::R0
|
||||
},
|
||||
data_items
|
||||
.map(Item::Data)
|
||||
.chain(meta_items.map(Item::Metadata))
|
||||
|
@ -1159,6 +1164,11 @@ impl UnifiedIncomingViewingKey {
|
|||
.chain(self.expiry_time.map(unified::MetadataItem::ExpiryTime));
|
||||
|
||||
zcash_address::unified::Uivk::try_from_items(
|
||||
if self.expiry_height.is_some() || self.expiry_time.is_some() {
|
||||
Revision::R1
|
||||
} else {
|
||||
Revision::R0
|
||||
},
|
||||
data_items
|
||||
.map(Item::Data)
|
||||
.chain(meta_items.map(Item::Metadata))
|
||||
|
|
Loading…
Reference in New Issue