Merge pull request #1185 from nerdcash/nonhardenedchildindex
Declare and use NonHardenedChildIndex for transparent
This commit is contained in:
commit
0477ec0dc5
|
@ -52,6 +52,7 @@ and this library adheres to Rust's notion of
|
||||||
- `wallet::input_selection::ShieldingSelector` has been
|
- `wallet::input_selection::ShieldingSelector` has been
|
||||||
factored out from the `InputSelector` trait to separate out transparent
|
factored out from the `InputSelector` trait to separate out transparent
|
||||||
functionality and move it behind the `transparent-inputs` feature flag.
|
functionality and move it behind the `transparent-inputs` feature flag.
|
||||||
|
- `TransparentAddressMetadata` (which replaces `zcash_keys::address::AddressMetadata`).
|
||||||
- `zcash_client_backend::fees::{standard, sapling}`
|
- `zcash_client_backend::fees::{standard, sapling}`
|
||||||
- `zcash_client_backend::fees::ChangeValue::new`
|
- `zcash_client_backend::fees::ChangeValue::new`
|
||||||
- `zcash_client_backend::wallet`:
|
- `zcash_client_backend::wallet`:
|
||||||
|
@ -114,6 +115,8 @@ and this library adheres to Rust's notion of
|
||||||
been removed from `error::Error`.
|
been removed from `error::Error`.
|
||||||
- A new variant `UnsupportedPoolType` has been added.
|
- A new variant `UnsupportedPoolType` has been added.
|
||||||
- A new variant `NoSupportedReceivers` has been added.
|
- A new variant `NoSupportedReceivers` has been added.
|
||||||
|
- A new variant `NoSpendingKey` has been added.
|
||||||
|
- Variant `ChildIndexOutOfRange` has been removed.
|
||||||
- `wallet::shield_transparent_funds` no longer takes a `memo` argument;
|
- `wallet::shield_transparent_funds` no longer takes a `memo` argument;
|
||||||
instead, memos to be associated with the shielded outputs should be
|
instead, memos to be associated with the shielded outputs should be
|
||||||
specified in the construction of the value of the `input_selector`
|
specified in the construction of the value of the `input_selector`
|
||||||
|
@ -161,6 +164,9 @@ and this library adheres to Rust's notion of
|
||||||
`get_unspent_transparent_outputs` have been removed; use
|
`get_unspent_transparent_outputs` have been removed; use
|
||||||
`data_api::InputSource` instead.
|
`data_api::InputSource` instead.
|
||||||
- Added `get_account_ids`.
|
- Added `get_account_ids`.
|
||||||
|
- `get_transparent_receivers` now returns
|
||||||
|
`zcash_client_backend::data_api::TransparentAddressMetadata` instead of
|
||||||
|
`zcash_keys::address::AddressMetadata`.
|
||||||
- `wallet::{propose_shielding, shield_transparent_funds}` now takes their
|
- `wallet::{propose_shielding, shield_transparent_funds}` now takes their
|
||||||
`min_confirmations` arguments as `u32` rather than a `NonZeroU32` to permit
|
`min_confirmations` arguments as `u32` rather than a `NonZeroU32` to permit
|
||||||
implmentations to enable zero-conf shielding.
|
implmentations to enable zero-conf shielding.
|
||||||
|
|
|
@ -14,7 +14,7 @@ use shardtree::{error::ShardTreeError, store::ShardStore, ShardTree};
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
consensus::BlockHeight,
|
consensus::BlockHeight,
|
||||||
legacy::TransparentAddress,
|
legacy::{NonHardenedChildIndex, TransparentAddress},
|
||||||
memo::{Memo, MemoBytes},
|
memo::{Memo, MemoBytes},
|
||||||
transaction::{
|
transaction::{
|
||||||
components::amount::{Amount, BalanceError, NonNegativeAmount},
|
components::amount::{Amount, BalanceError, NonNegativeAmount},
|
||||||
|
@ -24,7 +24,7 @@ use zcash_primitives::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
address::{AddressMetadata, UnifiedAddress},
|
address::UnifiedAddress,
|
||||||
decrypt::DecryptedOutput,
|
decrypt::DecryptedOutput,
|
||||||
keys::{UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey},
|
keys::{UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey},
|
||||||
proto::service::TreeState,
|
proto::service::TreeState,
|
||||||
|
@ -56,6 +56,30 @@ pub enum NullifierQuery {
|
||||||
All,
|
All,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Describes the derivation of a transparent address.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct TransparentAddressMetadata {
|
||||||
|
scope: zip32::Scope,
|
||||||
|
address_index: NonHardenedChildIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransparentAddressMetadata {
|
||||||
|
pub fn new(scope: zip32::Scope, address_index: NonHardenedChildIndex) -> Self {
|
||||||
|
Self {
|
||||||
|
scope,
|
||||||
|
address_index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scope(&self) -> zip32::Scope {
|
||||||
|
self.scope
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn address_index(&self) -> NonHardenedChildIndex {
|
||||||
|
self.address_index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Balance information for a value within a single pool in an account.
|
/// Balance information for a value within a single pool in an account.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct Balance {
|
pub struct Balance {
|
||||||
|
@ -560,7 +584,7 @@ pub trait WalletRead {
|
||||||
fn get_transparent_receivers(
|
fn get_transparent_receivers(
|
||||||
&self,
|
&self,
|
||||||
account: AccountId,
|
account: AccountId,
|
||||||
) -> Result<HashMap<TransparentAddress, AddressMetadata>, Self::Error>;
|
) -> Result<HashMap<TransparentAddress, Option<TransparentAddressMetadata>>, Self::Error>;
|
||||||
|
|
||||||
/// Returns a mapping from transparent receiver to not-yet-shielded UTXO balance,
|
/// Returns a mapping from transparent receiver to not-yet-shielded UTXO balance,
|
||||||
/// for each address associated with a nonzero balance.
|
/// for each address associated with a nonzero balance.
|
||||||
|
@ -1116,7 +1140,7 @@ pub mod testing {
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
address::{AddressMetadata, UnifiedAddress},
|
address::UnifiedAddress,
|
||||||
keys::{UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey},
|
keys::{UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey},
|
||||||
wallet::{Note, NoteId, ReceivedNote, WalletTransparentOutput},
|
wallet::{Note, NoteId, ReceivedNote, WalletTransparentOutput},
|
||||||
ShieldedProtocol,
|
ShieldedProtocol,
|
||||||
|
@ -1125,7 +1149,8 @@ pub mod testing {
|
||||||
use super::{
|
use super::{
|
||||||
chain::CommitmentTreeRoot, scanning::ScanRange, AccountBirthday, BlockMetadata,
|
chain::CommitmentTreeRoot, scanning::ScanRange, AccountBirthday, BlockMetadata,
|
||||||
DecryptedTransaction, InputSource, NullifierQuery, ScannedBlock, SentTransaction,
|
DecryptedTransaction, InputSource, NullifierQuery, ScannedBlock, SentTransaction,
|
||||||
WalletCommitmentTrees, WalletRead, WalletSummary, WalletWrite, SAPLING_SHARD_HEIGHT,
|
TransparentAddressMetadata, WalletCommitmentTrees, WalletRead, WalletSummary, WalletWrite,
|
||||||
|
SAPLING_SHARD_HEIGHT,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct MockWalletDb {
|
pub struct MockWalletDb {
|
||||||
|
@ -1284,7 +1309,8 @@ pub mod testing {
|
||||||
fn get_transparent_receivers(
|
fn get_transparent_receivers(
|
||||||
&self,
|
&self,
|
||||||
_account: AccountId,
|
_account: AccountId,
|
||||||
) -> Result<HashMap<TransparentAddress, AddressMetadata>, Self::Error> {
|
) -> Result<HashMap<TransparentAddress, Option<TransparentAddressMetadata>>, Self::Error>
|
||||||
|
{
|
||||||
Ok(HashMap::new())
|
Ok(HashMap::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ use crate::data_api::wallet::input_selection::InputSelectorError;
|
||||||
use crate::PoolType;
|
use crate::PoolType;
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
use zcash_primitives::{legacy::TransparentAddress, zip32::DiversifierIndex};
|
use zcash_primitives::legacy::TransparentAddress;
|
||||||
|
|
||||||
use crate::wallet::NoteId;
|
use crate::wallet::NoteId;
|
||||||
|
|
||||||
|
@ -64,15 +64,18 @@ pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError> {
|
||||||
/// Attempted to create a spend to an unsupported Unified Address receiver
|
/// Attempted to create a spend to an unsupported Unified Address receiver
|
||||||
NoSupportedReceivers(Vec<u32>),
|
NoSupportedReceivers(Vec<u32>),
|
||||||
|
|
||||||
|
/// A proposed transaction cannot be built because it requires spending an input
|
||||||
|
/// for which no spending key is available.
|
||||||
|
///
|
||||||
|
/// The argument is the address of the note or UTXO being spent.
|
||||||
|
NoSpendingKey(String),
|
||||||
|
|
||||||
/// A note being spent does not correspond to either the internal or external
|
/// A note being spent does not correspond to either the internal or external
|
||||||
/// full viewing key for an account.
|
/// full viewing key for an account.
|
||||||
NoteMismatch(NoteId),
|
NoteMismatch(NoteId),
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
AddressNotRecognized(TransparentAddress),
|
AddressNotRecognized(TransparentAddress),
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
|
||||||
ChildIndexOutOfRange(DiversifierIndex),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<DE, CE, SE, FE> fmt::Display for Error<DE, CE, SE, FE>
|
impl<DE, CE, SE, FE> fmt::Display for Error<DE, CE, SE, FE>
|
||||||
|
@ -122,20 +125,13 @@ where
|
||||||
Error::MemoForbidden => write!(f, "It is not possible to send a memo to a transparent address."),
|
Error::MemoForbidden => write!(f, "It is not possible to send a memo to a transparent address."),
|
||||||
Error::UnsupportedPoolType(t) => write!(f, "Attempted to send to an unsupported pool: {}", t),
|
Error::UnsupportedPoolType(t) => write!(f, "Attempted to send to an unsupported pool: {}", t),
|
||||||
Error::NoSupportedReceivers(t) => write!(f, "Unified address contained only unsupported receiver types: {:?}", &t[..]),
|
Error::NoSupportedReceivers(t) => write!(f, "Unified address contained only unsupported receiver types: {:?}", &t[..]),
|
||||||
|
Error::NoSpendingKey(addr) => write!(f, "No spending key available for address: {}", addr),
|
||||||
Error::NoteMismatch(n) => write!(f, "A note being spent ({:?}) does not correspond to either the internal or external full viewing key for the provided spending key.", n),
|
Error::NoteMismatch(n) => write!(f, "A note being spent ({:?}) does not correspond to either the internal or external full viewing key for the provided spending key.", n),
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
Error::AddressNotRecognized(_) => {
|
Error::AddressNotRecognized(_) => {
|
||||||
write!(f, "The specified transparent address was not recognized as belonging to the wallet.")
|
write!(f, "The specified transparent address was not recognized as belonging to the wallet.")
|
||||||
}
|
}
|
||||||
#[cfg(feature = "transparent-inputs")]
|
|
||||||
Error::ChildIndexOutOfRange(i) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"The diversifier index {:?} is out of range for transparent addresses.",
|
|
||||||
i
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ use sapling::{
|
||||||
note_encryption::{try_sapling_note_decryption, PreparedIncomingViewingKey},
|
note_encryption::{try_sapling_note_decryption, PreparedIncomingViewingKey},
|
||||||
prover::{OutputProver, SpendProver},
|
prover::{OutputProver, SpendProver},
|
||||||
};
|
};
|
||||||
|
use zcash_keys::encoding::AddressCodec;
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
consensus::{self, NetworkUpgrade},
|
consensus::{self, NetworkUpgrade},
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
|
@ -639,17 +640,17 @@ where
|
||||||
for utxo in proposal.transparent_inputs() {
|
for utxo in proposal.transparent_inputs() {
|
||||||
utxos.push(utxo.clone());
|
utxos.push(utxo.clone());
|
||||||
|
|
||||||
let diversifier_index = known_addrs
|
let address_metadata = known_addrs
|
||||||
.get(utxo.recipient_address())
|
.get(utxo.recipient_address())
|
||||||
.ok_or_else(|| Error::AddressNotRecognized(*utxo.recipient_address()))?
|
.ok_or_else(|| Error::AddressNotRecognized(*utxo.recipient_address()))?
|
||||||
.diversifier_index();
|
.clone()
|
||||||
|
.ok_or_else(|| {
|
||||||
let child_index = u32::try_from(*diversifier_index)
|
Error::NoSpendingKey(utxo.recipient_address().encode(params))
|
||||||
.map_err(|_| Error::ChildIndexOutOfRange(*diversifier_index))?;
|
})?;
|
||||||
|
|
||||||
let secret_key = usk
|
let secret_key = usk
|
||||||
.transparent()
|
.transparent()
|
||||||
.derive_external_secret_key(child_index)
|
.derive_external_secret_key(address_metadata.address_index())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
builder.add_transparent_input(
|
builder.add_transparent_input(
|
||||||
|
|
|
@ -58,14 +58,14 @@ use zcash_primitives::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use zcash_client_backend::{
|
use zcash_client_backend::{
|
||||||
address::{AddressMetadata, UnifiedAddress},
|
address::UnifiedAddress,
|
||||||
data_api::{
|
data_api::{
|
||||||
self,
|
self,
|
||||||
chain::{BlockSource, CommitmentTreeRoot},
|
chain::{BlockSource, CommitmentTreeRoot},
|
||||||
scanning::{ScanPriority, ScanRange},
|
scanning::{ScanPriority, ScanRange},
|
||||||
AccountBirthday, BlockMetadata, DecryptedTransaction, InputSource, NullifierQuery,
|
AccountBirthday, BlockMetadata, DecryptedTransaction, InputSource, NullifierQuery,
|
||||||
ScannedBlock, SentTransaction, WalletCommitmentTrees, WalletRead, WalletSummary,
|
ScannedBlock, SentTransaction, TransparentAddressMetadata, WalletCommitmentTrees,
|
||||||
WalletWrite, SAPLING_SHARD_HEIGHT,
|
WalletRead, WalletSummary, WalletWrite, SAPLING_SHARD_HEIGHT,
|
||||||
},
|
},
|
||||||
keys::{UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey},
|
keys::{UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey},
|
||||||
proto::compact_formats::CompactBlock,
|
proto::compact_formats::CompactBlock,
|
||||||
|
@ -346,7 +346,7 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for W
|
||||||
fn get_transparent_receivers(
|
fn get_transparent_receivers(
|
||||||
&self,
|
&self,
|
||||||
_account: AccountId,
|
_account: AccountId,
|
||||||
) -> Result<HashMap<TransparentAddress, AddressMetadata>, Self::Error> {
|
) -> Result<HashMap<TransparentAddress, Option<TransparentAddressMetadata>>, Self::Error> {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
return wallet::get_transparent_receivers(self.conn.borrow(), &self.params, _account);
|
return wallet::get_transparent_receivers(self.conn.borrow(), &self.params, _account);
|
||||||
|
|
||||||
|
|
|
@ -112,9 +112,9 @@ use {
|
||||||
crate::UtxoId,
|
crate::UtxoId,
|
||||||
rusqlite::Row,
|
rusqlite::Row,
|
||||||
std::collections::BTreeSet,
|
std::collections::BTreeSet,
|
||||||
zcash_client_backend::{address::AddressMetadata, wallet::WalletTransparentOutput},
|
zcash_client_backend::{data_api::TransparentAddressMetadata, wallet::WalletTransparentOutput},
|
||||||
zcash_primitives::{
|
zcash_primitives::{
|
||||||
legacy::{keys::IncomingViewingKey, Script, TransparentAddress},
|
legacy::{keys::IncomingViewingKey, NonHardenedChildIndex, Script, TransparentAddress},
|
||||||
transaction::components::{OutPoint, TxOut},
|
transaction::components::{OutPoint, TxOut},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -349,8 +349,8 @@ pub(crate) fn get_transparent_receivers<P: consensus::Parameters>(
|
||||||
conn: &rusqlite::Connection,
|
conn: &rusqlite::Connection,
|
||||||
params: &P,
|
params: &P,
|
||||||
account: AccountId,
|
account: AccountId,
|
||||||
) -> Result<HashMap<TransparentAddress, AddressMetadata>, SqliteClientError> {
|
) -> Result<HashMap<TransparentAddress, Option<TransparentAddressMetadata>>, SqliteClientError> {
|
||||||
let mut ret = HashMap::new();
|
let mut ret: HashMap<TransparentAddress, Option<TransparentAddressMetadata>> = HashMap::new();
|
||||||
|
|
||||||
// Get all UAs derived
|
// Get all UAs derived
|
||||||
let mut ua_query = conn
|
let mut ua_query = conn
|
||||||
|
@ -360,12 +360,12 @@ pub(crate) fn get_transparent_receivers<P: consensus::Parameters>(
|
||||||
while let Some(row) = rows.next()? {
|
while let Some(row) = rows.next()? {
|
||||||
let ua_str: String = row.get(0)?;
|
let ua_str: String = row.get(0)?;
|
||||||
let di_vec: Vec<u8> = row.get(1)?;
|
let di_vec: Vec<u8> = row.get(1)?;
|
||||||
let mut di_be: [u8; 11] = di_vec.try_into().map_err(|_| {
|
let mut di: [u8; 11] = di_vec.try_into().map_err(|_| {
|
||||||
SqliteClientError::CorruptedData(
|
SqliteClientError::CorruptedData(
|
||||||
"Diverisifier index is not an 11-byte value".to_owned(),
|
"Diverisifier index is not an 11-byte value".to_owned(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
di_be.reverse();
|
di.reverse(); // BE -> LE conversion
|
||||||
|
|
||||||
let ua = Address::decode(params, &ua_str)
|
let ua = Address::decode(params, &ua_str)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
|
@ -380,16 +380,34 @@ pub(crate) fn get_transparent_receivers<P: consensus::Parameters>(
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if let Some(taddr) = ua.transparent() {
|
if let Some(taddr) = ua.transparent() {
|
||||||
|
let index = NonHardenedChildIndex::from_index(
|
||||||
|
DiversifierIndex::from(di).try_into().map_err(|_| {
|
||||||
|
SqliteClientError::CorruptedData(
|
||||||
|
"Unable to get diversifier for transparent address.".to_string(),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
SqliteClientError::CorruptedData(
|
||||||
|
"Unexpected hardened index for transparent address.".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
ret.insert(
|
ret.insert(
|
||||||
*taddr,
|
*taddr,
|
||||||
AddressMetadata::new(account, DiversifierIndex::from(di_be)),
|
Some(TransparentAddressMetadata::new(Scope::External, index)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((taddr, diversifier_index)) = get_legacy_transparent_address(params, conn, account)?
|
if let Some((taddr, child_index)) = get_legacy_transparent_address(params, conn, account)? {
|
||||||
{
|
ret.insert(
|
||||||
ret.insert(taddr, AddressMetadata::new(account, diversifier_index));
|
taddr,
|
||||||
|
Some(TransparentAddressMetadata::new(
|
||||||
|
Scope::External,
|
||||||
|
child_index,
|
||||||
|
)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
|
@ -400,7 +418,7 @@ pub(crate) fn get_legacy_transparent_address<P: consensus::Parameters>(
|
||||||
params: &P,
|
params: &P,
|
||||||
conn: &rusqlite::Connection,
|
conn: &rusqlite::Connection,
|
||||||
account: AccountId,
|
account: AccountId,
|
||||||
) -> Result<Option<(TransparentAddress, DiversifierIndex)>, SqliteClientError> {
|
) -> Result<Option<(TransparentAddress, NonHardenedChildIndex)>, SqliteClientError> {
|
||||||
// Get the UFVK for the account.
|
// Get the UFVK for the account.
|
||||||
let ufvk_str: Option<String> = conn
|
let ufvk_str: Option<String> = conn
|
||||||
.query_row(
|
.query_row(
|
||||||
|
@ -418,10 +436,7 @@ pub(crate) fn get_legacy_transparent_address<P: consensus::Parameters>(
|
||||||
ufvk.transparent()
|
ufvk.transparent()
|
||||||
.map(|tfvk| {
|
.map(|tfvk| {
|
||||||
tfvk.derive_external_ivk()
|
tfvk.derive_external_ivk()
|
||||||
.map(|tivk| {
|
.map(|tivk| tivk.default_address())
|
||||||
let (taddr, child_index) = tivk.default_address();
|
|
||||||
(taddr, DiversifierIndex::from(child_index))
|
|
||||||
})
|
|
||||||
.map_err(SqliteClientError::HdwalletError)
|
.map_err(SqliteClientError::HdwalletError)
|
||||||
})
|
})
|
||||||
.transpose()
|
.transpose()
|
||||||
|
|
|
@ -396,7 +396,9 @@ mod tests {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
fn migrate_from_wm2() {
|
fn migrate_from_wm2() {
|
||||||
use zcash_client_backend::keys::UnifiedAddressRequest;
|
use zcash_client_backend::keys::UnifiedAddressRequest;
|
||||||
use zcash_primitives::transaction::components::amount::NonNegativeAmount;
|
use zcash_primitives::{
|
||||||
|
legacy::NonHardenedChildIndex, transaction::components::amount::NonNegativeAmount,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::UA_TRANSPARENT;
|
use crate::UA_TRANSPARENT;
|
||||||
|
|
||||||
|
@ -450,7 +452,7 @@ mod tests {
|
||||||
.and_then(|k| {
|
.and_then(|k| {
|
||||||
k.derive_external_ivk()
|
k.derive_external_ivk()
|
||||||
.ok()
|
.ok()
|
||||||
.map(|k| k.derive_address(0).unwrap())
|
.map(|k| k.derive_address(NonHardenedChildIndex::ZERO).unwrap())
|
||||||
})
|
})
|
||||||
.map(|a| a.encode(&network));
|
.map(|a| a.encode(&network));
|
||||||
|
|
||||||
|
|
|
@ -292,13 +292,13 @@ mod tests {
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
consensus::{BlockHeight, Network, NetworkUpgrade, Parameters},
|
consensus::{BlockHeight, Network, NetworkUpgrade, Parameters},
|
||||||
legacy::keys::IncomingViewingKey,
|
legacy::{keys::IncomingViewingKey, NonHardenedChildIndex},
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
transaction::{
|
transaction::{
|
||||||
builder::{BuildConfig, BuildResult, Builder},
|
builder::{BuildConfig, BuildResult, Builder},
|
||||||
components::amount::NonNegativeAmount,
|
components::{amount::NonNegativeAmount, transparent},
|
||||||
|
fees::fixed,
|
||||||
},
|
},
|
||||||
transaction::{components::transparent, fees::fixed},
|
|
||||||
zip32::{AccountId, Scope},
|
zip32::{AccountId, Scope},
|
||||||
};
|
};
|
||||||
use zcash_proofs::prover::LocalTxProver;
|
use zcash_proofs::prover::LocalTxProver;
|
||||||
|
@ -354,7 +354,9 @@ mod tests {
|
||||||
);
|
);
|
||||||
builder
|
builder
|
||||||
.add_transparent_input(
|
.add_transparent_input(
|
||||||
usk0.transparent().derive_external_secret_key(0).unwrap(),
|
usk0.transparent()
|
||||||
|
.derive_external_secret_key(NonHardenedChildIndex::ZERO)
|
||||||
|
.unwrap(),
|
||||||
transparent::OutPoint::new([1; 32], 0),
|
transparent::OutPoint::new([1; 32], 0),
|
||||||
transparent::TxOut {
|
transparent::TxOut {
|
||||||
value: NonNegativeAmount::const_from_u64(EXTERNAL_VALUE + INTERNAL_VALUE),
|
value: NonNegativeAmount::const_from_u64(EXTERNAL_VALUE + INTERNAL_VALUE),
|
||||||
|
|
|
@ -44,3 +44,7 @@ The entries below are relative to the `zcash_client_backend` crate as of
|
||||||
`transparent-inputs` feature is enabled.
|
`transparent-inputs` feature is enabled.
|
||||||
- `UnifiedFullViewingKey::new` no longer takes an Orchard full viewing key
|
- `UnifiedFullViewingKey::new` no longer takes an Orchard full viewing key
|
||||||
argument unless the `orchard` feature is enabled.
|
argument unless the `orchard` feature is enabled.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- `zcash_keys::address::AddressMetadata` has been moved to
|
||||||
|
`zcash_client_backend::data_api::TransparentAddressMetadata` and fields changed.
|
||||||
|
|
|
@ -7,33 +7,7 @@ use zcash_address::{
|
||||||
unified::{self, Container, Encoding},
|
unified::{self, Container, Encoding},
|
||||||
ConversionError, Network, ToAddress, TryFromRawAddress, ZcashAddress,
|
ConversionError, Network, ToAddress, TryFromRawAddress, ZcashAddress,
|
||||||
};
|
};
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{consensus, legacy::TransparentAddress};
|
||||||
consensus,
|
|
||||||
legacy::TransparentAddress,
|
|
||||||
zip32::{AccountId, DiversifierIndex},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct AddressMetadata {
|
|
||||||
account: AccountId,
|
|
||||||
diversifier_index: DiversifierIndex,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AddressMetadata {
|
|
||||||
pub fn new(account: AccountId, diversifier_index: DiversifierIndex) -> Self {
|
|
||||||
Self {
|
|
||||||
account,
|
|
||||||
diversifier_index,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn account(&self) -> AccountId {
|
|
||||||
self.account
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn diversifier_index(&self) -> &DiversifierIndex {
|
|
||||||
&self.diversifier_index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A Unified Address.
|
/// A Unified Address.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
|
|
@ -7,6 +7,9 @@ use zcash_primitives::{
|
||||||
|
|
||||||
use crate::address::UnifiedAddress;
|
use crate::address::UnifiedAddress;
|
||||||
|
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
use zcash_primitives::legacy::NonHardenedChildIndex;
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
use {
|
use {
|
||||||
std::convert::TryInto,
|
std::convert::TryInto,
|
||||||
|
@ -68,13 +71,13 @@ pub mod sapling {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
fn to_transparent_child_index(j: DiversifierIndex) -> Option<u32> {
|
fn to_transparent_child_index(j: DiversifierIndex) -> Option<NonHardenedChildIndex> {
|
||||||
let (low_4_bytes, rest) = j.as_bytes().split_at(4);
|
let (low_4_bytes, rest) = j.as_bytes().split_at(4);
|
||||||
let transparent_j = u32::from_le_bytes(low_4_bytes.try_into().unwrap());
|
let transparent_j = u32::from_le_bytes(low_4_bytes.try_into().unwrap());
|
||||||
if transparent_j > (0x7FFFFFFF) || rest.iter().any(|b| b != &0) {
|
if rest.iter().any(|b| b != &0) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(transparent_j)
|
NonHardenedChildIndex::from_index(transparent_j)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,7 +391,7 @@ impl UnifiedSpendingKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "test-dependencies", feature = "transparent-inputs"))]
|
#[cfg(all(feature = "test-dependencies", feature = "transparent-inputs"))]
|
||||||
pub fn default_transparent_address(&self) -> (TransparentAddress, u32) {
|
pub fn default_transparent_address(&self) -> (TransparentAddress, NonHardenedChildIndex) {
|
||||||
self.transparent()
|
self.transparent()
|
||||||
.to_account_pubkey()
|
.to_account_pubkey()
|
||||||
.derive_external_ivk()
|
.derive_external_ivk()
|
||||||
|
@ -812,13 +815,15 @@ mod tests {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
#[test]
|
#[test]
|
||||||
fn pk_to_taddr() {
|
fn pk_to_taddr() {
|
||||||
|
use zcash_primitives::legacy::NonHardenedChildIndex;
|
||||||
|
|
||||||
let taddr =
|
let taddr =
|
||||||
legacy::keys::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId::ZERO)
|
legacy::keys::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId::ZERO)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_account_pubkey()
|
.to_account_pubkey()
|
||||||
.derive_external_ivk()
|
.derive_external_ivk()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.derive_address(0)
|
.derive_address(NonHardenedChildIndex::ZERO)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.encode(&MAIN_NETWORK);
|
.encode(&MAIN_NETWORK);
|
||||||
assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string());
|
assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string());
|
||||||
|
|
|
@ -7,6 +7,7 @@ and this library adheres to Rust's notion of
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
|
- `zcash_primitives::legacy::keys::NonHardenedChildIndex`
|
||||||
- Dependency on `bellman 0.14`.
|
- Dependency on `bellman 0.14`.
|
||||||
- `zcash_primitives::consensus::sapling_zip212_enforcement`
|
- `zcash_primitives::consensus::sapling_zip212_enforcement`
|
||||||
- `zcash_primitives::transaction`:
|
- `zcash_primitives::transaction`:
|
||||||
|
@ -126,6 +127,7 @@ and this library adheres to Rust's notion of
|
||||||
- `zcash_client_backend` changes related to `local-consensus` feature:
|
- `zcash_client_backend` changes related to `local-consensus` feature:
|
||||||
- added tests that verify `zip321` supports Payment URIs with `Local(P)`
|
- added tests that verify `zip321` supports Payment URIs with `Local(P)`
|
||||||
network parameters.
|
network parameters.
|
||||||
|
- `zcash_primitives::legacy::keys::derive_external_secret_key` parameter type changed from `u32` to `NonHardenedChildIndex`.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- `zcash_primitives::constants`:
|
- `zcash_primitives::constants`:
|
||||||
|
|
|
@ -6,6 +6,11 @@ use std::fmt;
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::ops::Shl;
|
use std::ops::Shl;
|
||||||
|
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
use hdwallet::KeyIndex;
|
||||||
|
|
||||||
|
use subtle::{Choice, ConstantTimeEq};
|
||||||
|
|
||||||
use zcash_encoding::Vector;
|
use zcash_encoding::Vector;
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
@ -402,6 +407,63 @@ impl TransparentAddress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A child index for a derived transparent address.
|
||||||
|
///
|
||||||
|
/// Only NON-hardened derivation is supported.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub struct NonHardenedChildIndex(u32);
|
||||||
|
|
||||||
|
impl ConstantTimeEq for NonHardenedChildIndex {
|
||||||
|
fn ct_eq(&self, other: &Self) -> Choice {
|
||||||
|
self.0.ct_eq(&other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NonHardenedChildIndex {
|
||||||
|
pub const ZERO: NonHardenedChildIndex = NonHardenedChildIndex(0);
|
||||||
|
|
||||||
|
/// Parses the given ZIP 32 child index.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the hardened bit is set.
|
||||||
|
pub fn from_index(i: u32) -> Option<Self> {
|
||||||
|
if i < (1 << 31) {
|
||||||
|
Some(NonHardenedChildIndex(i))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the index as a 32-bit integer.
|
||||||
|
pub fn index(&self) -> u32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&self) -> Option<Self> {
|
||||||
|
// overflow cannot happen because self.0 is 31 bits, and the next index is at most 32 bits
|
||||||
|
// which in that case would lead from_index to return None.
|
||||||
|
Self::from_index(self.0 + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
impl TryFrom<KeyIndex> for NonHardenedChildIndex {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: KeyIndex) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
KeyIndex::Normal(i) => NonHardenedChildIndex::from_index(i).ok_or(()),
|
||||||
|
KeyIndex::Hardened(_) => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
impl From<NonHardenedChildIndex> for KeyIndex {
|
||||||
|
fn from(value: NonHardenedChildIndex) -> Self {
|
||||||
|
Self::Normal(value.index())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-dependencies"))]
|
#[cfg(any(test, feature = "test-dependencies"))]
|
||||||
pub mod testing {
|
pub mod testing {
|
||||||
use proptest::prelude::{any, prop_compose};
|
use proptest::prelude::{any, prop_compose};
|
||||||
|
@ -417,7 +479,9 @@ pub mod testing {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{OpCode, Script, TransparentAddress};
|
use super::{NonHardenedChildIndex, OpCode, Script, TransparentAddress};
|
||||||
|
use hdwallet::KeyIndex;
|
||||||
|
use subtle::ConstantTimeEq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn script_opcode() {
|
fn script_opcode() {
|
||||||
|
@ -484,4 +548,55 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(addr.script().address(), Some(addr));
|
assert_eq!(addr.script().address(), Some(addr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nonhardened_indexes_accepted() {
|
||||||
|
assert_eq!(0, NonHardenedChildIndex::from_index(0).unwrap().index());
|
||||||
|
assert_eq!(
|
||||||
|
0x7fffffff,
|
||||||
|
NonHardenedChildIndex::from_index(0x7fffffff)
|
||||||
|
.unwrap()
|
||||||
|
.index()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hardened_indexes_rejected() {
|
||||||
|
assert!(NonHardenedChildIndex::from_index(0x80000000).is_none());
|
||||||
|
assert!(NonHardenedChildIndex::from_index(0xffffffff).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nonhardened_index_next() {
|
||||||
|
assert_eq!(1, NonHardenedChildIndex::ZERO.next().unwrap().index());
|
||||||
|
assert!(NonHardenedChildIndex::from_index(0x7fffffff)
|
||||||
|
.unwrap()
|
||||||
|
.next()
|
||||||
|
.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nonhardened_index_ct_eq() {
|
||||||
|
assert!(check(
|
||||||
|
NonHardenedChildIndex::ZERO,
|
||||||
|
NonHardenedChildIndex::ZERO
|
||||||
|
));
|
||||||
|
assert!(!check(
|
||||||
|
NonHardenedChildIndex::ZERO,
|
||||||
|
NonHardenedChildIndex::ZERO.next().unwrap()
|
||||||
|
));
|
||||||
|
|
||||||
|
fn check<T: ConstantTimeEq>(v1: T, v2: T) -> bool {
|
||||||
|
v1.ct_eq(&v2).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
fn nonhardened_index_tryfrom_keyindex() {
|
||||||
|
let nh: NonHardenedChildIndex = KeyIndex::Normal(0).try_into().unwrap();
|
||||||
|
assert_eq!(nh.index(), 0);
|
||||||
|
|
||||||
|
assert!(NonHardenedChildIndex::try_from(KeyIndex::Hardened(0)).is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,7 @@ use zcash_spec::PrfExpand;
|
||||||
|
|
||||||
use crate::{consensus, zip32::AccountId};
|
use crate::{consensus, zip32::AccountId};
|
||||||
|
|
||||||
use super::TransparentAddress;
|
use super::{NonHardenedChildIndex, TransparentAddress};
|
||||||
|
|
||||||
const MAX_TRANSPARENT_CHILD_INDEX: u32 = 0x7FFFFFFF;
|
|
||||||
|
|
||||||
/// A [BIP44] private key at the account path level `m/44'/<coin_type>'/<account>'`.
|
/// A [BIP44] private key at the account path level `m/44'/<coin_type>'/<account>'`.
|
||||||
///
|
///
|
||||||
|
@ -50,11 +48,11 @@ impl AccountPrivKey {
|
||||||
/// `m/44'/<coin_type>'/<account>'/0/<child_index>`.
|
/// `m/44'/<coin_type>'/<account>'/0/<child_index>`.
|
||||||
pub fn derive_external_secret_key(
|
pub fn derive_external_secret_key(
|
||||||
&self,
|
&self,
|
||||||
child_index: u32,
|
child_index: NonHardenedChildIndex,
|
||||||
) -> Result<secp256k1::SecretKey, hdwallet::error::Error> {
|
) -> Result<secp256k1::SecretKey, hdwallet::error::Error> {
|
||||||
self.0
|
self.0
|
||||||
.derive_private_key(KeyIndex::Normal(0))?
|
.derive_private_key(KeyIndex::Normal(0))?
|
||||||
.derive_private_key(KeyIndex::Normal(child_index))
|
.derive_private_key(child_index.into())
|
||||||
.map(|k| k.private_key)
|
.map(|k| k.private_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,30 +184,31 @@ pub trait IncomingViewingKey: private::SealedChangeLevelKey + std::marker::Sized
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
fn derive_address(
|
fn derive_address(
|
||||||
&self,
|
&self,
|
||||||
child_index: u32,
|
child_index: NonHardenedChildIndex,
|
||||||
) -> Result<TransparentAddress, hdwallet::error::Error> {
|
) -> Result<TransparentAddress, hdwallet::error::Error> {
|
||||||
let child_key = self
|
let child_key = self
|
||||||
.extended_pubkey()
|
.extended_pubkey()
|
||||||
.derive_public_key(KeyIndex::Normal(child_index))?;
|
.derive_public_key(child_index.into())?;
|
||||||
Ok(pubkey_to_address(&child_key.public_key))
|
Ok(pubkey_to_address(&child_key.public_key))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Searches the space of child indexes for an index that will
|
/// Searches the space of child indexes for an index that will
|
||||||
/// generate a valid transparent address, and returns the resulting
|
/// generate a valid transparent address, and returns the resulting
|
||||||
/// address and the index at which it was generated.
|
/// address and the index at which it was generated.
|
||||||
fn default_address(&self) -> (TransparentAddress, u32) {
|
fn default_address(&self) -> (TransparentAddress, NonHardenedChildIndex) {
|
||||||
let mut child_index = 0;
|
let mut child_index = NonHardenedChildIndex::ZERO;
|
||||||
while child_index <= MAX_TRANSPARENT_CHILD_INDEX {
|
loop {
|
||||||
match self.derive_address(child_index) {
|
match self.derive_address(child_index) {
|
||||||
Ok(addr) => {
|
Ok(addr) => {
|
||||||
return (addr, child_index);
|
return (addr, child_index);
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
child_index += 1;
|
child_index = child_index.next().unwrap_or_else(|| {
|
||||||
|
panic!("Exhausted child index space attempting to find a default address.");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic!("Exhausted child index space attempting to find a default address.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize(&self) -> Vec<u8> {
|
fn serialize(&self) -> Vec<u8> {
|
||||||
|
|
|
@ -950,6 +950,7 @@ mod tests {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
fn binding_sig_absent_if_no_shielded_spend_or_output() {
|
fn binding_sig_absent_if_no_shielded_spend_or_output() {
|
||||||
use crate::consensus::NetworkUpgrade;
|
use crate::consensus::NetworkUpgrade;
|
||||||
|
use crate::legacy::NonHardenedChildIndex;
|
||||||
use crate::transaction::builder::{self, TransparentBuilder};
|
use crate::transaction::builder::{self, TransparentBuilder};
|
||||||
|
|
||||||
let sapling_activation_height = TEST_NETWORK
|
let sapling_activation_height = TEST_NETWORK
|
||||||
|
@ -984,13 +985,14 @@ mod tests {
|
||||||
.to_account_pubkey()
|
.to_account_pubkey()
|
||||||
.derive_external_ivk()
|
.derive_external_ivk()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.derive_address(0)
|
.derive_address(NonHardenedChildIndex::ZERO)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.script(),
|
.script(),
|
||||||
};
|
};
|
||||||
builder
|
builder
|
||||||
.add_transparent_input(
|
.add_transparent_input(
|
||||||
tsk.derive_external_secret_key(0).unwrap(),
|
tsk.derive_external_secret_key(NonHardenedChildIndex::ZERO)
|
||||||
|
.unwrap(),
|
||||||
OutPoint::new([0u8; 32], 1),
|
OutPoint::new([0u8; 32], 1),
|
||||||
prev_coin,
|
prev_coin,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue