Compare commits
8 Commits
87c050d829
...
e15551b618
Author | SHA1 | Date |
---|---|---|
Kris Nuttycombe | e15551b618 | |
Kris Nuttycombe | fa225d479a | |
Kris Nuttycombe | cab4d84464 | |
Kris Nuttycombe | aea04d1ad2 | |
Kris Nuttycombe | c0542c9589 | |
Kris Nuttycombe | 34ec1e5bdb | |
Kris Nuttycombe | 0341171c84 | |
Kris Nuttycombe | c3e1750007 |
|
@ -9,11 +9,29 @@ and this library adheres to Rust's notion of
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `zcash_address::ZcashAddress::{can_receive_memo, can_receive_as, matches_receiver}`
|
- `zcash_address::ZcashAddress::{can_receive_memo, can_receive_as, matches_receiver}`
|
||||||
- `zcash_address::unified::Address::{can_receive_memo, has_receiver_of_type, contains_receiver}`
|
- `zcash_address::unified`:
|
||||||
|
- `Address::{can_receive_memo, has_receiver_of_type, contains_receiver, receivers}`
|
||||||
|
- `Container::revision`
|
||||||
|
- `DataTypecode`
|
||||||
|
- `Item`
|
||||||
|
- `MetadataItem`
|
||||||
|
- `MetadataTypecode`
|
||||||
|
- `Revision`
|
||||||
- Module `zcash_address::testing` under the `test-dependencies` feature.
|
- Module `zcash_address::testing` under the `test-dependencies` feature.
|
||||||
- Module `zcash_address::unified::address::testing` under the
|
- Module `zcash_address::unified::address::testing` under the
|
||||||
`test-dependencies` feature.
|
`test-dependencies` feature.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `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
|
||||||
|
significant when considering unified address receivers; use
|
||||||
|
`Address::receivers` instead.
|
||||||
|
|
||||||
## [0.3.2] - 2024-03-06
|
## [0.3.2] - 2024-03-06
|
||||||
### Added
|
### Added
|
||||||
- `zcash_address::convert`:
|
- `zcash_address::convert`:
|
||||||
|
|
|
@ -28,6 +28,7 @@ proptest = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_matches.workspace = true
|
assert_matches.workspace = true
|
||||||
|
proptest.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
test-dependencies = ["dep:proptest"]
|
test-dependencies = ["dep:proptest"]
|
||||||
|
|
|
@ -180,7 +180,11 @@ mod tests {
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{kind::unified, Network};
|
use crate::{
|
||||||
|
kind::unified,
|
||||||
|
unified::{Item, Receiver, Revision},
|
||||||
|
Network,
|
||||||
|
};
|
||||||
|
|
||||||
fn encoding(encoded: &str, decoded: ZcashAddress) {
|
fn encoding(encoded: &str, decoded: ZcashAddress) {
|
||||||
assert_eq!(decoded.to_string(), encoded);
|
assert_eq!(decoded.to_string(), encoded);
|
||||||
|
@ -230,21 +234,30 @@ mod tests {
|
||||||
"u1qpatys4zruk99pg59gcscrt7y6akvl9vrhcfyhm9yxvxz7h87q6n8cgrzzpe9zru68uq39uhmlpp5uefxu0su5uqyqfe5zp3tycn0ecl",
|
"u1qpatys4zruk99pg59gcscrt7y6akvl9vrhcfyhm9yxvxz7h87q6n8cgrzzpe9zru68uq39uhmlpp5uefxu0su5uqyqfe5zp3tycn0ecl",
|
||||||
ZcashAddress {
|
ZcashAddress {
|
||||||
net: Network::Main,
|
net: Network::Main,
|
||||||
kind: AddressKind::Unified(unified::Address(vec![unified::address::Receiver::Sapling([0; 43])])),
|
kind: AddressKind::Unified(unified::Address {
|
||||||
|
revision: Revision::R0,
|
||||||
|
receivers: vec![Item::Data(Receiver::Sapling([0; 43]))]
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
encoding(
|
encoding(
|
||||||
"utest10c5kutapazdnf8ztl3pu43nkfsjx89fy3uuff8tsmxm6s86j37pe7uz94z5jhkl49pqe8yz75rlsaygexk6jpaxwx0esjr8wm5ut7d5s",
|
"utest10c5kutapazdnf8ztl3pu43nkfsjx89fy3uuff8tsmxm6s86j37pe7uz94z5jhkl49pqe8yz75rlsaygexk6jpaxwx0esjr8wm5ut7d5s",
|
||||||
ZcashAddress {
|
ZcashAddress {
|
||||||
net: Network::Test,
|
net: Network::Test,
|
||||||
kind: AddressKind::Unified(unified::Address(vec![unified::address::Receiver::Sapling([0; 43])])),
|
kind: AddressKind::Unified(unified::Address {
|
||||||
|
revision: Revision::R0,
|
||||||
|
receivers: vec![Item::Data(Receiver::Sapling([0; 43]))]
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
encoding(
|
encoding(
|
||||||
"uregtest15xk7vj4grjkay6mnfl93dhsflc2yeunhxwdh38rul0rq3dfhzzxgm5szjuvtqdha4t4p2q02ks0jgzrhjkrav70z9xlvq0plpcjkd5z3",
|
"uregtest15xk7vj4grjkay6mnfl93dhsflc2yeunhxwdh38rul0rq3dfhzzxgm5szjuvtqdha4t4p2q02ks0jgzrhjkrav70z9xlvq0plpcjkd5z3",
|
||||||
ZcashAddress {
|
ZcashAddress {
|
||||||
net: Network::Regtest,
|
net: Network::Regtest,
|
||||||
kind: AddressKind::Unified(unified::Address(vec![unified::address::Receiver::Sapling([0; 43])])),
|
kind: AddressKind::Unified(unified::Address {
|
||||||
|
revision: Revision::R0,
|
||||||
|
receivers: vec![Item::Data(Receiver::Sapling([0; 43]))]
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::convert::{TryFrom, TryInto};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::num::TryFromIntError;
|
use std::num::TryFromIntError;
|
||||||
|
use zcash_encoding::MAX_COMPACT_SIZE;
|
||||||
|
|
||||||
use crate::Network;
|
use crate::Network;
|
||||||
|
|
||||||
|
@ -22,9 +23,9 @@ const PADDING_LEN: usize = 16;
|
||||||
/// The known Receiver and Viewing Key types.
|
/// The known Receiver and Viewing Key types.
|
||||||
///
|
///
|
||||||
/// The typecodes `0xFFFA..=0xFFFF` reserved for experiments are currently not
|
/// The typecodes `0xFFFA..=0xFFFF` reserved for experiments are currently not
|
||||||
/// distinguished from unknown values, and will be parsed as [`Typecode::Unknown`].
|
/// distinguished from unknown values, and will be parsed as [`DataTypecode::Unknown`].
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum Typecode {
|
pub enum DataTypecode {
|
||||||
/// A transparent P2PKH address, FVK, or IVK encoding as specified in [ZIP 316](https://zips.z.cash/zip-0316).
|
/// A transparent P2PKH address, FVK, or IVK encoding as specified in [ZIP 316](https://zips.z.cash/zip-0316).
|
||||||
P2pkh,
|
P2pkh,
|
||||||
/// A transparent P2SH address.
|
/// A transparent P2SH address.
|
||||||
|
@ -39,7 +40,37 @@ pub enum Typecode {
|
||||||
Unknown(u32),
|
Unknown(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Typecode {
|
impl TryFrom<u32> for DataTypecode {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(typecode: u32) -> Result<Self, Self::Error> {
|
||||||
|
match typecode {
|
||||||
|
0x00 => Ok(DataTypecode::P2pkh),
|
||||||
|
0x01 => Ok(DataTypecode::P2sh),
|
||||||
|
0x02 => Ok(DataTypecode::Sapling),
|
||||||
|
0x03 => Ok(DataTypecode::Orchard),
|
||||||
|
0x04..=0xBF | 0xFD..=MAX_COMPACT_SIZE => Ok(DataTypecode::Unknown(typecode)),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DataTypecode> for u32 {
|
||||||
|
fn from(t: DataTypecode) -> Self {
|
||||||
|
match t {
|
||||||
|
DataTypecode::P2pkh => 0x00,
|
||||||
|
DataTypecode::P2sh => 0x01,
|
||||||
|
DataTypecode::Sapling => 0x02,
|
||||||
|
DataTypecode::Orchard => 0x03,
|
||||||
|
DataTypecode::Unknown(typecode) => typecode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataTypecode {
|
||||||
|
/// A total ordering over the data typecodes that can be used to sort
|
||||||
|
/// receivers and/or key items in order of decreasing priority,
|
||||||
|
/// as specified in [ZIP 316](https://zips.z.cash/zip-0316#encoding-of-unified-addresses)
|
||||||
pub fn preference_order(a: &Self, b: &Self) -> cmp::Ordering {
|
pub fn preference_order(a: &Self, b: &Self) -> cmp::Ordering {
|
||||||
match (a, b) {
|
match (a, b) {
|
||||||
// Trivial equality checks.
|
// Trivial equality checks.
|
||||||
|
@ -69,51 +100,213 @@ impl Typecode {
|
||||||
(_, Self::P2pkh) => cmp::Ordering::Greater,
|
(_, Self::P2pkh) => cmp::Ordering::Greater,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn encoding_order(a: &Self, b: &Self) -> cmp::Ordering {
|
/// The known Metadata Typecodes
|
||||||
u32::from(*a).cmp(&u32::from(*b))
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum MetadataTypecode {
|
||||||
|
/// Expiration height metadata as specified in [ZIP 316, Revision 1](https://zips.z.cash/zip-0316)
|
||||||
|
ExpiryHeight,
|
||||||
|
/// Expiration height metadata as specified in [ZIP 316, Revision 1](https://zips.z.cash/zip-0316)
|
||||||
|
ExpiryTime,
|
||||||
|
/// An unknown MUST-understand metadata item as specified in
|
||||||
|
/// [ZIP 316, Revision 1](https://zips.z.cash/zip-0316)
|
||||||
|
///
|
||||||
|
/// A parser encountering this typecode MUST halt with an error.
|
||||||
|
MustUnderstand(u32),
|
||||||
|
/// An unknown metadata item as specified in [ZIP 316, Revision 1](https://zips.z.cash/zip-0316)
|
||||||
|
Unknown(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for MetadataTypecode {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(typecode: u32) -> Result<Self, Self::Error> {
|
||||||
|
match typecode {
|
||||||
|
0xC0..=0xDF => Ok(MetadataTypecode::Unknown(typecode)),
|
||||||
|
0xE0 => Ok(MetadataTypecode::ExpiryHeight),
|
||||||
|
0xE1 => Ok(MetadataTypecode::ExpiryTime),
|
||||||
|
0xE2..=0xFC => Ok(MetadataTypecode::MustUnderstand(typecode)),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<MetadataTypecode> for u32 {
|
||||||
|
fn from(t: MetadataTypecode) -> Self {
|
||||||
|
match t {
|
||||||
|
MetadataTypecode::ExpiryHeight => 0xE0,
|
||||||
|
MetadataTypecode::ExpiryTime => 0xE1,
|
||||||
|
MetadataTypecode::MustUnderstand(value) => value,
|
||||||
|
MetadataTypecode::Unknown(value) => value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An enumeration of the Unified Container Item Typecodes.
|
||||||
|
///
|
||||||
|
/// Unified Address Items are partitioned into two sets: data items, which include
|
||||||
|
/// receivers and viewing keys, and metadata items.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Typecode {
|
||||||
|
/// A data (receiver or viewing key) typecode.
|
||||||
|
Data(DataTypecode),
|
||||||
|
/// A metadata typecode.
|
||||||
|
Metadata(MetadataTypecode),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Typecode {
|
||||||
|
/// The typecode for p2pkh data items.
|
||||||
|
pub const P2PKH: Typecode = Typecode::Data(DataTypecode::P2pkh);
|
||||||
|
/// The typecode for p2sh data items.
|
||||||
|
pub const P2SH: Typecode = Typecode::Data(DataTypecode::P2sh);
|
||||||
|
/// The typecode for Sapling data items.
|
||||||
|
pub const SAPLING: Typecode = Typecode::Data(DataTypecode::Sapling);
|
||||||
|
/// The typecode for Orchard data items.
|
||||||
|
pub const ORCHARD: Typecode = Typecode::Data(DataTypecode::Orchard);
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<u32> for Typecode {
|
impl TryFrom<u32> for Typecode {
|
||||||
type Error = ParseError;
|
type Error = ParseError;
|
||||||
|
|
||||||
fn try_from(typecode: u32) -> Result<Self, Self::Error> {
|
fn try_from(typecode: u32) -> Result<Self, Self::Error> {
|
||||||
match typecode {
|
DataTypecode::try_from(typecode)
|
||||||
0x00 => Ok(Typecode::P2pkh),
|
.map_or_else(
|
||||||
0x01 => Ok(Typecode::P2sh),
|
|()| MetadataTypecode::try_from(typecode).map(Typecode::Metadata),
|
||||||
0x02 => Ok(Typecode::Sapling),
|
|t| Ok(Typecode::Data(t)),
|
||||||
0x03 => Ok(Typecode::Orchard),
|
)
|
||||||
0x04..=0x02000000 => Ok(Typecode::Unknown(typecode)),
|
.map_err(|()| ParseError::InvalidTypecodeValue(typecode))
|
||||||
0x02000001..=u32::MAX => Err(ParseError::InvalidTypecodeValue(typecode as u64)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Typecode> for u32 {
|
impl From<Typecode> for u32 {
|
||||||
fn from(t: Typecode) -> Self {
|
fn from(t: Typecode) -> Self {
|
||||||
match t {
|
match t {
|
||||||
Typecode::P2pkh => 0x00,
|
Typecode::Data(tc) => tc.into(),
|
||||||
Typecode::P2sh => 0x01,
|
Typecode::Metadata(tc) => tc.into(),
|
||||||
Typecode::Sapling => 0x02,
|
|
||||||
Typecode::Orchard => 0x03,
|
|
||||||
Typecode::Unknown(typecode) => typecode,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<Typecode> for usize {
|
impl TryFrom<Typecode> for usize {
|
||||||
type Error = TryFromIntError;
|
type Error = TryFromIntError;
|
||||||
|
|
||||||
fn try_from(t: Typecode) -> Result<Self, Self::Error> {
|
fn try_from(t: Typecode) -> Result<Self, Self::Error> {
|
||||||
u32::from(t).try_into()
|
u32::from(t).try_into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Typecode {
|
/// An enumeration of known Unified Metadata Item types.
|
||||||
fn is_transparent(&self) -> bool {
|
///
|
||||||
// Unknown typecodes are treated as not transparent for the purpose of disallowing
|
/// Unknown MUST-understand metadata items are NOT represented using this type, as the presence of
|
||||||
// only-transparent UAs, which can be represented with existing address encodings.
|
/// an unrecognized metadata item with a typecode in the `MUST-understand` range will result in a
|
||||||
matches!(self, Typecode::P2pkh | Typecode::P2sh)
|
/// parse failure, instead of the construction of a metadata item.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum MetadataItem {
|
||||||
|
/// The expiry height for a Unified Address or Unified Viewing Key
|
||||||
|
ExpiryHeight(u32),
|
||||||
|
/// The expiry time for a Unified Address or Unified Viewing Key
|
||||||
|
ExpiryTime(u64),
|
||||||
|
/// A Metadata Item with an unrecognized Typecode. MUST-understand metadata items are NOT
|
||||||
|
/// represented using this type, as the presence of an unrecognized metadata item with a
|
||||||
|
/// typecode in the `MUST-understand` range will result in a parse failure.
|
||||||
|
Unknown { typecode: u32, data: Vec<u8> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetadataItem {
|
||||||
|
/// Parse a metadata item for the specified metadata typecode from the provided bytes.
|
||||||
|
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)
|
||||||
|
.map_err(|_| {
|
||||||
|
ParseError::InvalidEncoding(
|
||||||
|
"Expiry height must be a 32-bit little-endian value.".to_string(),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
(R1, ExpiryTime) => data
|
||||||
|
.try_into()
|
||||||
|
.map(u64::from_le_bytes)
|
||||||
|
.map(MetadataItem::ExpiryTime)
|
||||||
|
.map_err(|_| {
|
||||||
|
ParseError::InvalidEncoding(
|
||||||
|
"Expiry time must be a 64-bit little-endian value.".to_string(),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
(R0, ExpiryHeight | ExpiryTime) => Err(ParseError::NotUnderstood(typecode.into())),
|
||||||
|
(R0 | R1, MustUnderstand(tc)) => Err(ParseError::NotUnderstood(tc)),
|
||||||
|
// This implementation treats the 0xC0..OxFD range as unknown metadata for both R0 and
|
||||||
|
// R1, as no typecodes were specified in this range for R0 and were "reclaimed" as
|
||||||
|
// metadata codes by ZIP 316 at the time R1 was introduced.
|
||||||
|
(R0 | R1, Unknown(typecode)) => Ok(MetadataItem::Unknown {
|
||||||
|
typecode,
|
||||||
|
data: data.to_vec(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the typecode for this metadata item.
|
||||||
|
pub fn typecode(&self) -> MetadataTypecode {
|
||||||
|
match self {
|
||||||
|
MetadataItem::ExpiryHeight(_) => MetadataTypecode::ExpiryHeight,
|
||||||
|
MetadataItem::ExpiryTime(_) => MetadataTypecode::ExpiryTime,
|
||||||
|
MetadataItem::Unknown { typecode, .. } => MetadataTypecode::Unknown(*typecode),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the raw bytes of this metadata item.
|
||||||
|
pub fn data(&self) -> Vec<u8> {
|
||||||
|
match self {
|
||||||
|
MetadataItem::ExpiryHeight(h) => h.to_le_bytes().to_vec(),
|
||||||
|
MetadataItem::ExpiryTime(t) => t.to_le_bytes().to_vec(),
|
||||||
|
MetadataItem::Unknown { data, .. } => data.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Unified Encoding Item.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Item<T> {
|
||||||
|
/// A data item; either a receiver (for Unified Addresses) or a key (for Unified Viewing Keys)
|
||||||
|
Data(T),
|
||||||
|
/// A metadata item.
|
||||||
|
Metadata(MetadataItem),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: private::SealedDataItem> Item<T> {
|
||||||
|
/// Returns the typecode for this item.
|
||||||
|
pub fn typecode(&self) -> Typecode {
|
||||||
|
match self {
|
||||||
|
Item::Data(d) => Typecode::Data(d.typecode()),
|
||||||
|
Item::Metadata(m) => Typecode::Metadata(m.typecode()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The total ordering over items by their typecodes, used for encoding as specified
|
||||||
|
/// in [ZIP 316](https://zips.z.cash/zip-0316#encoding-of-unified-addresses)
|
||||||
|
pub fn encoding_order(a: &Self, b: &Self) -> cmp::Ordering {
|
||||||
|
u32::from(a.typecode()).cmp(&u32::from(b.typecode()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the raw binary representation of the data for this item.
|
||||||
|
pub fn data(&self) -> Vec<u8> {
|
||||||
|
match self {
|
||||||
|
Item::Data(d) => d.data().to_vec(),
|
||||||
|
Item::Metadata(m) => m.data(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this item is a transparent receiver or key.
|
||||||
|
pub fn is_transparent_data_item(&self) -> bool {
|
||||||
|
self.typecode() == Typecode::P2PKH || self.typecode() == Typecode::P2SH
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +318,7 @@ pub enum ParseError {
|
||||||
/// The unified container contains a duplicated typecode.
|
/// The unified container contains a duplicated typecode.
|
||||||
DuplicateTypecode(Typecode),
|
DuplicateTypecode(Typecode),
|
||||||
/// The parsed typecode exceeds the maximum allowed CompactSize value.
|
/// The parsed typecode exceeds the maximum allowed CompactSize value.
|
||||||
InvalidTypecodeValue(u64),
|
InvalidTypecodeValue(u32),
|
||||||
/// The string is an invalid encoding.
|
/// The string is an invalid encoding.
|
||||||
InvalidEncoding(String),
|
InvalidEncoding(String),
|
||||||
/// The items in the unified container are not in typecode order.
|
/// The items in the unified container are not in typecode order.
|
||||||
|
@ -136,6 +329,8 @@ pub enum ParseError {
|
||||||
NotUnified,
|
NotUnified,
|
||||||
/// The Bech32m string has an unrecognized human-readable prefix.
|
/// The Bech32m string has an unrecognized human-readable prefix.
|
||||||
UnknownPrefix(String),
|
UnknownPrefix(String),
|
||||||
|
/// A `MUST-understand` metadata item was not recognized.
|
||||||
|
NotUnderstood(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ParseError {
|
impl fmt::Display for ParseError {
|
||||||
|
@ -151,67 +346,93 @@ impl fmt::Display for ParseError {
|
||||||
ParseError::UnknownPrefix(s) => {
|
ParseError::UnknownPrefix(s) => {
|
||||||
write!(f, "Unrecognized Bech32m human-readable prefix: {}", s)
|
write!(f, "Unrecognized Bech32m human-readable prefix: {}", s)
|
||||||
}
|
}
|
||||||
|
ParseError::NotUnderstood(tc) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"MUST-understand metadata item with typecode {} was not recognized; please upgrade.",
|
||||||
|
tc
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error 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 {
|
pub(crate) mod private {
|
||||||
use super::{ParseError, Typecode, PADDING_LEN};
|
use super::{DataTypecode, ParseError, Revision, Typecode, PADDING_LEN};
|
||||||
use crate::Network;
|
use crate::{
|
||||||
|
unified::{Item, MetadataItem},
|
||||||
|
Network,
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
cmp,
|
|
||||||
convert::{TryFrom, TryInto},
|
convert::{TryFrom, TryInto},
|
||||||
io::Write,
|
io::Write,
|
||||||
};
|
};
|
||||||
use zcash_encoding::CompactSize;
|
use zcash_encoding::CompactSize;
|
||||||
|
|
||||||
/// A raw address or viewing key.
|
/// A raw address or viewing key.
|
||||||
pub trait SealedItem: for<'a> TryFrom<(u32, &'a [u8]), Error = ParseError> + Clone {
|
pub trait SealedDataItem: Clone {
|
||||||
fn typecode(&self) -> Typecode;
|
/// Parse a data item for the specified data typecode from the provided bytes.
|
||||||
|
fn parse(tc: DataTypecode, value: &[u8]) -> Result<Self, ParseError>;
|
||||||
|
|
||||||
|
/// Returns the typecode of this data item.
|
||||||
|
fn typecode(&self) -> DataTypecode;
|
||||||
|
|
||||||
|
/// Returns the raw bytes of this data item.
|
||||||
fn data(&self) -> &[u8];
|
fn data(&self) -> &[u8];
|
||||||
|
|
||||||
fn preference_order(a: &Self, b: &Self) -> cmp::Ordering {
|
|
||||||
match Typecode::preference_order(&a.typecode(), &b.typecode()) {
|
|
||||||
cmp::Ordering::Equal => a.data().cmp(b.data()),
|
|
||||||
res => res,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn encoding_order(a: &Self, b: &Self) -> cmp::Ordering {
|
|
||||||
match Typecode::encoding_order(&a.typecode(), &b.typecode()) {
|
|
||||||
cmp::Ordering::Equal => a.data().cmp(b.data()),
|
|
||||||
res => res,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Unified Container containing addresses or viewing keys.
|
/// A Unified Container containing addresses or viewing keys.
|
||||||
pub trait SealedContainer: super::Container + std::marker::Sized {
|
pub trait SealedContainer: super::Container + std::marker::Sized {
|
||||||
const MAINNET: &'static str;
|
const MAINNET_R0: &'static str;
|
||||||
const TESTNET: &'static str;
|
const TESTNET_R0: &'static str;
|
||||||
const REGTEST: &'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
|
/// Implementations of this method should act as unchecked constructors
|
||||||
/// of the container type; the caller is guaranteed to check the
|
/// of the container type; the caller is guaranteed to check the
|
||||||
/// general invariants that apply to all unified containers.
|
/// general invariants that apply to all unified containers.
|
||||||
fn from_inner(items: Vec<Self::Item>) -> Self;
|
fn from_inner(revision: Revision, items: Vec<Item<Self::DataItem>>) -> Self;
|
||||||
|
|
||||||
fn network_hrp(network: &Network) -> &'static str {
|
fn network_hrp(revision: Revision, network: &Network) -> &'static str {
|
||||||
match network {
|
match (revision, network) {
|
||||||
Network::Main => Self::MAINNET,
|
(Revision::R0, Network::Main) => Self::MAINNET_R0,
|
||||||
Network::Test => Self::TESTNET,
|
(Revision::R0, Network::Test) => Self::TESTNET_R0,
|
||||||
Network::Regtest => Self::REGTEST,
|
(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> {
|
fn hrp_network(hrp: &str) -> Option<Network> {
|
||||||
if hrp == Self::MAINNET {
|
if hrp == Self::MAINNET_R0 || hrp == Self::MAINNET_R1 {
|
||||||
Some(Network::Main)
|
Some(Network::Main)
|
||||||
} else if hrp == Self::TESTNET {
|
} else if hrp == Self::TESTNET_R0 || hrp == Self::TESTNET_R1 {
|
||||||
Some(Network::Test)
|
Some(Network::Test)
|
||||||
} else if hrp == Self::REGTEST {
|
} else if hrp == Self::REGTEST_R0 || hrp == Self::REGTEST_R1 {
|
||||||
Some(Network::Regtest)
|
Some(Network::Regtest)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -227,7 +448,7 @@ pub(crate) mod private {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
CompactSize::write(&mut writer, data.len()).unwrap();
|
CompactSize::write(&mut writer, data.len()).unwrap();
|
||||||
writer.write_all(data).unwrap();
|
writer.write_all(&data).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,10 +469,15 @@ pub(crate) mod private {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the items of the unified container.
|
/// Parse the items of the unified container.
|
||||||
fn parse_items<T: Into<Vec<u8>>>(hrp: &str, buf: T) -> Result<Vec<Self::Item>, ParseError> {
|
#[allow(clippy::type_complexity)]
|
||||||
fn read_receiver<R: SealedItem>(
|
fn parse_items<T: Into<Vec<u8>>>(
|
||||||
|
hrp: &str,
|
||||||
|
buf: T,
|
||||||
|
) -> Result<(Revision, Vec<Item<Self::DataItem>>), ParseError> {
|
||||||
|
fn read_item<R: SealedDataItem>(
|
||||||
|
revision: Revision,
|
||||||
mut cursor: &mut std::io::Cursor<&[u8]>,
|
mut cursor: &mut std::io::Cursor<&[u8]>,
|
||||||
) -> Result<R, ParseError> {
|
) -> Result<Item<R>, ParseError> {
|
||||||
let typecode = CompactSize::read(&mut cursor)
|
let typecode = CompactSize::read(&mut cursor)
|
||||||
.map(|v| u32::try_from(v).expect("CompactSize::read enforces MAX_SIZE limit"))
|
.map(|v| u32::try_from(v).expect("CompactSize::read enforces MAX_SIZE limit"))
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
@ -279,12 +505,18 @@ pub(crate) mod private {
|
||||||
length
|
length
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
let result = R::try_from((
|
// The "as usize" casts cannot change the values, because both
|
||||||
typecode,
|
// cursor.position() and addr_end are u64 values <= buf.len()
|
||||||
&buf[cursor.position() as usize..addr_end as usize],
|
// which is usize.
|
||||||
));
|
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(revision, tc, data)?)
|
||||||
|
}
|
||||||
|
};
|
||||||
cursor.set_position(addr_end);
|
cursor.set_position(addr_end);
|
||||||
result
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here we allocate if necessary to get a mutable Vec<u8> to unjumble.
|
// Here we allocate if necessary to get a mutable Vec<u8> to unjumble.
|
||||||
|
@ -308,22 +540,27 @@ 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 cursor = std::io::Cursor::new(encoded);
|
||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
while cursor.position() < encoded.len().try_into().unwrap() {
|
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());
|
assert_eq!(cursor.position(), encoded.len().try_into().unwrap());
|
||||||
|
|
||||||
Ok(result)
|
Ok((revision, result))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A private function that constructs a unified container with the
|
/// A private function that constructs a unified container with the
|
||||||
/// specified items, which must be in ascending typecode order.
|
/// specified items, which must be in ascending typecode order.
|
||||||
fn try_from_items_internal(items: Vec<Self::Item>) -> Result<Self, ParseError> {
|
fn try_from_items_internal(
|
||||||
assert!(u32::from(Typecode::P2sh) == u32::from(Typecode::P2pkh) + 1);
|
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;
|
|
||||||
let mut prev_code = None; // less than any Some
|
let mut prev_code = None; // less than any Some
|
||||||
for item in &items {
|
for item in &items {
|
||||||
let t = item.typecode();
|
let t = item.typecode();
|
||||||
|
@ -332,47 +569,46 @@ pub(crate) mod private {
|
||||||
return Err(ParseError::InvalidTypecodeOrder);
|
return Err(ParseError::InvalidTypecodeOrder);
|
||||||
} else if t_code == prev_code {
|
} else if t_code == prev_code {
|
||||||
return Err(ParseError::DuplicateTypecode(t));
|
return Err(ParseError::DuplicateTypecode(t));
|
||||||
} else if t == Typecode::P2sh && prev_code == Some(u32::from(Typecode::P2pkh)) {
|
} else if t == Typecode::P2SH && prev_code == Some(u32::from(DataTypecode::P2pkh)) {
|
||||||
// P2pkh and P2sh can only be in that order and next to each other,
|
// P2pkh and P2sh can only be in that order and next to each other,
|
||||||
// otherwise we would detect an out-of-order or duplicate typecode.
|
// otherwise we would detect an out-of-order or duplicate typecode.
|
||||||
return Err(ParseError::BothP2phkAndP2sh);
|
return Err(ParseError::BothP2phkAndP2sh);
|
||||||
} else {
|
} else {
|
||||||
prev_code = t_code;
|
prev_code = t_code;
|
||||||
only_transparent = only_transparent && t.is_transparent();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if only_transparent {
|
// All checks pass!
|
||||||
Err(ParseError::OnlyTransparent)
|
Ok(Self::from_inner(revision, items))
|
||||||
} else {
|
|
||||||
// All checks pass!
|
|
||||||
Ok(Self::from_inner(items))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_internal<T: Into<Vec<u8>>>(hrp: &str, buf: T) -> Result<Self, ParseError> {
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use private::SealedItem;
|
use private::SealedDataItem;
|
||||||
|
|
||||||
/// Trait providing common encoding and decoding logic for Unified containers.
|
/// Trait providing common encoding and decoding logic for Unified containers.
|
||||||
pub trait Encoding: private::SealedContainer {
|
pub trait Encoding: private::SealedContainer {
|
||||||
/// Constructs a value of a unified container type from a vector
|
/// Constructs a value of a unified container type from a vector of container
|
||||||
/// of container items, sorted according to typecode as specified
|
/// items. These items will be sorted according to typecode as specified in ZIP
|
||||||
/// in ZIP 316.
|
/// 316, so this method is not necessarily round-trip compatible with
|
||||||
|
/// [`Container::items_as_parsed`].
|
||||||
///
|
///
|
||||||
/// This function will return an error in the case that the following ZIP 316
|
/// This function will return an error in the case that the following ZIP 316
|
||||||
/// invariants concerning the composition of a unified container are
|
/// invariants concerning the composition of a unified container are
|
||||||
/// violated:
|
/// violated:
|
||||||
/// * the item list may not contain two items having the same typecode
|
/// * 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.
|
/// * the item list may not contain both P2PKH and P2SH items.
|
||||||
fn try_from_items(mut items: Vec<Self::Item>) -> Result<Self, ParseError> {
|
fn try_from_items(
|
||||||
items.sort_unstable_by(Self::Item::encoding_order);
|
revision: Revision,
|
||||||
Self::try_from_items_internal(items)
|
mut items: Vec<Item<Self::DataItem>>,
|
||||||
|
) -> Result<Self, ParseError> {
|
||||||
|
items.sort_unstable_by(Item::encoding_order);
|
||||||
|
Self::try_from_items_internal(revision, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes a unified container from its string representation, preserving
|
/// Decodes a unified container from its string representation, preserving
|
||||||
|
@ -399,7 +635,7 @@ pub trait Encoding: private::SealedContainer {
|
||||||
/// ordering of the contained items such that it correctly obeys round-trip
|
/// ordering of the contained items such that it correctly obeys round-trip
|
||||||
/// serialization invariants.
|
/// serialization invariants.
|
||||||
fn encode(&self, network: &Network) -> String {
|
fn encode(&self, network: &Network) -> String {
|
||||||
let hrp = Self::network_hrp(network);
|
let hrp = Self::network_hrp(self.revision(), network);
|
||||||
bech32::encode(
|
bech32::encode(
|
||||||
hrp,
|
hrp,
|
||||||
self.to_jumbled_bytes(hrp).to_base32(),
|
self.to_jumbled_bytes(hrp).to_base32(),
|
||||||
|
@ -411,20 +647,13 @@ pub trait Encoding: private::SealedContainer {
|
||||||
|
|
||||||
/// Trait for for Unified containers, that exposes the items within them.
|
/// Trait for for Unified containers, that exposes the items within them.
|
||||||
pub trait Container {
|
pub trait Container {
|
||||||
/// The type of item in this unified container.
|
/// The type of data items in this unified container.
|
||||||
type Item: SealedItem;
|
type DataItem: SealedDataItem;
|
||||||
|
|
||||||
/// Returns the items contained within this container, sorted in preference order.
|
/// Returns the items in encoding order.
|
||||||
fn items(&self) -> Vec<Self::Item> {
|
fn items_as_parsed(&self) -> &[Item<Self::DataItem>];
|
||||||
let mut items = self.items_as_parsed().to_vec();
|
|
||||||
// Unstable sorting is fine, because all items are guaranteed by construction
|
|
||||||
// to have distinct typecodes.
|
|
||||||
items.sort_unstable_by(Self::Item::preference_order);
|
|
||||||
items
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the items in the order they were parsed from the string encoding.
|
/// Returns the revision of the ZIP 316 standard that this unified container
|
||||||
///
|
/// conforms to.
|
||||||
/// This API is for advanced usage; in most cases you should use `Self::items`.
|
fn revision(&self) -> Revision;
|
||||||
fn items_as_parsed(&self) -> &[Self::Item];
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use zcash_protocol::{PoolType, ShieldedProtocol};
|
use zcash_protocol::{PoolType, ShieldedProtocol};
|
||||||
|
|
||||||
use super::{private::SealedItem, ParseError, Typecode};
|
use super::{private::SealedDataItem, DataTypecode, Item, ParseError, Revision};
|
||||||
|
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::{cmp, convert::TryInto};
|
||||||
|
|
||||||
/// The set of known Receivers for Unified Addresses.
|
/// The enumeration of Unified Address Receivers of known types.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum Receiver {
|
pub enum Receiver {
|
||||||
Orchard([u8; 43]),
|
Orchard([u8; 43]),
|
||||||
|
@ -14,34 +14,39 @@ pub enum Receiver {
|
||||||
Unknown { typecode: u32, data: Vec<u8> },
|
Unknown { typecode: u32, data: Vec<u8> },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<(u32, &[u8])> for Receiver {
|
impl Receiver {
|
||||||
type Error = ParseError;
|
fn preference_order(a: &Self, b: &Self) -> cmp::Ordering {
|
||||||
|
DataTypecode::preference_order(&a.typecode(), &b.typecode())
|
||||||
fn try_from((typecode, addr): (u32, &[u8])) -> Result<Self, Self::Error> {
|
|
||||||
match typecode.try_into()? {
|
|
||||||
Typecode::P2pkh => addr.try_into().map(Receiver::P2pkh),
|
|
||||||
Typecode::P2sh => addr.try_into().map(Receiver::P2sh),
|
|
||||||
Typecode::Sapling => addr.try_into().map(Receiver::Sapling),
|
|
||||||
Typecode::Orchard => addr.try_into().map(Receiver::Orchard),
|
|
||||||
Typecode::Unknown(_) => Ok(Receiver::Unknown {
|
|
||||||
typecode,
|
|
||||||
data: addr.to_vec(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
.map_err(|e| {
|
|
||||||
ParseError::InvalidEncoding(format!("Invalid address for typecode {}: {}", typecode, e))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SealedItem for Receiver {
|
impl SealedDataItem for Receiver {
|
||||||
fn typecode(&self) -> Typecode {
|
fn parse(typecode: DataTypecode, data: &[u8]) -> Result<Self, ParseError> {
|
||||||
|
match typecode {
|
||||||
|
DataTypecode::P2pkh => data.try_into().map(Receiver::P2pkh),
|
||||||
|
DataTypecode::P2sh => data.try_into().map(Receiver::P2sh),
|
||||||
|
DataTypecode::Sapling => data.try_into().map(Receiver::Sapling),
|
||||||
|
DataTypecode::Orchard => data.try_into().map(Receiver::Orchard),
|
||||||
|
DataTypecode::Unknown(typecode) => Ok(Receiver::Unknown {
|
||||||
|
typecode,
|
||||||
|
data: data.to_vec(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
.map_err(|e| {
|
||||||
|
ParseError::InvalidEncoding(format!(
|
||||||
|
"Invalid address for typecode {:?}: {:?}",
|
||||||
|
typecode, e
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typecode(&self) -> DataTypecode {
|
||||||
match self {
|
match self {
|
||||||
Receiver::P2pkh(_) => Typecode::P2pkh,
|
Receiver::P2pkh(_) => DataTypecode::P2pkh,
|
||||||
Receiver::P2sh(_) => Typecode::P2sh,
|
Receiver::P2sh(_) => DataTypecode::P2sh,
|
||||||
Receiver::Sapling(_) => Typecode::Sapling,
|
Receiver::Sapling(_) => DataTypecode::Sapling,
|
||||||
Receiver::Orchard(_) => Typecode::Orchard,
|
Receiver::Orchard(_) => DataTypecode::Orchard,
|
||||||
Receiver::Unknown { typecode, .. } => Typecode::Unknown(*typecode),
|
Receiver::Unknown { typecode, .. } => DataTypecode::Unknown(*typecode),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +69,7 @@ impl SealedItem for Receiver {
|
||||||
/// # use std::convert::Infallible;
|
/// # use std::convert::Infallible;
|
||||||
/// # use std::error::Error;
|
/// # use std::error::Error;
|
||||||
/// use zcash_address::{
|
/// use zcash_address::{
|
||||||
/// unified::{self, Container, Encoding},
|
/// unified::{self, Container, Encoding, Item, Revision},
|
||||||
/// ConversionError, TryFromRawAddress, ZcashAddress,
|
/// ConversionError, TryFromRawAddress, ZcashAddress,
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
|
@ -92,74 +97,132 @@ impl SealedItem for Receiver {
|
||||||
///
|
///
|
||||||
/// // We can obtain the receivers for the UA in preference order
|
/// // We can obtain the receivers for the UA in preference order
|
||||||
/// // (the order in which wallets should prefer to use them):
|
/// // (the order in which wallets should prefer to use them):
|
||||||
/// let receivers: Vec<unified::Receiver> = ua.items();
|
/// let receivers: Vec<unified::Receiver> = ua.receivers();
|
||||||
///
|
///
|
||||||
/// // And we can create the UA from a list of receivers:
|
/// // And we can create the UA from a list of receivers:
|
||||||
/// let new_ua = unified::Address::try_from_items(receivers)?;
|
/// let new_ua = unified::Address::try_from_items(Revision::R0, receivers.into_iter().map(Item::Data).collect())?;
|
||||||
/// assert_eq!(new_ua, ua);
|
/// assert_eq!(new_ua, ua);
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct Address(pub(crate) Vec<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.
|
||||||
|
///
|
||||||
|
/// The receiver for a wallet to send to can safely be chosen by selecting the first receiver
|
||||||
|
/// of a type that wallet supports from the result.
|
||||||
|
pub fn receivers(&self) -> Vec<Receiver> {
|
||||||
|
let mut result = self
|
||||||
|
.receivers
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| match item {
|
||||||
|
Item::Data(r) => Some(r.clone()),
|
||||||
|
Item::Metadata(_) => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<Receiver>>();
|
||||||
|
result.sort_unstable_by(Receiver::preference_order);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Address {
|
impl Address {
|
||||||
/// Returns whether this address has the ability to receive transfers of the given pool type.
|
/// 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 {
|
pub fn has_receiver_of_type(&self, pool_type: PoolType) -> bool {
|
||||||
self.0.iter().any(|r| match r {
|
self.receivers.iter().any(|item| match item {
|
||||||
Receiver::Orchard(_) => pool_type == PoolType::Shielded(ShieldedProtocol::Orchard),
|
Item::Data(Receiver::Orchard(_)) => {
|
||||||
Receiver::Sapling(_) => pool_type == PoolType::Shielded(ShieldedProtocol::Sapling),
|
pool_type == PoolType::Shielded(ShieldedProtocol::Orchard)
|
||||||
Receiver::P2pkh(_) | Receiver::P2sh(_) => pool_type == PoolType::Transparent,
|
}
|
||||||
Receiver::Unknown { .. } => false,
|
Item::Data(Receiver::Sapling(_)) => {
|
||||||
|
pool_type == PoolType::Shielded(ShieldedProtocol::Sapling)
|
||||||
|
}
|
||||||
|
Item::Data(Receiver::P2pkh(_)) | Item::Data(Receiver::P2sh(_)) => {
|
||||||
|
pool_type == PoolType::Transparent
|
||||||
|
}
|
||||||
|
Item::Data(Receiver::Unknown { .. }) => false,
|
||||||
|
Item::Metadata(_) => false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether this address contains the given receiver.
|
/// Returns whether this address contains the given receiver.
|
||||||
pub fn contains_receiver(&self, receiver: &Receiver) -> bool {
|
pub fn contains_receiver(&self, receiver: &Receiver) -> bool {
|
||||||
self.0.contains(receiver)
|
self.receivers
|
||||||
|
.iter()
|
||||||
|
.any(|item| matches!(item, Item::Data(r) if r == receiver))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether this address can receive a memo.
|
/// Returns whether this address can receive a memo.
|
||||||
pub fn can_receive_memo(&self) -> bool {
|
pub fn can_receive_memo(&self) -> bool {
|
||||||
self.0
|
self.receivers.iter().any(|r| {
|
||||||
.iter()
|
matches!(
|
||||||
.any(|r| matches!(r, Receiver::Sapling(_) | Receiver::Orchard(_)))
|
r,
|
||||||
|
Item::Data(Receiver::Sapling(_)) | Item::Data(Receiver::Orchard(_))
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::private::SealedContainer for 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].
|
/// Defined in [ZIP 316][zip-0316].
|
||||||
///
|
///
|
||||||
/// [zip-0316]: https://zips.z.cash/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].
|
/// Defined in [ZIP 316][zip-0316].
|
||||||
///
|
///
|
||||||
/// [zip-0316]: https://zips.z.cash/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.
|
/// The HRP for a Bech32m-encoded regtest Revision 0 Unified Address.
|
||||||
const REGTEST: &'static str = "uregtest";
|
const REGTEST_R0: &'static str = "uregtest";
|
||||||
|
|
||||||
fn from_inner(receivers: Vec<Self::Item>) -> Self {
|
/// The HRP for a Bech32m-encoded mainnet Revision 1 Unified Address.
|
||||||
Self(receivers)
|
///
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Encoding for Address {}
|
impl super::Encoding for Address {}
|
||||||
impl super::Container for Address {
|
impl super::Container for Address {
|
||||||
type Item = Receiver;
|
type DataItem = Receiver;
|
||||||
|
|
||||||
fn items_as_parsed(&self) -> &[Receiver] {
|
fn items_as_parsed(&self) -> &[Item<Receiver>] {
|
||||||
&self.0
|
&self.receivers
|
||||||
|
}
|
||||||
|
|
||||||
|
fn revision(&self) -> Revision {
|
||||||
|
self.revision
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "test-dependencies")]
|
#[cfg(any(test, feature = "test-dependencies"))]
|
||||||
pub mod testing {
|
pub mod testing {
|
||||||
use proptest::{
|
use proptest::{
|
||||||
array::{uniform11, uniform20, uniform32},
|
array::{uniform11, uniform20, uniform32},
|
||||||
|
@ -168,10 +231,10 @@ pub mod testing {
|
||||||
sample::select,
|
sample::select,
|
||||||
strategy::Strategy,
|
strategy::Strategy,
|
||||||
};
|
};
|
||||||
use zcash_encoding::MAX_COMPACT_SIZE;
|
|
||||||
|
|
||||||
use super::{Address, Receiver};
|
use super::{Address, Receiver};
|
||||||
use crate::unified::Typecode;
|
use crate::unified::{DataTypecode, Item, Revision};
|
||||||
|
use zcash_encoding::MAX_COMPACT_SIZE;
|
||||||
|
|
||||||
prop_compose! {
|
prop_compose! {
|
||||||
fn uniform43()(a in uniform11(0u8..), b in uniform32(0u8..)) -> [u8; 43] {
|
fn uniform43()(a in uniform11(0u8..), b in uniform32(0u8..)) -> [u8; 43] {
|
||||||
|
@ -183,61 +246,65 @@ pub mod testing {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A strategy to generate an arbitrary transparent typecode.
|
/// A strategy to generate an arbitrary transparent typecode.
|
||||||
pub fn arb_transparent_typecode() -> impl Strategy<Value = Typecode> {
|
fn arb_transparent_typecode() -> impl Strategy<Value = DataTypecode> {
|
||||||
select(vec![Typecode::P2pkh, Typecode::P2sh])
|
select(vec![DataTypecode::P2pkh, DataTypecode::P2sh])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A strategy to generate an arbitrary shielded (Sapling, Orchard, or unknown) typecode.
|
/// A strategy to generate an arbitrary shielded (Sapling, Orchard, or unknown) typecode.
|
||||||
pub fn arb_shielded_typecode() -> impl Strategy<Value = Typecode> {
|
fn arb_shielded_typecode() -> impl Strategy<Value = DataTypecode> {
|
||||||
prop_oneof![
|
prop_oneof![
|
||||||
Just(Typecode::Sapling),
|
Just(DataTypecode::Sapling),
|
||||||
Just(Typecode::Orchard),
|
Just(DataTypecode::Orchard),
|
||||||
((<u32>::from(Typecode::Orchard) + 1)..MAX_COMPACT_SIZE).prop_map(Typecode::Unknown)
|
((<u32>::from(DataTypecode::Orchard) + 1)..MAX_COMPACT_SIZE)
|
||||||
|
.prop_map(DataTypecode::Unknown)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A strategy to generate an arbitrary valid set of typecodes without
|
/// A strategy to generate an arbitrary valid set of typecodes without
|
||||||
/// duplication and containing only one of P2sh and P2pkh transparent
|
/// duplication and containing only one of P2sh and P2pkh transparent
|
||||||
/// typecodes. The resulting vector will be sorted in encoding order.
|
/// typecodes. The resulting vector will be sorted in encoding order.
|
||||||
pub fn arb_typecodes() -> impl Strategy<Value = Vec<Typecode>> {
|
fn arb_typecodes() -> impl Strategy<Value = Vec<DataTypecode>> {
|
||||||
prop::option::of(arb_transparent_typecode()).prop_flat_map(|transparent| {
|
prop::option::of(arb_transparent_typecode()).prop_flat_map(|transparent| {
|
||||||
prop::collection::hash_set(arb_shielded_typecode(), 1..4).prop_map(move |xs| {
|
prop::collection::hash_set(arb_shielded_typecode(), 1..4)
|
||||||
let mut typecodes: Vec<_> = xs.into_iter().chain(transparent).collect();
|
.prop_map(move |xs| xs.into_iter().chain(transparent).collect::<Vec<_>>())
|
||||||
typecodes.sort_unstable_by(Typecode::encoding_order);
|
|
||||||
typecodes
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates an arbitrary Unified address containing receivers corresponding to the provided
|
/// A strategy to generate a vector of unified address receivers containing random data. The
|
||||||
/// set of typecodes. The receivers of this address are likely to not represent valid protocol
|
/// resulting receivers may not be valid according to protocol rules; this generator is only
|
||||||
/// receivers, and should only be used for testing parsing and/or encoding functions that do
|
/// intended for use in testing parsing and serialization.
|
||||||
/// not concern themselves with the validity of the underlying receivers.
|
fn arb_unified_address_receivers(
|
||||||
pub fn arb_unified_address_for_typecodes(
|
typecodes: Vec<DataTypecode>,
|
||||||
typecodes: Vec<Typecode>,
|
|
||||||
) -> impl Strategy<Value = Vec<Receiver>> {
|
) -> impl Strategy<Value = Vec<Receiver>> {
|
||||||
typecodes
|
typecodes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|tc| match tc {
|
.map(|tc| match tc {
|
||||||
Typecode::P2pkh => uniform20(0u8..).prop_map(Receiver::P2pkh).boxed(),
|
DataTypecode::P2pkh => uniform20(0u8..).prop_map(Receiver::P2pkh).boxed(),
|
||||||
Typecode::P2sh => uniform20(0u8..).prop_map(Receiver::P2sh).boxed(),
|
DataTypecode::P2sh => uniform20(0u8..).prop_map(Receiver::P2sh).boxed(),
|
||||||
Typecode::Sapling => uniform43().prop_map(Receiver::Sapling).boxed(),
|
DataTypecode::Sapling => uniform43().prop_map(Receiver::Sapling).boxed(),
|
||||||
Typecode::Orchard => uniform43().prop_map(Receiver::Orchard).boxed(),
|
DataTypecode::Orchard => uniform43().prop_map(Receiver::Orchard).boxed(),
|
||||||
Typecode::Unknown(typecode) => vec(any::<u8>(), 32..256)
|
DataTypecode::Unknown(typecode) => vec(any::<u8>(), 32..256)
|
||||||
.prop_map(move |data| Receiver::Unknown { typecode, data })
|
.prop_map(move |data| Receiver::Unknown { typecode, data })
|
||||||
.boxed(),
|
.boxed(),
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates an arbitrary Unified address. The receivers of this address are likely to not
|
/// A strategy to generate an arbitrary Unified Address containing only receivers, without
|
||||||
/// represent valid protocol receivers, and should only be used for testing parsing and/or
|
/// additional metadata. The items in this address will be sorted in encoding order. The
|
||||||
/// encoding functions that do not concern themselves with the validity of the underlying
|
/// receivers in the resulting address may not be valid according to protocol rules; this
|
||||||
/// receivers.
|
/// generator is only intended for use in testing parsing and serialization.
|
||||||
pub fn arb_unified_address() -> impl Strategy<Value = Address> {
|
pub fn arb_unified_address() -> impl Strategy<Value = Address> {
|
||||||
arb_typecodes()
|
arb_typecodes()
|
||||||
.prop_flat_map(arb_unified_address_for_typecodes)
|
.prop_flat_map(arb_unified_address_receivers)
|
||||||
.prop_map(Address)
|
.prop_map(|rs| {
|
||||||
|
let mut receivers = rs.into_iter().map(Item::Data).collect::<Vec<_>>();
|
||||||
|
receivers.sort_unstable_by(Item::encoding_order);
|
||||||
|
Address {
|
||||||
|
revision: Revision::R0,
|
||||||
|
receivers,
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,14 +316,14 @@ mod tests {
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
kind::unified::{private::SealedContainer, Container, Encoding},
|
kind::unified::{private::SealedContainer, Encoding},
|
||||||
unified::address::testing::arb_unified_address,
|
unified::{address::testing::arb_unified_address, Item, Revision, Typecode},
|
||||||
Network,
|
Network,
|
||||||
};
|
};
|
||||||
|
|
||||||
use proptest::{prelude::*, sample::select};
|
use proptest::{prelude::*, sample::select};
|
||||||
|
|
||||||
use super::{Address, ParseError, Receiver, Typecode};
|
use super::{Address, ParseError, Receiver};
|
||||||
|
|
||||||
proptest! {
|
proptest! {
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -285,7 +352,7 @@ mod tests {
|
||||||
0x7b, 0x28, 0x69, 0xc9, 0x84,
|
0x7b, 0x28, 0x69, 0xc9, 0x84,
|
||||||
];
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Address::parse_internal(Address::MAINNET, &invalid_padding[..]),
|
Address::parse_internal(Address::MAINNET_R0, &invalid_padding[..]),
|
||||||
Err(ParseError::InvalidEncoding(
|
Err(ParseError::InvalidEncoding(
|
||||||
"Invalid padding bytes".to_owned()
|
"Invalid padding bytes".to_owned()
|
||||||
))
|
))
|
||||||
|
@ -300,7 +367,7 @@ mod tests {
|
||||||
0x4b, 0x31, 0xee, 0x5a,
|
0x4b, 0x31, 0xee, 0x5a,
|
||||||
];
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Address::parse_internal(Address::MAINNET, &truncated_padding[..]),
|
Address::parse_internal(Address::MAINNET_R0, &truncated_padding[..]),
|
||||||
Err(ParseError::InvalidEncoding(
|
Err(ParseError::InvalidEncoding(
|
||||||
"Invalid padding bytes".to_owned()
|
"Invalid padding bytes".to_owned()
|
||||||
))
|
))
|
||||||
|
@ -325,7 +392,7 @@ mod tests {
|
||||||
0xc6, 0x5e, 0x68, 0xa2, 0x78, 0x6c, 0x9e,
|
0xc6, 0x5e, 0x68, 0xa2, 0x78, 0x6c, 0x9e,
|
||||||
];
|
];
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
Address::parse_internal(Address::MAINNET, &truncated_sapling_data[..]),
|
Address::parse_internal(Address::MAINNET_R0, &truncated_sapling_data[..]),
|
||||||
Err(ParseError::InvalidEncoding(_))
|
Err(ParseError::InvalidEncoding(_))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -338,7 +405,7 @@ mod tests {
|
||||||
0xe6, 0x70, 0x36, 0x5b, 0x7b, 0x9e,
|
0xe6, 0x70, 0x36, 0x5b, 0x7b, 0x9e,
|
||||||
];
|
];
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
Address::parse_internal(Address::MAINNET, &truncated_after_sapling_typecode[..]),
|
Address::parse_internal(Address::MAINNET_R0, &truncated_after_sapling_typecode[..]),
|
||||||
Err(ParseError::InvalidEncoding(_))
|
Err(ParseError::InvalidEncoding(_))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -347,11 +414,17 @@ mod tests {
|
||||||
fn duplicate_typecode() {
|
fn duplicate_typecode() {
|
||||||
// Construct and serialize an invalid UA. This must be done using private
|
// 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.
|
// methods, as the public API does not permit construction of such invalid values.
|
||||||
let ua = Address(vec![Receiver::Sapling([1; 43]), Receiver::Sapling([2; 43])]);
|
let ua = Address {
|
||||||
let encoded = ua.to_jumbled_bytes(Address::MAINNET);
|
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_R0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Address::parse_internal(Address::MAINNET, &encoded[..]),
|
Address::parse_internal(Address::MAINNET_R0, &encoded[..]),
|
||||||
Err(ParseError::DuplicateTypecode(Typecode::Sapling))
|
Err(ParseError::DuplicateTypecode(Typecode::SAPLING))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,11 +432,17 @@ mod tests {
|
||||||
fn p2pkh_and_p2sh() {
|
fn p2pkh_and_p2sh() {
|
||||||
// Construct and serialize an invalid UA. This must be done using private
|
// 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.
|
// methods, as the public API does not permit construction of such invalid values.
|
||||||
let ua = Address(vec![Receiver::P2pkh([0; 20]), Receiver::P2sh([0; 20])]);
|
let ua = Address {
|
||||||
let encoded = ua.to_jumbled_bytes(Address::MAINNET);
|
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_R0);
|
||||||
// ensure that decoding catches the error
|
// ensure that decoding catches the error
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Address::parse_internal(Address::MAINNET, &encoded[..]),
|
Address::parse_internal(Address::MAINNET_R0, &encoded[..]),
|
||||||
Err(ParseError::BothP2phkAndP2sh)
|
Err(ParseError::BothP2phkAndP2sh)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -372,11 +451,17 @@ mod tests {
|
||||||
fn addresses_out_of_order() {
|
fn addresses_out_of_order() {
|
||||||
// Construct and serialize an invalid UA. This must be done using private
|
// 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.
|
// methods, as the public API does not permit construction of such invalid values.
|
||||||
let ua = Address(vec![Receiver::Sapling([0; 43]), Receiver::P2pkh([0; 20])]);
|
let ua = Address {
|
||||||
let encoded = ua.to_jumbled_bytes(Address::MAINNET);
|
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_R0);
|
||||||
// ensure that decoding catches the error
|
// ensure that decoding catches the error
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Address::parse_internal(Address::MAINNET, &encoded[..]),
|
Address::parse_internal(Address::MAINNET_R0, &encoded[..]),
|
||||||
Err(ParseError::InvalidTypecodeOrder)
|
Err(ParseError::InvalidTypecodeOrder)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -395,7 +480,7 @@ mod tests {
|
||||||
// with only one of them we don't have sufficient data for F4Jumble (so we hit a
|
// with only one of them we don't have sufficient data for F4Jumble (so we hit a
|
||||||
// different error).
|
// different error).
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
Address::parse_internal(Address::MAINNET, &encoded[..]),
|
Address::parse_internal(Address::MAINNET_R0, &encoded[..]),
|
||||||
Err(ParseError::InvalidEncoding(_))
|
Err(ParseError::InvalidEncoding(_))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -403,19 +488,22 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn receivers_are_sorted() {
|
fn receivers_are_sorted() {
|
||||||
// Construct a UA with receivers in an unsorted order.
|
// Construct a UA with receivers in an unsorted order.
|
||||||
let ua = Address(vec![
|
let ua = Address {
|
||||||
Receiver::P2pkh([0; 20]),
|
revision: Revision::R0,
|
||||||
Receiver::Orchard([0; 43]),
|
receivers: vec![
|
||||||
Receiver::Unknown {
|
Item::Data(Receiver::P2pkh([0; 20])),
|
||||||
typecode: 0xff,
|
Item::Data(Receiver::Orchard([0; 43])),
|
||||||
data: vec![],
|
Item::Data(Receiver::Unknown {
|
||||||
},
|
typecode: 0xff,
|
||||||
Receiver::Sapling([0; 43]),
|
data: vec![],
|
||||||
]);
|
}),
|
||||||
|
Item::Data(Receiver::Sapling([0; 43])),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
// `Address::receivers` sorts the receivers in priority order.
|
// `Address::receivers` sorts the receivers in priority order.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ua.items(),
|
ua.receivers(),
|
||||||
vec![
|
vec![
|
||||||
Receiver::Orchard([0; 43]),
|
Receiver::Orchard([0; 43]),
|
||||||
Receiver::Sapling([0; 43]),
|
Receiver::Sapling([0; 43]),
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
private::{SealedContainer, SealedItem},
|
private::{SealedContainer, SealedDataItem},
|
||||||
Container, Encoding, ParseError, Typecode,
|
Container, DataTypecode, Encoding, Item, ParseError, Revision,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The set of known FVKs for Unified FVKs.
|
/// The set of known FVKs for Unified FVKs.
|
||||||
|
@ -39,31 +39,27 @@ pub enum Fvk {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<(u32, &[u8])> for Fvk {
|
impl SealedDataItem for Fvk {
|
||||||
type Error = ParseError;
|
fn parse(typecode: DataTypecode, data: &[u8]) -> Result<Self, ParseError> {
|
||||||
|
|
||||||
fn try_from((typecode, data): (u32, &[u8])) -> Result<Self, Self::Error> {
|
|
||||||
let data = data.to_vec();
|
let data = data.to_vec();
|
||||||
match typecode.try_into()? {
|
match typecode {
|
||||||
Typecode::P2pkh => data.try_into().map(Fvk::P2pkh),
|
DataTypecode::P2pkh => data.try_into().map(Fvk::P2pkh),
|
||||||
Typecode::P2sh => Err(data),
|
DataTypecode::P2sh => Err(data),
|
||||||
Typecode::Sapling => data.try_into().map(Fvk::Sapling),
|
DataTypecode::Sapling => data.try_into().map(Fvk::Sapling),
|
||||||
Typecode::Orchard => data.try_into().map(Fvk::Orchard),
|
DataTypecode::Orchard => data.try_into().map(Fvk::Orchard),
|
||||||
Typecode::Unknown(_) => Ok(Fvk::Unknown { typecode, data }),
|
DataTypecode::Unknown(typecode) => Ok(Fvk::Unknown { typecode, data }),
|
||||||
}
|
}
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
ParseError::InvalidEncoding(format!("Invalid fvk for typecode {}: {:?}", typecode, e))
|
ParseError::InvalidEncoding(format!("Invalid fvk for typecode {:?}: {:?}", typecode, e))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl SealedItem for Fvk {
|
fn typecode(&self) -> DataTypecode {
|
||||||
fn typecode(&self) -> Typecode {
|
|
||||||
match self {
|
match self {
|
||||||
Fvk::P2pkh(_) => Typecode::P2pkh,
|
Fvk::P2pkh(_) => DataTypecode::P2pkh,
|
||||||
Fvk::Sapling(_) => Typecode::Sapling,
|
Fvk::Sapling(_) => DataTypecode::Sapling,
|
||||||
Fvk::Orchard(_) => Typecode::Orchard,
|
Fvk::Orchard(_) => DataTypecode::Orchard,
|
||||||
Fvk::Unknown { typecode, .. } => Typecode::Unknown(*typecode),
|
Fvk::Unknown { typecode, .. } => DataTypecode::Unknown(*typecode),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +79,7 @@ impl SealedItem for Fvk {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use std::error::Error;
|
/// # use std::error::Error;
|
||||||
/// use zcash_address::unified::{self, Container, Encoding};
|
/// use zcash_address::unified::{self, Container, Encoding, Item, Revision};
|
||||||
///
|
///
|
||||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||||
/// # let ufvk_from_user = || "uview1cgrqnry478ckvpr0f580t6fsahp0a5mj2e9xl7hv2d2jd4ldzy449mwwk2l9yeuts85wjls6hjtghdsy5vhhvmjdw3jxl3cxhrg3vs296a3czazrycrr5cywjhwc5c3ztfyjdhmz0exvzzeyejamyp0cr9z8f9wj0953fzht0m4lenk94t70ruwgjxag2tvp63wn9ftzhtkh20gyre3w5s24f6wlgqxnjh40gd2lxe75sf3z8h5y2x0atpxcyf9t3em4h0evvsftluruqne6w4sm066sw0qe5y8qg423grple5fftxrqyy7xmqmatv7nzd7tcjadu8f7mqz4l83jsyxy4t8pkayytyk7nrp467ds85knekdkvnd7hqkfer8mnqd7pv";
|
/// # let ufvk_from_user = || "uview1cgrqnry478ckvpr0f580t6fsahp0a5mj2e9xl7hv2d2jd4ldzy449mwwk2l9yeuts85wjls6hjtghdsy5vhhvmjdw3jxl3cxhrg3vs296a3czazrycrr5cywjhwc5c3ztfyjdhmz0exvzzeyejamyp0cr9z8f9wj0953fzht0m4lenk94t70ruwgjxag2tvp63wn9ftzhtkh20gyre3w5s24f6wlgqxnjh40gd2lxe75sf3z8h5y2x0atpxcyf9t3em4h0evvsftluruqne6w4sm066sw0qe5y8qg423grple5fftxrqyy7xmqmatv7nzd7tcjadu8f7mqz4l83jsyxy4t8pkayytyk7nrp467ds85knekdkvnd7hqkfer8mnqd7pv";
|
||||||
|
@ -91,54 +87,72 @@ impl SealedItem for Fvk {
|
||||||
///
|
///
|
||||||
/// let (network, ufvk) = unified::Ufvk::decode(example_ufvk)?;
|
/// let (network, ufvk) = unified::Ufvk::decode(example_ufvk)?;
|
||||||
///
|
///
|
||||||
/// // We can obtain the pool-specific Full Viewing Keys for the UFVK in preference
|
/// // We can obtain the pool-specific Full Viewing Keys for the UFVK.
|
||||||
/// // order (the order in which wallets should prefer to use their corresponding
|
/// let fvks: &[Item<unified::Fvk>] = ufvk.items_as_parsed();
|
||||||
/// // address receivers):
|
|
||||||
/// let fvks: Vec<unified::Fvk> = ufvk.items();
|
|
||||||
///
|
///
|
||||||
/// // And we can create the UFVK from a list of FVKs:
|
/// // And we can create the UFVK from a list of FVKs:
|
||||||
/// let new_ufvk = unified::Ufvk::try_from_items(fvks)?;
|
/// let new_ufvk = unified::Ufvk::try_from_items(Revision::R0, fvks.to_vec())?;
|
||||||
/// assert_eq!(new_ufvk, ufvk);
|
/// assert_eq!(new_ufvk, ufvk);
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct Ufvk(pub(crate) Vec<Fvk>);
|
pub struct Ufvk {
|
||||||
|
pub(crate) revision: Revision,
|
||||||
|
pub(crate) fvks: Vec<Item<Fvk>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Container for Ufvk {
|
impl Container for Ufvk {
|
||||||
type Item = Fvk;
|
type DataItem = Fvk;
|
||||||
|
|
||||||
/// Returns the FVKs contained within this UFVK, in the order they were
|
fn items_as_parsed(&self) -> &[Item<Fvk>] {
|
||||||
/// parsed from the string encoding.
|
&self.fvks
|
||||||
///
|
}
|
||||||
/// This API is for advanced usage; in most cases you should use `Ufvk::receivers`.
|
|
||||||
fn items_as_parsed(&self) -> &[Fvk] {
|
fn revision(&self) -> Revision {
|
||||||
&self.0
|
self.revision
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encoding for Ufvk {}
|
impl Encoding for Ufvk {}
|
||||||
|
|
||||||
impl SealedContainer 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].
|
/// Defined in [ZIP 316][zip-0316].
|
||||||
///
|
///
|
||||||
/// [zip-0316]: https://zips.z.cash/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].
|
/// Defined in [ZIP 316][zip-0316].
|
||||||
///
|
///
|
||||||
/// [zip-0316]: https://zips.z.cash/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.
|
/// The HRP for a Bech32m-encoded regtest Revision 0 Unified FVK.
|
||||||
const REGTEST: &'static str = "uviewregtest";
|
const REGTEST_R0: &'static str = "uviewregtest";
|
||||||
|
|
||||||
fn from_inner(fvks: Vec<Self::Item>) -> Self {
|
/// The HRP for a Bech32m-encoded mainnet Revision 1 Unified FVK.
|
||||||
Self(fvks)
|
///
|
||||||
|
/// 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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,12 +162,10 @@ mod tests {
|
||||||
|
|
||||||
use proptest::{array::uniform1, array::uniform32, prelude::*, sample::select};
|
use proptest::{array::uniform1, array::uniform32, prelude::*, sample::select};
|
||||||
|
|
||||||
use super::{Fvk, ParseError, Typecode, Ufvk};
|
use super::{Fvk, ParseError, Ufvk};
|
||||||
use crate::{
|
use crate::{
|
||||||
kind::unified::{
|
kind::unified::{private::SealedContainer, Encoding},
|
||||||
private::{SealedContainer, SealedItem},
|
unified::{Item, Revision, Typecode},
|
||||||
Container, Encoding,
|
|
||||||
},
|
|
||||||
Network,
|
Network,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -211,9 +223,9 @@ mod tests {
|
||||||
shielded in arb_shielded_fvk(),
|
shielded in arb_shielded_fvk(),
|
||||||
transparent in prop::option::of(arb_transparent_fvk()),
|
transparent in prop::option::of(arb_transparent_fvk()),
|
||||||
) -> Ufvk {
|
) -> Ufvk {
|
||||||
let mut items: Vec<_> = transparent.into_iter().chain(shielded).collect();
|
let mut fvks: Vec<_> = transparent.into_iter().chain(shielded).map(Item::Data).collect();
|
||||||
items.sort_unstable_by(Fvk::encoding_order);
|
fvks.sort_unstable_by(Item::encoding_order);
|
||||||
Ufvk(items)
|
Ufvk { revision: Revision::R0, fvks }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,7 +257,7 @@ mod tests {
|
||||||
0xdf, 0x63, 0xe7, 0xef, 0x65, 0x6b, 0x18, 0x23, 0xf7, 0x3e, 0x35, 0x7c, 0xf3, 0xc4,
|
0xdf, 0x63, 0xe7, 0xef, 0x65, 0x6b, 0x18, 0x23, 0xf7, 0x3e, 0x35, 0x7c, 0xf3, 0xc4,
|
||||||
];
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Ufvk::parse_internal(Ufvk::MAINNET, &invalid_padding[..]),
|
Ufvk::parse_internal(Ufvk::MAINNET_R0, &invalid_padding[..]),
|
||||||
Err(ParseError::InvalidEncoding(
|
Err(ParseError::InvalidEncoding(
|
||||||
"Invalid padding bytes".to_owned()
|
"Invalid padding bytes".to_owned()
|
||||||
))
|
))
|
||||||
|
@ -263,7 +275,7 @@ mod tests {
|
||||||
0x43, 0x8e, 0xc0, 0x3e, 0x9f, 0xf4, 0xf1, 0x80, 0x32, 0xcf, 0x2f, 0x7e, 0x7f, 0x91,
|
0x43, 0x8e, 0xc0, 0x3e, 0x9f, 0xf4, 0xf1, 0x80, 0x32, 0xcf, 0x2f, 0x7e, 0x7f, 0x91,
|
||||||
];
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Ufvk::parse_internal(Ufvk::MAINNET, &truncated_padding[..]),
|
Ufvk::parse_internal(Ufvk::MAINNET_R0, &truncated_padding[..]),
|
||||||
Err(ParseError::InvalidEncoding(
|
Err(ParseError::InvalidEncoding(
|
||||||
"Invalid padding bytes".to_owned()
|
"Invalid padding bytes".to_owned()
|
||||||
))
|
))
|
||||||
|
@ -295,7 +307,7 @@ mod tests {
|
||||||
0x8c, 0x7a, 0xbf, 0x7b, 0x9a, 0xdd, 0xee, 0x18, 0x2c, 0x2d, 0xc2, 0xfc,
|
0x8c, 0x7a, 0xbf, 0x7b, 0x9a, 0xdd, 0xee, 0x18, 0x2c, 0x2d, 0xc2, 0xfc,
|
||||||
];
|
];
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
Ufvk::parse_internal(Ufvk::MAINNET, &truncated_sapling_data[..]),
|
Ufvk::parse_internal(Ufvk::MAINNET_R0, &truncated_sapling_data[..]),
|
||||||
Err(ParseError::InvalidEncoding(_))
|
Err(ParseError::InvalidEncoding(_))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -310,7 +322,7 @@ mod tests {
|
||||||
0x54, 0xd1, 0x9e, 0xec, 0x8b, 0xef, 0x35, 0xb8, 0x44, 0xdd, 0xab, 0x9a, 0x8d,
|
0x54, 0xd1, 0x9e, 0xec, 0x8b, 0xef, 0x35, 0xb8, 0x44, 0xdd, 0xab, 0x9a, 0x8d,
|
||||||
];
|
];
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
Ufvk::parse_internal(Ufvk::MAINNET, &truncated_after_sapling_typecode[..]),
|
Ufvk::parse_internal(Ufvk::MAINNET_R0, &truncated_after_sapling_typecode[..]),
|
||||||
Err(ParseError::InvalidEncoding(_))
|
Err(ParseError::InvalidEncoding(_))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -319,11 +331,17 @@ mod tests {
|
||||||
fn duplicate_typecode() {
|
fn duplicate_typecode() {
|
||||||
// Construct and serialize an invalid Ufvk. This must be done using private
|
// 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.
|
// methods, as the public API does not permit construction of such invalid values.
|
||||||
let ufvk = Ufvk(vec![Fvk::Sapling([1; 128]), Fvk::Sapling([2; 128])]);
|
let ufvk = Ufvk {
|
||||||
let encoded = ufvk.to_jumbled_bytes(Ufvk::MAINNET);
|
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_R0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Ufvk::parse_internal(Ufvk::MAINNET, &encoded[..]),
|
Ufvk::parse_internal(Ufvk::MAINNET_R0, &encoded[..]),
|
||||||
Err(ParseError::DuplicateTypecode(Typecode::Sapling))
|
Err(ParseError::DuplicateTypecode(Typecode::SAPLING))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,37 +357,6 @@ mod tests {
|
||||||
0xf4, 0xf5, 0x16, 0xef, 0x5c, 0xe0, 0x26, 0xbc, 0x23, 0x73, 0x76, 0x3f, 0x4b,
|
0xf4, 0xf5, 0x16, 0xef, 0x5c, 0xe0, 0x26, 0xbc, 0x23, 0x73, 0x76, 0x3f, 0x4b,
|
||||||
];
|
];
|
||||||
|
|
||||||
assert_eq!(
|
assert_matches!(Ufvk::parse_internal(Ufvk::MAINNET_R0, &encoded[..]), Ok(_));
|
||||||
Ufvk::parse_internal(Ufvk::MAINNET, &encoded[..]),
|
|
||||||
Err(ParseError::OnlyTransparent)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fvks_are_sorted() {
|
|
||||||
// Construct a UFVK with fvks in an unsorted order.
|
|
||||||
let ufvk = Ufvk(vec![
|
|
||||||
Fvk::P2pkh([0; 65]),
|
|
||||||
Fvk::Orchard([0; 96]),
|
|
||||||
Fvk::Unknown {
|
|
||||||
typecode: 0xff,
|
|
||||||
data: vec![],
|
|
||||||
},
|
|
||||||
Fvk::Sapling([0; 128]),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// `Ufvk::items` sorts the fvks in priority order.
|
|
||||||
assert_eq!(
|
|
||||||
ufvk.items(),
|
|
||||||
vec![
|
|
||||||
Fvk::Orchard([0; 96]),
|
|
||||||
Fvk::Sapling([0; 128]),
|
|
||||||
Fvk::P2pkh([0; 65]),
|
|
||||||
Fvk::Unknown {
|
|
||||||
typecode: 0xff,
|
|
||||||
data: vec![],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
private::{SealedContainer, SealedItem},
|
private::{SealedContainer, SealedDataItem},
|
||||||
Container, Encoding, ParseError, Typecode,
|
Container, DataTypecode, Encoding, Item, ParseError, Revision,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The set of known IVKs for Unified IVKs.
|
/// The set of known IVKs for Unified IVKs.
|
||||||
|
@ -44,31 +44,27 @@ pub enum Ivk {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<(u32, &[u8])> for Ivk {
|
impl SealedDataItem for Ivk {
|
||||||
type Error = ParseError;
|
fn parse(typecode: DataTypecode, data: &[u8]) -> Result<Self, ParseError> {
|
||||||
|
|
||||||
fn try_from((typecode, data): (u32, &[u8])) -> Result<Self, Self::Error> {
|
|
||||||
let data = data.to_vec();
|
let data = data.to_vec();
|
||||||
match typecode.try_into()? {
|
match typecode {
|
||||||
Typecode::P2pkh => data.try_into().map(Ivk::P2pkh),
|
DataTypecode::P2pkh => data.try_into().map(Ivk::P2pkh),
|
||||||
Typecode::P2sh => Err(data),
|
DataTypecode::P2sh => Err(data),
|
||||||
Typecode::Sapling => data.try_into().map(Ivk::Sapling),
|
DataTypecode::Sapling => data.try_into().map(Ivk::Sapling),
|
||||||
Typecode::Orchard => data.try_into().map(Ivk::Orchard),
|
DataTypecode::Orchard => data.try_into().map(Ivk::Orchard),
|
||||||
Typecode::Unknown(_) => Ok(Ivk::Unknown { typecode, data }),
|
DataTypecode::Unknown(typecode) => Ok(Ivk::Unknown { typecode, data }),
|
||||||
}
|
}
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
ParseError::InvalidEncoding(format!("Invalid ivk for typecode {}: {:?}", typecode, e))
|
ParseError::InvalidEncoding(format!("Invalid ivk for typecode {:?}: {:?}", typecode, e))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl SealedItem for Ivk {
|
fn typecode(&self) -> DataTypecode {
|
||||||
fn typecode(&self) -> Typecode {
|
|
||||||
match self {
|
match self {
|
||||||
Ivk::P2pkh(_) => Typecode::P2pkh,
|
Ivk::P2pkh(_) => DataTypecode::P2pkh,
|
||||||
Ivk::Sapling(_) => Typecode::Sapling,
|
Ivk::Sapling(_) => DataTypecode::Sapling,
|
||||||
Ivk::Orchard(_) => Typecode::Orchard,
|
Ivk::Orchard(_) => DataTypecode::Orchard,
|
||||||
Ivk::Unknown { typecode, .. } => Typecode::Unknown(*typecode),
|
Ivk::Unknown { typecode, .. } => DataTypecode::Unknown(*typecode),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +84,7 @@ impl SealedItem for Ivk {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use std::error::Error;
|
/// # use std::error::Error;
|
||||||
/// use zcash_address::unified::{self, Container, Encoding};
|
/// use zcash_address::unified::{self, Container, Encoding, Item, Revision};
|
||||||
///
|
///
|
||||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||||
/// # let uivk_from_user = || "uivk1djetqg3fws7y7qu5tekynvcdhz69gsyq07ewvppmzxdqhpfzdgmx8urnkqzv7ylz78ez43ux266pqjhecd59fzhn7wpe6zarnzh804hjtkyad25ryqla5pnc8p5wdl3phj9fczhz64zprun3ux7y9jc08567xryumuz59rjmg4uuflpjqwnq0j0tzce0x74t4tv3gfjq7nczkawxy6y7hse733ae3vw7qfjd0ss0pytvezxp42p6rrpzeh6t2zrz7zpjk0xhngcm6gwdppxs58jkx56gsfflugehf5vjlmu7vj3393gj6u37wenavtqyhdvcdeaj86s6jczl4zq";
|
/// # let uivk_from_user = || "uivk1djetqg3fws7y7qu5tekynvcdhz69gsyq07ewvppmzxdqhpfzdgmx8urnkqzv7ylz78ez43ux266pqjhecd59fzhn7wpe6zarnzh804hjtkyad25ryqla5pnc8p5wdl3phj9fczhz64zprun3ux7y9jc08567xryumuz59rjmg4uuflpjqwnq0j0tzce0x74t4tv3gfjq7nczkawxy6y7hse733ae3vw7qfjd0ss0pytvezxp42p6rrpzeh6t2zrz7zpjk0xhngcm6gwdppxs58jkx56gsfflugehf5vjlmu7vj3393gj6u37wenavtqyhdvcdeaj86s6jczl4zq";
|
||||||
|
@ -96,54 +92,72 @@ impl SealedItem for Ivk {
|
||||||
///
|
///
|
||||||
/// let (network, uivk) = unified::Uivk::decode(example_uivk)?;
|
/// let (network, uivk) = unified::Uivk::decode(example_uivk)?;
|
||||||
///
|
///
|
||||||
/// // We can obtain the pool-specific Incoming Viewing Keys for the UIVK in
|
/// // We can obtain the pool-specific Incoming Viewing Keys for the UIVK.
|
||||||
/// // preference order (the order in which wallets should prefer to use their
|
/// let ivks: &[Item<unified::Ivk>] = uivk.items_as_parsed();
|
||||||
/// // corresponding address receivers):
|
|
||||||
/// let ivks: Vec<unified::Ivk> = uivk.items();
|
|
||||||
///
|
///
|
||||||
/// // And we can create the UIVK from a list of IVKs:
|
/// // And we can create the UIVK from a vector of IVKs:
|
||||||
/// let new_uivk = unified::Uivk::try_from_items(ivks)?;
|
/// let new_uivk = unified::Uivk::try_from_items(Revision::R0, ivks.to_vec())?;
|
||||||
/// assert_eq!(new_uivk, uivk);
|
/// assert_eq!(new_uivk, uivk);
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct Uivk(pub(crate) Vec<Ivk>);
|
pub struct Uivk {
|
||||||
|
pub(crate) revision: Revision,
|
||||||
|
pub(crate) ivks: Vec<Item<Ivk>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Container for Uivk {
|
impl Container for Uivk {
|
||||||
type Item = Ivk;
|
type DataItem = Ivk;
|
||||||
|
|
||||||
/// Returns the IVKs contained within this UIVK, in the order they were
|
fn items_as_parsed(&self) -> &[Item<Ivk>] {
|
||||||
/// parsed from the string encoding.
|
&self.ivks
|
||||||
///
|
}
|
||||||
/// This API is for advanced usage; in most cases you should use `Uivk::items`.
|
|
||||||
fn items_as_parsed(&self) -> &[Ivk] {
|
fn revision(&self) -> Revision {
|
||||||
&self.0
|
self.revision
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encoding for Uivk {}
|
impl Encoding for Uivk {}
|
||||||
|
|
||||||
impl SealedContainer 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].
|
/// Defined in [ZIP 316][zip-0316].
|
||||||
///
|
///
|
||||||
/// [zip-0316]: https://zips.z.cash/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].
|
/// Defined in [ZIP 316][zip-0316].
|
||||||
///
|
///
|
||||||
/// [zip-0316]: https://zips.z.cash/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.
|
/// The HRP for a Bech32m-encoded regtest Revision 0 Unified IVK.
|
||||||
const REGTEST: &'static str = "uivkregtest";
|
const REGTEST_R0: &'static str = "uivkregtest";
|
||||||
|
|
||||||
fn from_inner(ivks: Vec<Self::Item>) -> Self {
|
/// The HRP for a Bech32m-encoded mainnet Revision 1 Unified IVK.
|
||||||
Self(ivks)
|
///
|
||||||
|
/// 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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,12 +171,10 @@ mod tests {
|
||||||
sample::select,
|
sample::select,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{Ivk, ParseError, Typecode, Uivk};
|
use super::{Ivk, ParseError, Uivk};
|
||||||
use crate::{
|
use crate::{
|
||||||
kind::unified::{
|
kind::unified::{private::SealedContainer, Encoding},
|
||||||
private::{SealedContainer, SealedItem},
|
unified::{Item, Revision, Typecode},
|
||||||
Container, Encoding,
|
|
||||||
},
|
|
||||||
Network,
|
Network,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -204,9 +216,9 @@ mod tests {
|
||||||
shielded in arb_shielded_ivk(),
|
shielded in arb_shielded_ivk(),
|
||||||
transparent in prop::option::of(arb_transparent_ivk()),
|
transparent in prop::option::of(arb_transparent_ivk()),
|
||||||
) -> Uivk {
|
) -> Uivk {
|
||||||
let mut items: Vec<_> = transparent.into_iter().chain(shielded).collect();
|
let mut ivks: Vec<_> = transparent.into_iter().chain(shielded).map(Item::Data).collect();
|
||||||
items.sort_unstable_by(Ivk::encoding_order);
|
ivks.sort_unstable_by(Item::encoding_order);
|
||||||
Uivk(items)
|
Uivk { revision: Revision::R0, ivks }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +248,7 @@ mod tests {
|
||||||
0x83, 0xe8, 0x92, 0x18, 0x28, 0x70, 0x1e, 0x81, 0x76, 0x56, 0xb6, 0x15,
|
0x83, 0xe8, 0x92, 0x18, 0x28, 0x70, 0x1e, 0x81, 0x76, 0x56, 0xb6, 0x15,
|
||||||
];
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Uivk::parse_internal(Uivk::MAINNET, &invalid_padding[..]),
|
Uivk::parse_internal(Uivk::MAINNET_R0, &invalid_padding[..]),
|
||||||
Err(ParseError::InvalidEncoding(
|
Err(ParseError::InvalidEncoding(
|
||||||
"Invalid padding bytes".to_owned()
|
"Invalid padding bytes".to_owned()
|
||||||
))
|
))
|
||||||
|
@ -252,7 +264,7 @@ mod tests {
|
||||||
0xf9, 0x65, 0x49, 0x14, 0xab, 0x7c, 0x55, 0x7b, 0x39, 0x47,
|
0xf9, 0x65, 0x49, 0x14, 0xab, 0x7c, 0x55, 0x7b, 0x39, 0x47,
|
||||||
];
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Uivk::parse_internal(Uivk::MAINNET, &truncated_padding[..]),
|
Uivk::parse_internal(Uivk::MAINNET_R0, &truncated_padding[..]),
|
||||||
Err(ParseError::InvalidEncoding(
|
Err(ParseError::InvalidEncoding(
|
||||||
"Invalid padding bytes".to_owned()
|
"Invalid padding bytes".to_owned()
|
||||||
))
|
))
|
||||||
|
@ -280,7 +292,7 @@ mod tests {
|
||||||
0xf5, 0xd5, 0x8a, 0xb5, 0x1a,
|
0xf5, 0xd5, 0x8a, 0xb5, 0x1a,
|
||||||
];
|
];
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
Uivk::parse_internal(Uivk::MAINNET, &truncated_sapling_data[..]),
|
Uivk::parse_internal(Uivk::MAINNET_R0, &truncated_sapling_data[..]),
|
||||||
Err(ParseError::InvalidEncoding(_))
|
Err(ParseError::InvalidEncoding(_))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -293,7 +305,7 @@ mod tests {
|
||||||
0xd8, 0x21, 0x5e, 0x8, 0xa, 0x82, 0x95, 0x21, 0x74,
|
0xd8, 0x21, 0x5e, 0x8, 0xa, 0x82, 0x95, 0x21, 0x74,
|
||||||
];
|
];
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
Uivk::parse_internal(Uivk::MAINNET, &truncated_after_sapling_typecode[..]),
|
Uivk::parse_internal(Uivk::MAINNET_R0, &truncated_after_sapling_typecode[..]),
|
||||||
Err(ParseError::InvalidEncoding(_))
|
Err(ParseError::InvalidEncoding(_))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -301,11 +313,17 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn duplicate_typecode() {
|
fn duplicate_typecode() {
|
||||||
// Construct and serialize an invalid UIVK.
|
// Construct and serialize an invalid UIVK.
|
||||||
let uivk = Uivk(vec![Ivk::Sapling([1; 64]), Ivk::Sapling([2; 64])]);
|
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);
|
let encoded = uivk.encode(&Network::Main);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Uivk::decode(&encoded),
|
Uivk::decode(&encoded),
|
||||||
Err(ParseError::DuplicateTypecode(Typecode::Sapling))
|
Err(ParseError::DuplicateTypecode(Typecode::SAPLING))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,37 +339,6 @@ mod tests {
|
||||||
0xbd, 0xfe, 0xa4, 0xb7, 0x47, 0x20, 0x92, 0x6, 0xf0, 0x0, 0xf9, 0x64,
|
0xbd, 0xfe, 0xa4, 0xb7, 0x47, 0x20, 0x92, 0x6, 0xf0, 0x0, 0xf9, 0x64,
|
||||||
];
|
];
|
||||||
|
|
||||||
assert_eq!(
|
assert_matches!(Uivk::parse_internal(Uivk::MAINNET_R0, &encoded[..]), Ok(_));
|
||||||
Uivk::parse_internal(Uivk::MAINNET, &encoded[..]),
|
|
||||||
Err(ParseError::OnlyTransparent)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ivks_are_sorted() {
|
|
||||||
// Construct a UIVK with ivks in an unsorted order.
|
|
||||||
let uivk = Uivk(vec![
|
|
||||||
Ivk::P2pkh([0; 65]),
|
|
||||||
Ivk::Orchard([0; 64]),
|
|
||||||
Ivk::Unknown {
|
|
||||||
typecode: 0xff,
|
|
||||||
data: vec![],
|
|
||||||
},
|
|
||||||
Ivk::Sapling([0; 64]),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// `Uivk::items` sorts the ivks in priority order.
|
|
||||||
assert_eq!(
|
|
||||||
uivk.items(),
|
|
||||||
vec![
|
|
||||||
Ivk::Orchard([0; 64]),
|
|
||||||
Ivk::Sapling([0; 64]),
|
|
||||||
Ivk::P2pkh([0; 65]),
|
|
||||||
Ivk::Unknown {
|
|
||||||
typecode: 0xff,
|
|
||||||
data: vec![],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ use {
|
||||||
unified::{
|
unified::{
|
||||||
self,
|
self,
|
||||||
address::{test_vectors::TEST_VECTORS, Receiver},
|
address::{test_vectors::TEST_VECTORS, Receiver},
|
||||||
|
Item, Revision,
|
||||||
},
|
},
|
||||||
Network, ToAddress, ZcashAddress,
|
Network, ToAddress, ZcashAddress,
|
||||||
},
|
},
|
||||||
|
@ -36,9 +37,16 @@ fn unified() {
|
||||||
data: data.to_vec(),
|
data: data.to_vec(),
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
|
.map(Item::Data)
|
||||||
.collect();
|
.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
|
// Test parsing
|
||||||
let addr: ZcashAddress = tv.unified_addr.parse().unwrap();
|
let addr: ZcashAddress = tv.unified_addr.parse().unwrap();
|
||||||
|
|
|
@ -799,6 +799,7 @@ pub mod testing {
|
||||||
use zcash_protocol::{consensus::NetworkType, value::testing::arb_zatoshis};
|
use zcash_protocol::{consensus::NetworkType, value::testing::arb_zatoshis};
|
||||||
|
|
||||||
use super::{MemoBytes, Payment, TransactionRequest};
|
use super::{MemoBytes, Payment, TransactionRequest};
|
||||||
|
|
||||||
pub const VALID_PARAMNAME: &str = "[a-zA-Z][a-zA-Z0-9+-]*";
|
pub const VALID_PARAMNAME: &str = "[a-zA-Z][a-zA-Z0-9+-]*";
|
||||||
|
|
||||||
prop_compose! {
|
prop_compose! {
|
||||||
|
|
|
@ -133,6 +133,9 @@ and this library adheres to Rust's notion of
|
||||||
- Arguments to `ChangeStrategy::compute_balance` have changed.
|
- Arguments to `ChangeStrategy::compute_balance` have changed.
|
||||||
- `ChangeError::DustInputs` now has an `orchard` field behind the `orchard`
|
- `ChangeError::DustInputs` now has an `orchard` field behind the `orchard`
|
||||||
feature flag.
|
feature flag.
|
||||||
|
- `zcash_client_backend::wallet`:
|
||||||
|
- The address variants of `Recipient` now `Box` their contents to avoid large
|
||||||
|
discrepancies in enum variant sizing.
|
||||||
- `zcash_client_backend::proto`:
|
- `zcash_client_backend::proto`:
|
||||||
- `ProposalDecodingError` has a new variant `TransparentMemo`.
|
- `ProposalDecodingError` has a new variant `TransparentMemo`.
|
||||||
- `zcash_client_backend::wallet::Recipient::InternalAccount` is now a structured
|
- `zcash_client_backend::wallet::Recipient::InternalAccount` is now a structured
|
||||||
|
|
|
@ -859,7 +859,7 @@ where
|
||||||
.convert_if_network(params.network_type())?;
|
.convert_if_network(params.network_type())?;
|
||||||
|
|
||||||
let recipient_taddr = match recipient_address {
|
let recipient_taddr = match recipient_address {
|
||||||
Address::Transparent(t) => Some(t),
|
Address::Transparent(t) => Some(t.as_ref()),
|
||||||
Address::Unified(uaddr) => uaddr.transparent(),
|
Address::Unified(uaddr) => uaddr.transparent(),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
@ -1022,7 +1022,7 @@ where
|
||||||
let memo = payment.memo().map_or_else(MemoBytes::empty, |m| m.clone());
|
let memo = payment.memo().map_or_else(MemoBytes::empty, |m| m.clone());
|
||||||
builder.add_sapling_output(
|
builder.add_sapling_output(
|
||||||
sapling_external_ovk,
|
sapling_external_ovk,
|
||||||
addr,
|
*addr,
|
||||||
payment.amount(),
|
payment.amount(),
|
||||||
memo.clone(),
|
memo.clone(),
|
||||||
)?;
|
)?;
|
||||||
|
|
|
@ -8,7 +8,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use nonempty::NonEmpty;
|
use nonempty::NonEmpty;
|
||||||
use zcash_address::ConversionError;
|
use zcash_address::{ConversionError, ZcashAddress};
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
consensus::{self, BlockHeight},
|
consensus::{self, BlockHeight},
|
||||||
transaction::{
|
transaction::{
|
||||||
|
@ -21,7 +21,7 @@ use zcash_primitives::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
address::{Address, UnifiedAddress},
|
address::Address,
|
||||||
data_api::{InputSource, SimpleNoteRetention, SpendableNotes},
|
data_api::{InputSource, SimpleNoteRetention, SpendableNotes},
|
||||||
fees::{sapling, ChangeError, ChangeStrategy, DustOutputPolicy},
|
fees::{sapling, ChangeError, ChangeStrategy, DustOutputPolicy},
|
||||||
proposal::{Proposal, ProposalError, ShieldedInputs},
|
proposal::{Proposal, ProposalError, ShieldedInputs},
|
||||||
|
@ -221,7 +221,7 @@ pub enum GreedyInputSelectorError<ChangeStrategyErrT, NoteRefT> {
|
||||||
/// An intermediate value overflowed or underflowed the valid monetary range.
|
/// An intermediate value overflowed or underflowed the valid monetary range.
|
||||||
Balance(BalanceError),
|
Balance(BalanceError),
|
||||||
/// A unified address did not contain a supported receiver.
|
/// A unified address did not contain a supported receiver.
|
||||||
UnsupportedAddress(Box<UnifiedAddress>),
|
UnsupportedAddress(ZcashAddress),
|
||||||
/// An error was encountered in change selection.
|
/// An error was encountered in change selection.
|
||||||
Change(ChangeError<ChangeStrategyErrT, NoteRefT>),
|
Change(ChangeError<ChangeStrategyErrT, NoteRefT>),
|
||||||
}
|
}
|
||||||
|
@ -234,10 +234,12 @@ impl<CE: fmt::Display, N: fmt::Display> fmt::Display for GreedyInputSelectorErro
|
||||||
"A balance calculation violated amount validity bounds: {:?}.",
|
"A balance calculation violated amount validity bounds: {:?}.",
|
||||||
e
|
e
|
||||||
),
|
),
|
||||||
GreedyInputSelectorError::UnsupportedAddress(_) => {
|
GreedyInputSelectorError::UnsupportedAddress(addr) => {
|
||||||
// we can't encode the UA to its string representation because we
|
write!(
|
||||||
// don't have network parameters here
|
f,
|
||||||
write!(f, "Unified address contains no supported receivers.")
|
"Unified address {} contains no supported receivers.",
|
||||||
|
addr.encode()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
GreedyInputSelectorError::Change(err) => {
|
GreedyInputSelectorError::Change(err) => {
|
||||||
write!(f, "An error occurred computing change and fees: {}", err)
|
write!(f, "An error occurred computing change and fees: {}", err)
|
||||||
|
@ -401,7 +403,9 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(InputSelectorError::Selection(
|
return Err(InputSelectorError::Selection(
|
||||||
GreedyInputSelectorError::UnsupportedAddress(Box::new(addr)),
|
GreedyInputSelectorError::UnsupportedAddress(
|
||||||
|
payment.recipient_address().clone(),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,7 +133,7 @@ pub(crate) const UA_TRANSPARENT: bool = false;
|
||||||
pub(crate) const UA_TRANSPARENT: bool = true;
|
pub(crate) const UA_TRANSPARENT: bool = true;
|
||||||
|
|
||||||
pub(crate) const DEFAULT_UA_REQUEST: UnifiedAddressRequest =
|
pub(crate) const DEFAULT_UA_REQUEST: UnifiedAddressRequest =
|
||||||
UnifiedAddressRequest::unsafe_new(UA_ORCHARD, true, UA_TRANSPARENT);
|
UnifiedAddressRequest::unsafe_new_without_expiry(UA_ORCHARD, true, UA_TRANSPARENT);
|
||||||
|
|
||||||
/// The ID type for accounts.
|
/// The ID type for accounts.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
|
||||||
|
@ -1157,7 +1157,6 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
||||||
|
|
||||||
Recipient::External(wallet_address, PoolType::Shielded(ShieldedProtocol::Orchard))
|
Recipient::External(wallet_address, PoolType::Shielded(ShieldedProtocol::Orchard))
|
||||||
};
|
};
|
||||||
|
|
||||||
wallet::put_sent_output(
|
wallet::put_sent_output(
|
||||||
wdb.conn.0,
|
wdb.conn.0,
|
||||||
*output.account(),
|
*output.account(),
|
||||||
|
|
|
@ -1682,7 +1682,7 @@ fn fake_compact_block_spending<P: consensus::Parameters, Fvk: TestFvk>(
|
||||||
compact_sapling_output(
|
compact_sapling_output(
|
||||||
params,
|
params,
|
||||||
height,
|
height,
|
||||||
recipient,
|
*recipient,
|
||||||
value,
|
value,
|
||||||
fvk.sapling_ovk(),
|
fvk.sapling_ovk(),
|
||||||
&mut rng,
|
&mut rng,
|
||||||
|
|
|
@ -332,7 +332,7 @@ pub(crate) fn send_multi_step_proposed_transfer<T: ShieldedPoolTester>() {
|
||||||
// spends the first step's output.
|
// spends the first step's output.
|
||||||
|
|
||||||
// The first step will deshield to the wallet's default transparent address
|
// The first step will deshield to the wallet's default transparent address
|
||||||
let to0 = Address::Transparent(account.usk().default_transparent_address().0);
|
let to0 = Address::from(account.usk().default_transparent_address().0);
|
||||||
let request0 = zip321::TransactionRequest::new(vec![Payment::without_memo(
|
let request0 = zip321::TransactionRequest::new(vec![Payment::without_memo(
|
||||||
to0.to_zcash_address(&st.network()),
|
to0.to_zcash_address(&st.network()),
|
||||||
NonNegativeAmount::const_from_u64(50000),
|
NonNegativeAmount::const_from_u64(50000),
|
||||||
|
@ -364,7 +364,7 @@ pub(crate) fn send_multi_step_proposed_transfer<T: ShieldedPoolTester>() {
|
||||||
|
|
||||||
// We'll use an internal transparent address that hasn't been added to the wallet
|
// We'll use an internal transparent address that hasn't been added to the wallet
|
||||||
// to simulate an external transparent recipient.
|
// to simulate an external transparent recipient.
|
||||||
let to1 = Address::Transparent(
|
let to1 = Address::from(
|
||||||
account
|
account
|
||||||
.usk()
|
.usk()
|
||||||
.transparent()
|
.transparent()
|
||||||
|
@ -1620,7 +1620,7 @@ pub(crate) fn fully_funded_send_to_t<P0: ShieldedPoolTester, P1: ShieldedPoolTes
|
||||||
|
|
||||||
let transfer_amount = NonNegativeAmount::const_from_u64(200000);
|
let transfer_amount = NonNegativeAmount::const_from_u64(200000);
|
||||||
let p0_to_p1 = zip321::TransactionRequest::new(vec![Payment::without_memo(
|
let p0_to_p1 = zip321::TransactionRequest::new(vec![Payment::without_memo(
|
||||||
Address::Transparent(p1_to).to_zcash_address(&st.network()),
|
Address::Transparent(Box::new(p1_to)).to_zcash_address(&st.network()),
|
||||||
transfer_amount,
|
transfer_amount,
|
||||||
)])
|
)])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -555,7 +555,7 @@ pub(crate) fn get_current_address<P: consensus::Parameters>(
|
||||||
SqliteClientError::CorruptedData("Not a valid Zcash recipient address".to_owned())
|
SqliteClientError::CorruptedData("Not a valid Zcash recipient address".to_owned())
|
||||||
})
|
})
|
||||||
.and_then(|addr| match addr {
|
.and_then(|addr| match addr {
|
||||||
Address::Unified(ua) => Ok(ua),
|
Address::Unified(ua) => Ok(*ua),
|
||||||
_ => Err(SqliteClientError::CorruptedData(format!(
|
_ => Err(SqliteClientError::CorruptedData(format!(
|
||||||
"Addresses table contains {} which is not a unified address",
|
"Addresses table contains {} which is not a unified address",
|
||||||
addr_str,
|
addr_str,
|
||||||
|
@ -683,7 +683,7 @@ pub(crate) fn get_legacy_transparent_address<P: consensus::Parameters>(
|
||||||
conn: &rusqlite::Connection,
|
conn: &rusqlite::Connection,
|
||||||
account_id: AccountId,
|
account_id: AccountId,
|
||||||
) -> Result<Option<(TransparentAddress, NonHardenedChildIndex)>, SqliteClientError> {
|
) -> Result<Option<(TransparentAddress, NonHardenedChildIndex)>, SqliteClientError> {
|
||||||
use zcash_address::unified::Container;
|
use zcash_address::unified::{Container, Item};
|
||||||
use zcash_primitives::legacy::keys::ExternalIvk;
|
use zcash_primitives::legacy::keys::ExternalIvk;
|
||||||
|
|
||||||
// Get the UIVK for the account.
|
// Get the UIVK for the account.
|
||||||
|
@ -705,9 +705,9 @@ pub(crate) fn get_legacy_transparent_address<P: consensus::Parameters>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Derive the default transparent address (if it wasn't already part of a derived UA).
|
// Derive the default transparent address (if it wasn't already part of a derived UA).
|
||||||
for item in uivk.items() {
|
for item in uivk.items_as_parsed() {
|
||||||
if let Ivk::P2pkh(tivk_bytes) = item {
|
if let Item::Data(Ivk::P2pkh(tivk_bytes)) = item {
|
||||||
let tivk = ExternalIvk::deserialize(&tivk_bytes)?;
|
let tivk = ExternalIvk::deserialize(tivk_bytes)?;
|
||||||
return Ok(Some(tivk.default_address()));
|
return Ok(Some(tivk.default_address()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2386,7 +2386,7 @@ pub(crate) fn select_receiving_address<P: consensus::Parameters>(
|
||||||
FROM addresses
|
FROM addresses
|
||||||
WHERE cached_transparent_receiver_address = :taddr",
|
WHERE cached_transparent_receiver_address = :taddr",
|
||||||
named_params! {
|
named_params! {
|
||||||
":taddr": Address::Transparent(*taddr).encode(_params)
|
":taddr": Address::Transparent(Box::new(*taddr)).encode(_params)
|
||||||
},
|
},
|
||||||
|row| row.get::<_, String>(0),
|
|row| row.get::<_, String>(0),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1418,8 +1418,9 @@ mod tests {
|
||||||
|
|
||||||
// Unified addresses at the time of the addition of migrations did not contain an
|
// Unified addresses at the time of the addition of migrations did not contain an
|
||||||
// Orchard component.
|
// Orchard component.
|
||||||
let ua_request = UnifiedAddressRequest::unsafe_new(false, true, UA_TRANSPARENT);
|
let ua_request =
|
||||||
let address_str = Address::Unified(
|
UnifiedAddressRequest::unsafe_new_without_expiry(false, true, UA_TRANSPARENT);
|
||||||
|
let address_str = Address::from(
|
||||||
ufvk.default_address(ua_request)
|
ufvk.default_address(ua_request)
|
||||||
.expect("A valid default address exists for the UFVK")
|
.expect("A valid default address exists for the UFVK")
|
||||||
.0,
|
.0,
|
||||||
|
@ -1438,7 +1439,7 @@ mod tests {
|
||||||
// add a transparent "sent note"
|
// add a transparent "sent note"
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
{
|
{
|
||||||
let taddr = Address::Transparent(
|
let taddr = Address::from(
|
||||||
*ufvk
|
*ufvk
|
||||||
.default_address(ua_request)
|
.default_address(ua_request)
|
||||||
.expect("A valid default address exists for the UFVK")
|
.expect("A valid default address exists for the UFVK")
|
||||||
|
@ -1545,7 +1546,8 @@ mod tests {
|
||||||
assert_eq!(tv.unified_addr, ua.encode(&Network::MainNetwork));
|
assert_eq!(tv.unified_addr, ua.encode(&Network::MainNetwork));
|
||||||
|
|
||||||
// hardcoded with knowledge of what's coming next
|
// hardcoded with knowledge of what's coming next
|
||||||
let ua_request = UnifiedAddressRequest::unsafe_new(false, true, true);
|
let ua_request =
|
||||||
|
UnifiedAddressRequest::unsafe_new_without_expiry(false, true, true);
|
||||||
db_data
|
db_data
|
||||||
.get_next_available_address(account_id, ua_request)
|
.get_next_available_address(account_id, ua_request)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -443,12 +443,12 @@ mod tests {
|
||||||
let usk = UnifiedSpendingKey::from_seed(&network, &[0u8; 32][..], AccountId::ZERO).unwrap();
|
let usk = UnifiedSpendingKey::from_seed(&network, &[0u8; 32][..], AccountId::ZERO).unwrap();
|
||||||
let ufvk = usk.to_unified_full_viewing_key();
|
let ufvk = usk.to_unified_full_viewing_key();
|
||||||
let (ua, _) = ufvk
|
let (ua, _) = ufvk
|
||||||
.default_address(UnifiedAddressRequest::unsafe_new(
|
.default_address(UnifiedAddressRequest::unsafe_new_without_expiry(
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
UA_TRANSPARENT,
|
UA_TRANSPARENT,
|
||||||
))
|
))
|
||||||
.expect("A valid default address exists for the UFVK");
|
.unwrap();
|
||||||
let taddr = ufvk
|
let taddr = ufvk
|
||||||
.transparent()
|
.transparent()
|
||||||
.and_then(|k| {
|
.and_then(|k| {
|
||||||
|
|
|
@ -79,20 +79,20 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
let decoded_address = if let Address::Unified(ua) = decoded {
|
let decoded_address = if let Address::Unified(ua) = decoded {
|
||||||
ua
|
*ua
|
||||||
} else {
|
} else {
|
||||||
return Err(WalletMigrationError::CorruptedData(
|
return Err(WalletMigrationError::CorruptedData(
|
||||||
"Address in accounts table was not a Unified Address.".to_string(),
|
"Address in accounts table was not a Unified Address.".to_string(),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
let (expected_address, idx) = ufvk.default_address(
|
let (expected_address, idx) = ufvk.default_address(
|
||||||
UnifiedAddressRequest::unsafe_new(false, true, UA_TRANSPARENT),
|
UnifiedAddressRequest::unsafe_new_without_expiry(false, true, UA_TRANSPARENT),
|
||||||
)?;
|
)?;
|
||||||
if decoded_address != expected_address {
|
if decoded_address != expected_address {
|
||||||
return Err(WalletMigrationError::CorruptedData(format!(
|
return Err(WalletMigrationError::CorruptedData(format!(
|
||||||
"Decoded UA {} does not match the UFVK's default address {} at {:?}.",
|
"Decoded UA {} does not match the UFVK's default address {} at {:?}.",
|
||||||
address,
|
address,
|
||||||
Address::Unified(expected_address).encode(&self.params),
|
Address::from(expected_address).encode(&self.params),
|
||||||
idx,
|
idx,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
||||||
let decoded_transparent_address = if let Address::Transparent(addr) =
|
let decoded_transparent_address = if let Address::Transparent(addr) =
|
||||||
decoded_transparent
|
decoded_transparent
|
||||||
{
|
{
|
||||||
addr
|
*addr
|
||||||
} else {
|
} else {
|
||||||
return Err(WalletMigrationError::CorruptedData(
|
return Err(WalletMigrationError::CorruptedData(
|
||||||
"Address in transparent_address column of accounts table was not a transparent address.".to_string(),
|
"Address in transparent_address column of accounts table was not a transparent address.".to_string(),
|
||||||
|
@ -157,11 +157,9 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let (address, d_idx) = ufvk.default_address(UnifiedAddressRequest::unsafe_new(
|
let (address, d_idx) = ufvk.default_address(
|
||||||
false,
|
UnifiedAddressRequest::unsafe_new_without_expiry(false, true, UA_TRANSPARENT),
|
||||||
true,
|
)?;
|
||||||
UA_TRANSPARENT,
|
|
||||||
))?;
|
|
||||||
insert_address(transaction, &self.params, account, d_idx, &address)?;
|
insert_address(transaction, &self.params, account, d_idx, &address)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let (default_addr, diversifier_index) = uivk.default_address(
|
let (default_addr, diversifier_index) = uivk.default_address(
|
||||||
UnifiedAddressRequest::unsafe_new(UA_ORCHARD, true, UA_TRANSPARENT),
|
UnifiedAddressRequest::unsafe_new_without_expiry(UA_ORCHARD, true, UA_TRANSPARENT),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut di_be = *diversifier_index.as_bytes();
|
let mut di_be = *diversifier_index.as_bytes();
|
||||||
|
@ -144,7 +144,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (addr, diversifier_index) = ufvk
|
let (addr, diversifier_index) = ufvk
|
||||||
.default_address(UnifiedAddressRequest::unsafe_new(
|
.default_address(UnifiedAddressRequest::unsafe_new_without_expiry(
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
UA_TRANSPARENT,
|
UA_TRANSPARENT,
|
||||||
|
|
|
@ -83,7 +83,8 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
||||||
// our second assumption above, and we report this as corrupted data.
|
// our second assumption above, and we report this as corrupted data.
|
||||||
let mut seed_is_relevant = false;
|
let mut seed_is_relevant = false;
|
||||||
|
|
||||||
let ua_request = UnifiedAddressRequest::unsafe_new(false, true, UA_TRANSPARENT);
|
let ua_request =
|
||||||
|
UnifiedAddressRequest::unsafe_new_without_expiry(false, true, UA_TRANSPARENT);
|
||||||
let mut rows = stmt_fetch_accounts.query([])?;
|
let mut rows = stmt_fetch_accounts.query([])?;
|
||||||
while let Some(row) = rows.next()? {
|
while let Some(row) = rows.next()? {
|
||||||
// We only need to check for the presence of the seed if we have keys that
|
// We only need to check for the presence of the seed if we have keys that
|
||||||
|
@ -119,12 +120,12 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
||||||
let dfvk = ufvk.sapling().ok_or_else(||
|
let dfvk = ufvk.sapling().ok_or_else(||
|
||||||
WalletMigrationError::CorruptedData("Derivation should have produced a UFVK containing a Sapling component.".to_owned()))?;
|
WalletMigrationError::CorruptedData("Derivation should have produced a UFVK containing a Sapling component.".to_owned()))?;
|
||||||
let (idx, expected_address) = dfvk.default_address();
|
let (idx, expected_address) = dfvk.default_address();
|
||||||
if decoded_address != expected_address {
|
if *decoded_address != expected_address {
|
||||||
return Err(if seed_is_relevant {
|
return Err(if seed_is_relevant {
|
||||||
WalletMigrationError::CorruptedData(
|
WalletMigrationError::CorruptedData(
|
||||||
format!("Decoded Sapling address {} does not match the ufvk's Sapling address {} at {:?}.",
|
format!("Decoded Sapling address {} does not match the ufvk's Sapling address {} at {:?}.",
|
||||||
address,
|
address,
|
||||||
Address::Sapling(expected_address).encode(&self.params),
|
Address::from(expected_address).encode(&self.params),
|
||||||
idx))
|
idx))
|
||||||
} else {
|
} else {
|
||||||
WalletMigrationError::SeedNotRelevant
|
WalletMigrationError::SeedNotRelevant
|
||||||
|
@ -137,12 +138,12 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
||||||
}
|
}
|
||||||
Address::Unified(decoded_address) => {
|
Address::Unified(decoded_address) => {
|
||||||
let (expected_address, idx) = ufvk.default_address(ua_request)?;
|
let (expected_address, idx) = ufvk.default_address(ua_request)?;
|
||||||
if decoded_address != expected_address {
|
if *decoded_address != expected_address {
|
||||||
return Err(if seed_is_relevant {
|
return Err(if seed_is_relevant {
|
||||||
WalletMigrationError::CorruptedData(
|
WalletMigrationError::CorruptedData(
|
||||||
format!("Decoded unified address {} does not match the ufvk's default address {} at {:?}.",
|
format!("Decoded unified address {} does not match the ufvk's default address {} at {:?}.",
|
||||||
address,
|
address,
|
||||||
Address::Unified(expected_address).encode(&self.params),
|
Address::from(expected_address).encode(&self.params),
|
||||||
idx))
|
idx))
|
||||||
} else {
|
} else {
|
||||||
WalletMigrationError::SeedNotRelevant
|
WalletMigrationError::SeedNotRelevant
|
||||||
|
|
|
@ -463,7 +463,6 @@ pub(crate) mod tests {
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.unwrap()
|
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -545,9 +544,7 @@ pub(crate) mod tests {
|
||||||
return Ok(result.map(|(note, addr, memo)| {
|
return Ok(result.map(|(note, addr, memo)| {
|
||||||
(
|
(
|
||||||
Note::Orchard(note),
|
Note::Orchard(note),
|
||||||
UnifiedAddress::from_receivers(Some(addr), None, None)
|
UnifiedAddress::from_receivers(Some(addr), None, None).into(),
|
||||||
.unwrap()
|
|
||||||
.into(),
|
|
||||||
MemoBytes::from_bytes(&memo).expect("correct length"),
|
MemoBytes::from_bytes(&memo).expect("correct length"),
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -12,7 +12,12 @@ and this library adheres to Rust's notion of
|
||||||
## [0.2.0] - 2024-03-25
|
## [0.2.0] - 2024-03-25
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `zcash_keys::address::Address::has_receiver`
|
- `zcash_keys::address`:
|
||||||
|
- `Address::has_receiver`
|
||||||
|
- `UnifiedAddress::{
|
||||||
|
new, expiry_height, expiry_time,
|
||||||
|
unknown_data, unknown_metadata
|
||||||
|
}`
|
||||||
- `impl Display for zcash_keys::keys::AddressGenerationError`
|
- `impl Display for zcash_keys::keys::AddressGenerationError`
|
||||||
- `impl std::error::Error for zcash_keys::keys::AddressGenerationError`
|
- `impl std::error::Error for zcash_keys::keys::AddressGenerationError`
|
||||||
- `impl From<hdwallet::error::Error> for zcash_keys::keys::DerivationError`
|
- `impl From<hdwallet::error::Error> for zcash_keys::keys::DerivationError`
|
||||||
|
@ -31,10 +36,15 @@ and this library adheres to Rust's notion of
|
||||||
must be enabled for the `keys` module to be accessible.
|
must be enabled for the `keys` module to be accessible.
|
||||||
- Updated to `zcash_primitives-0.15.0`
|
- Updated to `zcash_primitives-0.15.0`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `zcash_keys::address::Address` variants now `Box` their contents to
|
||||||
|
avoid large discrepancies in enum variant sizing.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- `UnifiedFullViewingKey::new` has been placed behind the `test-dependencies`
|
- `UnifiedFullViewingKey::new` has been placed behind the `test-dependencies`
|
||||||
feature flag. UFVKs should only be produced by derivation from the USK, or
|
feature flag. UFVKs should only be produced by derivation from the USK, or
|
||||||
parsed from their string representation.
|
parsed from their string representation.
|
||||||
|
- `zcash_keys::address::UnifiedAddress::from_receivers`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- `UnifiedFullViewingKey::find_address` can now find an address for a diversifier
|
- `UnifiedFullViewingKey::find_address` can now find an address for a diversifier
|
||||||
|
@ -69,28 +79,47 @@ The entries below are relative to the `zcash_client_backend` crate as of
|
||||||
- A new `orchard` feature flag has been added to make it possible to
|
- A new `orchard` feature flag has been added to make it possible to
|
||||||
build client code without `orchard` dependendencies.
|
build client code without `orchard` dependendencies.
|
||||||
- `zcash_keys::address::Address::to_zcash_address`
|
- `zcash_keys::address::Address::to_zcash_address`
|
||||||
|
- A new `sapling` feature flag has been added to make it possible to
|
||||||
|
build client code without `sapling` dependendencies.
|
||||||
|
- A new `transparent-inputs` feature flag has been added to make it possible to
|
||||||
|
build client code without providing support for generating transparent
|
||||||
|
addresses.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- The following methods and enum variants have been placed behind an `orchard`
|
- The following methods, method arguments, and enum variants have been placed
|
||||||
feature flag:
|
behind the `orchard` feature flag:
|
||||||
|
- `zcash_keys::address::UnifiedAddress::from_receivers` no longer takes an
|
||||||
|
Orchard receiver argument unless the `orchard` feature is enabled.
|
||||||
|
- `zcash_keys::keys::UnifiedFullViewingKey::new` no longer takes
|
||||||
|
an Orchard key argument unless the `orchard` feature is enabled.
|
||||||
- `zcash_keys::address::UnifiedAddress::orchard`
|
- `zcash_keys::address::UnifiedAddress::orchard`
|
||||||
- `zcash_keys::keys::DerivationError::Orchard`
|
- `zcash_keys::keys::DerivationError::Orchard`
|
||||||
- `zcash_keys::keys::UnifiedSpendingKey::orchard`
|
- `zcash_keys::keys::UnifiedSpendingKey::orchard`
|
||||||
|
- `zcash_keys::keys::UnifiedFullViewingKey::orchard`
|
||||||
|
- The following methods and method arguments have been placed behind the
|
||||||
|
`sapling` feature flag:
|
||||||
|
- `UnifiedAddress::from_receivers` no longer takes a Sapling receiver
|
||||||
|
argument unless the `sapling` feature is enabled.
|
||||||
|
- `zcash_keys::keys::UnifiedFullViewingKey::new` no longer takes
|
||||||
|
a Sapling key argument unless the `sapling` feature is enabled.
|
||||||
|
- `zcash_keys::address::UnifiedAddress::sapling`
|
||||||
|
- `zcash_keys::keys::UnifiedSpendingKey::sapling`
|
||||||
|
- `zcash_keys::keys::UnifiedFullViewingKey::sapling`
|
||||||
|
- The following methods and method arguments have been placed behind the
|
||||||
|
`transparent-inputs` feature flag:
|
||||||
|
- `zcash_keys::keys::UnifiedFullViewingKey::transparent` no longer takes
|
||||||
|
a transparent key argument unless the `transparent-inputs` feature is enabled.
|
||||||
|
- `zcash_keys::keys::UnifiedSpendingKey::transparent`
|
||||||
|
- `zcash_keys::keys::UnifiedFullViewingKey::transparent`
|
||||||
- `zcash_keys::address`:
|
- `zcash_keys::address`:
|
||||||
- `RecipientAddress` has been renamed to `Address`.
|
- `RecipientAddress` has been renamed to `Address`.
|
||||||
- `Address::Shielded` has been renamed to `Address::Sapling`.
|
- `Address::Shielded` has been renamed to `Address::Sapling`.
|
||||||
- `UnifiedAddress::from_receivers` no longer takes an Orchard receiver
|
|
||||||
argument unless the `orchard` feature is enabled.
|
|
||||||
- `zcash_keys::keys`:
|
- `zcash_keys::keys`:
|
||||||
- `UnifiedSpendingKey::address` now takes an argument that specifies the
|
- `UnifiedSpendingKey::address` now takes an argument that specifies the
|
||||||
receivers to be generated in the resulting address. Also, it now returns
|
receivers to be generated in the resulting address. Also, it now returns
|
||||||
`Result<UnifiedAddress, AddressGenerationError>` instead of
|
`Result<UnifiedAddress, AddressGenerationError>` instead of
|
||||||
`Option<UnifiedAddress>` so that we may better report to the user how
|
`Option<UnifiedAddress>` so that we may better report to the user how
|
||||||
address generation has failed.
|
address generation has failed.
|
||||||
- `UnifiedSpendingKey::transparent` is now only available when the
|
|
||||||
`transparent-inputs` feature is enabled.
|
|
||||||
- `UnifiedFullViewingKey::new` no longer takes an Orchard full viewing key
|
|
||||||
argument unless the `orchard` feature is enabled.
|
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- `zcash_keys::address::AddressMetadata`
|
- `zcash_keys::address::AddressMetadata`
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
//! Structs for handling supported address types.
|
//! Structs for handling supported address types.
|
||||||
|
|
||||||
use zcash_address::{
|
use zcash_address::{
|
||||||
unified::{self, Container, Encoding, Typecode},
|
unified::{self, Container, DataTypecode, Encoding, Item, Revision, Typecode},
|
||||||
ConversionError, ToAddress, TryFromRawAddress, ZcashAddress,
|
ConversionError, ToAddress, TryFromRawAddress, ZcashAddress,
|
||||||
};
|
};
|
||||||
use zcash_primitives::legacy::TransparentAddress;
|
use zcash_primitives::legacy::TransparentAddress;
|
||||||
use zcash_protocol::consensus::{self, NetworkType};
|
use zcash_protocol::{
|
||||||
|
consensus::{self, BlockHeight, NetworkType},
|
||||||
|
PoolType, ShieldedProtocol,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
use sapling::PaymentAddress;
|
use sapling::PaymentAddress;
|
||||||
use zcash_protocol::{PoolType, ShieldedProtocol};
|
|
||||||
|
|
||||||
/// A Unified Address.
|
/// A Unified Address.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -19,7 +21,10 @@ pub struct UnifiedAddress {
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
sapling: Option<PaymentAddress>,
|
sapling: Option<PaymentAddress>,
|
||||||
transparent: Option<TransparentAddress>,
|
transparent: Option<TransparentAddress>,
|
||||||
unknown: Vec<(u32, Vec<u8>)>,
|
unknown_data: Vec<(u32, Vec<u8>)>,
|
||||||
|
expiry_height: Option<BlockHeight>,
|
||||||
|
expiry_time: Option<u64>,
|
||||||
|
unknown_metadata: Vec<(u32, Vec<u8>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<unified::Address> for UnifiedAddress {
|
impl TryFrom<unified::Address> for UnifiedAddress {
|
||||||
|
@ -31,14 +36,16 @@ impl TryFrom<unified::Address> for UnifiedAddress {
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
let mut sapling = None;
|
let mut sapling = None;
|
||||||
let mut transparent = None;
|
let mut transparent = None;
|
||||||
|
let mut unknown_data = vec![];
|
||||||
let mut unknown: Vec<(u32, Vec<u8>)> = vec![];
|
let mut expiry_height = None;
|
||||||
|
let mut expiry_time = None;
|
||||||
|
let mut unknown_metadata = vec![];
|
||||||
|
|
||||||
// We can use as-parsed order here for efficiency, because we're breaking out the
|
// We can use as-parsed order here for efficiency, because we're breaking out the
|
||||||
// receivers we support from the unknown receivers.
|
// receivers we support from the unknown receivers.
|
||||||
for item in ua.items_as_parsed() {
|
for item in ua.items_as_parsed() {
|
||||||
match item {
|
match item {
|
||||||
unified::Receiver::Orchard(data) => {
|
Item::Data(unified::Receiver::Orchard(data)) => {
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
{
|
{
|
||||||
orchard = Some(
|
orchard = Some(
|
||||||
|
@ -48,11 +55,11 @@ impl TryFrom<unified::Address> for UnifiedAddress {
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "orchard"))]
|
#[cfg(not(feature = "orchard"))]
|
||||||
{
|
{
|
||||||
unknown.push((unified::Typecode::Orchard.into(), data.to_vec()));
|
unknown_data.push((unified::Typecode::ORCHARD.into(), data.to_vec()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unified::Receiver::Sapling(data) => {
|
Item::Data(unified::Receiver::Sapling(data)) => {
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
{
|
{
|
||||||
sapling = Some(
|
sapling = Some(
|
||||||
|
@ -62,20 +69,26 @@ impl TryFrom<unified::Address> for UnifiedAddress {
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "sapling"))]
|
#[cfg(not(feature = "sapling"))]
|
||||||
{
|
{
|
||||||
unknown.push((unified::Typecode::Sapling.into(), data.to_vec()));
|
unknown_data.push((unified::Typecode::SAPLING.into(), data.to_vec()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Item::Data(unified::Receiver::P2pkh(data)) => {
|
||||||
unified::Receiver::P2pkh(data) => {
|
|
||||||
transparent = Some(TransparentAddress::PublicKeyHash(*data));
|
transparent = Some(TransparentAddress::PublicKeyHash(*data));
|
||||||
}
|
}
|
||||||
|
Item::Data(unified::Receiver::P2sh(data)) => {
|
||||||
unified::Receiver::P2sh(data) => {
|
|
||||||
transparent = Some(TransparentAddress::ScriptHash(*data));
|
transparent = Some(TransparentAddress::ScriptHash(*data));
|
||||||
}
|
}
|
||||||
|
Item::Data(unified::Receiver::Unknown { typecode, data }) => {
|
||||||
unified::Receiver::Unknown { typecode, data } => {
|
unknown_data.push((*typecode, data.clone()));
|
||||||
unknown.push((*typecode, data.clone()));
|
}
|
||||||
|
Item::Metadata(unified::MetadataItem::ExpiryHeight(h)) => {
|
||||||
|
expiry_height = Some(BlockHeight::from(*h));
|
||||||
|
}
|
||||||
|
Item::Metadata(unified::MetadataItem::ExpiryTime(t)) => {
|
||||||
|
expiry_time = Some(*t);
|
||||||
|
}
|
||||||
|
Item::Metadata(unified::MetadataItem::Unknown { typecode, data }) => {
|
||||||
|
unknown_metadata.push((*typecode, data.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +99,10 @@ impl TryFrom<unified::Address> for UnifiedAddress {
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
sapling,
|
sapling,
|
||||||
transparent,
|
transparent,
|
||||||
unknown,
|
unknown_data,
|
||||||
|
expiry_height,
|
||||||
|
expiry_time,
|
||||||
|
unknown_metadata,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,36 +110,43 @@ impl TryFrom<unified::Address> for UnifiedAddress {
|
||||||
impl UnifiedAddress {
|
impl UnifiedAddress {
|
||||||
/// Constructs a Unified Address from a given set of receivers.
|
/// Constructs a Unified Address from a given set of receivers.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the receivers would produce an invalid Unified Address (namely,
|
/// This method is only available when the `test-dependencies` feature is enabled, as
|
||||||
/// if no shielded receiver is provided).
|
/// derivation from the UFVK or UIVK, or deserialization from the serialized form should be
|
||||||
|
/// used instead.
|
||||||
|
#[cfg(any(test, feature = "test-dependencies"))]
|
||||||
pub fn from_receivers(
|
pub fn from_receivers(
|
||||||
#[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
|
#[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
|
||||||
#[cfg(feature = "sapling")] sapling: Option<PaymentAddress>,
|
#[cfg(feature = "sapling")] sapling: Option<PaymentAddress>,
|
||||||
transparent: Option<TransparentAddress>,
|
transparent: Option<TransparentAddress>,
|
||||||
// TODO: Add handling for address metadata items.
|
) -> Self {
|
||||||
) -> Option<Self> {
|
Self::new_internal(
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let has_orchard = orchard.is_some();
|
orchard,
|
||||||
#[cfg(not(feature = "orchard"))]
|
#[cfg(feature = "sapling")]
|
||||||
let has_orchard = false;
|
sapling,
|
||||||
|
transparent,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sapling")]
|
pub(crate) fn new_internal(
|
||||||
let has_sapling = sapling.is_some();
|
#[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
|
||||||
#[cfg(not(feature = "sapling"))]
|
#[cfg(feature = "sapling")] sapling: Option<PaymentAddress>,
|
||||||
let has_sapling = false;
|
transparent: Option<TransparentAddress>,
|
||||||
|
expiry_height: Option<BlockHeight>,
|
||||||
if has_orchard || has_sapling {
|
expiry_time: Option<u64>,
|
||||||
Some(Self {
|
) -> Self {
|
||||||
#[cfg(feature = "orchard")]
|
Self {
|
||||||
orchard,
|
#[cfg(feature = "orchard")]
|
||||||
#[cfg(feature = "sapling")]
|
orchard,
|
||||||
sapling,
|
#[cfg(feature = "sapling")]
|
||||||
transparent,
|
sapling,
|
||||||
unknown: vec![],
|
transparent,
|
||||||
})
|
unknown_data: vec![],
|
||||||
} else {
|
expiry_height,
|
||||||
// UAs require at least one shielded receiver.
|
expiry_time,
|
||||||
None
|
unknown_metadata: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,22 +191,60 @@ impl UnifiedAddress {
|
||||||
self.transparent.as_ref()
|
self.transparent.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the set of unknown receivers of the unified address.
|
/// Returns any unknown data items parsed from the encoded form of the address.
|
||||||
pub fn unknown(&self) -> &[(u32, Vec<u8>)] {
|
pub fn unknown_data(&self) -> &[(u32, Vec<u8>)] {
|
||||||
&self.unknown
|
self.unknown_data.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the expiration height for this address.
|
||||||
|
pub fn expiry_height(&self) -> Option<BlockHeight> {
|
||||||
|
self.expiry_height
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the expiry height of this address.
|
||||||
|
pub fn set_expiry_height(&mut self, height: BlockHeight) {
|
||||||
|
self.expiry_height = Some(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the expiry height from this address.
|
||||||
|
pub fn unset_expiry_height(&mut self) {
|
||||||
|
self.expiry_height = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the expiration time for this address as a Unix Epoch Time.
|
||||||
|
pub fn expiry_time(&self) -> Option<u64> {
|
||||||
|
self.expiry_time
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the expiry time of this address.
|
||||||
|
pub fn set_expiry_time(&mut self, time: u64) {
|
||||||
|
self.expiry_time = Some(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the expiry time from this address.
|
||||||
|
pub fn unset_expiry_time(&mut self) {
|
||||||
|
self.expiry_time = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns any unknown metadata items parsed from the encoded form of the address.
|
||||||
|
///
|
||||||
|
/// Unknown metadata items are guaranteed by construction and parsing to not have keys in the
|
||||||
|
/// MUST-understand metadata typecode range.
|
||||||
|
pub fn unknown_metadata(&self) -> &[(u32, Vec<u8>)] {
|
||||||
|
self.unknown_metadata.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_address(&self, net: NetworkType) -> ZcashAddress {
|
fn to_address(&self, net: NetworkType) -> ZcashAddress {
|
||||||
let items = self
|
let data_items =
|
||||||
.unknown
|
self.unknown_data
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(typecode, data)| unified::Receiver::Unknown {
|
.map(|(typecode, data)| unified::Receiver::Unknown {
|
||||||
typecode: *typecode,
|
typecode: *typecode,
|
||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let items = items.chain(
|
let data_items = data_items.chain(
|
||||||
self.orchard
|
self.orchard
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|addr| addr.to_raw_address_bytes())
|
.map(|addr| addr.to_raw_address_bytes())
|
||||||
|
@ -191,20 +252,46 @@ impl UnifiedAddress {
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
let items = items.chain(
|
let data_items = data_items.chain(
|
||||||
self.sapling
|
self.sapling
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|pa| pa.to_bytes())
|
.map(|pa| pa.to_bytes())
|
||||||
.map(unified::Receiver::Sapling),
|
.map(unified::Receiver::Sapling),
|
||||||
);
|
);
|
||||||
|
|
||||||
let items = items.chain(self.transparent.as_ref().map(|taddr| match taddr {
|
let data_items = data_items.chain(self.transparent.as_ref().map(|taddr| match taddr {
|
||||||
TransparentAddress::PublicKeyHash(data) => unified::Receiver::P2pkh(*data),
|
TransparentAddress::PublicKeyHash(data) => unified::Receiver::P2pkh(*data),
|
||||||
TransparentAddress::ScriptHash(data) => unified::Receiver::P2sh(*data),
|
TransparentAddress::ScriptHash(data) => unified::Receiver::P2sh(*data),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let ua = unified::Address::try_from_items(items.collect())
|
let meta_items = self
|
||||||
.expect("UnifiedAddress should only be constructed safely");
|
.unknown_metadata
|
||||||
|
.iter()
|
||||||
|
.map(|(typecode, data)| unified::MetadataItem::Unknown {
|
||||||
|
typecode: *typecode,
|
||||||
|
data: data.clone(),
|
||||||
|
})
|
||||||
|
.chain(
|
||||||
|
self.expiry_height
|
||||||
|
.map(|h| unified::MetadataItem::ExpiryHeight(u32::from(h))),
|
||||||
|
)
|
||||||
|
.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()
|
||||||
|
|| !(self.has_orchard() || self.has_sapling())
|
||||||
|
{
|
||||||
|
Revision::R1
|
||||||
|
} else {
|
||||||
|
Revision::R0
|
||||||
|
},
|
||||||
|
data_items
|
||||||
|
.map(Item::Data)
|
||||||
|
.chain(meta_items.map(Item::Metadata))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.expect("UnifiedAddress should only be constructed safely");
|
||||||
ZcashAddress::from_unified(net, ua)
|
ZcashAddress::from_unified(net, ua)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,17 +304,17 @@ impl UnifiedAddress {
|
||||||
pub fn receiver_types(&self) -> Vec<Typecode> {
|
pub fn receiver_types(&self) -> Vec<Typecode> {
|
||||||
let result = std::iter::empty();
|
let result = std::iter::empty();
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let result = result.chain(self.orchard.map(|_| Typecode::Orchard));
|
let result = result.chain(self.orchard.map(|_| Typecode::ORCHARD));
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
let result = result.chain(self.sapling.map(|_| Typecode::Sapling));
|
let result = result.chain(self.sapling.map(|_| Typecode::SAPLING));
|
||||||
let result = result.chain(self.transparent.map(|taddr| match taddr {
|
let result = result.chain(self.transparent.map(|taddr| match taddr {
|
||||||
TransparentAddress::PublicKeyHash(_) => Typecode::P2pkh,
|
TransparentAddress::PublicKeyHash(_) => Typecode::P2PKH,
|
||||||
TransparentAddress::ScriptHash(_) => Typecode::P2sh,
|
TransparentAddress::ScriptHash(_) => Typecode::P2SH,
|
||||||
}));
|
}));
|
||||||
let result = result.chain(
|
let result = result.chain(
|
||||||
self.unknown()
|
self.unknown_data()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(typecode, _)| Typecode::Unknown(*typecode)),
|
.map(|(typecode, _)| Typecode::Data(DataTypecode::Unknown(*typecode))),
|
||||||
);
|
);
|
||||||
result.collect()
|
result.collect()
|
||||||
}
|
}
|
||||||
|
@ -256,8 +343,9 @@ impl Receiver {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
Receiver::Orchard(addr) => {
|
Receiver::Orchard(addr) => {
|
||||||
let receiver = unified::Receiver::Orchard(addr.to_raw_address_bytes());
|
let receiver =
|
||||||
let ua = unified::Address::try_from_items(vec![receiver])
|
unified::Item::Data(unified::Receiver::Orchard(addr.to_raw_address_bytes()));
|
||||||
|
let ua = unified::Address::try_from_items(Revision::R0, vec![receiver])
|
||||||
.expect("A unified address may contain a single Orchard receiver.");
|
.expect("A unified address may contain a single Orchard receiver.");
|
||||||
ZcashAddress::from_unified(net, ua)
|
ZcashAddress::from_unified(net, ua)
|
||||||
}
|
}
|
||||||
|
@ -294,27 +382,27 @@ impl Receiver {
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum Address {
|
pub enum Address {
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
Sapling(PaymentAddress),
|
Sapling(Box<PaymentAddress>),
|
||||||
Transparent(TransparentAddress),
|
Transparent(Box<TransparentAddress>),
|
||||||
Unified(UnifiedAddress),
|
Unified(Box<UnifiedAddress>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
impl From<PaymentAddress> for Address {
|
impl From<PaymentAddress> for Address {
|
||||||
fn from(addr: PaymentAddress) -> Self {
|
fn from(addr: PaymentAddress) -> Self {
|
||||||
Address::Sapling(addr)
|
Address::Sapling(Box::new(addr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TransparentAddress> for Address {
|
impl From<TransparentAddress> for Address {
|
||||||
fn from(addr: TransparentAddress) -> Self {
|
fn from(addr: TransparentAddress) -> Self {
|
||||||
Address::Transparent(addr)
|
Address::Transparent(Box::new(addr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<UnifiedAddress> for Address {
|
impl From<UnifiedAddress> for Address {
|
||||||
fn from(addr: UnifiedAddress) -> Self {
|
fn from(addr: UnifiedAddress) -> Self {
|
||||||
Address::Unified(addr)
|
Address::Unified(Box::new(addr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,12 +458,12 @@ impl Address {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
Address::Sapling(pa) => ZcashAddress::from_sapling(net, pa.to_bytes()),
|
Address::Sapling(pa) => ZcashAddress::from_sapling(net, pa.to_bytes()),
|
||||||
Address::Transparent(addr) => match addr {
|
Address::Transparent(addr) => match **addr {
|
||||||
TransparentAddress::PublicKeyHash(data) => {
|
TransparentAddress::PublicKeyHash(data) => {
|
||||||
ZcashAddress::from_transparent_p2pkh(net, *data)
|
ZcashAddress::from_transparent_p2pkh(net, data)
|
||||||
}
|
}
|
||||||
TransparentAddress::ScriptHash(data) => {
|
TransparentAddress::ScriptHash(data) => {
|
||||||
ZcashAddress::from_transparent_p2sh(net, *data)
|
ZcashAddress::from_transparent_p2sh(net, data)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Address::Unified(ua) => ua.to_address(net),
|
Address::Unified(ua) => ua.to_address(net),
|
||||||
|
@ -440,23 +528,23 @@ pub mod testing {
|
||||||
params: Network,
|
params: Network,
|
||||||
request: UnifiedAddressRequest,
|
request: UnifiedAddressRequest,
|
||||||
) -> impl Strategy<Value = UnifiedAddress> {
|
) -> impl Strategy<Value = UnifiedAddress> {
|
||||||
arb_unified_spending_key(params).prop_map(move |k| k.default_address(request).0)
|
arb_unified_spending_key(params).prop_map(move |k| k.default_address(request).unwrap().0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
pub fn arb_addr(request: UnifiedAddressRequest) -> impl Strategy<Value = Address> {
|
pub fn arb_addr(request: UnifiedAddressRequest) -> impl Strategy<Value = Address> {
|
||||||
prop_oneof![
|
prop_oneof![
|
||||||
arb_payment_address().prop_map(Address::Sapling),
|
arb_payment_address().prop_map(Address::from),
|
||||||
arb_transparent_addr().prop_map(Address::Transparent),
|
arb_transparent_addr().prop_map(Address::from),
|
||||||
arb_unified_addr(Network::TestNetwork, request).prop_map(Address::Unified),
|
arb_unified_addr(Network::TestNetwork, request).prop_map(Address::from),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "sapling"))]
|
#[cfg(not(feature = "sapling"))]
|
||||||
pub fn arb_addr(request: UnifiedAddressRequest) -> impl Strategy<Value = Address> {
|
pub fn arb_addr(request: UnifiedAddressRequest) -> impl Strategy<Value = Address> {
|
||||||
return prop_oneof![
|
return prop_oneof![
|
||||||
arb_transparent_addr().prop_map(Address::Transparent),
|
arb_transparent_addr().prop_map(Address::from),
|
||||||
arb_unified_addr(Network::TestNetwork, request).prop_map(Address::Unified),
|
arb_unified_addr(Network::TestNetwork, request).prop_map(Address::from),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -495,15 +583,15 @@ mod tests {
|
||||||
let transparent = None;
|
let transparent = None;
|
||||||
|
|
||||||
#[cfg(all(feature = "orchard", feature = "sapling"))]
|
#[cfg(all(feature = "orchard", feature = "sapling"))]
|
||||||
let ua = UnifiedAddress::from_receivers(orchard, sapling, transparent).unwrap();
|
let ua = UnifiedAddress::new_internal(orchard, sapling, transparent, None, None);
|
||||||
|
|
||||||
#[cfg(all(not(feature = "orchard"), feature = "sapling"))]
|
#[cfg(all(not(feature = "orchard"), feature = "sapling"))]
|
||||||
let ua = UnifiedAddress::from_receivers(sapling, transparent).unwrap();
|
let ua = UnifiedAddress::new_internal(sapling, transparent, None, None);
|
||||||
|
|
||||||
#[cfg(all(feature = "orchard", not(feature = "sapling")))]
|
#[cfg(all(feature = "orchard", not(feature = "sapling")))]
|
||||||
let ua = UnifiedAddress::from_receivers(orchard, transparent).unwrap();
|
let ua = UnifiedAddress::new_internal(orchard, transparent, None, None);
|
||||||
|
|
||||||
let addr = Address::Unified(ua);
|
let addr = Address::from(ua);
|
||||||
let addr_str = addr.encode(&MAIN_NETWORK);
|
let addr_str = addr.encode(&MAIN_NETWORK);
|
||||||
assert_eq!(Address::decode(&MAIN_NETWORK, &addr_str), Some(addr));
|
assert_eq!(Address::decode(&MAIN_NETWORK, &addr_str), Some(addr));
|
||||||
}
|
}
|
||||||
|
@ -512,7 +600,7 @@ mod tests {
|
||||||
#[cfg(not(any(feature = "orchard", feature = "sapling")))]
|
#[cfg(not(any(feature = "orchard", feature = "sapling")))]
|
||||||
fn ua_round_trip() {
|
fn ua_round_trip() {
|
||||||
let transparent = None;
|
let transparent = None;
|
||||||
assert_eq!(UnifiedAddress::from_receivers(transparent), None)
|
assert_eq!(UnifiedAddress::new_internal(transparent, None, None), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue