Merge pull request #1168 from nuttycom/zcash_keys_sapling_feature
zcash_keys: Add `sapling` and `transparent` feature flags.
This commit is contained in:
commit
5ed788dc79
|
@ -27,7 +27,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
zcash_address.workspace = true
|
zcash_address.workspace = true
|
||||||
zcash_encoding.workspace = true
|
zcash_encoding.workspace = true
|
||||||
zcash_keys.workspace = true
|
zcash_keys = { workspace = true, features = ["sapling"] }
|
||||||
zcash_note_encryption.workspace = true
|
zcash_note_encryption.workspace = true
|
||||||
zcash_primitives.workspace = true
|
zcash_primitives.workspace = true
|
||||||
zip32.workspace = true
|
zip32.workspace = true
|
||||||
|
|
|
@ -140,7 +140,7 @@ where
|
||||||
/// };
|
/// };
|
||||||
/// use zcash_proofs::prover::LocalTxProver;
|
/// use zcash_proofs::prover::LocalTxProver;
|
||||||
/// use zcash_client_backend::{
|
/// use zcash_client_backend::{
|
||||||
/// keys::UnifiedSpendingKey,
|
/// keys::{UnifiedSpendingKey, UnifiedAddressRequest},
|
||||||
/// data_api::{wallet::create_spend_to_address, error::Error, testing},
|
/// data_api::{wallet::create_spend_to_address, error::Error, testing},
|
||||||
/// wallet::OvkPolicy,
|
/// wallet::OvkPolicy,
|
||||||
/// };
|
/// };
|
||||||
|
@ -166,8 +166,9 @@ where
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// let account = AccountId::from(0);
|
/// let account = AccountId::from(0);
|
||||||
|
/// let req = UnifiedAddressRequest::new(false, true, true);
|
||||||
/// let usk = UnifiedSpendingKey::from_seed(&Network::TestNetwork, &[0; 32][..], account).unwrap();
|
/// let usk = UnifiedSpendingKey::from_seed(&Network::TestNetwork, &[0; 32][..], account).unwrap();
|
||||||
/// let to = usk.to_unified_full_viewing_key().default_address().0.into();
|
/// let to = usk.to_unified_full_viewing_key().default_address(req).0.into();
|
||||||
///
|
///
|
||||||
/// let mut db_read = testing::MockWalletDb {
|
/// let mut db_read = testing::MockWalletDb {
|
||||||
/// network: Network::TestNetwork
|
/// network: Network::TestNetwork
|
||||||
|
|
|
@ -22,7 +22,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||||
zcash_address.workspace = true
|
zcash_address.workspace = true
|
||||||
zcash_client_backend = { workspace = true, features = ["unstable-serialization", "unstable-spanning-tree"] }
|
zcash_client_backend = { workspace = true, features = ["unstable-serialization", "unstable-spanning-tree"] }
|
||||||
zcash_encoding.workspace = true
|
zcash_encoding.workspace = true
|
||||||
zcash_keys = { workspace = true, features = ["orchard"] }
|
zcash_keys = { workspace = true, features = ["orchard", "sapling"] }
|
||||||
zcash_primitives.workspace = true
|
zcash_primitives.workspace = true
|
||||||
|
|
||||||
# Dependencies exposed in a public API:
|
# Dependencies exposed in a public API:
|
||||||
|
@ -90,7 +90,7 @@ multicore = ["maybe-rayon/threads", "zcash_primitives/multicore"]
|
||||||
|
|
||||||
## Enables support for storing data related to the sending and receiving of
|
## Enables support for storing data related to the sending and receiving of
|
||||||
## Orchard funds.
|
## Orchard funds.
|
||||||
orchard = ["dep:orchard", "zcash_client_backend/orchard"]
|
orchard = ["dep:orchard", "zcash_client_backend/orchard", "zcash_keys/orchard"]
|
||||||
|
|
||||||
## Exposes APIs that are useful for testing, such as `proptest` strategies.
|
## Exposes APIs that are useful for testing, such as `proptest` strategies.
|
||||||
test-dependencies = [
|
test-dependencies = [
|
||||||
|
@ -100,8 +100,12 @@ test-dependencies = [
|
||||||
"incrementalmerkletree/test-dependencies",
|
"incrementalmerkletree/test-dependencies",
|
||||||
]
|
]
|
||||||
|
|
||||||
## Enables receiving transparent funds and shielding them.
|
## Enables receiving transparent funds and sending to transparent recipients
|
||||||
transparent-inputs = ["dep:hdwallet", "zcash_client_backend/transparent-inputs"]
|
transparent-inputs = [
|
||||||
|
"dep:hdwallet",
|
||||||
|
"zcash_keys/transparent-inputs",
|
||||||
|
"zcash_client_backend/transparent-inputs"
|
||||||
|
]
|
||||||
|
|
||||||
#! ### Experimental features
|
#! ### Experimental features
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ subtle.workspace = true
|
||||||
bls12_381.workspace = true
|
bls12_381.workspace = true
|
||||||
group.workspace = true
|
group.workspace = true
|
||||||
orchard = { workspace = true, optional = true }
|
orchard = { workspace = true, optional = true }
|
||||||
sapling.workspace = true
|
sapling = { workspace = true, optional = true }
|
||||||
|
|
||||||
# - Test dependencies
|
# - Test dependencies
|
||||||
proptest = { workspace = true, optional = true }
|
proptest = { workspace = true, optional = true }
|
||||||
|
@ -75,6 +75,9 @@ transparent-inputs = ["dep:hdwallet", "zcash_primitives/transparent-inputs"]
|
||||||
## Enables use of Orchard key parts and addresses
|
## Enables use of Orchard key parts and addresses
|
||||||
orchard = ["dep:orchard"]
|
orchard = ["dep:orchard"]
|
||||||
|
|
||||||
|
## Enables use of Sapling key parts and addresses
|
||||||
|
sapling = ["dep:sapling"]
|
||||||
|
|
||||||
## Exposes APIs that are useful for testing, such as `proptest` strategies.
|
## Exposes APIs that are useful for testing, such as `proptest` strategies.
|
||||||
test-dependencies = [
|
test-dependencies = [
|
||||||
"dep:proptest",
|
"dep:proptest",
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
//! Structs for handling supported address types.
|
//! Structs for handling supported address types.
|
||||||
|
|
||||||
use sapling::PaymentAddress;
|
|
||||||
use zcash_address::{
|
use zcash_address::{
|
||||||
unified::{self, Container, Encoding, Typecode},
|
unified::{self, Container, Encoding, Typecode},
|
||||||
ConversionError, Network, ToAddress, TryFromRawAddress, ZcashAddress,
|
ConversionError, Network, ToAddress, TryFromRawAddress, ZcashAddress,
|
||||||
};
|
};
|
||||||
use zcash_primitives::{consensus, legacy::TransparentAddress};
|
use zcash_primitives::{consensus, legacy::TransparentAddress};
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
|
use sapling::PaymentAddress;
|
||||||
|
|
||||||
/// A Unified Address.
|
/// A Unified Address.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct UnifiedAddress {
|
pub struct UnifiedAddress {
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard: Option<orchard::Address>,
|
orchard: Option<orchard::Address>,
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
sapling: Option<PaymentAddress>,
|
sapling: Option<PaymentAddress>,
|
||||||
transparent: Option<TransparentAddress>,
|
transparent: Option<TransparentAddress>,
|
||||||
unknown: Vec<(u32, Vec<u8>)>,
|
unknown: Vec<(u32, Vec<u8>)>,
|
||||||
|
@ -23,53 +26,62 @@ impl TryFrom<unified::Address> for UnifiedAddress {
|
||||||
fn try_from(ua: unified::Address) -> Result<Self, Self::Error> {
|
fn try_from(ua: unified::Address) -> Result<Self, Self::Error> {
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let mut orchard = None;
|
let mut orchard = None;
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
let mut sapling = None;
|
let mut sapling = None;
|
||||||
let mut transparent = None;
|
let mut transparent = None;
|
||||||
|
|
||||||
|
let mut unknown: Vec<(u32, Vec<u8>)> = 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.
|
||||||
let unknown = ua
|
for item in ua.items_as_parsed() {
|
||||||
.items_as_parsed()
|
match item {
|
||||||
.iter()
|
|
||||||
.filter_map(|receiver| match receiver {
|
|
||||||
#[cfg(feature = "orchard")]
|
|
||||||
unified::Receiver::Orchard(data) => {
|
unified::Receiver::Orchard(data) => {
|
||||||
Option::from(orchard::Address::from_raw_address_bytes(data))
|
#[cfg(feature = "orchard")]
|
||||||
.ok_or("Invalid Orchard receiver in Unified Address")
|
{
|
||||||
.map(|addr| {
|
orchard = Some(
|
||||||
orchard = Some(addr);
|
Option::from(orchard::Address::from_raw_address_bytes(data))
|
||||||
None
|
.ok_or("Invalid Orchard receiver in Unified Address")?,
|
||||||
})
|
);
|
||||||
.transpose()
|
}
|
||||||
|
#[cfg(not(feature = "orchard"))]
|
||||||
|
{
|
||||||
|
unknown.push((unified::Typecode::Orchard.into(), data.to_vec()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "orchard"))]
|
|
||||||
unified::Receiver::Orchard(data) => {
|
unified::Receiver::Sapling(data) => {
|
||||||
Some(Ok((unified::Typecode::Orchard.into(), data.to_vec())))
|
#[cfg(feature = "sapling")]
|
||||||
|
{
|
||||||
|
sapling = Some(
|
||||||
|
PaymentAddress::from_bytes(data)
|
||||||
|
.ok_or("Invalid Sapling receiver in Unified Address")?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "sapling"))]
|
||||||
|
{
|
||||||
|
unknown.push((unified::Typecode::Sapling.into(), data.to_vec()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
unified::Receiver::Sapling(data) => PaymentAddress::from_bytes(data)
|
|
||||||
.ok_or("Invalid Sapling receiver in Unified Address")
|
|
||||||
.map(|pa| {
|
|
||||||
sapling = Some(pa);
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.transpose(),
|
|
||||||
unified::Receiver::P2pkh(data) => {
|
unified::Receiver::P2pkh(data) => {
|
||||||
transparent = Some(TransparentAddress::PublicKeyHash(*data));
|
transparent = Some(TransparentAddress::PublicKeyHash(*data));
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unified::Receiver::P2sh(data) => {
|
unified::Receiver::P2sh(data) => {
|
||||||
transparent = Some(TransparentAddress::ScriptHash(*data));
|
transparent = Some(TransparentAddress::ScriptHash(*data));
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unified::Receiver::Unknown { typecode, data } => {
|
unified::Receiver::Unknown { typecode, data } => {
|
||||||
Some(Ok((*typecode, data.clone())))
|
unknown.push((*typecode, data.clone()));
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.collect::<Result<_, _>>()?;
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard,
|
orchard,
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
sapling,
|
sapling,
|
||||||
transparent,
|
transparent,
|
||||||
unknown,
|
unknown,
|
||||||
|
@ -84,18 +96,25 @@ impl UnifiedAddress {
|
||||||
/// if no shielded receiver is provided).
|
/// if no shielded receiver is provided).
|
||||||
pub fn from_receivers(
|
pub fn from_receivers(
|
||||||
#[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
|
#[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
|
||||||
sapling: Option<PaymentAddress>,
|
#[cfg(feature = "sapling")] sapling: Option<PaymentAddress>,
|
||||||
transparent: Option<TransparentAddress>,
|
transparent: Option<TransparentAddress>,
|
||||||
|
// TODO: Add handling for address metadata items.
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let has_orchard = orchard.is_some();
|
let has_orchard = orchard.is_some();
|
||||||
#[cfg(not(feature = "orchard"))]
|
#[cfg(not(feature = "orchard"))]
|
||||||
let has_orchard = false;
|
let has_orchard = false;
|
||||||
|
|
||||||
if has_orchard || sapling.is_some() {
|
#[cfg(feature = "sapling")]
|
||||||
|
let has_sapling = sapling.is_some();
|
||||||
|
#[cfg(not(feature = "sapling"))]
|
||||||
|
let has_sapling = false;
|
||||||
|
|
||||||
|
if has_orchard || has_sapling {
|
||||||
Some(Self {
|
Some(Self {
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard,
|
orchard,
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
sapling,
|
sapling,
|
||||||
transparent,
|
transparent,
|
||||||
unknown: vec![],
|
unknown: vec![],
|
||||||
|
@ -128,6 +147,7 @@ impl UnifiedAddress {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the Sapling receiver within this Unified Address, if any.
|
/// Returns the Sapling receiver within this Unified Address, if any.
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
pub fn sapling(&self) -> Option<&PaymentAddress> {
|
pub fn sapling(&self) -> Option<&PaymentAddress> {
|
||||||
self.sapling.as_ref()
|
self.sapling.as_ref()
|
||||||
}
|
}
|
||||||
|
@ -148,36 +168,37 @@ impl UnifiedAddress {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_address(&self, net: Network) -> ZcashAddress {
|
fn to_address(&self, net: Network) -> ZcashAddress {
|
||||||
#[cfg(feature = "orchard")]
|
let items = self
|
||||||
let orchard_receiver = self
|
.unknown
|
||||||
.orchard
|
.iter()
|
||||||
.as_ref()
|
.map(|(typecode, data)| unified::Receiver::Unknown {
|
||||||
.map(|addr| addr.to_raw_address_bytes())
|
typecode: *typecode,
|
||||||
.map(unified::Receiver::Orchard);
|
data: data.clone(),
|
||||||
#[cfg(not(feature = "orchard"))]
|
});
|
||||||
let orchard_receiver = None;
|
|
||||||
|
|
||||||
let ua = unified::Address::try_from_items(
|
#[cfg(feature = "orchard")]
|
||||||
self.unknown
|
let items = items.chain(
|
||||||
.iter()
|
self.orchard
|
||||||
.map(|(typecode, data)| unified::Receiver::Unknown {
|
.as_ref()
|
||||||
typecode: *typecode,
|
.map(|addr| addr.to_raw_address_bytes())
|
||||||
data: data.clone(),
|
.map(unified::Receiver::Orchard),
|
||||||
})
|
);
|
||||||
.chain(self.transparent.as_ref().map(|taddr| match taddr {
|
|
||||||
TransparentAddress::PublicKeyHash(data) => unified::Receiver::P2pkh(*data),
|
#[cfg(feature = "sapling")]
|
||||||
TransparentAddress::ScriptHash(data) => unified::Receiver::P2sh(*data),
|
let items = items.chain(
|
||||||
}))
|
self.sapling
|
||||||
.chain(
|
.as_ref()
|
||||||
self.sapling
|
.map(|pa| pa.to_bytes())
|
||||||
.as_ref()
|
.map(unified::Receiver::Sapling),
|
||||||
.map(|pa| pa.to_bytes())
|
);
|
||||||
.map(unified::Receiver::Sapling),
|
|
||||||
)
|
let items = items.chain(self.transparent.as_ref().map(|taddr| match taddr {
|
||||||
.chain(orchard_receiver)
|
TransparentAddress::PublicKeyHash(data) => unified::Receiver::P2pkh(*data),
|
||||||
.collect(),
|
TransparentAddress::ScriptHash(data) => unified::Receiver::P2sh(*data),
|
||||||
)
|
}));
|
||||||
.expect("UnifiedAddress should only be constructed safely");
|
|
||||||
|
let ua = unified::Address::try_from_items(items.collect())
|
||||||
|
.expect("UnifiedAddress should only be constructed safely");
|
||||||
ZcashAddress::from_unified(net, ua)
|
ZcashAddress::from_unified(net, ua)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,11 +230,13 @@ impl UnifiedAddress {
|
||||||
/// An address that funds can be sent to.
|
/// An address that funds can be sent to.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum Address {
|
pub enum Address {
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
Sapling(PaymentAddress),
|
Sapling(PaymentAddress),
|
||||||
Transparent(TransparentAddress),
|
Transparent(TransparentAddress),
|
||||||
Unified(UnifiedAddress),
|
Unified(UnifiedAddress),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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(addr)
|
||||||
|
@ -235,6 +258,7 @@ impl From<UnifiedAddress> for Address {
|
||||||
impl TryFromRawAddress for Address {
|
impl TryFromRawAddress for Address {
|
||||||
type Error = &'static str;
|
type Error = &'static str;
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
fn try_from_raw_sapling(data: [u8; 43]) -> Result<Self, ConversionError<Self::Error>> {
|
fn try_from_raw_sapling(data: [u8; 43]) -> Result<Self, ConversionError<Self::Error>> {
|
||||||
let pa = PaymentAddress::from_bytes(&data).ok_or("Invalid Sapling payment address")?;
|
let pa = PaymentAddress::from_bytes(&data).ok_or("Invalid Sapling payment address")?;
|
||||||
Ok(pa.into())
|
Ok(pa.into())
|
||||||
|
@ -270,6 +294,7 @@ impl Address {
|
||||||
let net = params.address_network().expect("Unrecognized network");
|
let net = params.address_network().expect("Unrecognized network");
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
|
#[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) => {
|
||||||
|
@ -288,13 +313,16 @@ impl Address {
|
||||||
#[cfg(any(test, feature = "test-dependencies"))]
|
#[cfg(any(test, feature = "test-dependencies"))]
|
||||||
pub mod testing {
|
pub mod testing {
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
use sapling::testing::arb_payment_address;
|
use zcash_primitives::consensus::Network;
|
||||||
use zcash_primitives::{consensus::Network, legacy::testing::arb_transparent_addr};
|
|
||||||
|
|
||||||
use crate::keys::{testing::arb_unified_spending_key, UnifiedAddressRequest};
|
use crate::keys::{testing::arb_unified_spending_key, UnifiedAddressRequest};
|
||||||
|
|
||||||
use super::{Address, UnifiedAddress};
|
use super::{Address, UnifiedAddress};
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
|
use sapling::testing::arb_payment_address;
|
||||||
|
use zcash_primitives::legacy::testing::arb_transparent_addr;
|
||||||
|
|
||||||
pub fn arb_unified_addr(
|
pub fn arb_unified_addr(
|
||||||
params: Network,
|
params: Network,
|
||||||
request: UnifiedAddressRequest,
|
request: UnifiedAddressRequest,
|
||||||
|
@ -302,6 +330,7 @@ pub mod testing {
|
||||||
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).0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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::Sapling),
|
||||||
|
@ -309,17 +338,34 @@ pub mod testing {
|
||||||
arb_unified_addr(Network::TestNetwork, request).prop_map(Address::Unified),
|
arb_unified_addr(Network::TestNetwork, request).prop_map(Address::Unified),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "sapling"))]
|
||||||
|
pub fn arb_addr(request: UnifiedAddressRequest) -> impl Strategy<Value = Address> {
|
||||||
|
return prop_oneof![
|
||||||
|
arb_transparent_addr().prop_map(Address::Transparent),
|
||||||
|
arb_unified_addr(Network::TestNetwork, request).prop_map(Address::Unified),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use zcash_address::test_vectors;
|
use zcash_address::test_vectors;
|
||||||
use zcash_primitives::{consensus::MAIN_NETWORK, zip32::AccountId};
|
use zcash_primitives::consensus::MAIN_NETWORK;
|
||||||
|
|
||||||
use super::{Address, UnifiedAddress};
|
use super::Address;
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
use crate::keys::sapling;
|
use crate::keys::sapling;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "orchard", feature = "sapling"))]
|
||||||
|
use zcash_primitives::zip32::AccountId;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "orchard", feature = "sapling"))]
|
||||||
|
use super::UnifiedAddress;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(any(feature = "orchard", feature = "sapling"))]
|
||||||
fn ua_round_trip() {
|
fn ua_round_trip() {
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let orchard = {
|
let orchard = {
|
||||||
|
@ -329,37 +375,49 @@ mod tests {
|
||||||
Some(fvk.address_at(0u32, orchard::keys::Scope::External))
|
Some(fvk.address_at(0u32, orchard::keys::Scope::External))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
let sapling = {
|
let sapling = {
|
||||||
let extsk = sapling::spending_key(&[0; 32], 0, AccountId::ZERO);
|
let extsk = sapling::spending_key(&[0; 32], 0, AccountId::ZERO);
|
||||||
let dfvk = extsk.to_diversifiable_full_viewing_key();
|
let dfvk = extsk.to_diversifiable_full_viewing_key();
|
||||||
Some(dfvk.default_address().1)
|
Some(dfvk.default_address().1)
|
||||||
};
|
};
|
||||||
|
|
||||||
let transparent = { None };
|
let transparent = None;
|
||||||
|
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(all(feature = "orchard", feature = "sapling"))]
|
||||||
let ua = UnifiedAddress::from_receivers(orchard, sapling, transparent).unwrap();
|
let ua = UnifiedAddress::from_receivers(orchard, sapling, transparent).unwrap();
|
||||||
|
|
||||||
#[cfg(not(feature = "orchard"))]
|
#[cfg(all(not(feature = "orchard"), feature = "sapling"))]
|
||||||
let ua = UnifiedAddress::from_receivers(sapling, transparent).unwrap();
|
let ua = UnifiedAddress::from_receivers(sapling, transparent).unwrap();
|
||||||
|
|
||||||
|
#[cfg(all(feature = "orchard", not(feature = "sapling")))]
|
||||||
|
let ua = UnifiedAddress::from_receivers(orchard, transparent).unwrap();
|
||||||
|
|
||||||
let addr = Address::Unified(ua);
|
let addr = Address::Unified(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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(any(feature = "orchard", feature = "sapling")))]
|
||||||
|
fn ua_round_trip() {
|
||||||
|
let transparent = None;
|
||||||
|
assert_eq!(UnifiedAddress::from_receivers(transparent), None)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ua_parsing() {
|
fn ua_parsing() {
|
||||||
for tv in test_vectors::UNIFIED {
|
for tv in test_vectors::UNIFIED {
|
||||||
match Address::decode(&MAIN_NETWORK, tv.unified_addr) {
|
match Address::decode(&MAIN_NETWORK, tv.unified_addr) {
|
||||||
Some(Address::Unified(ua)) => {
|
Some(Address::Unified(_ua)) => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ua.transparent().is_some(),
|
_ua.transparent().is_some(),
|
||||||
tv.p2pkh_bytes.is_some() || tv.p2sh_bytes.is_some()
|
tv.p2pkh_bytes.is_some() || tv.p2sh_bytes.is_some()
|
||||||
);
|
);
|
||||||
assert_eq!(ua.sapling().is_some(), tv.sapling_raw_addr.is_some());
|
#[cfg(feature = "sapling")]
|
||||||
|
assert_eq!(_ua.sapling().is_some(), tv.sapling_raw_addr.is_some());
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
assert_eq!(ua.orchard().is_some(), tv.orchard_raw_addr.is_some());
|
assert_eq!(_ua.orchard().is_some(), tv.orchard_raw_addr.is_some());
|
||||||
}
|
}
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
panic!(
|
panic!(
|
||||||
|
|
|
@ -4,15 +4,20 @@
|
||||||
//! [zcash_primitives::constants] module.
|
//! [zcash_primitives::constants] module.
|
||||||
|
|
||||||
use crate::address::UnifiedAddress;
|
use crate::address::UnifiedAddress;
|
||||||
use bech32::{self, Error, FromBase32, ToBase32, Variant};
|
|
||||||
use bs58::{self, decode::Error as Bs58Error};
|
use bs58::{self, decode::Error as Bs58Error};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::{self, Write};
|
|
||||||
|
|
||||||
use sapling::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey};
|
|
||||||
use zcash_address::unified::{self, Encoding};
|
use zcash_address::unified::{self, Encoding};
|
||||||
use zcash_primitives::{consensus, legacy::TransparentAddress};
|
use zcash_primitives::{consensus, legacy::TransparentAddress};
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
|
use {
|
||||||
|
bech32::{self, Error, FromBase32, ToBase32, Variant},
|
||||||
|
sapling::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
||||||
|
std::io::{self, Write},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
fn bech32_encode<F>(hrp: &str, write: F) -> String
|
fn bech32_encode<F>(hrp: &str, write: F) -> String
|
||||||
where
|
where
|
||||||
F: Fn(&mut dyn Write) -> io::Result<()>,
|
F: Fn(&mut dyn Write) -> io::Result<()>,
|
||||||
|
@ -23,6 +28,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
pub enum Bech32DecodeError {
|
pub enum Bech32DecodeError {
|
||||||
Bech32Error(Error),
|
Bech32Error(Error),
|
||||||
IncorrectVariant(Variant),
|
IncorrectVariant(Variant),
|
||||||
|
@ -30,12 +36,14 @@ pub enum Bech32DecodeError {
|
||||||
HrpMismatch { expected: String, actual: String },
|
HrpMismatch { expected: String, actual: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
impl From<Error> for Bech32DecodeError {
|
impl From<Error> for Bech32DecodeError {
|
||||||
fn from(err: Error) -> Self {
|
fn from(err: Error) -> Self {
|
||||||
Bech32DecodeError::Bech32Error(err)
|
Bech32DecodeError::Bech32Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
impl fmt::Display for Bech32DecodeError {
|
impl fmt::Display for Bech32DecodeError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match &self {
|
match &self {
|
||||||
|
@ -57,6 +65,7 @@ impl fmt::Display for Bech32DecodeError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
fn bech32_decode<T, F>(hrp: &str, s: &str, read: F) -> Result<T, Bech32DecodeError>
|
fn bech32_decode<T, F>(hrp: &str, s: &str, read: F) -> Result<T, Bech32DecodeError>
|
||||||
where
|
where
|
||||||
F: Fn(Vec<u8>) -> Option<T>,
|
F: Fn(Vec<u8>) -> Option<T>,
|
||||||
|
@ -140,6 +149,7 @@ impl<P: consensus::Parameters> AddressCodec<P> for TransparentAddress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
impl<P: consensus::Parameters> AddressCodec<P> for sapling::PaymentAddress {
|
impl<P: consensus::Parameters> AddressCodec<P> for sapling::PaymentAddress {
|
||||||
type Error = Bech32DecodeError;
|
type Error = Bech32DecodeError;
|
||||||
|
|
||||||
|
@ -193,6 +203,7 @@ impl<P: consensus::Parameters> AddressCodec<P> for UnifiedAddress {
|
||||||
/// let encoded = encode_extended_spending_key(HRP_SAPLING_EXTENDED_SPENDING_KEY, &extsk);
|
/// let encoded = encode_extended_spending_key(HRP_SAPLING_EXTENDED_SPENDING_KEY, &extsk);
|
||||||
/// ```
|
/// ```
|
||||||
/// [`ExtendedSpendingKey`]: sapling::zip32::ExtendedSpendingKey
|
/// [`ExtendedSpendingKey`]: sapling::zip32::ExtendedSpendingKey
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
pub fn encode_extended_spending_key(hrp: &str, extsk: &ExtendedSpendingKey) -> String {
|
pub fn encode_extended_spending_key(hrp: &str, extsk: &ExtendedSpendingKey) -> String {
|
||||||
bech32_encode(hrp, |w| extsk.write(w))
|
bech32_encode(hrp, |w| extsk.write(w))
|
||||||
}
|
}
|
||||||
|
@ -200,6 +211,7 @@ pub fn encode_extended_spending_key(hrp: &str, extsk: &ExtendedSpendingKey) -> S
|
||||||
/// Decodes an [`ExtendedSpendingKey`] from a Bech32-encoded string.
|
/// Decodes an [`ExtendedSpendingKey`] from a Bech32-encoded string.
|
||||||
///
|
///
|
||||||
/// [`ExtendedSpendingKey`]: sapling::zip32::ExtendedSpendingKey
|
/// [`ExtendedSpendingKey`]: sapling::zip32::ExtendedSpendingKey
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
pub fn decode_extended_spending_key(
|
pub fn decode_extended_spending_key(
|
||||||
hrp: &str,
|
hrp: &str,
|
||||||
s: &str,
|
s: &str,
|
||||||
|
@ -227,6 +239,7 @@ pub fn decode_extended_spending_key(
|
||||||
/// let encoded = encode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, &extfvk);
|
/// let encoded = encode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, &extfvk);
|
||||||
/// ```
|
/// ```
|
||||||
/// [`ExtendedFullViewingKey`]: sapling::zip32::ExtendedFullViewingKey
|
/// [`ExtendedFullViewingKey`]: sapling::zip32::ExtendedFullViewingKey
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
pub fn encode_extended_full_viewing_key(hrp: &str, extfvk: &ExtendedFullViewingKey) -> String {
|
pub fn encode_extended_full_viewing_key(hrp: &str, extfvk: &ExtendedFullViewingKey) -> String {
|
||||||
bech32_encode(hrp, |w| extfvk.write(w))
|
bech32_encode(hrp, |w| extfvk.write(w))
|
||||||
}
|
}
|
||||||
|
@ -234,6 +247,7 @@ pub fn encode_extended_full_viewing_key(hrp: &str, extfvk: &ExtendedFullViewingK
|
||||||
/// Decodes an [`ExtendedFullViewingKey`] from a Bech32-encoded string.
|
/// Decodes an [`ExtendedFullViewingKey`] from a Bech32-encoded string.
|
||||||
///
|
///
|
||||||
/// [`ExtendedFullViewingKey`]: sapling::zip32::ExtendedFullViewingKey
|
/// [`ExtendedFullViewingKey`]: sapling::zip32::ExtendedFullViewingKey
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
pub fn decode_extended_full_viewing_key(
|
pub fn decode_extended_full_viewing_key(
|
||||||
hrp: &str,
|
hrp: &str,
|
||||||
s: &str,
|
s: &str,
|
||||||
|
@ -269,6 +283,7 @@ pub fn decode_extended_full_viewing_key(
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
/// [`PaymentAddress`]: sapling::PaymentAddress
|
/// [`PaymentAddress`]: sapling::PaymentAddress
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
pub fn encode_payment_address(hrp: &str, addr: &sapling::PaymentAddress) -> String {
|
pub fn encode_payment_address(hrp: &str, addr: &sapling::PaymentAddress) -> String {
|
||||||
bech32_encode(hrp, |w| w.write_all(&addr.to_bytes()))
|
bech32_encode(hrp, |w| w.write_all(&addr.to_bytes()))
|
||||||
}
|
}
|
||||||
|
@ -278,6 +293,7 @@ pub fn encode_payment_address(hrp: &str, addr: &sapling::PaymentAddress) -> Stri
|
||||||
/// network parameters.
|
/// network parameters.
|
||||||
///
|
///
|
||||||
/// [`PaymentAddress`]: sapling::PaymentAddress
|
/// [`PaymentAddress`]: sapling::PaymentAddress
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
pub fn encode_payment_address_p<P: consensus::Parameters>(
|
pub fn encode_payment_address_p<P: consensus::Parameters>(
|
||||||
params: &P,
|
params: &P,
|
||||||
addr: &sapling::PaymentAddress,
|
addr: &sapling::PaymentAddress,
|
||||||
|
@ -316,6 +332,7 @@ pub fn encode_payment_address_p<P: consensus::Parameters>(
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
/// [`PaymentAddress`]: sapling::PaymentAddress
|
/// [`PaymentAddress`]: sapling::PaymentAddress
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
pub fn decode_payment_address(
|
pub fn decode_payment_address(
|
||||||
hrp: &str,
|
hrp: &str,
|
||||||
s: &str,
|
s: &str,
|
||||||
|
@ -454,15 +471,15 @@ pub fn decode_transparent_address(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
#[cfg(feature = "sapling")]
|
||||||
use sapling::{zip32::ExtendedSpendingKey, PaymentAddress};
|
mod tests_sapling {
|
||||||
use zcash_primitives::constants;
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
decode_extended_full_viewing_key, decode_extended_spending_key, decode_payment_address,
|
decode_extended_full_viewing_key, decode_extended_spending_key, decode_payment_address,
|
||||||
encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address,
|
encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address,
|
||||||
Bech32DecodeError,
|
Bech32DecodeError,
|
||||||
};
|
};
|
||||||
|
use sapling::{zip32::ExtendedSpendingKey, PaymentAddress};
|
||||||
|
use zcash_primitives::constants;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn extended_spending_key() {
|
fn extended_spending_key() {
|
||||||
|
|
|
@ -31,6 +31,7 @@ use {
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
use orchard::{self, keys::Scope};
|
use orchard::{self, keys::Scope};
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
pub mod sapling {
|
pub mod sapling {
|
||||||
pub use sapling::zip32::{
|
pub use sapling::zip32::{
|
||||||
DiversifiableFullViewingKey, ExtendedFullViewingKey, ExtendedSpendingKey,
|
DiversifiableFullViewingKey, ExtendedFullViewingKey, ExtendedSpendingKey,
|
||||||
|
@ -167,6 +168,7 @@ impl Era {
|
||||||
pub struct UnifiedSpendingKey {
|
pub struct UnifiedSpendingKey {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
transparent: legacy::AccountPrivKey,
|
transparent: legacy::AccountPrivKey,
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
sapling: sapling::ExtendedSpendingKey,
|
sapling: sapling::ExtendedSpendingKey,
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard: orchard::keys::SpendingKey,
|
orchard: orchard::keys::SpendingKey,
|
||||||
|
@ -175,29 +177,27 @@ pub struct UnifiedSpendingKey {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
impl UnifiedSpendingKey {
|
impl UnifiedSpendingKey {
|
||||||
pub fn from_seed<P: consensus::Parameters>(
|
pub fn from_seed<P: consensus::Parameters>(
|
||||||
params: &P,
|
_params: &P,
|
||||||
seed: &[u8],
|
seed: &[u8],
|
||||||
account: AccountId,
|
_account: AccountId,
|
||||||
) -> Result<UnifiedSpendingKey, DerivationError> {
|
) -> Result<UnifiedSpendingKey, DerivationError> {
|
||||||
if seed.len() < 32 {
|
if seed.len() < 32 {
|
||||||
panic!("ZIP 32 seeds MUST be at least 32 bytes");
|
panic!("ZIP 32 seeds MUST be at least 32 bytes");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "orchard")]
|
|
||||||
let orchard =
|
|
||||||
orchard::keys::SpendingKey::from_zip32_seed(seed, params.coin_type(), account)
|
|
||||||
.map_err(DerivationError::Orchard)?;
|
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
|
||||||
let transparent = legacy::AccountPrivKey::from_seed(params, seed, account)
|
|
||||||
.map_err(DerivationError::Transparent)?;
|
|
||||||
|
|
||||||
Ok(UnifiedSpendingKey {
|
Ok(UnifiedSpendingKey {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
transparent,
|
transparent: legacy::AccountPrivKey::from_seed(_params, seed, _account)
|
||||||
sapling: sapling::spending_key(seed, params.coin_type(), account),
|
.map_err(DerivationError::Transparent)?,
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
|
sapling: sapling::spending_key(seed, _params.coin_type(), _account),
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard,
|
orchard: orchard::keys::SpendingKey::from_zip32_seed(
|
||||||
|
seed,
|
||||||
|
_params.coin_type(),
|
||||||
|
_account,
|
||||||
|
)
|
||||||
|
.map_err(DerivationError::Orchard)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,6 +205,7 @@ impl UnifiedSpendingKey {
|
||||||
UnifiedFullViewingKey {
|
UnifiedFullViewingKey {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
transparent: Some(self.transparent.to_account_pubkey()),
|
transparent: Some(self.transparent.to_account_pubkey()),
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
sapling: Some(self.sapling.to_diversifiable_full_viewing_key()),
|
sapling: Some(self.sapling.to_diversifiable_full_viewing_key()),
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard: Some((&self.orchard).into()),
|
orchard: Some((&self.orchard).into()),
|
||||||
|
@ -220,6 +221,7 @@ impl UnifiedSpendingKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the Sapling extended spending key component of this unified spending key.
|
/// Returns the Sapling extended spending key component of this unified spending key.
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
pub fn sapling(&self) -> &sapling::ExtendedSpendingKey {
|
pub fn sapling(&self) -> &sapling::ExtendedSpendingKey {
|
||||||
&self.sapling
|
&self.sapling
|
||||||
}
|
}
|
||||||
|
@ -254,15 +256,16 @@ impl UnifiedSpendingKey {
|
||||||
result.write_all(orchard_key_bytes).unwrap();
|
result.write_all(orchard_key_bytes).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// sapling
|
#[cfg(feature = "sapling")]
|
||||||
let sapling_key = self.sapling();
|
{
|
||||||
CompactSize::write(&mut result, usize::try_from(Typecode::Sapling).unwrap()).unwrap();
|
let sapling_key = self.sapling();
|
||||||
|
CompactSize::write(&mut result, usize::try_from(Typecode::Sapling).unwrap()).unwrap();
|
||||||
|
|
||||||
let sapling_key_bytes = sapling_key.to_bytes();
|
let sapling_key_bytes = sapling_key.to_bytes();
|
||||||
CompactSize::write(&mut result, sapling_key_bytes.len()).unwrap();
|
CompactSize::write(&mut result, sapling_key_bytes.len()).unwrap();
|
||||||
result.write_all(&sapling_key_bytes).unwrap();
|
result.write_all(&sapling_key_bytes).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// transparent
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
{
|
{
|
||||||
let account_tkey = self.transparent();
|
let account_tkey = self.transparent();
|
||||||
|
@ -294,6 +297,7 @@ impl UnifiedSpendingKey {
|
||||||
|
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let mut orchard = None;
|
let mut orchard = None;
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
let mut sapling = None;
|
let mut sapling = None;
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
let mut transparent = None;
|
let mut transparent = None;
|
||||||
|
@ -335,12 +339,15 @@ impl UnifiedSpendingKey {
|
||||||
source
|
source
|
||||||
.read_exact(&mut key)
|
.read_exact(&mut key)
|
||||||
.map_err(|_| DecodingError::InsufficientData(Typecode::Sapling))?;
|
.map_err(|_| DecodingError::InsufficientData(Typecode::Sapling))?;
|
||||||
sapling = Some(
|
|
||||||
sapling::ExtendedSpendingKey::from_bytes(&key)
|
#[cfg(feature = "sapling")]
|
||||||
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::Sapling))?,
|
{
|
||||||
);
|
sapling = Some(
|
||||||
|
sapling::ExtendedSpendingKey::from_bytes(&key)
|
||||||
|
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::Sapling))?,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "transparent-inputs")]
|
|
||||||
Typecode::P2pkh => {
|
Typecode::P2pkh => {
|
||||||
if len != 64 {
|
if len != 64 {
|
||||||
return Err(DecodingError::LengthMismatch(Typecode::P2pkh, len));
|
return Err(DecodingError::LengthMismatch(Typecode::P2pkh, len));
|
||||||
|
@ -350,10 +357,14 @@ impl UnifiedSpendingKey {
|
||||||
source
|
source
|
||||||
.read_exact(&mut key)
|
.read_exact(&mut key)
|
||||||
.map_err(|_| DecodingError::InsufficientData(Typecode::P2pkh))?;
|
.map_err(|_| DecodingError::InsufficientData(Typecode::P2pkh))?;
|
||||||
transparent = Some(
|
|
||||||
legacy::AccountPrivKey::from_bytes(&key)
|
#[cfg(feature = "transparent-inputs")]
|
||||||
.ok_or(DecodingError::KeyDataInvalid(Typecode::P2pkh))?,
|
{
|
||||||
);
|
transparent = Some(
|
||||||
|
legacy::AccountPrivKey::from_bytes(&key)
|
||||||
|
.ok_or(DecodingError::KeyDataInvalid(Typecode::P2pkh))?,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(DecodingError::TypecodeInvalid);
|
return Err(DecodingError::TypecodeInvalid);
|
||||||
|
@ -365,15 +376,21 @@ impl UnifiedSpendingKey {
|
||||||
#[cfg(not(feature = "orchard"))]
|
#[cfg(not(feature = "orchard"))]
|
||||||
let has_orchard = true;
|
let has_orchard = true;
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
|
let has_sapling = sapling.is_some();
|
||||||
|
#[cfg(not(feature = "sapling"))]
|
||||||
|
let has_sapling = true;
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
let has_transparent = transparent.is_some();
|
let has_transparent = transparent.is_some();
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
#[cfg(not(feature = "transparent-inputs"))]
|
||||||
let has_transparent = true;
|
let has_transparent = true;
|
||||||
|
|
||||||
if has_orchard && sapling.is_some() && has_transparent {
|
if has_orchard && has_sapling && has_transparent {
|
||||||
return Ok(UnifiedSpendingKey {
|
return Ok(UnifiedSpendingKey {
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard: orchard.unwrap(),
|
orchard: orchard.unwrap(),
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
sapling: sapling.unwrap(),
|
sapling: sapling.unwrap(),
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
transparent: transparent.unwrap(),
|
transparent: transparent.unwrap(),
|
||||||
|
@ -382,7 +399,7 @@ impl UnifiedSpendingKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "test-dependencies")]
|
#[cfg(any(test, feature = "test-dependencies"))]
|
||||||
pub fn default_address(
|
pub fn default_address(
|
||||||
&self,
|
&self,
|
||||||
request: UnifiedAddressRequest,
|
request: UnifiedAddressRequest,
|
||||||
|
@ -390,7 +407,10 @@ impl UnifiedSpendingKey {
|
||||||
self.to_unified_full_viewing_key().default_address(request)
|
self.to_unified_full_viewing_key().default_address(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "test-dependencies", feature = "transparent-inputs"))]
|
#[cfg(all(
|
||||||
|
feature = "transparent-inputs",
|
||||||
|
any(test, feature = "test-dependencies")
|
||||||
|
))]
|
||||||
pub fn default_transparent_address(&self) -> (TransparentAddress, NonHardenedChildIndex) {
|
pub fn default_transparent_address(&self) -> (TransparentAddress, NonHardenedChildIndex) {
|
||||||
self.transparent()
|
self.transparent()
|
||||||
.to_account_pubkey()
|
.to_account_pubkey()
|
||||||
|
@ -405,8 +425,10 @@ impl UnifiedSpendingKey {
|
||||||
pub enum AddressGenerationError {
|
pub enum AddressGenerationError {
|
||||||
/// The requested diversifier index was outside the range of valid transparent
|
/// The requested diversifier index was outside the range of valid transparent
|
||||||
/// child address indices.
|
/// child address indices.
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
InvalidTransparentChildIndex(DiversifierIndex),
|
InvalidTransparentChildIndex(DiversifierIndex),
|
||||||
/// The diversifier index could not be mapped to a valid Sapling diversifier.
|
/// The diversifier index could not be mapped to a valid Sapling diversifier.
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
InvalidSaplingDiversifierIndex(DiversifierIndex),
|
InvalidSaplingDiversifierIndex(DiversifierIndex),
|
||||||
/// A requested address typecode was not recognized, so we are unable to generate the address
|
/// A requested address typecode was not recognized, so we are unable to generate the address
|
||||||
/// as requested.
|
/// as requested.
|
||||||
|
@ -422,13 +444,12 @@ pub enum AddressGenerationError {
|
||||||
/// Specification for how a unified address should be generated from a unified viewing key.
|
/// Specification for how a unified address should be generated from a unified viewing key.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct UnifiedAddressRequest {
|
pub struct UnifiedAddressRequest {
|
||||||
_has_orchard: bool,
|
has_orchard: bool,
|
||||||
has_sapling: bool,
|
has_sapling: bool,
|
||||||
has_p2pkh: bool,
|
has_p2pkh: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnifiedAddressRequest {
|
impl UnifiedAddressRequest {
|
||||||
/// Construct a new unified address request from its constituent parts
|
|
||||||
pub fn new(has_orchard: bool, has_sapling: bool, has_p2pkh: bool) -> Option<Self> {
|
pub fn new(has_orchard: bool, has_sapling: bool, has_p2pkh: bool) -> Option<Self> {
|
||||||
let has_shielded_receiver = has_orchard || has_sapling;
|
let has_shielded_receiver = has_orchard || has_sapling;
|
||||||
|
|
||||||
|
@ -436,7 +457,7 @@ impl UnifiedAddressRequest {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(Self {
|
Some(Self {
|
||||||
_has_orchard: has_orchard,
|
has_orchard,
|
||||||
has_sapling,
|
has_sapling,
|
||||||
has_p2pkh,
|
has_p2pkh,
|
||||||
})
|
})
|
||||||
|
@ -452,7 +473,7 @@ impl UnifiedAddressRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
_has_orchard: has_orchard,
|
has_orchard,
|
||||||
has_sapling,
|
has_sapling,
|
||||||
has_p2pkh,
|
has_p2pkh,
|
||||||
}
|
}
|
||||||
|
@ -465,6 +486,7 @@ impl UnifiedAddressRequest {
|
||||||
pub struct UnifiedFullViewingKey {
|
pub struct UnifiedFullViewingKey {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
transparent: Option<legacy::AccountPubKey>,
|
transparent: Option<legacy::AccountPubKey>,
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
sapling: Option<sapling::DiversifiableFullViewingKey>,
|
sapling: Option<sapling::DiversifiableFullViewingKey>,
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard: Option<orchard::keys::FullViewingKey>,
|
orchard: Option<orchard::keys::FullViewingKey>,
|
||||||
|
@ -476,15 +498,24 @@ impl UnifiedFullViewingKey {
|
||||||
/// Construct a new unified full viewing key, if the required components are present.
|
/// Construct a new unified full viewing key, if the required components are present.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
#[cfg(feature = "transparent-inputs")] transparent: Option<legacy::AccountPubKey>,
|
#[cfg(feature = "transparent-inputs")] transparent: Option<legacy::AccountPubKey>,
|
||||||
sapling: Option<sapling::DiversifiableFullViewingKey>,
|
#[cfg(feature = "sapling")] sapling: Option<sapling::DiversifiableFullViewingKey>,
|
||||||
#[cfg(feature = "orchard")] orchard: Option<orchard::keys::FullViewingKey>,
|
#[cfg(feature = "orchard")] orchard: Option<orchard::keys::FullViewingKey>,
|
||||||
|
// TODO: Implement construction of UFVKs with metadata items.
|
||||||
) -> Option<UnifiedFullViewingKey> {
|
) -> Option<UnifiedFullViewingKey> {
|
||||||
if sapling.is_none() {
|
#[cfg(feature = "orchard")]
|
||||||
None
|
let has_orchard = orchard.is_some();
|
||||||
} else {
|
#[cfg(not(feature = "orchard"))]
|
||||||
|
let has_orchard = false;
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
|
let has_sapling = sapling.is_some();
|
||||||
|
#[cfg(not(feature = "sapling"))]
|
||||||
|
let has_sapling = false;
|
||||||
|
|
||||||
|
if has_orchard || has_sapling {
|
||||||
Some(UnifiedFullViewingKey {
|
Some(UnifiedFullViewingKey {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
transparent,
|
transparent,
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
sapling,
|
sapling,
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard,
|
orchard,
|
||||||
|
@ -492,6 +523,8 @@ impl UnifiedFullViewingKey {
|
||||||
// this to allow parsing such UFVKs.
|
// this to allow parsing such UFVKs.
|
||||||
unknown: vec![],
|
unknown: vec![],
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,6 +543,7 @@ impl UnifiedFullViewingKey {
|
||||||
|
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let mut orchard = None;
|
let mut orchard = None;
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
let mut sapling = None;
|
let mut sapling = None;
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
let mut transparent = None;
|
let mut transparent = None;
|
||||||
|
@ -529,9 +563,11 @@ impl UnifiedFullViewingKey {
|
||||||
})
|
})
|
||||||
.transpose(),
|
.transpose(),
|
||||||
#[cfg(not(feature = "orchard"))]
|
#[cfg(not(feature = "orchard"))]
|
||||||
unified::Fvk::Orchard(data) => {
|
unified::Fvk::Orchard(data) => Some(Ok::<_, &'static str>((
|
||||||
Some(Ok((unified::Typecode::Orchard.into(), data.to_vec())))
|
u32::from(unified::Typecode::Orchard),
|
||||||
}
|
data.to_vec(),
|
||||||
|
))),
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
unified::Fvk::Sapling(data) => {
|
unified::Fvk::Sapling(data) => {
|
||||||
sapling::DiversifiableFullViewingKey::from_bytes(data)
|
sapling::DiversifiableFullViewingKey::from_bytes(data)
|
||||||
.ok_or("Invalid Sapling FVK in Unified FVK")
|
.ok_or("Invalid Sapling FVK in Unified FVK")
|
||||||
|
@ -541,6 +577,11 @@ impl UnifiedFullViewingKey {
|
||||||
})
|
})
|
||||||
.transpose()
|
.transpose()
|
||||||
}
|
}
|
||||||
|
#[cfg(not(feature = "sapling"))]
|
||||||
|
unified::Fvk::Sapling(data) => Some(Ok::<_, &'static str>((
|
||||||
|
u32::from(unified::Typecode::Sapling),
|
||||||
|
data.to_vec(),
|
||||||
|
))),
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
unified::Fvk::P2pkh(data) => legacy::AccountPubKey::deserialize(data)
|
unified::Fvk::P2pkh(data) => legacy::AccountPubKey::deserialize(data)
|
||||||
.map_err(|_| "Invalid transparent FVK in Unified FVK")
|
.map_err(|_| "Invalid transparent FVK in Unified FVK")
|
||||||
|
@ -550,9 +591,10 @@ impl UnifiedFullViewingKey {
|
||||||
})
|
})
|
||||||
.transpose(),
|
.transpose(),
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
#[cfg(not(feature = "transparent-inputs"))]
|
||||||
unified::Fvk::P2pkh(data) => {
|
unified::Fvk::P2pkh(data) => Some(Ok::<_, &'static str>((
|
||||||
Some(Ok((unified::Typecode::P2pkh.into(), data.to_vec())))
|
u32::from(unified::Typecode::P2pkh),
|
||||||
}
|
data.to_vec(),
|
||||||
|
))),
|
||||||
unified::Fvk::Unknown { typecode, data } => Some(Ok((*typecode, data.clone()))),
|
unified::Fvk::Unknown { typecode, data } => Some(Ok((*typecode, data.clone()))),
|
||||||
})
|
})
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
|
@ -560,6 +602,7 @@ impl UnifiedFullViewingKey {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
transparent,
|
transparent,
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
sapling,
|
sapling,
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard,
|
orchard,
|
||||||
|
@ -569,21 +612,12 @@ impl UnifiedFullViewingKey {
|
||||||
|
|
||||||
/// Returns the string encoding of this `UnifiedFullViewingKey` for the given network.
|
/// Returns the string encoding of this `UnifiedFullViewingKey` for the given network.
|
||||||
pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
|
pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
|
||||||
let items = std::iter::empty()
|
let items = std::iter::empty().chain(self.unknown.iter().map(|(typecode, data)| {
|
||||||
.chain(
|
unified::Fvk::Unknown {
|
||||||
self.sapling
|
typecode: *typecode,
|
||||||
.as_ref()
|
data: data.clone(),
|
||||||
.map(|dfvk| dfvk.to_bytes())
|
}
|
||||||
.map(unified::Fvk::Sapling),
|
}));
|
||||||
)
|
|
||||||
.chain(
|
|
||||||
self.unknown
|
|
||||||
.iter()
|
|
||||||
.map(|(typecode, data)| unified::Fvk::Unknown {
|
|
||||||
typecode: *typecode,
|
|
||||||
data: data.clone(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let items = items.chain(
|
let items = items.chain(
|
||||||
self.orchard
|
self.orchard
|
||||||
|
@ -591,6 +625,13 @@ impl UnifiedFullViewingKey {
|
||||||
.map(|fvk| fvk.to_bytes())
|
.map(|fvk| fvk.to_bytes())
|
||||||
.map(unified::Fvk::Orchard),
|
.map(unified::Fvk::Orchard),
|
||||||
);
|
);
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
|
let items = items.chain(
|
||||||
|
self.sapling
|
||||||
|
.as_ref()
|
||||||
|
.map(|dfvk| dfvk.to_bytes())
|
||||||
|
.map(unified::Fvk::Sapling),
|
||||||
|
);
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
let items = items.chain(
|
let items = items.chain(
|
||||||
self.transparent
|
self.transparent
|
||||||
|
@ -612,6 +653,7 @@ impl UnifiedFullViewingKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the Sapling diversifiable full viewing key component of this unified key.
|
/// Returns the Sapling diversifiable full viewing key component of this unified key.
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
pub fn sapling(&self) -> Option<&sapling::DiversifiableFullViewingKey> {
|
pub fn sapling(&self) -> Option<&sapling::DiversifiableFullViewingKey> {
|
||||||
self.sapling.as_ref()
|
self.sapling.as_ref()
|
||||||
}
|
}
|
||||||
|
@ -628,39 +670,52 @@ impl UnifiedFullViewingKey {
|
||||||
/// Returns `None` if the specified index does not produce a valid diversifier.
|
/// Returns `None` if the specified index does not produce a valid diversifier.
|
||||||
pub fn address(
|
pub fn address(
|
||||||
&self,
|
&self,
|
||||||
j: DiversifierIndex,
|
_j: DiversifierIndex,
|
||||||
request: UnifiedAddressRequest,
|
_request: UnifiedAddressRequest,
|
||||||
) -> Result<UnifiedAddress, AddressGenerationError> {
|
) -> Result<UnifiedAddress, AddressGenerationError> {
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let orchard = if request._has_orchard {
|
let mut orchard = None;
|
||||||
|
if _request.has_orchard {
|
||||||
|
#[cfg(not(feature = "orchard"))]
|
||||||
|
return Err(AddressGenerationError::ReceiverTypeNotSupported(
|
||||||
|
Typecode::Orchard,
|
||||||
|
));
|
||||||
|
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
if let Some(ofvk) = &self.orchard {
|
if let Some(ofvk) = &self.orchard {
|
||||||
let orchard_j = orchard::keys::DiversifierIndex::from(*j.as_bytes());
|
let orchard_j = orchard::keys::DiversifierIndex::from(*_j.as_bytes());
|
||||||
Some(ofvk.address_at(orchard_j, Scope::External))
|
orchard = Some(ofvk.address_at(orchard_j, Scope::External))
|
||||||
} else {
|
} else {
|
||||||
return Err(AddressGenerationError::KeyNotAvailable(Typecode::Orchard));
|
return Err(AddressGenerationError::KeyNotAvailable(Typecode::Orchard));
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let sapling = if request.has_sapling {
|
#[cfg(feature = "sapling")]
|
||||||
|
let mut sapling = None;
|
||||||
|
if _request.has_sapling {
|
||||||
|
#[cfg(not(feature = "sapling"))]
|
||||||
|
return Err(AddressGenerationError::ReceiverTypeNotSupported(
|
||||||
|
Typecode::Sapling,
|
||||||
|
));
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
if let Some(extfvk) = &self.sapling {
|
if let Some(extfvk) = &self.sapling {
|
||||||
// If a Sapling receiver type is requested, we must be able to construct an
|
// If a Sapling receiver type is requested, we must be able to construct an
|
||||||
// address; if we're unable to do so, then no Unified Address exists at this
|
// address; if we're unable to do so, then no Unified Address exists at this
|
||||||
// diversifier and we use `?` to early-return from this method.
|
// diversifier and we use `?` to early-return from this method.
|
||||||
Some(
|
sapling = Some(
|
||||||
extfvk
|
extfvk
|
||||||
.address(j)
|
.address(_j)
|
||||||
.ok_or(AddressGenerationError::InvalidSaplingDiversifierIndex(j))?,
|
.ok_or(AddressGenerationError::InvalidSaplingDiversifierIndex(_j))?,
|
||||||
)
|
);
|
||||||
} else {
|
} else {
|
||||||
return Err(AddressGenerationError::KeyNotAvailable(Typecode::Sapling));
|
return Err(AddressGenerationError::KeyNotAvailable(Typecode::Sapling));
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let transparent = if request.has_p2pkh {
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
let mut transparent = None;
|
||||||
|
if _request.has_p2pkh {
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
#[cfg(not(feature = "transparent-inputs"))]
|
||||||
return Err(AddressGenerationError::ReceiverTypeNotSupported(
|
return Err(AddressGenerationError::ReceiverTypeNotSupported(
|
||||||
Typecode::P2pkh,
|
Typecode::P2pkh,
|
||||||
|
@ -671,30 +726,25 @@ impl UnifiedFullViewingKey {
|
||||||
// If a transparent receiver type is requested, we must be able to construct an
|
// If a transparent receiver type is requested, we must be able to construct an
|
||||||
// address; if we're unable to do so, then no Unified Address exists at this
|
// address; if we're unable to do so, then no Unified Address exists at this
|
||||||
// diversifier.
|
// diversifier.
|
||||||
match to_transparent_child_index(j) {
|
let transparent_j = to_transparent_child_index(_j)
|
||||||
Some(transparent_j) => match tfvk
|
.ok_or(AddressGenerationError::InvalidTransparentChildIndex(_j))?;
|
||||||
.derive_external_ivk()
|
|
||||||
|
transparent = Some(
|
||||||
|
tfvk.derive_external_ivk()
|
||||||
.and_then(|tivk| tivk.derive_address(transparent_j))
|
.and_then(|tivk| tivk.derive_address(transparent_j))
|
||||||
{
|
.map_err(|_| AddressGenerationError::InvalidTransparentChildIndex(_j))?,
|
||||||
Ok(taddr) => Some(taddr),
|
);
|
||||||
Err(_) => {
|
|
||||||
return Err(AddressGenerationError::InvalidTransparentChildIndex(j))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Diversifier doesn't generate a valid transparent child index, so we eagerly
|
|
||||||
// return `None`.
|
|
||||||
None => return Err(AddressGenerationError::InvalidTransparentChildIndex(j)),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return Err(AddressGenerationError::KeyNotAvailable(Typecode::P2pkh));
|
return Err(AddressGenerationError::KeyNotAvailable(Typecode::P2pkh));
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
None
|
#[cfg(not(feature = "transparent-inputs"))]
|
||||||
};
|
let transparent = None;
|
||||||
|
|
||||||
UnifiedAddress::from_receivers(
|
UnifiedAddress::from_receivers(
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard,
|
orchard,
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
sapling,
|
sapling,
|
||||||
transparent,
|
transparent,
|
||||||
)
|
)
|
||||||
|
@ -706,6 +756,7 @@ impl UnifiedFullViewingKey {
|
||||||
/// diversifier along with the index at which the valid diversifier was found.
|
/// diversifier along with the index at which the valid diversifier was found.
|
||||||
///
|
///
|
||||||
/// Returns `None` if no valid diversifier exists
|
/// Returns `None` if no valid diversifier exists
|
||||||
|
#[allow(unused_mut)]
|
||||||
pub fn find_address(
|
pub fn find_address(
|
||||||
&self,
|
&self,
|
||||||
mut j: DiversifierIndex,
|
mut j: DiversifierIndex,
|
||||||
|
@ -726,6 +777,7 @@ impl UnifiedFullViewingKey {
|
||||||
Ok(ua) => {
|
Ok(ua) => {
|
||||||
break Some((ua, j));
|
break Some((ua, j));
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
Err(AddressGenerationError::InvalidSaplingDiversifierIndex(_)) => {
|
Err(AddressGenerationError::InvalidSaplingDiversifierIndex(_)) => {
|
||||||
if j.increment().is_err() {
|
if j.increment().is_err() {
|
||||||
break None;
|
break None;
|
||||||
|
@ -776,10 +828,15 @@ pub mod testing {
|
||||||
mod tests {
|
mod tests {
|
||||||
use proptest::prelude::proptest;
|
use proptest::prelude::proptest;
|
||||||
|
|
||||||
use super::{sapling, UnifiedFullViewingKey};
|
use super::UnifiedFullViewingKey;
|
||||||
use zcash_primitives::consensus::MAIN_NETWORK;
|
use zcash_primitives::consensus::MAIN_NETWORK;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "orchard", feature = "sapling"))]
|
||||||
use zip32::AccountId;
|
use zip32::AccountId;
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
|
use super::sapling;
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
use {
|
use {
|
||||||
crate::{address::Address, encoding::AddressCodec},
|
crate::{address::Address, encoding::AddressCodec},
|
||||||
|
@ -808,6 +865,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
fn spending_key_panics_on_short_seed() {
|
fn spending_key_panics_on_short_seed() {
|
||||||
let _ = sapling::spending_key(&[0; 31][..], 0, AccountId::ZERO);
|
let _ = sapling::spending_key(&[0; 31][..], 0, AccountId::ZERO);
|
||||||
}
|
}
|
||||||
|
@ -831,8 +889,6 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ufvk_round_trip() {
|
fn ufvk_round_trip() {
|
||||||
let account = AccountId::ZERO;
|
|
||||||
|
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let orchard = {
|
let orchard = {
|
||||||
let sk =
|
let sk =
|
||||||
|
@ -840,76 +896,122 @@ mod tests {
|
||||||
Some(orchard::keys::FullViewingKey::from(&sk))
|
Some(orchard::keys::FullViewingKey::from(&sk))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
let sapling = {
|
let sapling = {
|
||||||
let extsk = sapling::spending_key(&[0; 32], 0, account);
|
let extsk = sapling::spending_key(&[0; 32], 0, AccountId::ZERO);
|
||||||
Some(extsk.to_diversifiable_full_viewing_key())
|
Some(extsk.to_diversifiable_full_viewing_key())
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
let transparent = {
|
let transparent = {
|
||||||
let privkey = AccountPrivKey::from_seed(&MAIN_NETWORK, &[0; 32], account).unwrap();
|
let privkey =
|
||||||
|
AccountPrivKey::from_seed(&MAIN_NETWORK, &[0; 32], AccountId::ZERO).unwrap();
|
||||||
Some(privkey.to_account_pubkey())
|
Some(privkey.to_account_pubkey())
|
||||||
};
|
};
|
||||||
|
|
||||||
let ufvk = UnifiedFullViewingKey::new(
|
let ufvk = UnifiedFullViewingKey::new(
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
transparent,
|
transparent,
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
sapling,
|
sapling,
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard,
|
orchard,
|
||||||
)
|
);
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let encoded = ufvk.encode(&MAIN_NETWORK);
|
#[cfg(not(any(feature = "orchard", feature = "sapling")))]
|
||||||
|
assert_eq!(ufvk, None);
|
||||||
|
|
||||||
// Test encoded form against known values; these test vectors contain Orchard receivers
|
#[cfg(any(feature = "orchard", feature = "sapling"))]
|
||||||
// that will be treated as unknown if the `orchard` feature is not enabled.
|
|
||||||
let encoded_with_t = "uview1tg6rpjgju2s2j37gkgjq79qrh5lvzr6e0ed3n4sf4hu5qd35vmsh7avl80xa6mx7ryqce9hztwaqwrdthetpy4pc0kce25x453hwcmax02p80pg5savlg865sft9reat07c5vlactr6l2pxtlqtqunt2j9gmvr8spcuzf07af80h5qmut38h0gvcfa9k4rwujacwwca9vu8jev7wq6c725huv8qjmhss3hdj2vh8cfxhpqcm2qzc34msyrfxk5u6dqttt4vv2mr0aajreww5yufpk0gn4xkfm888467k7v6fmw7syqq6cceu078yw8xja502jxr0jgum43lhvpzmf7eu5dmnn6cr6f7p43yw8znzgxg598mllewnx076hljlvynhzwn5es94yrv65tdg3utuz2u3sras0wfcq4adxwdvlk387d22g3q98t5z74quw2fa4wed32escx8dwh4mw35t4jwf35xyfxnu83mk5s4kw2glkgsshmxk";
|
|
||||||
let _encoded_no_t = "uview12z384wdq76ceewlsu0esk7d97qnd23v2qnvhujxtcf2lsq8g4hwzpx44fwxssnm5tg8skyh4tnc8gydwxefnnm0hd0a6c6etmj0pp9jqkdsllkr70u8gpf7ndsfqcjlqn6dec3faumzqlqcmtjf8vp92h7kj38ph2786zx30hq2wru8ae3excdwc8w0z3t9fuw7mt7xy5sn6s4e45kwm0cjp70wytnensgdnev286t3vew3yuwt2hcz865y037k30e428dvgne37xvyeal2vu8yjnznphf9t2rw3gdp0hk5zwq00ws8f3l3j5n3qkqgsyzrwx4qzmgq0xwwk4vz2r6vtsykgz089jncvycmem3535zjwvvtvjw8v98y0d5ydwte575gjm7a7k";
|
|
||||||
|
|
||||||
// We test the full roundtrip only with the `orchard` feature enabled, because we will
|
|
||||||
// not generate the `orchard` part of the encoding if the UFVK does not have an Orchard
|
|
||||||
// part.
|
|
||||||
#[cfg(feature = "orchard")]
|
|
||||||
{
|
{
|
||||||
|
let ufvk = ufvk.expect("Orchard or Sapling fvk is present.");
|
||||||
|
let encoded = ufvk.encode(&MAIN_NETWORK);
|
||||||
|
|
||||||
|
// Test encoded form against known values; these test vectors contain Orchard receivers
|
||||||
|
// that will be treated as unknown if the `orchard` feature is not enabled.
|
||||||
|
let encoded_with_t = "uview1tg6rpjgju2s2j37gkgjq79qrh5lvzr6e0ed3n4sf4hu5qd35vmsh7avl80xa6mx7ryqce9hztwaqwrdthetpy4pc0kce25x453hwcmax02p80pg5savlg865sft9reat07c5vlactr6l2pxtlqtqunt2j9gmvr8spcuzf07af80h5qmut38h0gvcfa9k4rwujacwwca9vu8jev7wq6c725huv8qjmhss3hdj2vh8cfxhpqcm2qzc34msyrfxk5u6dqttt4vv2mr0aajreww5yufpk0gn4xkfm888467k7v6fmw7syqq6cceu078yw8xja502jxr0jgum43lhvpzmf7eu5dmnn6cr6f7p43yw8znzgxg598mllewnx076hljlvynhzwn5es94yrv65tdg3utuz2u3sras0wfcq4adxwdvlk387d22g3q98t5z74quw2fa4wed32escx8dwh4mw35t4jwf35xyfxnu83mk5s4kw2glkgsshmxk";
|
||||||
|
let _encoded_no_t = "uview12z384wdq76ceewlsu0esk7d97qnd23v2qnvhujxtcf2lsq8g4hwzpx44fwxssnm5tg8skyh4tnc8gydwxefnnm0hd0a6c6etmj0pp9jqkdsllkr70u8gpf7ndsfqcjlqn6dec3faumzqlqcmtjf8vp92h7kj38ph2786zx30hq2wru8ae3excdwc8w0z3t9fuw7mt7xy5sn6s4e45kwm0cjp70wytnensgdnev286t3vew3yuwt2hcz865y037k30e428dvgne37xvyeal2vu8yjnznphf9t2rw3gdp0hk5zwq00ws8f3l3j5n3qkqgsyzrwx4qzmgq0xwwk4vz2r6vtsykgz089jncvycmem3535zjwvvtvjw8v98y0d5ydwte575gjm7a7k";
|
||||||
|
|
||||||
|
// We test the full roundtrip only with the `sapling` and `orchard` features enabled,
|
||||||
|
// because we will not generate these parts of the encoding if the UFVK does not have an
|
||||||
|
// these parts.
|
||||||
|
#[cfg(all(feature = "sapling", feature = "orchard"))]
|
||||||
|
{
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
assert_eq!(encoded, encoded_with_t);
|
||||||
|
#[cfg(not(feature = "transparent-inputs"))]
|
||||||
|
assert_eq!(encoded, _encoded_no_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
let decoded = UnifiedFullViewingKey::decode(&MAIN_NETWORK, &encoded).unwrap();
|
||||||
|
let reencoded = decoded.encode(&MAIN_NETWORK);
|
||||||
|
assert_eq!(encoded, reencoded);
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
assert_eq!(encoded, encoded_with_t);
|
assert_eq!(
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
decoded.transparent.map(|t| t.serialize()),
|
||||||
assert_eq!(encoded, _encoded_no_t);
|
ufvk.transparent.as_ref().map(|t| t.serialize()),
|
||||||
|
);
|
||||||
|
#[cfg(feature = "sapling")]
|
||||||
|
assert_eq!(
|
||||||
|
decoded.sapling.map(|s| s.to_bytes()),
|
||||||
|
ufvk.sapling.map(|s| s.to_bytes()),
|
||||||
|
);
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
assert_eq!(
|
||||||
|
decoded.orchard.map(|o| o.to_bytes()),
|
||||||
|
ufvk.orchard.map(|o| o.to_bytes()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let decoded_with_t =
|
||||||
|
UnifiedFullViewingKey::decode(&MAIN_NETWORK, encoded_with_t).unwrap();
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
assert_eq!(
|
||||||
|
decoded_with_t.transparent.map(|t| t.serialize()),
|
||||||
|
ufvk.transparent.as_ref().map(|t| t.serialize()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Both Orchard and Sapling enabled
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "orchard",
|
||||||
|
feature = "sapling",
|
||||||
|
feature = "transparent-inputs"
|
||||||
|
))]
|
||||||
|
assert_eq!(decoded_with_t.unknown.len(), 0);
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "orchard",
|
||||||
|
feature = "sapling",
|
||||||
|
not(feature = "transparent-inputs")
|
||||||
|
))]
|
||||||
|
assert_eq!(decoded_with_t.unknown.len(), 1);
|
||||||
|
|
||||||
|
// Orchard enabled
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "orchard",
|
||||||
|
not(feature = "sapling"),
|
||||||
|
feature = "transparent-inputs"
|
||||||
|
))]
|
||||||
|
assert_eq!(decoded_with_t.unknown.len(), 1);
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "orchard",
|
||||||
|
not(feature = "sapling"),
|
||||||
|
not(feature = "transparent-inputs")
|
||||||
|
))]
|
||||||
|
assert_eq!(decoded_with_t.unknown.len(), 2);
|
||||||
|
|
||||||
|
// Sapling enabled
|
||||||
|
#[cfg(all(
|
||||||
|
not(feature = "orchard"),
|
||||||
|
feature = "sapling",
|
||||||
|
feature = "transparent-inputs"
|
||||||
|
))]
|
||||||
|
assert_eq!(decoded_with_t.unknown.len(), 1);
|
||||||
|
#[cfg(all(
|
||||||
|
not(feature = "orchard"),
|
||||||
|
feature = "sapling",
|
||||||
|
not(feature = "transparent-inputs")
|
||||||
|
))]
|
||||||
|
assert_eq!(decoded_with_t.unknown.len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
let decoded = UnifiedFullViewingKey::decode(&MAIN_NETWORK, &encoded).unwrap();
|
|
||||||
let reencoded = decoded.encode(&MAIN_NETWORK);
|
|
||||||
assert_eq!(encoded, reencoded);
|
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
|
||||||
assert_eq!(
|
|
||||||
decoded.transparent.map(|t| t.serialize()),
|
|
||||||
ufvk.transparent.as_ref().map(|t| t.serialize()),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decoded.sapling.map(|s| s.to_bytes()),
|
|
||||||
ufvk.sapling.map(|s| s.to_bytes()),
|
|
||||||
);
|
|
||||||
#[cfg(feature = "orchard")]
|
|
||||||
assert_eq!(
|
|
||||||
decoded.orchard.map(|o| o.to_bytes()),
|
|
||||||
ufvk.orchard.map(|o| o.to_bytes()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let decoded_with_t = UnifiedFullViewingKey::decode(&MAIN_NETWORK, encoded_with_t).unwrap();
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
|
||||||
assert_eq!(
|
|
||||||
decoded_with_t.transparent.map(|t| t.serialize()),
|
|
||||||
ufvk.transparent.as_ref().map(|t| t.serialize()),
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg(all(not(feature = "orchard"), not(feature = "transparent-inputs")))]
|
|
||||||
assert_eq!(decoded_with_t.unknown.len(), 2);
|
|
||||||
#[cfg(all(feature = "transparent-inputs", not(feature = "orchard")))]
|
|
||||||
assert_eq!(decoded_with_t.unknown.len(), 1);
|
|
||||||
#[cfg(all(feature = "orchard", not(feature = "transparent-inputs")))]
|
|
||||||
assert_eq!(decoded_with_t.unknown.len(), 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in New Issue