Merge pull request #504 from nuttycom/merge_non_consensus_changes

Merge all non-consensus changes staged for NU5 to `master`.
This commit is contained in:
str4d 2022-02-02 22:25:30 +00:00 committed by GitHub
commit e63979e80a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 2842 additions and 539 deletions

View File

@ -20,3 +20,5 @@ codegen-units = 1
[patch.crates-io]
zcash_encoding = { path = "components/zcash_encoding" }
zcash_note_encryption = { path = "components/zcash_note_encryption" }
hdwallet = { git = "https://github.com/nuttycom/hdwallet", rev = "576683b9f2865f1118c309017ff36e01f84420c9" }

View File

@ -352,10 +352,10 @@ pub trait ShieldedOutput<D: Domain, const CIPHERTEXT_SIZE: usize> {
/// use ff::Field;
/// use rand_core::OsRng;
/// use zcash_primitives::{
/// keys::{OutgoingViewingKey, prf_expand},
/// consensus::{TEST_NETWORK, TestNetwork, NetworkUpgrade, Parameters},
/// memo::MemoBytes,
/// sapling::{
/// keys::{OutgoingViewingKey, prf_expand},
/// note_encryption::sapling_note_encryption,
/// util::generate_random_rseed,
/// Diversifier, PaymentAddress, Rseed, ValueCommitment

View File

@ -6,6 +6,37 @@ and this library adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Functionality that enables the receiving and spending of transparent funds,
behind the new `transparent-inputs` feature flag.
- A new `data_api::wallet::shield_transparent_funds` method has been added
to facilitate the automatic shielding of transparent funds received
by the wallet.
- `zcash_client_backend::data_api::WalletReadTransparent` read-only operations
related to information the wallet maintains about transparent funds.
- `zcash_client_backend::data_api::WalletWriteTransparent` operations
related persisting information the wallet maintains about transparent funds.
- A `zcash_client_backend::wallet::WalletTransparentOutput` type
has been added under the `transparent-inputs` feature flag in support
of autoshielding functionality.
- A new `data_api::wallet::spend` method has been added, which is
intended to supersede the `data_api::wallet::create_spend_to_address`
method. This new method now constructs transactions via interpretation
of a `zcash_client_backend::zip321::TransactionRequest` value.
This facilitates the implementation of ZIP 321 support in wallets and
provides substantially greater flexibility in transaction creation.
- `zcash_client_backend::zip321::TransactionRequest` methods:
- `TransactionRequest::new` for constructing a request from `Vec<Payment>`.
- `TransactionRequest::payments` for accessing the `Payments` that make up a
request.
- New experimental APIs that should be considered unstable, and are
likely to be modified and/or moved to a different module in a future
release:
- `zcash_client_backend::keys::{UnifiedSpendingKey`, `UnifiedFullViewingKey`}
- `zcash_client_backend::encoding::AddressCodec`
- `zcash_client_backend::encoding::encode_payment_address`
- `zcash_client_backend::encoding::encode_transparent_address`
### Changed
- MSRV is now 1.51.0.
- Bumped dependencies to `ff 0.11`, `group 0.11`, `bls12_381 0.6`, `jubjub 0.8`.
@ -15,10 +46,55 @@ and this library adheres to Rust's notion of
been replaced by `ephemeral_key`.
- `zcash_client_backend::proto::compact_formats::CompactOutput`: the `epk`
method has been replaced by `ephemeral_key`.
- `data_api::wallet::spend_to_address` now takes a `min_confirmations`
parameter, which the caller can provide to specify the minimum number of
confirmations required for notes being selected. A default value of 10
confirmations is recommended.
- Renamed the following in `zcash_client_backend::data_api` to use lower-case
abbreviations (matching Rust naming conventions):
- `error::Error::InvalidExtSK` to `Error::InvalidExtSk`
- `testing::MockWalletDB` to `testing::MockWalletDb`
- Changes to the `data_api::WalletRead` trait:
- `WalletRead::get_target_and_anchor_heights` now takes
a `min_confirmations` argument that is used to compute an upper bound on
the anchor height being returned; this had previously been hardcoded to
`data_api::wallet::ANCHOR_OFFSET`.
- `WalletRead::get_spendable_notes` has been renamed to
`get_spendable_sapling_notes`
- `WalletRead::select_spendable_notes` has been renamed to
`select_spendable_sapling_notes`
- `WalletRead::get_all_nullifiers` has been
added. This method provides access to all Sapling nullifiers, including
for notes that have been previously marked spent.
- The `zcash_client_backend::data_api::SentTransaction` type has been
substantially modified to accommodate handling of transparent inputs.
Per-output data has been split out into a new struct `SentTransactionOutput`
and `SentTransaction` can now contain multiple outputs.
- `data_api::WalletWrite::store_received_tx` has been renamed to
`store_decrypted_tx`.
- `data_api::ReceivedTransaction` has been renamed to `DecryptedTransaction`,
and its `outputs` field has been renamed to `sapling_outputs`.
- An `Error::MemoForbidden` error has been added to the
`data_api::error::Error` enum to report the condition where a memo was
specified to be sent to a transparent recipient.
- If no memo is provided when sending to a shielded recipient, the
empty memo will be used
- `zcash_client_backend::keys::spending_key` has been moved to the
`zcash_client_backend::keys::sapling` module.
- `zcash_client_backend::zip321::MemoError` has been renamed and
expanded into a more comprehensive `Zip321Error` type, and functions in the
`zip321` module have been updated to use this unified error type. The
following error cases have been added:
- `Zip321Error::TooManyPayments(usize)`
- `Zip321Error::DuplicateParameter(parse::Param, usize)`
- `Zip321Error::TransparentMemo(usize)`
- `Zip321Error::RecipientMissing(usize)`
- `Zip321Error::ParseError(String)`
### Removed
- The hardcoded `data_api::wallet::ANCHOR_OFFSET` constant.
- `zcash_client_backend::wallet::AccountId` (moved to `zcash_primitives::zip32::AccountId`).
## [0.5.0] - 2021-03-26
### Added

View File

@ -20,12 +20,17 @@ base64 = "0.13"
ff = "0.11"
group = "0.11"
hex = "0.4"
hdwallet = { version = "0.3.0", optional = true }
jubjub = "0.8"
log = "0.4"
nom = "7"
percent-encoding = "2.1.0"
proptest = { version = "1.0.0", optional = true }
protobuf = ">=2.20,<2.26" # protobuf 2.26 bumped MSRV to 1.52.1
rand_core = "0.6"
ripemd = { version = "0.1", optional = true }
secp256k1 = { version = "0.20", optional = true }
sha2 = { version = "0.10.1", optional = true }
subtle = "2.2.3"
time = "0.2"
zcash_note_encryption = { version = "0.1", path = "../components/zcash_note_encryption" }
@ -38,10 +43,10 @@ protobuf-codegen-pure = ">=2.20,<2.26" # protobuf 2.26 bumped MSRV to 1.52.1
gumdrop = "0.8"
rand_xorshift = "0.3"
tempfile = "3.1.0"
zcash_client_sqlite = { version = "0.3", path = "../zcash_client_sqlite" }
zcash_proofs = { version = "0.5", path = "../zcash_proofs" }
[features]
transparent-inputs = ["ripemd", "hdwallet", "sha2", "secp256k1", "zcash_primitives/transparent-inputs"]
test-dependencies = ["proptest", "zcash_primitives/test-dependencies"]
[lib]

View File

@ -3,8 +3,8 @@
use zcash_primitives::{consensus, legacy::TransparentAddress, sapling::PaymentAddress};
use crate::encoding::{
decode_payment_address, decode_transparent_address, encode_payment_address,
encode_transparent_address,
decode_payment_address, decode_transparent_address, encode_payment_address_p,
encode_transparent_address_p,
};
/// An address that funds can be sent to.
@ -44,14 +44,8 @@ impl RecipientAddress {
pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
match self {
RecipientAddress::Shielded(pa) => {
encode_payment_address(params.hrp_sapling_payment_address(), pa)
}
RecipientAddress::Transparent(addr) => encode_transparent_address(
&params.b58_pubkey_address_prefix(),
&params.b58_script_address_prefix(),
addr,
),
RecipientAddress::Shielded(pa) => encode_payment_address_p(params, pa),
RecipientAddress::Transparent(addr) => encode_transparent_address_p(params, addr),
}
}
}

View File

@ -11,15 +11,20 @@ use zcash_primitives::{
merkle_tree::{CommitmentTree, IncrementalWitness},
sapling::{Node, Nullifier, PaymentAddress},
transaction::{components::Amount, Transaction, TxId},
zip32::ExtendedFullViewingKey,
zip32::{AccountId, ExtendedFullViewingKey},
};
use crate::{
address::RecipientAddress,
data_api::wallet::ANCHOR_OFFSET,
decrypt::DecryptedOutput,
proto::compact_formats::CompactBlock,
wallet::{AccountId, SpendableNote, WalletTx},
wallet::{SpendableNote, WalletTx},
};
#[cfg(feature = "transparent-inputs")]
use {
crate::wallet::WalletTransparentOutput,
zcash_primitives::{legacy::TransparentAddress, transaction::components::OutPoint},
};
pub mod chain;
@ -28,10 +33,9 @@ pub mod wallet;
/// Read-only operations required for light wallet functions.
///
/// This trait defines the read-only portion of the storage
/// interface atop which higher-level wallet operations are
/// implemented. It serves to allow wallet functions to be
/// abstracted away from any particular data storage substrate.
/// This trait defines the read-only portion of the storage interface atop which
/// higher-level wallet operations are implemented. It serves to allow wallet functions to
/// be abstracted away from any particular data storage substrate.
pub trait WalletRead {
/// The type of errors produced by a wallet backend.
type Error;
@ -62,15 +66,16 @@ pub trait WalletRead {
/// This will return `Ok(None)` if no block data is present in the database.
fn get_target_and_anchor_heights(
&self,
min_confirmations: u32,
) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
self.block_height_extrema().map(|heights| {
heights.map(|(min_height, max_height)| {
let target_height = max_height + 1;
// Select an anchor ANCHOR_OFFSET back from the target block,
// Select an anchor min_confirmations back from the target block,
// unless that would be before the earliest block we have.
let anchor_height = BlockHeight::from(cmp::max(
u32::from(target_height).saturating_sub(ANCHOR_OFFSET),
u32::from(target_height).saturating_sub(min_confirmations),
u32::from(min_height),
));
@ -142,6 +147,9 @@ pub trait WalletRead {
/// does not appear in the backing data store.
fn get_memo(&self, id_note: Self::NoteRef) -> Result<Memo, Self::Error>;
/// Returns a transaction.
fn get_transaction(&self, id_tx: Self::TxRef) -> Result<Transaction, Self::Error>;
/// Returns the note commitment tree at the specified block height.
fn get_commitment_tree(
&self,
@ -155,20 +163,26 @@ pub trait WalletRead {
block_height: BlockHeight,
) -> Result<Vec<(Self::NoteRef, IncrementalWitness<Node>)>, Self::Error>;
/// Returns the unspent nullifiers, along with the account identifiers
/// with which they are associated.
/// Returns the nullifiers for notes that the wallet is tracking, along with their
/// associated account IDs, that are either unspent or have not yet been confirmed as
/// spent (in that the spending transaction has not yet been included in a block).
fn get_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error>;
/// Return all spendable notes.
fn get_spendable_notes(
/// Returns all nullifiers for notes that the wallet is tracking
/// (including those for notes that have been previously spent),
/// along with the account identifiers with which they are associated.
fn get_all_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error>;
/// Return all unspent Sapling notes.
fn get_spendable_sapling_notes(
&self,
account: AccountId,
anchor_height: BlockHeight,
) -> Result<Vec<SpendableNote>, Self::Error>;
/// Returns a list of spendable notes sufficient to cover the specified
/// Returns a list of spendable Sapling notes sufficient to cover the specified
/// target value, if possible.
fn select_spendable_notes(
fn select_spendable_sapling_notes(
&self,
account: AccountId,
target_value: Amount,
@ -176,6 +190,17 @@ pub trait WalletRead {
) -> Result<Vec<SpendableNote>, Self::Error>;
}
#[cfg(feature = "transparent-inputs")]
pub trait WalletReadTransparent: WalletRead {
/// Returns a list of unspent transparent UTXOs that appear in the chain at heights up to and
/// including `max_height`.
fn get_unspent_transparent_outputs(
&self,
address: &TransparentAddress,
max_height: BlockHeight,
) -> Result<Vec<WalletTransparentOutput>, Self::Error>;
}
/// The subset of information that is relevant to this wallet that has been
/// decrypted and extracted from a [CompactBlock].
pub struct PrunedBlock<'a> {
@ -191,9 +216,9 @@ pub struct PrunedBlock<'a> {
///
/// The purpose of this struct is to permit atomic updates of the
/// wallet database when transactions are successfully decrypted.
pub struct ReceivedTransaction<'a> {
pub struct DecryptedTransaction<'a> {
pub tx: &'a Transaction,
pub outputs: &'a Vec<DecryptedOutput>,
pub sapling_outputs: &'a Vec<DecryptedOutput>,
}
/// A transaction that was constructed and sent by the wallet.
@ -204,6 +229,13 @@ pub struct ReceivedTransaction<'a> {
pub struct SentTransaction<'a> {
pub tx: &'a Transaction,
pub created: time::OffsetDateTime,
pub account: AccountId,
pub outputs: Vec<SentTransactionOutput<'a>>,
#[cfg(feature = "transparent-inputs")]
pub utxos_spent: Vec<OutPoint>,
}
pub struct SentTransactionOutput<'a> {
/// The index within the transaction that contains the recipient output.
///
/// - If `recipient_address` is a Sapling address, this is an index into the Sapling
@ -211,7 +243,6 @@ pub struct SentTransaction<'a> {
/// - If `recipient_address` is a transparent address, this is an index into the
/// transparent outputs of the transaction.
pub output_index: usize,
pub account: AccountId,
pub recipient_address: &'a RecipientAddress,
pub value: Amount,
pub memo: Option<MemoBytes>,
@ -220,6 +251,10 @@ pub struct SentTransaction<'a> {
/// This trait encapsulates the write capabilities required to update stored
/// wallet data.
pub trait WalletWrite: WalletRead {
/// Updates the state of the wallet database by persisting the provided
/// block information, along with the updated witness data that was
/// produced when scanning the block for transactions pertaining to
/// this wallet.
#[allow(clippy::type_complexity)]
fn advance_by_block(
&mut self,
@ -227,9 +262,10 @@ pub trait WalletWrite: WalletRead {
updated_witnesses: &[(Self::NoteRef, IncrementalWitness<Node>)],
) -> Result<Vec<(Self::NoteRef, IncrementalWitness<Node>)>, Self::Error>;
fn store_received_tx(
/// Caches a decrypted transaction in the persistent wallet store.
fn store_decrypted_tx(
&mut self,
received_tx: &ReceivedTransaction,
received_tx: &DecryptedTransaction,
) -> Result<Self::TxRef, Self::Error>;
fn store_sent_tx(&mut self, sent_tx: &SentTransaction) -> Result<Self::TxRef, Self::Error>;
@ -250,6 +286,16 @@ pub trait WalletWrite: WalletRead {
fn rewind_to_height(&mut self, block_height: BlockHeight) -> Result<(), Self::Error>;
}
#[cfg(feature = "transparent-inputs")]
pub trait WalletWriteTransparent: WalletWrite + WalletReadTransparent {
type UtxoRef;
fn put_received_transparent_utxo(
&mut self,
output: &WalletTransparentOutput,
) -> Result<Self::UtxoRef, Self::Error>;
}
/// This trait provides sequential access to raw blockchain data via a callback-oriented
/// API.
pub trait BlockSource {
@ -274,23 +320,27 @@ pub mod testing {
use zcash_primitives::{
block::BlockHash,
consensus::BlockHeight,
legacy::TransparentAddress,
memo::Memo,
merkle_tree::{CommitmentTree, IncrementalWitness},
sapling::{Node, Nullifier, PaymentAddress},
transaction::{components::Amount, TxId},
zip32::ExtendedFullViewingKey,
transaction::{components::Amount, Transaction, TxId},
zip32::{AccountId, ExtendedFullViewingKey},
};
use crate::{
proto::compact_formats::CompactBlock,
wallet::{AccountId, SpendableNote},
wallet::{SpendableNote, WalletTransparentOutput},
};
use super::{
error::Error, BlockSource, PrunedBlock, ReceivedTransaction, SentTransaction, WalletRead,
error::Error, BlockSource, DecryptedTransaction, PrunedBlock, SentTransaction, WalletRead,
WalletWrite,
};
#[cfg(feature = "transparent-inputs")]
use super::WalletReadTransparent;
pub struct MockBlockSource {}
impl BlockSource for MockBlockSource {
@ -361,6 +411,10 @@ pub mod testing {
Ok(Memo::Empty)
}
fn get_transaction(&self, _id_tx: Self::TxRef) -> Result<Transaction, Self::Error> {
Err(Error::ScanRequired) // wrong error but we'll fix it later.
}
fn get_commitment_tree(
&self,
_block_height: BlockHeight,
@ -380,7 +434,11 @@ pub mod testing {
Ok(Vec::new())
}
fn get_spendable_notes(
fn get_all_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error> {
Ok(Vec::new())
}
fn get_spendable_sapling_notes(
&self,
_account: AccountId,
_anchor_height: BlockHeight,
@ -388,7 +446,7 @@ pub mod testing {
Ok(Vec::new())
}
fn select_spendable_notes(
fn select_spendable_sapling_notes(
&self,
_account: AccountId,
_target_value: Amount,
@ -398,6 +456,17 @@ pub mod testing {
}
}
#[cfg(feature = "transparent-inputs")]
impl WalletReadTransparent for MockWalletDb {
fn get_unspent_transparent_outputs(
&self,
_address: &TransparentAddress,
_anchor_height: BlockHeight,
) -> Result<Vec<WalletTransparentOutput>, Self::Error> {
Ok(Vec::new())
}
}
impl WalletWrite for MockWalletDb {
#[allow(clippy::type_complexity)]
fn advance_by_block(
@ -408,9 +477,9 @@ pub mod testing {
Ok(vec![])
}
fn store_received_tx(
fn store_decrypted_tx(
&mut self,
_received_tx: &ReceivedTransaction,
_received_tx: &DecryptedTransaction,
) -> Result<Self::TxRef, Self::Error> {
Ok(TxId::from_bytes([0u8; 32]))
}

View File

@ -4,7 +4,8 @@
//! # Examples
//!
//! ```
//! use tempfile::NamedTempFile;
//! # #[cfg(feature = "test-dependencies")]
//! # {
//! use zcash_primitives::{
//! consensus::{BlockHeight, Network, Parameters}
//! };
@ -17,32 +18,18 @@
//! scan_cached_blocks,
//! },
//! error::Error,
//! testing,
//! },
//! };
//!
//! use zcash_client_sqlite::{
//! BlockDb,
//! WalletDb,
//! error::SqliteClientError,
//! wallet::{rewind_to_height},
//! wallet::init::{init_wallet_db},
//! };
//!
//! # // doctests have a problem with sqlite IO, so we ignore errors
//! # // generated in this example code as it's not really testing anything
//! # fn main() {
//! # test();
//! # }
//! #
//! # fn test() -> Result<(), SqliteClientError> {
//! # fn test() -> Result<(), Error<u32>> {
//! let network = Network::TestNetwork;
//! let cache_file = NamedTempFile::new()?;
//! let db_cache = BlockDb::for_path(cache_file)?;
//! let db_file = NamedTempFile::new()?;
//! let db_read = WalletDb::for_path(db_file, network)?;
//! init_wallet_db(&db_read)?;
//!
//! let mut db_data = db_read.get_update_ops()?;
//! let db_cache = testing::MockBlockSource {};
//! let mut db_data = testing::MockWalletDb {};
//!
//! // 1) Download new CompactBlocks into db_cache.
//!
@ -52,7 +39,7 @@
//! // errors are in the blocks we have previously cached or scanned.
//! if let Err(e) = validate_chain(&network, &db_cache, db_data.get_max_height_hash()?) {
//! match e {
//! SqliteClientError::BackendError(Error::InvalidChain(lower_bound, _)) => {
//! Error::InvalidChain(lower_bound, _) => {
//! // a) Pick a height to rewind to.
//! //
//! // This might be informed by some external chain reorg information, or
@ -72,10 +59,10 @@
//! // d) If there is some separate thread or service downloading
//! // CompactBlocks, tell it to go back and download from rewind_height
//! // onwards.
//! }
//! },
//! e => {
//! // Handle or return other errors.
//! return Err(e);
//! // handle or return other errors
//!
//! }
//! }
//! }
@ -87,6 +74,7 @@
//! // next time this codepath is executed after new blocks are received).
//! scan_cached_blocks(&network, &db_cache, &mut db_data, None)
//! # }
//! # }
//! ```
use std::fmt::Debug;
@ -96,7 +84,7 @@ use zcash_primitives::{
consensus::{self, BlockHeight, NetworkUpgrade},
merkle_tree::CommitmentTree,
sapling::Nullifier,
zip32::ExtendedFullViewingKey,
zip32::{AccountId, ExtendedFullViewingKey},
};
use crate::{
@ -105,7 +93,7 @@ use crate::{
BlockSource, PrunedBlock, WalletWrite,
},
proto::compact_formats::CompactBlock,
wallet::{AccountId, WalletTx},
wallet::WalletTx,
welding_rig::scan_block,
};
@ -198,44 +186,6 @@ where
///
/// Scanned blocks are required to be height-sequential. If a block is missing from the
/// cache, an error will be returned with kind [`ChainInvalid::BlockHeightDiscontinuity`].
///
/// # Examples
///
/// ```
/// use tempfile::NamedTempFile;
/// use zcash_primitives::consensus::{
/// Network,
/// Parameters,
/// };
/// use zcash_client_backend::{
/// data_api::chain::scan_cached_blocks,
/// };
/// use zcash_client_sqlite::{
/// BlockDb,
/// WalletDb,
/// error::SqliteClientError,
/// wallet::init::init_wallet_db,
/// };
///
/// # // doctests have a problem with sqlite IO, so we ignore errors
/// # // generated in this example code as it's not really testing anything
/// # fn main() {
/// # test();
/// # }
/// #
/// # fn test() -> Result<(), SqliteClientError> {
/// let cache_file = NamedTempFile::new().unwrap();
/// let cache = BlockDb::for_path(cache_file).unwrap();
///
/// let data_file = NamedTempFile::new().unwrap();
/// let db_read = WalletDb::for_path(data_file, Network::TestNetwork)?;
/// init_wallet_db(&db_read)?;
///
/// let mut data = db_read.get_update_ops()?;
/// scan_cached_blocks(&Network::TestNetwork, &cache, &mut data, None)?;
/// # Ok(())
/// # }
/// ```
pub fn scan_cached_blocks<E, N, P, C, D>(
params: &P,
cache: &C,

View File

@ -6,10 +6,9 @@ use zcash_primitives::{
consensus::BlockHeight,
sapling::Node,
transaction::{builder, components::amount::Amount, TxId},
zip32::AccountId,
};
use crate::wallet::AccountId;
#[derive(Debug)]
pub enum ChainInvalid {
/// The hash of the parent block given by a proposed new chain tip does
@ -28,6 +27,8 @@ pub enum Error<NoteId> {
InvalidAmount,
/// Unable to create a new spend because the wallet balance is not sufficient.
/// The first argument is the amount available, the second is the amount needed
/// to construct a valid transaction.
InsufficientBalance(Amount, Amount),
/// Chain validation detected an error in the block at the specified block height.
@ -60,6 +61,9 @@ pub enum Error<NoteId> {
/// The wallet attempted a sapling-only operation at a block
/// height when Sapling was not yet active.
SaplingNotActive,
/// It is forbidden to provide a memo when constructing a transparent output.
MemoForbidden,
}
impl ChainInvalid {
@ -104,6 +108,7 @@ impl<N: fmt::Display> fmt::Display for Error<N> {
Error::Builder(e) => write!(f, "{:?}", e),
Error::Protobuf(e) => write!(f, "{}", e),
Error::SaplingNotActive => write!(f, "Could not determine Sapling upgrade activation height."),
Error::MemoForbidden => write!(f, "It is not possible to send a memo to a transparent address."),
}
}
}

View File

@ -1,6 +1,4 @@
//! Functions for scanning the chain and extracting relevant information.
use std::fmt::Debug;
use zcash_primitives::{
consensus::{self, NetworkUpgrade},
memo::MemoBytes,
@ -10,17 +8,26 @@ use zcash_primitives::{
components::{amount::DEFAULT_FEE, Amount},
Transaction,
},
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
zip32::{AccountId, ExtendedFullViewingKey, ExtendedSpendingKey},
};
#[cfg(feature = "transparent-inputs")]
use zcash_primitives::{
keys::OutgoingViewingKey, legacy::keys as transparent, legacy::keys::IncomingViewingKey,
};
use crate::{
address::RecipientAddress,
data_api::{error::Error, ReceivedTransaction, SentTransaction, WalletWrite},
data_api::{
error::Error, DecryptedTransaction, SentTransaction, SentTransactionOutput, WalletWrite,
},
decrypt_transaction,
wallet::{AccountId, OvkPolicy},
wallet::OvkPolicy,
zip321::{Payment, TransactionRequest},
};
pub const ANCHOR_OFFSET: u32 = 10;
#[cfg(feature = "transparent-inputs")]
use crate::data_api::WalletWriteTransparent;
/// Scans a [`Transaction`] for any information that can be decrypted by the accounts in
/// the wallet, and saves it to the wallet.
@ -47,17 +54,16 @@ where
.or_else(|| params.activation_height(NetworkUpgrade::Sapling))
.ok_or(Error::SaplingNotActive)?;
let outputs = decrypt_transaction(params, height, tx, &extfvks);
if outputs.is_empty() {
Ok(())
} else {
data.store_received_tx(&ReceivedTransaction {
tx,
outputs: &outputs,
})?;
let sapling_outputs = decrypt_transaction(params, height, tx, &extfvks);
Ok(())
if !(sapling_outputs.is_empty() && tx.transparent_bundle().iter().all(|b| b.vout.is_empty())) {
data.store_decrypted_tx(&DecryptedTransaction {
tx,
sapling_outputs: &sapling_outputs,
})?;
}
Ok(())
}
#[allow(clippy::needless_doctest_main)]
@ -87,34 +93,48 @@ where
///
/// [ZIP 310]: https://zips.z.cash/zip-0310
///
/// Parameters:
/// * `wallet_db`: A read/write reference to the wallet database
/// * `params`: Consensus parameters
/// * `prover`: The TxProver to use in constructing the shielded transaction.
/// * `account`: The ZIP32 account identifier associated with the extended spending
/// key that controls the funds to be used in creating this transaction. This
/// procedure will return an error if this does not correctly correspond to `extsk`.
/// * `extsk`: The extended spending key that controls the funds that will be spent
/// in the resulting transaction.
/// * `amount`: The amount to send.
/// * `to`: The address to which `amount` will be paid.
/// * `memo`: A memo to be included in the output to the recipient.
/// * `ovk_policy`: The policy to use for constructing outgoing viewing keys that
/// can allow the sender to view the resulting notes on the blockchain.
/// * `min_confirmations`: The minimum number of confirmations that a previously
/// received note must have in the blockchain in order to be considered for being
/// spent. A value of 10 confirmations is recommended.
/// # Examples
///
/// ```
/// # #[cfg(feature = "test-dependencies")]
/// # {
/// use tempfile::NamedTempFile;
/// use zcash_primitives::{
/// consensus::{self, Network},
/// constants::testnet::COIN_TYPE,
/// transaction::components::Amount
/// transaction::{TxId, components::Amount},
/// zip32::AccountId,
/// };
/// use zcash_proofs::prover::LocalTxProver;
/// use zcash_client_backend::{
/// keys::spending_key,
/// data_api::wallet::create_spend_to_address,
/// wallet::{AccountId, OvkPolicy},
/// };
/// use zcash_client_sqlite::{
/// WalletDb,
/// error::SqliteClientError,
/// wallet::init::init_wallet_db,
/// keys::sapling,
/// data_api::{wallet::create_spend_to_address, error::Error, testing},
/// wallet::OvkPolicy,
/// };
///
/// # // doctests have a problem with sqlite IO, so we ignore errors
/// # // generated in this example code as it's not really testing anything
/// # fn main() {
/// # test();
/// # }
/// #
/// # fn test() -> Result<(), SqliteClientError> {
/// # fn test() -> Result<TxId, Error<u32>> {
///
/// let tx_prover = match LocalTxProver::with_default_location() {
/// Some(tx_prover) => tx_prover,
/// None => {
@ -123,16 +143,13 @@ where
/// };
///
/// let account = AccountId(0);
/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, account.0);
/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, account);
/// let to = extsk.default_address().1.into();
///
/// let data_file = NamedTempFile::new().unwrap();
/// let db_read = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// init_wallet_db(&db_read)?;
/// let mut db = db_read.get_update_ops()?;
/// let mut db_read = testing::MockWalletDb {};
///
/// create_spend_to_address(
/// &mut db,
/// &mut db_read,
/// &Network::TestNetwork,
/// tx_prover,
/// account,
@ -141,9 +158,10 @@ where
/// Amount::from_u64(1).unwrap(),
/// None,
/// OvkPolicy::Sender,
/// )?;
/// 10
/// )
///
/// # Ok(())
/// # }
/// # }
/// ```
#[allow(clippy::too_many_arguments)]
@ -154,9 +172,97 @@ pub fn create_spend_to_address<E, N, P, D, R>(
account: AccountId,
extsk: &ExtendedSpendingKey,
to: &RecipientAddress,
value: Amount,
amount: Amount,
memo: Option<MemoBytes>,
ovk_policy: OvkPolicy,
min_confirmations: u32,
) -> Result<R, E>
where
E: From<Error<N>>,
P: consensus::Parameters + Clone,
R: Copy + Debug,
D: WalletWrite<Error = E, TxRef = R>,
{
let req = TransactionRequest::new(vec![Payment {
recipient_address: to.clone(),
amount,
memo,
label: None,
message: None,
other_params: vec![],
}])
.expect(
"It should not be possible for this to violate ZIP 321 request construction invariants.",
);
spend(
wallet_db,
params,
prover,
extsk,
account,
&req,
ovk_policy,
min_confirmations,
)
}
/// Constructs a transaction that sends funds as specified by the `request` argument
/// and stores it to the wallet's "sent transactions" data store, and returns a
/// unique identifier for the transaction; this identifier is used only for internal
/// reference purposes and is not the same as the transaction's txid, although after v4
/// transactions have been made invalid in a future network upgrade, the txid could
/// potentially be used for this type (as it is non-malleable for v5+ transactions).
///
/// This procedure uses the wallet's underlying note selection algorithm to choose
/// inputs of sufficient value to satisfy the request, if possible.
///
/// Do not call this multiple times in parallel, or you will generate transactions that
/// double-spend the same notes.
///
/// # Transaction privacy
///
/// `ovk_policy` specifies the desired policy for which outgoing viewing key should be
/// able to decrypt the outputs of this transaction. This is primarily relevant to
/// wallet recovery from backup; in particular, [`OvkPolicy::Discard`] will prevent the
/// recipient's address, and the contents of `memo`, from ever being recovered from the
/// block chain. (The total value sent can always be inferred by the sender from the spent
/// notes and received change.)
///
/// Regardless of the specified policy, `create_spend_to_address` saves `to`, `value`, and
/// `memo` in `db_data`. This can be deleted independently of `ovk_policy`.
///
/// For details on what transaction information is visible to the holder of a full or
/// outgoing viewing key, refer to [ZIP 310].
///
/// [ZIP 310]: https://zips.z.cash/zip-0310
///
/// Parameters:
/// * `wallet_db`: A read/write reference to the wallet database
/// * `params`: Consensus parameters
/// * `prover`: The TxProver to use in constructing the shielded transaction.
/// * `extsk`: The extended spending key that controls the funds that will be spent
/// in the resulting transaction.
/// * `account`: The ZIP32 account identifier associated with the extended spending
/// key that controls the funds to be used in creating this transaction. This
/// procedure will return an error if this does not correctly correspond to `extsk`.
/// * `request`: The ZIP-321 payment request specifying the recipients and amounts
/// for the transaction.
/// * `ovk_policy`: The policy to use for constructing outgoing viewing keys that
/// can allow the sender to view the resulting notes on the blockchain.
/// * `min_confirmations`: The minimum number of confirmations that a previously
/// received note must have in the blockchain in order to be considered for being
/// spent. A value of 10 confirmations is recommended.
#[allow(clippy::too_many_arguments)]
pub fn spend<E, N, P, D, R>(
wallet_db: &mut D,
params: &P,
prover: impl TxProver,
extsk: &ExtendedSpendingKey,
account: AccountId,
request: &TransactionRequest,
ovk_policy: OvkPolicy,
min_confirmations: u32,
) -> Result<R, E>
where
E: From<Error<N>>,
@ -180,11 +286,18 @@ where
// Target the next block, assuming we are up-to-date.
let (height, anchor_height) = wallet_db
.get_target_and_anchor_heights()
.get_target_and_anchor_heights(min_confirmations)
.and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?;
let value = request
.payments()
.iter()
.map(|p| p.amount)
.sum::<Option<Amount>>()
.ok_or_else(|| E::from(Error::InvalidAmount))?;
let target_value = (value + DEFAULT_FEE).ok_or_else(|| E::from(Error::InvalidAmount))?;
let spendable_notes = wallet_db.select_spendable_notes(account, target_value, anchor_height)?;
let spendable_notes =
wallet_db.select_spendable_sapling_notes(account, target_value, anchor_height)?;
// Confirm we were able to select sufficient value
let selected_value = spendable_notes
@ -219,44 +332,182 @@ where
.map_err(Error::Builder)?;
}
match to {
RecipientAddress::Shielded(to) => {
builder.add_sapling_output(ovk, to.clone(), value, memo.clone())
}
RecipientAddress::Transparent(to) => builder.add_transparent_output(&to, value),
for payment in request.payments() {
match &payment.recipient_address {
RecipientAddress::Shielded(to) => builder
.add_sapling_output(
ovk,
to.clone(),
payment.amount,
payment.memo.clone().unwrap_or_else(MemoBytes::empty),
)
.map_err(Error::Builder),
RecipientAddress::Transparent(to) => {
if payment.memo.is_some() {
Err(Error::MemoForbidden)
} else {
builder
.add_transparent_output(&to, payment.amount)
.map_err(Error::Builder)
}
}
}?
}
.map_err(Error::Builder)?;
let (tx, tx_metadata) = builder.build(&prover).map_err(Error::Builder)?;
let output_index = match to {
// Sapling outputs are shuffled, so we need to look up where the output ended up.
RecipientAddress::Shielded(_) => match tx_metadata.output_index(0) {
Some(idx) => idx,
None => panic!("Output 0 should exist in the transaction"),
},
RecipientAddress::Transparent(addr) => {
let script = addr.script();
tx.transparent_bundle()
.and_then(|b| {
b.vout
.iter()
.enumerate()
.find(|(_, tx_out)| tx_out.script_pubkey == script)
})
.map(|(index, _)| index)
.expect("we sent to this address")
let sent_outputs = request.payments().iter().enumerate().map(|(i, payment)| {
let idx = match &payment.recipient_address {
// Sapling outputs are shuffled, so we need to look up where the output ended up.
RecipientAddress::Shielded(_) =>
tx_metadata.output_index(i).expect("An output should exist in the transaction for each shielded payment."),
RecipientAddress::Transparent(addr) => {
let script = addr.script();
tx.transparent_bundle()
.and_then(|b| {
b.vout
.iter()
.enumerate()
.find(|(_, tx_out)| tx_out.script_pubkey == script)
})
.map(|(index, _)| index)
.expect("An output should exist in the transaction for each transparent payment.")
}
};
SentTransactionOutput {
output_index: idx,
recipient_address: &payment.recipient_address,
value: payment.amount,
memo: payment.memo.clone()
}
};
}).collect();
wallet_db.store_sent_tx(&SentTransaction {
tx: &tx,
created: time::OffsetDateTime::now_utc(),
output_index,
outputs: sent_outputs,
account,
recipient_address: to,
value,
memo,
#[cfg(feature = "transparent-inputs")]
utxos_spent: vec![],
})
}
/// Constructs a transaction that consumes available transparent UTXOs belonging to
/// the specified secret key, and sends them to the default address for the provided Sapling
/// extended full viewing key.
///
/// This procedure will not attempt to shield transparent funds if the total amount being shielded
/// is less than the default fee to send the transaction. Fees will be paid only from the transparent
/// UTXOs being consumed.
///
/// Parameters:
/// * `wallet_db`: A read/write reference to the wallet database
/// * `params`: Consensus parameters
/// * `prover`: The TxProver to use in constructing the shielded transaction.
/// * `sk`: The secp256k1 secret key that will be used to detect and spend transparent
/// UTXOs.
/// * `extfvk`: The extended full viewing key that will be used to produce the
/// Sapling address to which funds will be sent.
/// * `account`: The ZIP32 account identifier associated with the the extended
/// full viewing key. This procedure will return an error if this does not correctly
/// correspond to `extfvk`.
/// * `memo`: A memo to be included in the output to the (internal) recipient.
/// This can be used to take notes about auto-shielding operations internal
/// to the wallet that the wallet can use to improve how it represents those
/// shielding transactions to the user.
/// * `min_confirmations`: The minimum number of confirmations that a previously
/// received UTXO must have in the blockchain in order to be considered for being
/// spent.
#[cfg(feature = "transparent-inputs")]
#[allow(clippy::too_many_arguments)]
pub fn shield_transparent_funds<E, N, P, D, R, U>(
wallet_db: &mut D,
params: &P,
prover: impl TxProver,
sk: &transparent::AccountPrivKey,
extfvk: &ExtendedFullViewingKey,
account: AccountId,
memo: &MemoBytes,
min_confirmations: u32,
) -> Result<D::TxRef, E>
where
E: From<Error<N>>,
P: consensus::Parameters,
R: Copy + Debug,
D: WalletWrite<Error = E, TxRef = R> + WalletWriteTransparent<UtxoRef = U>,
{
// Check that the ExtendedSpendingKey we have been given corresponds to the
// ExtendedFullViewingKey for the account we are spending from.
if !wallet_db.is_valid_account_extfvk(account, &extfvk)? {
return Err(E::from(Error::InvalidExtSk(account)));
}
let (latest_scanned_height, latest_anchor) = wallet_db
.get_target_and_anchor_heights(min_confirmations)
.and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?;
let account_pubkey = sk.to_account_pubkey();
let ovk = OutgoingViewingKey(account_pubkey.internal_ovk().as_bytes());
// derive own shielded address from the provided extended full viewing key
// TODO: this should become the internal change address derived from
// the wallet's UFVK
let z_address = extfvk.default_address().1;
// derive the t-address for the extpubkey at the minimum valid child index
let (taddr, child_index) = account_pubkey
.derive_external_ivk()
.unwrap()
.default_address();
// get UTXOs from DB
let utxos = wallet_db.get_unspent_transparent_outputs(&taddr, latest_anchor)?;
let total_amount = utxos
.iter()
.map(|utxo| utxo.txout.value)
.sum::<Option<Amount>>()
.ok_or_else(|| E::from(Error::InvalidAmount))?;
let fee = DEFAULT_FEE;
if fee >= total_amount {
return Err(E::from(Error::InsufficientBalance(total_amount, fee)));
}
let amount_to_shield = (total_amount - fee).ok_or_else(|| E::from(Error::InvalidAmount))?;
let mut builder = Builder::new(params.clone(), latest_scanned_height);
let secret_key = sk.derive_external_secret_key(child_index).unwrap();
for utxo in &utxos {
builder
.add_transparent_input(secret_key, utxo.outpoint.clone(), utxo.txout.clone())
.map_err(Error::Builder)?;
}
// there are no sapling notes so we set the change manually
builder.send_change_to(ovk, z_address.clone());
// add the sapling output to shield the funds
builder
.add_sapling_output(Some(ovk), z_address.clone(), amount_to_shield, memo.clone())
.map_err(Error::Builder)?;
let (tx, tx_metadata) = builder.build(&prover).map_err(Error::Builder)?;
let output_index = tx_metadata.output_index(0).expect(
"No sapling note was created in autoshielding transaction. This is a programming error.",
);
wallet_db.store_sent_tx(&SentTransaction {
tx: &tx,
created: time::OffsetDateTime::now_utc(),
account,
outputs: vec![SentTransactionOutput {
output_index,
recipient_address: &RecipientAddress::Shielded(z_address),
value: amount_to_shield,
memo: Some(memo.clone()),
}],
utxos_spent: utxos.iter().map(|utxo| utxo.outpoint.clone()).collect(),
})
}

View File

@ -8,11 +8,9 @@ use zcash_primitives::{
Note, PaymentAddress,
},
transaction::Transaction,
zip32::ExtendedFullViewingKey,
zip32::{AccountId, ExtendedFullViewingKey},
};
use crate::wallet::AccountId;
/// A decrypted shielded output.
pub struct DecryptedOutput {
/// The index of the output within [`shielded_outputs`].
@ -30,7 +28,7 @@ pub struct DecryptedOutput {
/// True if this output was recovered using an [`OutgoingViewingKey`], meaning that
/// this is a logical output of the transaction.
///
/// [`OutgoingViewingKey`]: zcash_primitives::sapling::keys::OutgoingViewingKey
/// [`OutgoingViewingKey`]: zcash_primitives::keys::OutgoingViewingKey
pub outgoing: bool,
}

View File

@ -8,8 +8,10 @@
use bech32::{self, Error, FromBase32, ToBase32, Variant};
use bs58::{self, decode::Error as Bs58Error};
use std::convert::TryInto;
use std::fmt;
use std::io::{self, Write};
use zcash_primitives::{
consensus,
legacy::TransparentAddress,
sapling::PaymentAddress,
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
@ -36,6 +38,61 @@ where
}
}
pub trait AddressCodec<P>
where
Self: std::marker::Sized,
{
type Error;
fn encode(&self, params: &P) -> String;
fn decode(params: &P, address: &str) -> Result<Self, Self::Error>;
}
#[derive(Debug)]
pub enum TransparentCodecError {
UnsupportedAddressType(String),
Base58(Bs58Error),
}
impl fmt::Display for TransparentCodecError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
TransparentCodecError::UnsupportedAddressType(s) => write!(
f,
"Could not recognize {} as a supported p2sh or p2pkh address.",
s
),
TransparentCodecError::Base58(e) => write!(f, "{}", e),
}
}
}
impl std::error::Error for TransparentCodecError {}
impl<P: consensus::Parameters> AddressCodec<P> for TransparentAddress {
type Error = TransparentCodecError;
fn encode(&self, params: &P) -> String {
encode_transparent_address(
&params.b58_pubkey_address_prefix(),
&params.b58_script_address_prefix(),
self,
)
}
fn decode(params: &P, address: &str) -> Result<TransparentAddress, TransparentCodecError> {
decode_transparent_address(
&params.b58_pubkey_address_prefix(),
&params.b58_script_address_prefix(),
address,
)
.map_err(TransparentCodecError::Base58)
.and_then(|opt| {
opt.ok_or_else(|| TransparentCodecError::UnsupportedAddressType(address.to_string()))
})
}
}
/// Writes an [`ExtendedSpendingKey`] as a Bech32-encoded string.
///
/// # Examples
@ -43,13 +100,14 @@ where
/// ```
/// use zcash_primitives::{
/// constants::testnet::{COIN_TYPE, HRP_SAPLING_EXTENDED_SPENDING_KEY},
/// zip32::AccountId,
/// };
/// use zcash_client_backend::{
/// encoding::encode_extended_spending_key,
/// keys::spending_key,
/// keys::sapling,
/// };
///
/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, 0);
/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId(0));
/// let encoded = encode_extended_spending_key(HRP_SAPLING_EXTENDED_SPENDING_KEY, &extsk);
/// ```
/// [`ExtendedSpendingKey`]: zcash_primitives::zip32::ExtendedSpendingKey
@ -74,14 +132,15 @@ pub fn decode_extended_spending_key(
/// ```
/// use zcash_primitives::{
/// constants::testnet::{COIN_TYPE, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY},
/// zip32::AccountId,
/// };
/// use zcash_client_backend::{
/// encoding::encode_extended_full_viewing_key,
/// keys::spending_key,
/// keys::sapling,
/// };
/// use zcash_primitives::zip32::ExtendedFullViewingKey;
///
/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, 0);
/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId(0));
/// let extfvk = ExtendedFullViewingKey::from(&extsk);
/// let encoded = encode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, &extfvk);
/// ```
@ -138,6 +197,16 @@ pub fn encode_payment_address(hrp: &str, addr: &PaymentAddress) -> String {
bech32_encode(hrp, |w| w.write_all(&addr.to_bytes()))
}
/// Writes a [`PaymentAddress`] as a Bech32-encoded string
/// using the human-readable prefix values defined in the specified
/// network parameters.
pub fn encode_payment_address_p<P: consensus::Parameters>(
params: &P,
addr: &PaymentAddress,
) -> String {
encode_payment_address(params.hrp_sapling_payment_address(), addr)
}
/// Decodes a [`PaymentAddress`] from a Bech32-encoded string.
///
/// # Examples
@ -241,6 +310,20 @@ pub fn encode_transparent_address(
bs58::encode(decoded).with_check().into_string()
}
/// Writes a [`TransparentAddress`] as a Base58Check-encoded string.
/// using the human-readable prefix values defined in the specified
/// network parameters.
pub fn encode_transparent_address_p<P: consensus::Parameters>(
params: &P,
addr: &TransparentAddress,
) -> String {
encode_transparent_address(
&params.b58_pubkey_address_prefix(),
&params.b58_script_address_prefix(),
addr,
)
}
/// Decodes a [`TransparentAddress`] from a Base58Check-encoded string.
///
/// # Examples

View File

@ -1,45 +1,205 @@
//! Helper functions for managing light client key material.
use zcash_primitives::{consensus, zip32::AccountId};
use zcash_primitives::zip32::{ChildIndex, ExtendedSpendingKey};
#[cfg(feature = "transparent-inputs")]
use zcash_primitives::legacy::keys as legacy;
/// Derives the ZIP 32 [`ExtendedSpendingKey`] for a given coin type and account from the
/// given seed.
///
/// # Panics
///
/// Panics if `seed` is shorter than 32 bytes.
///
/// # Examples
///
/// ```
/// use zcash_primitives::{constants::testnet::COIN_TYPE};
/// use zcash_client_backend::{keys::spending_key};
///
/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, 0);
/// ```
/// [`ExtendedSpendingKey`]: zcash_primitives::zip32::ExtendedSpendingKey
pub fn spending_key(seed: &[u8], coin_type: u32, account: u32) -> ExtendedSpendingKey {
if seed.len() < 32 {
panic!("ZIP 32 seeds MUST be at least 32 bytes");
pub mod sapling {
use zcash_primitives::zip32::{AccountId, ChildIndex};
pub use zcash_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey};
/// Derives the ZIP 32 [`ExtendedSpendingKey`] for a given coin type and account from the
/// given seed.
///
/// # Panics
///
/// Panics if `seed` is shorter than 32 bytes.
///
/// # Examples
///
/// ```
/// use zcash_primitives::{
/// constants::testnet::COIN_TYPE,
/// zip32::AccountId,
/// };
/// use zcash_client_backend::{
/// keys::sapling,
/// };
///
/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId(0));
/// ```
/// [`ExtendedSpendingKey`]: zcash_primitives::zip32::ExtendedSpendingKey
pub fn spending_key(seed: &[u8], coin_type: u32, account: AccountId) -> ExtendedSpendingKey {
if seed.len() < 32 {
panic!("ZIP 32 seeds MUST be at least 32 bytes");
}
ExtendedSpendingKey::from_path(
&ExtendedSpendingKey::master(&seed),
&[
ChildIndex::Hardened(32),
ChildIndex::Hardened(coin_type),
ChildIndex::Hardened(account.0),
],
)
}
}
#[derive(Debug)]
#[doc(hidden)]
pub enum DerivationError {
#[cfg(feature = "transparent-inputs")]
Transparent(hdwallet::error::Error),
}
/// A set of viewing keys that are all associated with a single
/// ZIP-0032 account identifier.
#[derive(Clone, Debug)]
#[doc(hidden)]
pub struct UnifiedSpendingKey {
account: AccountId,
#[cfg(feature = "transparent-inputs")]
transparent: legacy::AccountPrivKey,
sapling: sapling::ExtendedSpendingKey,
}
#[doc(hidden)]
impl UnifiedSpendingKey {
pub fn from_seed<P: consensus::Parameters>(
params: &P,
seed: &[u8],
account: AccountId,
) -> Result<UnifiedSpendingKey, DerivationError> {
if seed.len() < 32 {
panic!("ZIP 32 seeds MUST be at least 32 bytes");
}
#[cfg(feature = "transparent-inputs")]
let transparent = legacy::AccountPrivKey::from_seed(params, seed, account)
.map_err(DerivationError::Transparent)?;
Ok(UnifiedSpendingKey {
account,
#[cfg(feature = "transparent-inputs")]
transparent,
sapling: sapling::spending_key(seed, params.coin_type(), account),
})
}
ExtendedSpendingKey::from_path(
&ExtendedSpendingKey::master(&seed),
&[
ChildIndex::Hardened(32),
ChildIndex::Hardened(coin_type),
ChildIndex::Hardened(account),
],
)
pub fn to_unified_full_viewing_key(&self) -> UnifiedFullViewingKey {
UnifiedFullViewingKey {
account: self.account,
#[cfg(feature = "transparent-inputs")]
transparent: Some(self.transparent.to_account_pubkey()),
sapling: Some(sapling::ExtendedFullViewingKey::from(&self.sapling)),
}
}
pub fn account(&self) -> AccountId {
self.account
}
/// Returns the transparent component of the unified key at the
/// BIP44 path `m/44'/<coin_type>'/<account>'`.
#[cfg(feature = "transparent-inputs")]
pub fn transparent(&self) -> &legacy::AccountPrivKey {
&self.transparent
}
/// Returns the Sapling extended full viewing key component of this
/// unified key.
pub fn sapling(&self) -> &sapling::ExtendedSpendingKey {
&self.sapling
}
}
/// A set of viewing keys that are all associated with a single
/// ZIP-0032 account identifier.
#[derive(Clone, Debug)]
#[doc(hidden)]
pub struct UnifiedFullViewingKey {
account: AccountId,
#[cfg(feature = "transparent-inputs")]
transparent: Option<legacy::AccountPubKey>,
// TODO: This type is invalid for a UFVK; create a `sapling::DiversifiableFullViewingKey`
// to replace it.
sapling: Option<sapling::ExtendedFullViewingKey>,
}
#[doc(hidden)]
impl UnifiedFullViewingKey {
/// Construct a new unified full viewing key, if the required components are present.
pub fn new(
account: AccountId,
#[cfg(feature = "transparent-inputs")] transparent: Option<legacy::AccountPubKey>,
sapling: Option<sapling::ExtendedFullViewingKey>,
) -> Option<UnifiedFullViewingKey> {
if sapling.is_none() {
None
} else {
Some(UnifiedFullViewingKey {
account,
#[cfg(feature = "transparent-inputs")]
transparent,
sapling,
})
}
}
/// Returns the ZIP32 account identifier to which all component
/// keys are related.
pub fn account(&self) -> AccountId {
self.account
}
/// Returns the transparent component of the unified key at the
/// BIP44 path `m/44'/<coin_type>'/<account>'`.
#[cfg(feature = "transparent-inputs")]
pub fn transparent(&self) -> Option<&legacy::AccountPubKey> {
self.transparent.as_ref()
}
/// Returns the Sapling extended full viewing key component of this
/// unified key.
pub fn sapling(&self) -> Option<&sapling::ExtendedFullViewingKey> {
self.sapling.as_ref()
}
}
#[cfg(test)]
mod tests {
use super::spending_key;
use super::sapling;
use zcash_primitives::zip32::AccountId;
#[cfg(feature = "transparent-inputs")]
use {
crate::encoding::AddressCodec,
zcash_primitives::{consensus::MAIN_NETWORK, legacy, legacy::keys::IncomingViewingKey},
};
#[cfg(feature = "transparent-inputs")]
fn seed() -> Vec<u8> {
let seed_hex = "6ef5f84def6f4b9d38f466586a8380a38593bd47c8cda77f091856176da47f26b5bd1c8d097486e5635df5a66e820d28e1d73346f499801c86228d43f390304f";
hex::decode(&seed_hex).unwrap()
}
#[test]
#[should_panic]
fn spending_key_panics_on_short_seed() {
let _ = spending_key(&[0; 31][..], 0, 0);
let _ = sapling::spending_key(&[0; 31][..], 0, AccountId(0));
}
#[cfg(feature = "transparent-inputs")]
#[test]
fn pk_to_taddr() {
let taddr = legacy::keys::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId(0))
.unwrap()
.to_account_pubkey()
.derive_external_ivk()
.unwrap()
.derive_address(0)
.unwrap()
.encode(&MAIN_NETWORK);
assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string());
}
}

View File

@ -1,32 +1,21 @@
//! Structs representing transaction data scanned from the block chain by a wallet or
//! light client.
use subtle::{Choice, ConditionallySelectable};
use zcash_note_encryption::EphemeralKeyBytes;
use zcash_primitives::{
keys::OutgoingViewingKey,
merkle_tree::IncrementalWitness,
sapling::{
keys::OutgoingViewingKey, Diversifier, Node, Note, Nullifier, PaymentAddress, Rseed,
},
sapling::{Diversifier, Node, Note, Nullifier, PaymentAddress, Rseed},
transaction::{components::Amount, TxId},
zip32::AccountId,
};
/// A type-safe wrapper for account identifiers.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct AccountId(pub u32);
impl Default for AccountId {
fn default() -> Self {
AccountId(0)
}
}
impl ConditionallySelectable for AccountId {
fn conditional_select(a0: &Self, a1: &Self, c: Choice) -> Self {
AccountId(u32::conditional_select(&a0.0, &a1.0, c))
}
}
#[cfg(feature = "transparent-inputs")]
use zcash_primitives::{
consensus::BlockHeight,
legacy::TransparentAddress,
transaction::components::{OutPoint, TxOut},
};
/// A subset of a [`Transaction`] relevant to wallets and light clients.
///
@ -40,6 +29,20 @@ pub struct WalletTx<N> {
pub shielded_outputs: Vec<WalletShieldedOutput<N>>,
}
#[cfg(feature = "transparent-inputs")]
pub struct WalletTransparentOutput {
pub outpoint: OutPoint,
pub txout: TxOut,
pub height: BlockHeight,
}
#[cfg(feature = "transparent-inputs")]
impl WalletTransparentOutput {
pub fn address(&self) -> TransparentAddress {
self.txout.script_pubkey.address().unwrap()
}
}
/// A subset of a [`SpendDescription`] relevant to wallets and light clients.
///
/// [`SpendDescription`]: zcash_primitives::transaction::components::SpendDescription

View File

@ -13,11 +13,11 @@ use zcash_primitives::{
Node, Note, Nullifier, PaymentAddress, SaplingIvk,
},
transaction::components::sapling::CompactOutputDescription,
zip32::ExtendedFullViewingKey,
zip32::{AccountId, ExtendedFullViewingKey},
};
use crate::proto::compact_formats::{CompactBlock, CompactOutput};
use crate::wallet::{AccountId, WalletShieldedOutput, WalletShieldedSpend, WalletTx};
use crate::wallet::{WalletShieldedOutput, WalletShieldedSpend, WalletTx};
/// Scans a [`CompactOutput`] with a set of [`ScanningKey`]s.
///
@ -317,12 +317,11 @@ mod tests {
SaplingIvk,
},
transaction::components::Amount,
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
zip32::{AccountId, ExtendedFullViewingKey, ExtendedSpendingKey},
};
use super::scan_block;
use crate::proto::compact_formats::{CompactBlock, CompactOutput, CompactSpend, CompactTx};
use crate::wallet::AccountId;
fn random_compact_tx(mut rng: impl RngCore) -> CompactTx {
let fake_nf = {

View File

@ -22,11 +22,16 @@ use std::cmp::Ordering;
use crate::address::RecipientAddress;
/// Errors that may be produced in decoding of memos.
/// Errors that may be produced in decoding of payment requests.
#[derive(Debug)]
pub enum MemoError {
pub enum Zip321Error {
InvalidBase64(base64::DecodeError),
MemoBytesError(memo::Error),
TooManyPayments(usize),
DuplicateParameter(parse::Param, usize),
TransparentMemo(usize),
RecipientMissing(usize),
ParseError(String),
}
/// Converts a [`MemoBytes`] value to a ZIP 321 compatible base64-encoded string.
@ -39,10 +44,10 @@ pub fn memo_to_base64(memo: &MemoBytes) -> String {
/// Parse a [`MemoBytes`] value from a ZIP 321 compatible base64-encoded string.
///
/// [`MemoBytes`]: zcash_primitives::memo::MemoBytes
pub fn memo_from_base64(s: &str) -> Result<MemoBytes, MemoError> {
pub fn memo_from_base64(s: &str) -> Result<MemoBytes, Zip321Error> {
base64::decode_config(s, base64::URL_SAFE_NO_PAD)
.map_err(MemoError::InvalidBase64)
.and_then(|b| MemoBytes::from_bytes(&b).map_err(MemoError::MemoBytesError))
.map_err(Zip321Error::InvalidBase64)
.and_then(|b| MemoBytes::from_bytes(&b).map_err(Zip321Error::MemoBytesError))
}
/// A single payment being requested.
@ -110,6 +115,26 @@ pub struct TransactionRequest {
}
impl TransactionRequest {
/// Constructs a new transaction request that obeys the ZIP-321 invariants
pub fn new(payments: Vec<Payment>) -> Result<TransactionRequest, Zip321Error> {
let request = TransactionRequest { payments };
// Enforce validity requirements.
if !request.payments.is_empty() {
// It doesn't matter what params we use here, as none of the validity
// requirements depend on them.
let params = consensus::MAIN_NETWORK;
TransactionRequest::from_uri(&params, &request.to_uri(&params).unwrap())?;
}
Ok(request)
}
/// Returns the slice of payments that make up this request.
pub fn payments(&self) -> &[Payment] {
&self.payments[..]
}
/// A utility for use in tests to help check round-trip serialization properties.
#[cfg(any(test, feature = "test-dependencies"))]
pub(in crate::zip321) fn normalize<P: consensus::Parameters>(&mut self, params: &P) {
@ -202,17 +227,17 @@ impl TransactionRequest {
}
/// Parse the provided URI to a payment request value.
pub fn from_uri<P: consensus::Parameters>(params: &P, uri: &str) -> Result<Self, String> {
pub fn from_uri<P: consensus::Parameters>(params: &P, uri: &str) -> Result<Self, Zip321Error> {
// Parse the leading zcash:<address>
let (rest, primary_addr_param) =
parse::lead_addr(params)(uri).map_err(|e| e.to_string())?;
parse::lead_addr(params)(uri).map_err(|e| Zip321Error::ParseError(e.to_string()))?;
// Parse the remaining parameters as an undifferentiated list
let (_, xs) = all_consuming(preceded(
char('?'),
separated_list0(char('&'), parse::zcashparam(params)),
))(rest)
.map_err(|e| e.to_string())?;
.map_err(|e| Zip321Error::ParseError(e.to_string()))?;
// Construct sets of payment parameters, keyed by the payment index.
let mut params_by_index: HashMap<usize, Vec<parse::Param>> = HashMap::new();
@ -231,10 +256,7 @@ impl TransactionRequest {
Some(current) => {
if parse::has_duplicate_param(&current, &p.param) {
return Err(format!(
"Found duplicate parameter {:?} at index {}",
p.param, p.payment_index
));
return Err(Zip321Error::DuplicateParameter(p.param, p.payment_index));
} else {
current.push(p.param);
}
@ -365,7 +387,7 @@ mod parse {
use crate::address::RecipientAddress;
use super::{memo_from_base64, MemoBytes, Payment};
use super::{memo_from_base64, MemoBytes, Payment, Zip321Error};
/// A data type that defines the possible parameter types which may occur within a
/// ZIP 321 URI.
@ -411,14 +433,14 @@ mod parse {
/// This function performs checks to ensure that the resulting [`Payment`] is structurally
/// valid; for example, a request for memo contents may not be associated with a
/// transparent payment address.
pub fn to_payment(vs: Vec<Param>, i: usize) -> Result<Payment, String> {
pub fn to_payment(vs: Vec<Param>, i: usize) -> Result<Payment, Zip321Error> {
let addr = vs.iter().find_map(|v| match v {
Param::Addr(a) => Some(a.clone()),
_otherwise => None,
});
let mut payment = Payment {
recipient_address: addr.ok_or(format!("Payment {} had no recipient address.", i))?,
recipient_address: addr.ok_or(Zip321Error::RecipientMissing(i))?,
amount: Amount::zero(),
memo: None,
label: None,
@ -429,10 +451,10 @@ mod parse {
for v in vs {
match v {
Param::Amount(a) => payment.amount = a,
Param::Memo(m) => {
match payment.recipient_address {
RecipientAddress::Shielded(_) => payment.memo = Some(m),
RecipientAddress::Transparent(_) => return Err(format!("Payment {} attempted to associate a memo with a transparent recipient address", i)),
Param::Memo(m) => match payment.recipient_address {
RecipientAddress::Shielded(_) => payment.memo = Some(m),
RecipientAddress::Transparent(_) => {
return Err(Zip321Error::TransparentMemo(i))
}
},

View File

@ -6,6 +6,17 @@ and this library adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Implementations of `zcash_client_backend::data_api::WalletReadTransparent`
and `WalletWriteTransparent` have been added. These implementations
are available only when the `transparent-inputs` feature flag is
enabled.
- New error variants:
- `SqliteClientError::TransparentAddress`, to support handling of errors in
transparent address decoding.
- `SqliteClientError::RequestedRewindInvalid`, to report when requested
rewinds exceed supported bounds.
### Changed
- MSRV is now 1.51.0.
- Bumped dependencies to `ff 0.11`, `group 0.11`, `jubjub 0.8`.
@ -15,6 +26,61 @@ and this library adheres to Rust's notion of
- `zcash_client_sqlite::WalletDB` to `WalletDb`
- `zcash_client_sqlite::error::SqliteClientError::IncorrectHRPExtFVK` to
`IncorrectHrpExtFvk`.
- The SQLite implementations of `zcash_client_backend::data_api::WalletRead`
and `WalletWrite` have been updated to reflect the changes to those
traits.
- Renamed the following to reflect their Sapling-specific nature:
- `zcash_client_sqlite::wallet`:
- `get_spendable_notes` to `get_spendable_sapling_notes`.
- `select_spendable_notes` to `select_spendable_sapling_notes`.
- Altered the arguments to `zcash_client_sqlite::wallet::put_sent_note`
to take the components of a `DecryptedOutput` value to allow this
method to be used in contexts where a transaction has just been
constructed, rather than only in the case that a transaction has
been decrypted after being retrieved from the network.
- A new non-null column, `output_pool` has been added to the `sent_notes`
table to enable distinguishing between Sapling and transparent outputs
(and in the future, outputs to other pools). This will require a migration,
which may need to be performed in multiple steps. Values for this column
should be assigned by inference from the address type in the stored data.
### Deprecated
- A number of public API methods that are used internally to support the
`zcash_client_backend::data_api::{WalletRead, WalletWrite}` interfaces have
been deprecated, and will be removed from the public API in a future release.
Users should depend upon the versions of these methods exposed via the
`zcash_client_backend::data_api` traits mentioned above instead.
- Deprecated in `zcash_client_sqlite::wallet`:
- `get_address`
- `get_extended_full_viewing_keys`
- `is_valid_account_extfvk`
- `get_balance`
- `get_balance_at`
- `get_sent_memo`
- `block_height_extrema`
- `get_tx_height`
- `get_block_hash`
- `get_rewind_height`
- `get_commitment_tree`
- `get_witnesses`
- `get_nullifiers`
- `insert_block`
- `put_tx_meta`
- `put_tx_data`
- `mark_sapling_note_spent`
- `delete_utxos_above`
- `put_receiverd_note`
- `insert_witness`
- `prune_witnesses`
- `update_expired_notes`
- `put_sent_note`
- `put_sent_utxo`
- `insert_sent_note`
- `insert_sent_utxo`
- `get_address`
- Deprecated in `zcash_client_sqlite::wallet::transact`:
- `get_spendable_sapling_notes`
- `select_spendable_sapling_notes`
## [0.3.0] - 2021-03-26
This release contains a major refactor of the APIs to leverage the new Data

View File

@ -21,6 +21,7 @@ jubjub = "0.8"
protobuf = ">=2.20,<2.26" # protobuf 2.26 bumped MSRV to 1.52.1
rand_core = "0.6"
rusqlite = { version = "0.24", features = ["bundled", "time"] }
secp256k1 = { version = "0.20" }
time = "0.2"
zcash_client_backend = { version = "0.5", path = "../zcash_client_backend" }
zcash_primitives = { version = "0.5", path = "../zcash_primitives" }
@ -32,6 +33,7 @@ zcash_proofs = { version = "0.5", path = "../zcash_proofs" }
[features]
mainnet = []
test-dependencies = ["zcash_client_backend/test-dependencies"]
transparent-inputs = ["zcash_client_backend/transparent-inputs"]
[lib]
bench = false

View File

@ -65,13 +65,12 @@ where
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use tempfile::NamedTempFile;
use zcash_primitives::{
block::BlockHash,
transaction::components::Amount,
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
block::BlockHash, transaction::components::Amount, zip32::ExtendedSpendingKey,
};
use zcash_client_backend::data_api::WalletRead;
@ -84,14 +83,10 @@ mod tests {
chain::init::init_cache_database,
error::SqliteClientError,
tests::{
self, fake_compact_block, fake_compact_block_spending, insert_into_cache,
sapling_activation_height,
},
wallet::{
get_balance,
init::{init_accounts_table, init_wallet_db},
rewind_to_height,
self, fake_compact_block, fake_compact_block_spending, init_test_accounts_table,
insert_into_cache, sapling_activation_height,
},
wallet::{get_balance, init::init_wallet_db, rewind_to_height},
AccountId, BlockDb, NoteId, WalletDb,
};
@ -106,9 +101,7 @@ mod tests {
init_wallet_db(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap();
let (extfvk, _taddr) = init_test_accounts_table(&db_data);
// Empty chain should be valid
validate_chain(
@ -187,9 +180,7 @@ mod tests {
init_wallet_db(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap();
let (extfvk, _taddr) = init_test_accounts_table(&db_data);
// Create some fake CompactBlocks
let (cb, _) = fake_compact_block(
@ -259,9 +250,7 @@ mod tests {
init_wallet_db(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap();
let (extfvk, _taddr) = init_test_accounts_table(&db_data);
// Create some fake CompactBlocks
let (cb, _) = fake_compact_block(
@ -331,9 +320,7 @@ mod tests {
init_wallet_db(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap();
let (extfvk, _taddr) = init_test_accounts_table(&db_data);
// Account balance should be zero
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), Amount::zero());
@ -399,9 +386,7 @@ mod tests {
init_wallet_db(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap();
let (extfvk, _taddr) = init_test_accounts_table(&db_data);
// Create a block with height SAPLING_ACTIVATION_HEIGHT
let value = Amount::from_u64(50000).unwrap();
@ -460,9 +445,7 @@ mod tests {
init_wallet_db(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap();
let (extfvk, _taddr) = init_test_accounts_table(&db_data);
// Account balance should be zero
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), Amount::zero());
@ -511,9 +494,7 @@ mod tests {
init_wallet_db(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap();
let (extfvk, _taddr) = init_test_accounts_table(&db_data);
// Account balance should be zero
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), Amount::zero());

View File

@ -3,9 +3,10 @@
use std::error;
use std::fmt;
use zcash_client_backend::data_api;
use zcash_client_backend::{data_api, encoding::TransparentCodecError};
use zcash_primitives::consensus::BlockHeight;
use crate::NoteId;
use crate::{NoteId, PRUNING_HEIGHT};
/// The primary error type for the SQLite wallet backend.
#[derive(Debug)]
@ -32,6 +33,9 @@ pub enum SqliteClientError {
/// Base58 decoding error
Base58(bs58::decode::Error),
/// Base58 decoding error
TransparentAddress(TransparentCodecError),
/// Wrapper for rusqlite errors.
DbError(rusqlite::Error),
@ -41,6 +45,11 @@ pub enum SqliteClientError {
/// A received memo cannot be interpreted as a UTF-8 string.
InvalidMemo(zcash_primitives::memo::Error),
/// A requested rewind would violate invariants of the
/// storage layer. The payload returned with this error is
/// (safe rewind height, requested height).
RequestedRewindInvalid(BlockHeight, BlockHeight),
/// Wrapper for errors from zcash_client_backend
BackendError(data_api::error::Error<NoteId>),
}
@ -65,9 +74,13 @@ impl fmt::Display for SqliteClientError {
}
SqliteClientError::IncorrectHrpExtFvk => write!(f, "Incorrect HRP for extfvk"),
SqliteClientError::InvalidNote => write!(f, "Invalid note"),
SqliteClientError::InvalidNoteId => write!(f, "The note ID associated with an inserted witness must correspond to a received note."),
SqliteClientError::InvalidNoteId =>
write!(f, "The note ID associated with an inserted witness must correspond to a received note."),
SqliteClientError::RequestedRewindInvalid(h, r) =>
write!(f, "A rewind must be either of less than {} blocks, or at least back to block {} for your wallet; the requested height was {}.", PRUNING_HEIGHT, h, r),
SqliteClientError::Bech32(e) => write!(f, "{}", e),
SqliteClientError::Base58(e) => write!(f, "{}", e),
SqliteClientError::TransparentAddress(e) => write!(f, "{}", e),
SqliteClientError::TableNotEmpty => write!(f, "Table is not empty"),
SqliteClientError::DbError(e) => write!(f, "{}", e),
SqliteClientError::Io(e) => write!(f, "{}", e),

View File

@ -44,25 +44,40 @@ use zcash_primitives::{
memo::Memo,
merkle_tree::{CommitmentTree, IncrementalWitness},
sapling::{Node, Nullifier, PaymentAddress},
transaction::{components::Amount, TxId},
zip32::ExtendedFullViewingKey,
transaction::{components::Amount, Transaction, TxId},
zip32::{AccountId, ExtendedFullViewingKey},
};
use zcash_client_backend::{
address::RecipientAddress,
data_api::{
BlockSource, PrunedBlock, ReceivedTransaction, SentTransaction, WalletRead, WalletWrite,
BlockSource, DecryptedTransaction, PrunedBlock, SentTransaction, WalletRead, WalletWrite,
},
encoding::encode_payment_address,
proto::compact_formats::CompactBlock,
wallet::{AccountId, SpendableNote},
wallet::SpendableNote,
};
use crate::error::SqliteClientError;
#[cfg(feature = "transparent-inputs")]
use {
zcash_client_backend::{
data_api::{WalletReadTransparent, WalletWriteTransparent},
wallet::WalletTransparentOutput,
},
zcash_primitives::legacy::TransparentAddress,
};
pub mod chain;
pub mod error;
pub mod wallet;
/// The maximum number of blocks the wallet is allowed to rewind. This is
/// consistent with the bound in zcashd, and allows block data deeper than
/// this delta from the chain tip to be pruned.
pub(crate) const PRUNING_HEIGHT: u32 = 100;
/// A newtype wrapper for sqlite primary key values for the notes
/// table.
#[derive(Debug, Copy, Clone)]
@ -80,6 +95,11 @@ impl fmt::Display for NoteId {
}
}
/// A newtype wrapper for sqlite primary key values for the utxos
/// table.
#[derive(Debug, Copy, Clone)]
pub struct UtxoId(pub i64);
/// A wrapper for the SQLite connection to the wallet database.
pub struct WalletDb<P> {
conn: Connection,
@ -122,9 +142,24 @@ impl<P: consensus::Parameters> WalletDb<P> {
stmt_select_tx_ref: self.conn.prepare(
"SELECT id_tx FROM transactions WHERE txid = ?",
)?,
stmt_mark_recived_note_spent: self.conn.prepare(
stmt_mark_sapling_note_spent: self.conn.prepare(
"UPDATE received_notes SET spent = ? WHERE nf = ?"
)?,
#[cfg(feature = "transparent-inputs")]
stmt_mark_transparent_utxo_spent: self.conn.prepare(
"UPDATE utxos SET spent_in_tx = :spent_in_tx
WHERE prevout_txid = :prevout_txid
AND prevout_idx = :prevout_idx"
)?,
#[cfg(feature = "transparent-inputs")]
stmt_insert_received_transparent_utxo: self.conn.prepare(
"INSERT INTO utxos (address, prevout_txid, prevout_idx, script, value_zat, height)
VALUES (:address, :prevout_txid, :prevout_idx, :script, :value_zat, :height)"
)?,
#[cfg(feature = "transparent-inputs")]
stmt_delete_utxos: self.conn.prepare(
"DELETE FROM utxos WHERE address = :address AND height > :above_height"
)?,
stmt_insert_received_note: self.conn.prepare(
"INSERT INTO received_notes (tx, output_index, account, diversifier, value, rcm, memo, nf, is_change)
VALUES (:tx, :output_index, :account, :diversifier, :value, :rcm, :memo, :nf, :is_change)",
@ -146,11 +181,11 @@ impl<P: consensus::Parameters> WalletDb<P> {
stmt_update_sent_note: self.conn.prepare(
"UPDATE sent_notes
SET from_account = ?, address = ?, value = ?, memo = ?
WHERE tx = ? AND output_index = ?",
WHERE tx = ? AND output_pool = ? AND output_index = ?",
)?,
stmt_insert_sent_note: self.conn.prepare(
"INSERT INTO sent_notes (tx, output_index, from_account, address, value, memo)
VALUES (?, ?, ?, ?, ?, ?)",
"INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value, memo)
VALUES (?, ?, ?, ?, ?, ?, ?)",
)?,
stmt_insert_witness: self.conn.prepare(
"INSERT INTO sapling_witnesses (note, block, witness)
@ -176,25 +211,30 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
type TxRef = i64;
fn block_height_extrema(&self) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
wallet::block_height_extrema(self).map_err(SqliteClientError::from)
#[allow(deprecated)]
wallet::block_height_extrema(&self).map_err(SqliteClientError::from)
}
fn get_block_hash(&self, block_height: BlockHeight) -> Result<Option<BlockHash>, Self::Error> {
wallet::get_block_hash(self, block_height).map_err(SqliteClientError::from)
#[allow(deprecated)]
wallet::get_block_hash(&self, block_height).map_err(SqliteClientError::from)
}
fn get_tx_height(&self, txid: TxId) -> Result<Option<BlockHeight>, Self::Error> {
wallet::get_tx_height(self, txid).map_err(SqliteClientError::from)
#[allow(deprecated)]
wallet::get_tx_height(&self, txid).map_err(SqliteClientError::from)
}
fn get_extended_full_viewing_keys(
&self,
) -> Result<HashMap<AccountId, ExtendedFullViewingKey>, Self::Error> {
wallet::get_extended_full_viewing_keys(self)
#[allow(deprecated)]
wallet::get_extended_full_viewing_keys(&self)
}
fn get_address(&self, account: AccountId) -> Result<Option<PaymentAddress>, Self::Error> {
wallet::get_address(self, account)
#[allow(deprecated)]
wallet::get_address(&self, account)
}
fn is_valid_account_extfvk(
@ -202,7 +242,8 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
account: AccountId,
extfvk: &ExtendedFullViewingKey,
) -> Result<bool, Self::Error> {
wallet::is_valid_account_extfvk(self, account, extfvk)
#[allow(deprecated)]
wallet::is_valid_account_extfvk(&self, account, extfvk)
}
fn get_balance_at(
@ -210,10 +251,17 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
account: AccountId,
anchor_height: BlockHeight,
) -> Result<Amount, Self::Error> {
wallet::get_balance_at(self, account, anchor_height)
#[allow(deprecated)]
wallet::get_balance_at(&self, account, anchor_height)
}
fn get_transaction(&self, id_tx: i64) -> Result<Transaction, Self::Error> {
#[allow(deprecated)]
wallet::get_transaction(&self, id_tx)
}
fn get_memo(&self, id_note: Self::NoteRef) -> Result<Memo, Self::Error> {
#[allow(deprecated)]
match id_note {
NoteId::SentNoteId(id_note) => wallet::get_sent_memo(self, id_note),
NoteId::ReceivedNoteId(id_note) => wallet::get_received_memo(self, id_note),
@ -224,7 +272,8 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
&self,
block_height: BlockHeight,
) -> Result<Option<CommitmentTree<Node>>, Self::Error> {
wallet::get_commitment_tree(self, block_height)
#[allow(deprecated)]
wallet::get_commitment_tree(&self, block_height)
}
#[allow(clippy::type_complexity)]
@ -232,28 +281,53 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
&self,
block_height: BlockHeight,
) -> Result<Vec<(Self::NoteRef, IncrementalWitness<Node>)>, Self::Error> {
wallet::get_witnesses(self, block_height)
#[allow(deprecated)]
wallet::get_witnesses(&self, block_height)
}
fn get_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error> {
wallet::get_nullifiers(self)
#[allow(deprecated)]
wallet::get_nullifiers(&self)
}
fn get_spendable_notes(
fn get_all_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error> {
#[allow(deprecated)]
wallet::get_all_nullifiers(&self)
}
fn get_spendable_sapling_notes(
&self,
account: AccountId,
anchor_height: BlockHeight,
) -> Result<Vec<SpendableNote>, Self::Error> {
wallet::transact::get_spendable_notes(self, account, anchor_height)
#[allow(deprecated)]
wallet::transact::get_spendable_sapling_notes(&self, account, anchor_height)
}
fn select_spendable_notes(
fn select_spendable_sapling_notes(
&self,
account: AccountId,
target_value: Amount,
anchor_height: BlockHeight,
) -> Result<Vec<SpendableNote>, Self::Error> {
wallet::transact::select_spendable_notes(self, account, target_value, anchor_height)
#[allow(deprecated)]
wallet::transact::select_spendable_sapling_notes(
&self,
account,
target_value,
anchor_height,
)
}
}
#[cfg(feature = "transparent-inputs")]
impl<P: consensus::Parameters> WalletReadTransparent for WalletDb<P> {
fn get_unspent_transparent_outputs(
&self,
address: &TransparentAddress,
max_height: BlockHeight,
) -> Result<Vec<WalletTransparentOutput>, Self::Error> {
wallet::get_unspent_transparent_outputs(&self, address, max_height)
}
}
@ -275,8 +349,14 @@ pub struct DataConnStmtCache<'a, P> {
stmt_update_tx_data: Statement<'a>,
stmt_select_tx_ref: Statement<'a>,
stmt_mark_recived_note_spent: Statement<'a>,
stmt_mark_sapling_note_spent: Statement<'a>,
#[cfg(feature = "transparent-inputs")]
stmt_mark_transparent_utxo_spent: Statement<'a>,
#[cfg(feature = "transparent-inputs")]
stmt_insert_received_transparent_utxo: Statement<'a>,
#[cfg(feature = "transparent-inputs")]
stmt_delete_utxos: Statement<'a>,
stmt_insert_received_note: Statement<'a>,
stmt_update_received_note: Statement<'a>,
stmt_select_received_note: Statement<'a>,
@ -332,6 +412,10 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> {
self.wallet_db.get_balance_at(account, anchor_height)
}
fn get_transaction(&self, id_tx: i64) -> Result<Transaction, Self::Error> {
self.wallet_db.get_transaction(id_tx)
}
fn get_memo(&self, id_note: Self::NoteRef) -> Result<Memo, Self::Error> {
self.wallet_db.get_memo(id_note)
}
@ -355,22 +439,39 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> {
self.wallet_db.get_nullifiers()
}
fn get_spendable_notes(
fn get_all_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error> {
self.wallet_db.get_all_nullifiers()
}
fn get_spendable_sapling_notes(
&self,
account: AccountId,
anchor_height: BlockHeight,
) -> Result<Vec<SpendableNote>, Self::Error> {
self.wallet_db.get_spendable_notes(account, anchor_height)
self.wallet_db
.get_spendable_sapling_notes(account, anchor_height)
}
fn select_spendable_notes(
fn select_spendable_sapling_notes(
&self,
account: AccountId,
target_value: Amount,
anchor_height: BlockHeight,
) -> Result<Vec<SpendableNote>, Self::Error> {
self.wallet_db
.select_spendable_notes(account, target_value, anchor_height)
.select_spendable_sapling_notes(account, target_value, anchor_height)
}
}
#[cfg(feature = "transparent-inputs")]
impl<'a, P: consensus::Parameters> WalletReadTransparent for DataConnStmtCache<'a, P> {
fn get_unspent_transparent_outputs(
&self,
address: &TransparentAddress,
max_height: BlockHeight,
) -> Result<Vec<WalletTransparentOutput>, Self::Error> {
self.wallet_db
.get_unspent_transparent_outputs(address, max_height)
}
}
@ -402,6 +503,7 @@ impl<'a, P: consensus::Parameters> DataConnStmtCache<'a, P> {
}
}
#[allow(deprecated)]
impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
#[allow(clippy::type_complexity)]
fn advance_by_block(
@ -426,7 +528,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
// Mark notes as spent and remove them from the scanning cache
for spend in &tx.shielded_spends {
wallet::mark_spent(up, tx_row, &spend.nf)?;
wallet::mark_sapling_note_spent(up, tx_row, &spend.nf)?;
}
for output in &tx.shielded_outputs {
@ -447,8 +549,8 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
}
}
// Prune the stored witnesses (we only expect rollbacks of at most 100 blocks).
wallet::prune_witnesses(up, block.block_height - 100)?;
// Prune the stored witnesses (we only expect rollbacks of at most PRUNING_HEIGHT blocks).
wallet::prune_witnesses(up, block.block_height - PRUNING_HEIGHT)?;
// Update now-expired transactions that didn't get mined.
wallet::update_expired_notes(up, block.block_height)?;
@ -457,21 +559,64 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
})
}
fn store_received_tx(
fn store_decrypted_tx(
&mut self,
received_tx: &ReceivedTransaction,
d_tx: &DecryptedTransaction,
) -> Result<Self::TxRef, Self::Error> {
let nullifiers = self.wallet_db.get_all_nullifiers()?;
self.transactionally(|up| {
let tx_ref = wallet::put_tx_data(up, received_tx.tx, None)?;
let tx_ref = wallet::put_tx_data(up, d_tx.tx, None)?;
for output in received_tx.outputs {
let mut spending_account_id: Option<AccountId> = None;
for output in d_tx.sapling_outputs {
if output.outgoing {
wallet::put_sent_note(up, output, tx_ref)?;
wallet::put_sent_note(
up,
tx_ref,
output.index,
output.account,
&output.to,
Amount::from_u64(output.note.value)
.map_err(|_| SqliteClientError::CorruptedData("Note value invalid.".to_string()))?,
Some(&output.memo),
)?;
} else {
match spending_account_id {
Some(id) =>
if id != output.account {
panic!("Unable to determine a unique account identifier for z->t spend.");
}
None => {
spending_account_id = Some(output.account);
}
}
wallet::put_received_note(up, output, tx_ref)?;
}
}
// If we have some transparent outputs:
if !d_tx.tx.transparent_bundle().iter().any(|b| b.vout.is_empty()) {
// If the transaction contains shielded spends from our wallet, we will store z->t
// transactions we observe in the same way they would be stored by
// create_spend_to_address.
if let Some((account_id, _)) = nullifiers.iter().find(
|(_, nf)|
d_tx.tx.sapling_bundle().iter().flat_map(|b| b.shielded_spends.iter())
.any(|input| *nf == input.nullifier)
) {
for (output_index, txout) in d_tx.tx.transparent_bundle().iter().flat_map(|b| b.vout.iter()).enumerate() {
wallet::put_sent_utxo(
up,
tx_ref,
output_index,
*account_id,
&txout.script_pubkey.address().unwrap(),
txout.value,
)?;
}
}
}
Ok(tx_ref)
})
}
@ -491,19 +636,36 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
// reasonable assumption for a light client such as a mobile phone.
if let Some(bundle) = sent_tx.tx.sapling_bundle() {
for spend in &bundle.shielded_spends {
wallet::mark_spent(up, tx_ref, &spend.nullifier)?;
wallet::mark_sapling_note_spent(up, tx_ref, &spend.nullifier)?;
}
}
wallet::insert_sent_note(
up,
tx_ref,
sent_tx.output_index,
sent_tx.account,
sent_tx.recipient_address,
sent_tx.value,
sent_tx.memo.as_ref(),
)?;
#[cfg(feature = "transparent-inputs")]
for utxo_outpoint in &sent_tx.utxos_spent {
wallet::mark_transparent_utxo_spent(up, tx_ref, &utxo_outpoint)?;
}
for output in &sent_tx.outputs {
match output.recipient_address {
RecipientAddress::Shielded(addr) => wallet::insert_sent_note(
up,
tx_ref,
output.output_index,
sent_tx.account,
&addr,
output.value,
output.memo.as_ref(),
)?,
RecipientAddress::Transparent(addr) => wallet::insert_sent_utxo(
up,
tx_ref,
output.output_index,
sent_tx.account,
&addr,
output.value,
)?,
}
}
// Return the row number of the transaction, so the caller can fetch it for sending.
Ok(tx_ref)
@ -515,6 +677,18 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
}
}
#[cfg(feature = "transparent-inputs")]
impl<'a, P: consensus::Parameters> WalletWriteTransparent for DataConnStmtCache<'a, P> {
type UtxoRef = UtxoId;
fn put_received_transparent_utxo(
&mut self,
output: &WalletTransparentOutput,
) -> Result<Self::UtxoRef, Self::Error> {
wallet::put_received_transparent_utxo(self, output)
}
}
/// A wrapper for the SQLite connection to the block cache database.
pub struct BlockDb(Connection);
@ -557,13 +731,18 @@ mod tests {
use rand_core::{OsRng, RngCore};
use rusqlite::params;
use zcash_client_backend::proto::compact_formats::{
CompactBlock, CompactOutput, CompactSpend, CompactTx,
use zcash_client_backend::{
keys::{sapling, UnifiedFullViewingKey},
proto::compact_formats::{CompactBlock, CompactOutput, CompactSpend, CompactTx},
};
#[cfg(feature = "transparent-inputs")]
use zcash_primitives::{legacy, legacy::keys::IncomingViewingKey};
use zcash_primitives::{
block::BlockHash,
consensus::{BlockHeight, Network, NetworkUpgrade, Parameters},
legacy::TransparentAddress,
memo::MemoBytes,
sapling::{
note_encryption::sapling_note_encryption, util::generate_random_rseed, Note, Nullifier,
@ -573,6 +752,8 @@ mod tests {
zip32::ExtendedFullViewingKey,
};
use crate::{wallet::init::init_accounts_table, AccountId, WalletDb};
use super::BlockDb;
#[cfg(feature = "mainnet")]
@ -599,6 +780,40 @@ mod tests {
.unwrap()
}
#[cfg(test)]
pub(crate) fn init_test_accounts_table(
db_data: &WalletDb<Network>,
) -> (ExtendedFullViewingKey, Option<TransparentAddress>) {
let seed = [0u8; 32];
let account = AccountId(0);
let extsk = sapling::spending_key(&seed, network().coin_type(), account);
let extfvk = ExtendedFullViewingKey::from(&extsk);
#[cfg(feature = "transparent-inputs")]
let (tkey, taddr) = {
let tkey = legacy::keys::AccountPrivKey::from_seed(&network(), &seed, account)
.unwrap()
.to_account_pubkey();
let taddr = tkey.derive_external_ivk().unwrap().default_address().0;
(Some(tkey), Some(taddr))
};
#[cfg(not(feature = "transparent-inputs"))]
let taddr = None;
let ufvk = UnifiedFullViewingKey::new(
account,
#[cfg(feature = "transparent-inputs")]
tkey,
Some(extfvk.clone()),
)
.unwrap();
init_accounts_table(db_data, &[ufvk]).unwrap();
(extfvk, taddr)
}
/// Create a fake CompactBlock at the given height, containing a single output paying
/// the given address. Returns the CompactBlock and the nullifier for the new note.
pub(crate) fn fake_compact_block(

View File

@ -14,31 +14,60 @@ use std::convert::TryFrom;
use zcash_primitives::{
block::BlockHash,
consensus::{self, BlockHeight, NetworkUpgrade},
consensus::{self, BlockHeight, BranchId, NetworkUpgrade, Parameters},
memo::{Memo, MemoBytes},
merkle_tree::{CommitmentTree, IncrementalWitness},
sapling::{Node, Note, Nullifier, PaymentAddress},
transaction::{components::Amount, Transaction, TxId},
zip32::ExtendedFullViewingKey,
zip32::{AccountId, ExtendedFullViewingKey},
};
use zcash_client_backend::{
address::RecipientAddress,
data_api::error::Error,
encoding::{
decode_extended_full_viewing_key, decode_payment_address, encode_extended_full_viewing_key,
encode_payment_address,
encode_payment_address_p, encode_transparent_address_p,
},
wallet::{AccountId, WalletShieldedOutput, WalletTx},
wallet::{WalletShieldedOutput, WalletTx},
DecryptedOutput,
};
use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDb};
use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDb, PRUNING_HEIGHT};
use zcash_primitives::legacy::TransparentAddress;
#[cfg(feature = "transparent-inputs")]
use {
crate::UtxoId,
zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput},
zcash_primitives::{
legacy::Script,
transaction::components::{OutPoint, TxOut},
},
};
pub mod init;
pub mod transact;
enum PoolType {
Transparent,
Sapling,
}
impl PoolType {
fn typecode(&self) -> i64 {
// These constants are *incidentally* shared with the typecodes
// for unified addresses, but this is exclusively an internal
// implementation detail.
match self {
PoolType::Transparent => 0i64,
PoolType::Sapling => 2i64,
}
}
}
/// This trait provides a generalization over shielded output representations.
#[deprecated(note = "This trait will be removed in a future release.")]
pub trait ShieldedOutput {
fn index(&self) -> usize;
fn account(&self) -> AccountId;
@ -49,6 +78,7 @@ pub trait ShieldedOutput {
fn nullifier(&self) -> Option<Nullifier>;
}
#[allow(deprecated)]
impl ShieldedOutput for WalletShieldedOutput<Nullifier> {
fn index(&self) -> usize {
self.index
@ -74,6 +104,7 @@ impl ShieldedOutput for WalletShieldedOutput<Nullifier> {
}
}
#[allow(deprecated)]
impl ShieldedOutput for DecryptedOutput {
fn index(&self) -> usize {
self.index
@ -106,8 +137,8 @@ impl ShieldedOutput for DecryptedOutput {
/// use tempfile::NamedTempFile;
/// use zcash_primitives::{
/// consensus::{self, Network},
/// zip32::AccountId,
/// };
/// use zcash_client_backend::wallet::AccountId;
/// use zcash_client_sqlite::{
/// WalletDb,
/// wallet::get_address,
@ -117,6 +148,9 @@ impl ShieldedOutput for DecryptedOutput {
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let addr = get_address(&db, AccountId(0));
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_address instead."
)]
pub fn get_address<P: consensus::Parameters>(
wdb: &WalletDb<P>,
account: AccountId,
@ -135,6 +169,9 @@ pub fn get_address<P: consensus::Parameters>(
/// Returns the [`ExtendedFullViewingKey`]s for the wallet.
///
/// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_extended_full_viewing_keys instead."
)]
pub fn get_extended_full_viewing_keys<P: consensus::Parameters>(
wdb: &WalletDb<P>,
) -> Result<HashMap<AccountId, ExtendedFullViewingKey>, SqliteClientError> {
@ -172,6 +209,9 @@ pub fn get_extended_full_viewing_keys<P: consensus::Parameters>(
/// specified account.
///
/// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::is_valid_account_extfvk instead."
)]
pub fn is_valid_account_extfvk<P: consensus::Parameters>(
wdb: &WalletDb<P>,
account: AccountId,
@ -202,8 +242,10 @@ pub fn is_valid_account_extfvk<P: consensus::Parameters>(
///
/// ```
/// use tempfile::NamedTempFile;
/// use zcash_primitives::consensus::Network;
/// use zcash_client_backend::wallet::AccountId;
/// use zcash_primitives::{
/// consensus::Network,
/// zip32::AccountId,
/// };
/// use zcash_client_sqlite::{
/// WalletDb,
/// wallet::get_balance,
@ -213,6 +255,9 @@ pub fn is_valid_account_extfvk<P: consensus::Parameters>(
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let addr = get_balance(&db, AccountId(0));
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_balance_at instead."
)]
pub fn get_balance<P>(wdb: &WalletDb<P>, account: AccountId) -> Result<Amount, SqliteClientError> {
let balance = wdb.conn.query_row(
"SELECT SUM(value) FROM received_notes
@ -238,8 +283,10 @@ pub fn get_balance<P>(wdb: &WalletDb<P>, account: AccountId) -> Result<Amount, S
///
/// ```
/// use tempfile::NamedTempFile;
/// use zcash_primitives::consensus::{BlockHeight, Network};
/// use zcash_client_backend::wallet::AccountId;
/// use zcash_primitives::{
/// consensus::{BlockHeight, Network},
/// zip32::AccountId,
/// };
/// use zcash_client_sqlite::{
/// WalletDb,
/// wallet::get_balance_at,
@ -249,6 +296,9 @@ pub fn get_balance<P>(wdb: &WalletDb<P>, account: AccountId) -> Result<Amount, S
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let addr = get_balance_at(&db, AccountId(0), BlockHeight::from_u32(0));
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_balance_at instead."
)]
pub fn get_balance_at<P>(
wdb: &WalletDb<P>,
account: AccountId,
@ -290,6 +340,9 @@ pub fn get_balance_at<P>(
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let memo = get_received_memo(&db, 27);
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_memo instead."
)]
pub fn get_received_memo<P>(wdb: &WalletDb<P>, id_note: i64) -> Result<Memo, SqliteClientError> {
let memo_bytes: Vec<_> = wdb.conn.query_row(
"SELECT memo FROM received_notes
@ -303,6 +356,28 @@ pub fn get_received_memo<P>(wdb: &WalletDb<P>, id_note: i64) -> Result<Memo, Sql
.map_err(SqliteClientError::from)
}
/// Looks up a transaction by its internal database identifier.
pub(crate) fn get_transaction<P: Parameters>(
wdb: &WalletDb<P>,
id_tx: i64,
) -> Result<Transaction, SqliteClientError> {
let (tx_bytes, block_height): (Vec<_>, BlockHeight) = wdb.conn.query_row(
"SELECT raw, block FROM transactions
WHERE id_tx = ?",
&[id_tx],
|row| {
let h: u32 = row.get(1)?;
Ok((row.get(0)?, BlockHeight::from(h)))
},
)?;
Transaction::read(
&tx_bytes[..],
BranchId::for_height(&wdb.params, block_height),
)
.map_err(SqliteClientError::from)
}
/// Returns the memo for a sent note.
///
/// The note is identified by its row index in the `sent_notes` table within the wdb
@ -323,6 +398,9 @@ pub fn get_received_memo<P>(wdb: &WalletDb<P>, id_note: i64) -> Result<Memo, Sql
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let memo = get_sent_memo(&db, 12);
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_memo instead."
)]
pub fn get_sent_memo<P>(wdb: &WalletDb<P>, id_note: i64) -> Result<Memo, SqliteClientError> {
let memo_bytes: Vec<_> = wdb.conn.query_row(
"SELECT memo FROM sent_notes
@ -352,6 +430,9 @@ pub fn get_sent_memo<P>(wdb: &WalletDb<P>, id_note: i64) -> Result<Memo, SqliteC
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let bounds = block_height_extrema(&db);
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::block_height_extrema instead."
)]
pub fn block_height_extrema<P>(
wdb: &WalletDb<P>,
) -> Result<Option<(BlockHeight, BlockHeight)>, rusqlite::Error> {
@ -391,6 +472,9 @@ pub fn block_height_extrema<P>(
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let height = get_tx_height(&db, TxId::from_bytes([0u8; 32]));
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_tx_height instead."
)]
pub fn get_tx_height<P>(
wdb: &WalletDb<P>,
txid: TxId,
@ -421,6 +505,9 @@ pub fn get_tx_height<P>(
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let hash = get_block_hash(&db, H0);
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_block_hash instead."
)]
pub fn get_block_hash<P>(
wdb: &WalletDb<P>,
block_height: BlockHeight,
@ -437,13 +524,32 @@ pub fn get_block_hash<P>(
.optional()
}
/// Gets the height to which the database must be rewound if any rewind greater than the pruning
/// height is attempted.
#[deprecated(note = "This function will be removed in a future release.")]
pub fn get_rewind_height<P>(wdb: &WalletDb<P>) -> Result<Option<BlockHeight>, SqliteClientError> {
wdb.conn
.query_row(
"SELECT MIN(tx.block)
FROM received_notes n
JOIN transactions tx ON tx.id_tx = n.tx
WHERE n.spent IS NULL",
NO_PARAMS,
|row| {
row.get(0)
.map(|maybe_height: Option<u32>| maybe_height.map(|height| height.into()))
},
)
.map_err(SqliteClientError::from)
}
/// Rewinds the database to the given height.
///
/// If the requested height is greater than or equal to the height of the last scanned
/// block, this function does nothing.
///
/// This should only be executed inside a transactional context.
pub fn rewind_to_height<P: consensus::Parameters>(
pub(crate) fn rewind_to_height<P: consensus::Parameters>(
wdb: &WalletDb<P>,
block_height: BlockHeight,
) -> Result<(), SqliteClientError> {
@ -461,19 +567,49 @@ pub fn rewind_to_height<P: consensus::Parameters>(
.or(Ok(sapling_activation_height - 1))
})?;
if block_height < last_scanned_height - PRUNING_HEIGHT {
#[allow(deprecated)]
if let Some(h) = get_rewind_height(&wdb)? {
if block_height > h {
return Err(SqliteClientError::RequestedRewindInvalid(h, block_height));
}
}
}
// nothing to do if we're deleting back down to the max height
if block_height >= last_scanned_height {
Ok(())
} else {
if block_height < last_scanned_height {
// Decrement witnesses.
wdb.conn.execute(
"DELETE FROM sapling_witnesses WHERE block > ?",
&[u32::from(block_height)],
)?;
// Rewind received notes
wdb.conn.execute(
"DELETE FROM received_notes
WHERE id_note IN (
SELECT rn.id_note
FROM received_notes rn
LEFT OUTER JOIN transactions tx
ON tx.id_tx = rn.tx
WHERE tx.block IS NOT NULL AND tx.block > ?
);",
&[u32::from(block_height)],
)?;
// Do not delete sent notes; this can contain data that is not recoverable
// from the chain. Wallets must continue to operate correctly in the
// presence of stale sent notes that link to unmined transactions.
// Rewind utxos
wdb.conn.execute(
"DELETE FROM utxos WHERE height > ?",
&[u32::from(block_height)],
)?;
// Un-mine transactions.
wdb.conn.execute(
"UPDATE transactions SET block = NULL, tx_index = NULL WHERE block > ?",
"UPDATE transactions SET block = NULL, tx_index = NULL WHERE block IS NOT NULL AND block > ?",
&[u32::from(block_height)],
)?;
@ -482,9 +618,9 @@ pub fn rewind_to_height<P: consensus::Parameters>(
"DELETE FROM blocks WHERE height > ?",
&[u32::from(block_height)],
)?;
Ok(())
}
Ok(())
}
/// Returns the commitment tree for the block at the specified height,
@ -504,6 +640,9 @@ pub fn rewind_to_height<P: consensus::Parameters>(
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let tree = get_commitment_tree(&db, H0);
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_commitment_tree instead."
)]
pub fn get_commitment_tree<P>(
wdb: &WalletDb<P>,
block_height: BlockHeight,
@ -544,6 +683,9 @@ pub fn get_commitment_tree<P>(
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let witnesses = get_witnesses(&db, H0);
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_witnesses instead."
)]
pub fn get_witnesses<P>(
wdb: &WalletDb<P>,
block_height: BlockHeight,
@ -567,6 +709,9 @@ pub fn get_witnesses<P>(
/// Retrieve the nullifiers for notes that the wallet is tracking
/// that have not yet been confirmed as a consequence of the spending
/// transaction being included in a block.
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_nullifiers instead."
)]
pub fn get_nullifiers<P>(
wdb: &WalletDb<P>,
) -> Result<Vec<(AccountId, Nullifier)>, SqliteClientError> {
@ -588,7 +733,78 @@ pub fn get_nullifiers<P>(
Ok(res)
}
/// Returns the nullifiers for the notes that this wallet is tracking.
pub(crate) fn get_all_nullifiers<P>(
wdb: &WalletDb<P>,
) -> Result<Vec<(AccountId, Nullifier)>, SqliteClientError> {
// Get the nullifiers for the notes we are tracking
let mut stmt_fetch_nullifiers = wdb.conn.prepare(
"SELECT rn.id_note, rn.account, rn.nf
FROM received_notes rn",
)?;
let nullifiers = stmt_fetch_nullifiers.query_map(NO_PARAMS, |row| {
let account = AccountId(row.get(1)?);
let nf_bytes: Vec<u8> = row.get(2)?;
Ok((account, Nullifier::from_slice(&nf_bytes).unwrap()))
})?;
let res: Vec<_> = nullifiers.collect::<Result<_, _>>()?;
Ok(res)
}
/// Returns unspent transparent outputs that have been received by this wallet at the given
/// transparent address, such that the block that included the transaction was mined at a
/// height less than or equal to the provided `max_height`.
#[cfg(feature = "transparent-inputs")]
pub(crate) fn get_unspent_transparent_outputs<P: consensus::Parameters>(
wdb: &WalletDb<P>,
address: &TransparentAddress,
max_height: BlockHeight,
) -> Result<Vec<WalletTransparentOutput>, SqliteClientError> {
let mut stmt_blocks = wdb.conn.prepare(
"SELECT u.prevout_txid, u.prevout_idx, u.script, u.value_zat, u.height, tx.block as block
FROM utxos u
LEFT OUTER JOIN transactions tx
ON tx.id_tx = u.spent_in_tx
WHERE u.address = ?
AND u.height <= ?
AND block IS NULL",
)?;
let addr_str = address.encode(&wdb.params);
let rows = stmt_blocks.query_map(params![addr_str, u32::from(max_height)], |row| {
let id: Vec<u8> = row.get(0)?;
let mut txid_bytes = [0u8; 32];
txid_bytes.copy_from_slice(&id);
let index: i32 = row.get(1)?;
let script_pubkey = Script(row.get(2)?);
let value = Amount::from_i64(row.get(3)?).unwrap();
let height: u32 = row.get(4)?;
Ok(WalletTransparentOutput {
outpoint: OutPoint::new(txid_bytes, index as u32),
txout: TxOut {
value,
script_pubkey,
},
height: BlockHeight::from(height),
})
})?;
let mut utxos = Vec::<WalletTransparentOutput>::new();
for utxo in rows {
utxos.push(utxo.unwrap())
}
Ok(utxos)
}
/// Inserts information about a scanned block into the database.
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletWrite::advance_by_block instead."
)]
pub fn insert_block<'a, P>(
stmts: &mut DataConnStmtCache<'a, P>,
block_height: BlockHeight,
@ -611,6 +827,9 @@ pub fn insert_block<'a, P>(
/// Inserts information about a mined transaction that was observed to
/// contain a note related to this wallet into the database.
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletWrite::advance_by_block instead."
)]
pub fn put_tx_meta<'a, P, N>(
stmts: &mut DataConnStmtCache<'a, P>,
tx: &WalletTx<N>,
@ -638,6 +857,9 @@ pub fn put_tx_meta<'a, P, N>(
}
/// Inserts full transaction data into the database.
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletWrite::store_decrypted_tx instead."
)]
pub fn put_tx_data<'a, P>(
stmts: &mut DataConnStmtCache<'a, P>,
tx: &Transaction,
@ -676,21 +898,95 @@ pub fn put_tx_data<'a, P>(
///
/// Marking a note spent in this fashion does NOT imply that the
/// spending transaction has been mined.
pub fn mark_spent<'a, P>(
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletWrite::store_sent_tx instead."
)]
pub fn mark_sapling_note_spent<'a, P>(
stmts: &mut DataConnStmtCache<'a, P>,
tx_ref: i64,
nf: &Nullifier,
) -> Result<(), SqliteClientError> {
stmts
.stmt_mark_recived_note_spent
.stmt_mark_sapling_note_spent
.execute(&[tx_ref.to_sql()?, nf.0.to_sql()?])?;
Ok(())
}
/// Marks the given UTXO as having been spent.
#[cfg(feature = "transparent-inputs")]
pub(crate) fn mark_transparent_utxo_spent<'a, P>(
stmts: &mut DataConnStmtCache<'a, P>,
tx_ref: i64,
outpoint: &OutPoint,
) -> Result<(), SqliteClientError> {
let sql_args: &[(&str, &dyn ToSql)] = &[
(&":spent_in_tx", &tx_ref),
(&":prevout_txid", &outpoint.hash().to_vec()),
(&":prevout_idx", &outpoint.n()),
];
stmts
.stmt_mark_transparent_utxo_spent
.execute_named(&sql_args)?;
Ok(())
}
/// Adds the given received UTXO to the datastore.
#[cfg(feature = "transparent-inputs")]
pub(crate) fn put_received_transparent_utxo<'a, P: consensus::Parameters>(
stmts: &mut DataConnStmtCache<'a, P>,
output: &WalletTransparentOutput,
) -> Result<UtxoId, SqliteClientError> {
let sql_args: &[(&str, &dyn ToSql)] = &[
(
&":address",
&output.address().encode(&stmts.wallet_db.params),
),
(&":prevout_txid", &output.outpoint.hash().to_vec()),
(&":prevout_idx", &output.outpoint.n()),
(&":script", &output.txout.script_pubkey.0),
(&":value_zat", &i64::from(output.txout.value)),
(&":height", &u32::from(output.height)),
];
stmts
.stmt_insert_received_transparent_utxo
.execute_named(&sql_args)?;
Ok(UtxoId(stmts.wallet_db.conn.last_insert_rowid()))
}
/// Removes all records of UTXOs that were recorded as having been received
/// at block heights greater than the given height.
#[cfg(feature = "transparent-inputs")]
#[deprecated(
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletWrite::rewind_to_height instead."
)]
pub fn delete_utxos_above<'a, P: consensus::Parameters>(
stmts: &mut DataConnStmtCache<'a, P>,
taddr: &TransparentAddress,
height: BlockHeight,
) -> Result<usize, SqliteClientError> {
let sql_args: &[(&str, &dyn ToSql)] = &[
(&":address", &taddr.encode(&stmts.wallet_db.params)),
(&":above_height", &u32::from(height)),
];
let rows = stmts.stmt_delete_utxos.execute_named(&sql_args)?;
Ok(rows)
}
/// Records the specified shielded output as having been received.
// Assumptions:
// - A transaction will not contain more than 2^63 shielded outputs.
// - A note value will never exceed 2^63 zatoshis.
///
/// This implementation relies on the facts that:
/// - A transaction will not contain more than 2^63 shielded outputs.
/// - A note value will never exceed 2^63 zatoshis.
#[deprecated(
note = "This method will be removed in a future release. Use zcash_client_backend::data_api::WalletWrite::store_decrypted_tx instead."
)]
#[allow(deprecated)]
pub fn put_received_note<'a, P, T: ShieldedOutput>(
stmts: &mut DataConnStmtCache<'a, P>,
output: &T,
@ -740,6 +1036,9 @@ pub fn put_received_note<'a, P, T: ShieldedOutput>(
/// Records the incremental witness for the specified note,
/// as of the given block height.
#[deprecated(
note = "This method will be removed in a future release. Use zcash_client_backend::data_api::WalletWrite::store_decrypted_tx instead."
)]
pub fn insert_witness<'a, P>(
stmts: &mut DataConnStmtCache<'a, P>,
note_id: i64,
@ -757,6 +1056,9 @@ pub fn insert_witness<'a, P>(
}
/// Removes old incremental witnesses up to the given block height.
#[deprecated(
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletWrite::advance_by_block instead."
)]
pub fn prune_witnesses<P>(
stmts: &mut DataConnStmtCache<'_, P>,
below_height: BlockHeight,
@ -769,6 +1071,9 @@ pub fn prune_witnesses<P>(
/// Marks notes that have not been mined in transactions
/// as expired, up to the given block height.
#[deprecated(
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletWrite::advance_by_block instead."
)]
pub fn update_expired_notes<P>(
stmts: &mut DataConnStmtCache<'_, P>,
height: BlockHeight,
@ -778,40 +1083,68 @@ pub fn update_expired_notes<P>(
}
/// Records information about a note that your wallet created.
#[deprecated(
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletWrite::store_decrypted_tx instead."
)]
#[allow(deprecated)]
pub fn put_sent_note<'a, P: consensus::Parameters>(
stmts: &mut DataConnStmtCache<'a, P>,
output: &DecryptedOutput,
tx_ref: i64,
output_index: usize,
account: AccountId,
to: &PaymentAddress,
value: Amount,
memo: Option<&MemoBytes>,
) -> Result<(), SqliteClientError> {
let output_index = output.index as i64;
let account = output.account.0 as i64;
let value = output.note.value as i64;
let to_str = encode_payment_address(
stmts.wallet_db.params.hrp_sapling_payment_address(),
&output.to,
);
let ivalue: i64 = value.into();
// Try updating an existing sent note.
if stmts.stmt_update_sent_note.execute(params![
account,
to_str,
value,
&output.memo.as_slice(),
account.0 as i64,
encode_payment_address_p(&stmts.wallet_db.params, &to),
ivalue,
&memo.map(|m| m.as_slice()),
tx_ref,
output_index
PoolType::Sapling.typecode(),
output_index as i64,
])? == 0
{
// It isn't there, so insert.
insert_sent_note(
stmts,
tx_ref,
output.index,
output.account,
&RecipientAddress::Shielded(output.to.clone()),
Amount::from_u64(output.note.value)
.map_err(|_| SqliteClientError::CorruptedData("Note value invalid.".to_string()))?,
Some(&output.memo),
)?
insert_sent_note(stmts, tx_ref, output_index, account, &to, value, memo)?
}
Ok(())
}
/// Adds information about a sent transparent UTXO to the database if it does not already
/// exist, or updates it if a record for the UTXO already exists.
///
/// `output_index` is the index within transparent UTXOs of the transaction that contains the recipient output.
#[deprecated(
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletWrite::store_decrypted_tx instead."
)]
#[allow(deprecated)]
pub fn put_sent_utxo<'a, P: consensus::Parameters>(
stmts: &mut DataConnStmtCache<'a, P>,
tx_ref: i64,
output_index: usize,
account: AccountId,
to: &TransparentAddress,
value: Amount,
) -> Result<(), SqliteClientError> {
let ivalue: i64 = value.into();
// Try updating an existing sent UTXO.
if stmts.stmt_update_sent_note.execute(params![
account.0 as i64,
encode_transparent_address_p(&stmts.wallet_db.params, &to),
ivalue,
(None::<&[u8]>),
tx_ref,
PoolType::Transparent.typecode(),
output_index as i64,
])? == 0
{
// It isn't there, so insert.
insert_sent_utxo(stmts, tx_ref, output_index, account, &to, value)?
}
Ok(())
@ -825,19 +1158,23 @@ pub fn put_sent_note<'a, P: consensus::Parameters>(
/// transaction.
/// - If `to` is a transparent address, this is an index into the transparent outputs of
/// the transaction.
#[deprecated(
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletWrite::store_sent_tx instead."
)]
pub fn insert_sent_note<'a, P: consensus::Parameters>(
stmts: &mut DataConnStmtCache<'a, P>,
tx_ref: i64,
output_index: usize,
account: AccountId,
to: &RecipientAddress,
to: &PaymentAddress,
value: Amount,
memo: Option<&MemoBytes>,
) -> Result<(), SqliteClientError> {
let to_str = to.encode(&stmts.wallet_db.params);
let to_str = encode_payment_address_p(&stmts.wallet_db.params, to);
let ivalue: i64 = value.into();
stmts.stmt_insert_sent_note.execute(params![
tx_ref,
PoolType::Sapling.typecode(),
(output_index as i64),
account.0,
to_str,
@ -848,22 +1185,45 @@ pub fn insert_sent_note<'a, P: consensus::Parameters>(
Ok(())
}
/// Inserts information about a sent transparent UTXO into the wallet database.
///
/// `output_index` is the index within transparent UTXOs of the transaction that contains the recipient output.
#[deprecated(
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletWrite::store_sent_tx instead."
)]
pub fn insert_sent_utxo<'a, P: consensus::Parameters>(
stmts: &mut DataConnStmtCache<'a, P>,
tx_ref: i64,
output_index: usize,
account: AccountId,
to: &TransparentAddress,
value: Amount,
) -> Result<(), SqliteClientError> {
let to_str = encode_transparent_address_p(&stmts.wallet_db.params, to);
let ivalue: i64 = value.into();
stmts.stmt_insert_sent_note.execute(params![
tx_ref,
PoolType::Transparent.typecode(),
(output_index as i64),
account.0,
to_str,
ivalue,
(None::<&[u8]>),
])?;
Ok(())
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use tempfile::NamedTempFile;
use zcash_primitives::{
transaction::components::Amount,
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
};
use zcash_primitives::transaction::components::Amount;
use zcash_client_backend::data_api::WalletRead;
use crate::{
tests,
wallet::init::{init_accounts_table, init_wallet_db},
AccountId, WalletDb,
};
use crate::{tests, wallet::init::init_wallet_db, AccountId, WalletDb};
use super::{get_address, get_balance};
@ -874,15 +1234,13 @@ mod tests {
init_wallet_db(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvks = [ExtendedFullViewingKey::from(&extsk)];
init_accounts_table(&db_data, &extfvks).unwrap();
tests::init_test_accounts_table(&db_data);
// The account should be empty
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), Amount::zero());
// We can't get an anchor height, as we have not scanned any blocks.
assert_eq!((&db_data).get_target_and_anchor_heights().unwrap(), None);
assert_eq!((&db_data).get_target_and_anchor_heights(10).unwrap(), None);
// An invalid account has zero balance
assert!(get_address(&db_data, AccountId(1)).is_err());

View File

@ -1,19 +1,34 @@
//! Functions for initializing the various databases.
use rusqlite::{types::ToSql, NO_PARAMS};
use rusqlite::{params, types::ToSql, NO_PARAMS};
use zcash_primitives::{
block::BlockHash,
consensus::{self, BlockHeight},
zip32::ExtendedFullViewingKey,
};
use zcash_client_backend::encoding::encode_extended_full_viewing_key;
use zcash_client_backend::{
encoding::encode_extended_full_viewing_key, keys::UnifiedFullViewingKey,
};
use crate::{address_from_extfvk, error::SqliteClientError, WalletDb};
#[cfg(feature = "transparent-inputs")]
use {
zcash_client_backend::encoding::AddressCodec,
zcash_primitives::legacy::keys::IncomingViewingKey,
};
/// Sets up the internal structure of the data database.
///
/// It is safe to use a wallet database created without the ability to create transparent spends
/// with a build that enables transparent spends via use of the `transparent-inputs` feature flag.
/// The reverse is unsafe, as wallet balance calculations would ignore the transparent UTXOs
/// controlled by the wallet. Note that this currently applies only to wallet databases created
/// with the same _version_ of the wallet software; database migration operations currently must
/// be manually performed to update the structure of the database when changing versions.
/// Integrated migration utilities will be provided by a future version of this library.
///
/// # Examples
///
/// ```
@ -32,8 +47,9 @@ pub fn init_wallet_db<P>(wdb: &WalletDb<P>) -> Result<(), rusqlite::Error> {
wdb.conn.execute(
"CREATE TABLE IF NOT EXISTS accounts (
account INTEGER PRIMARY KEY,
extfvk TEXT NOT NULL,
address TEXT NOT NULL
extfvk TEXT,
address TEXT,
transparent_address TEXT
)",
NO_PARAMS,
)?;
@ -95,6 +111,7 @@ pub fn init_wallet_db<P>(wdb: &WalletDb<P>) -> Result<(), rusqlite::Error> {
"CREATE TABLE IF NOT EXISTS sent_notes (
id_note INTEGER PRIMARY KEY,
tx INTEGER NOT NULL,
output_pool INTEGER NOT NULL,
output_index INTEGER NOT NULL,
from_account INTEGER NOT NULL,
address TEXT NOT NULL,
@ -102,28 +119,52 @@ pub fn init_wallet_db<P>(wdb: &WalletDb<P>) -> Result<(), rusqlite::Error> {
memo BLOB,
FOREIGN KEY (tx) REFERENCES transactions(id_tx),
FOREIGN KEY (from_account) REFERENCES accounts(account),
CONSTRAINT tx_output UNIQUE (tx, output_index)
CONSTRAINT tx_output UNIQUE (tx, output_pool, output_index)
)",
NO_PARAMS,
)?;
wdb.conn.execute(
"CREATE TABLE IF NOT EXISTS utxos (
id_utxo INTEGER PRIMARY KEY,
address TEXT NOT NULL,
prevout_txid BLOB NOT NULL,
prevout_idx INTEGER NOT NULL,
script BLOB NOT NULL,
value_zat INTEGER NOT NULL,
height INTEGER NOT NULL,
spent_in_tx INTEGER,
FOREIGN KEY (spent_in_tx) REFERENCES transactions(id_tx),
CONSTRAINT tx_outpoint UNIQUE (prevout_txid, prevout_idx)
)",
NO_PARAMS,
)?;
Ok(())
}
/// Initialises the data database with the given [`ExtendedFullViewingKey`]s.
/// Initialises the data database with the given [`UnifiedFullViewingKey`]s.
///
/// The [`ExtendedFullViewingKey`]s are stored internally and used by other APIs such as
/// The [`UnifiedFullViewingKey`]s are stored internally and used by other APIs such as
/// [`get_address`], [`scan_cached_blocks`], and [`create_spend_to_address`]. `extfvks` **MUST**
/// be arranged in account-order; that is, the [`ExtendedFullViewingKey`] for ZIP 32
/// be arranged in account-order; that is, the [`UnifiedFullViewingKey`] for ZIP 32
/// account `i` **MUST** be at `extfvks[i]`.
///
/// # Examples
///
/// ```
/// # #[cfg(feature = "transparent-inputs")]
/// # {
/// use tempfile::NamedTempFile;
///
/// use zcash_primitives::{
/// consensus::Network,
/// zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}
/// consensus::{Network, Parameters},
/// zip32::{AccountId, ExtendedFullViewingKey, ExtendedSpendingKey}
/// };
///
/// use zcash_client_backend::{
/// keys::{
/// sapling,
/// UnifiedFullViewingKey
/// },
/// };
///
/// use zcash_client_sqlite::{
@ -135,9 +176,13 @@ pub fn init_wallet_db<P>(wdb: &WalletDb<P>) -> Result<(), rusqlite::Error> {
/// let db_data = WalletDb::for_path(data_file.path(), Network::TestNetwork).unwrap();
/// init_wallet_db(&db_data).unwrap();
///
/// let extsk = ExtendedSpendingKey::master(&[]);
/// let extfvks = [ExtendedFullViewingKey::from(&extsk)];
/// init_accounts_table(&db_data, &extfvks).unwrap();
/// let seed = [0u8; 32]; // insecure; replace with a strong random seed
/// let account = AccountId(0);
/// let extsk = sapling::spending_key(&seed, Network::TestNetwork.coin_type(), account);
/// let extfvk = ExtendedFullViewingKey::from(&extsk);
/// let ufvk = UnifiedFullViewingKey::new(account, None, Some(extfvk)).unwrap();
/// init_accounts_table(&db_data, &[ufvk]).unwrap();
/// # }
/// ```
///
/// [`get_address`]: crate::wallet::get_address
@ -145,7 +190,7 @@ pub fn init_wallet_db<P>(wdb: &WalletDb<P>) -> Result<(), rusqlite::Error> {
/// [`create_spend_to_address`]: zcash_client_backend::data_api::wallet::create_spend_to_address
pub fn init_accounts_table<P: consensus::Parameters>(
wdb: &WalletDb<P>,
extfvks: &[ExtendedFullViewingKey],
keys: &[UnifiedFullViewingKey],
) -> Result<(), SqliteClientError> {
let mut empty_check = wdb.conn.prepare("SELECT * FROM accounts LIMIT 1")?;
if empty_check.exists(NO_PARAMS)? {
@ -154,22 +199,30 @@ pub fn init_accounts_table<P: consensus::Parameters>(
// Insert accounts atomically
wdb.conn.execute("BEGIN IMMEDIATE", NO_PARAMS)?;
for (account, extfvk) in extfvks.iter().enumerate() {
let extfvk_str = encode_extended_full_viewing_key(
wdb.params.hrp_sapling_extended_full_viewing_key(),
extfvk,
);
for key in keys.iter() {
let extfvk_str: Option<String> = key.sapling().map(|extfvk| {
encode_extended_full_viewing_key(
wdb.params.hrp_sapling_extended_full_viewing_key(),
extfvk,
)
});
let address_str = address_from_extfvk(&wdb.params, extfvk);
let address_str: Option<String> = key
.sapling()
.map(|extfvk| address_from_extfvk(&wdb.params, extfvk));
#[cfg(feature = "transparent-inputs")]
let taddress_str: Option<String> = key.transparent().and_then(|k| {
k.derive_external_ivk()
.ok()
.map(|k| k.default_address().0.encode(&wdb.params))
});
#[cfg(not(feature = "transparent-inputs"))]
let taddress_str: Option<String> = None;
wdb.conn.execute(
"INSERT INTO accounts (account, extfvk, address)
VALUES (?, ?, ?)",
&[
(account as u32).to_sql()?,
extfvk_str.to_sql()?,
address_str.to_sql()?,
],
"INSERT INTO accounts (account, extfvk, address, transparent_address)
VALUES (?, ?, ?, ?)",
params![key.account().0, extfvk_str, address_str, taddress_str,],
)?;
}
wdb.conn.execute("COMMIT", NO_PARAMS)?;
@ -236,16 +289,26 @@ pub fn init_blocks_table<P>(
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use tempfile::NamedTempFile;
use zcash_client_backend::keys::{sapling, UnifiedFullViewingKey, UnifiedSpendingKey};
#[cfg(feature = "transparent-inputs")]
use zcash_primitives::legacy::keys as transparent;
use zcash_primitives::{
block::BlockHash,
consensus::BlockHeight,
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
consensus::{BlockHeight, Parameters},
zip32::ExtendedFullViewingKey,
};
use crate::{tests, wallet::get_address, AccountId, WalletDb};
use crate::{
tests::{self, network},
wallet::get_address,
AccountId, WalletDb,
};
use super::{init_accounts_table, init_blocks_table, init_wallet_db};
@ -259,15 +322,33 @@ mod tests {
init_accounts_table(&db_data, &[]).unwrap();
init_accounts_table(&db_data, &[]).unwrap();
let seed = [0u8; 32];
let account = AccountId(0);
// First call with data should initialise the accounts table
let extfvks = [ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(
&[],
))];
init_accounts_table(&db_data, &extfvks).unwrap();
let extsk = sapling::spending_key(&seed, network().coin_type(), account);
let extfvk = ExtendedFullViewingKey::from(&extsk);
#[cfg(feature = "transparent-inputs")]
let ufvk = UnifiedFullViewingKey::new(
account,
Some(
transparent::AccountPrivKey::from_seed(&network(), &seed, account)
.unwrap()
.to_account_pubkey(),
),
Some(extfvk),
)
.unwrap();
#[cfg(not(feature = "transparent-inputs"))]
let ufvk = UnifiedFullViewingKey::new(account, Some(extfvk)).unwrap();
init_accounts_table(&db_data, &[ufvk.clone()]).unwrap();
// Subsequent calls should return an error
init_accounts_table(&db_data, &[]).unwrap_err();
init_accounts_table(&db_data, &extfvks).unwrap_err();
init_accounts_table(&db_data, &[ufvk]).unwrap_err();
}
#[test]
@ -303,13 +384,17 @@ mod tests {
let db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
init_wallet_db(&db_data).unwrap();
let seed = [0u8; 32];
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvks = [ExtendedFullViewingKey::from(&extsk)];
init_accounts_table(&db_data, &extfvks).unwrap();
let account_id = AccountId(0);
let usk = UnifiedSpendingKey::from_seed(&tests::network(), &seed, account_id).unwrap();
let ufvk = usk.to_unified_full_viewing_key();
let expected_address = ufvk.sapling().unwrap().default_address().1;
init_accounts_table(&db_data, &[ufvk]).unwrap();
// The account's address should be in the data DB
let pa = get_address(&db_data, AccountId(0)).unwrap();
assert_eq!(pa.unwrap(), extsk.default_address().1);
assert_eq!(pa.unwrap(), expected_address);
}
}

View File

@ -10,9 +10,10 @@ use zcash_primitives::{
merkle_tree::IncrementalWitness,
sapling::{Diversifier, Rseed},
transaction::components::Amount,
zip32::AccountId,
};
use zcash_client_backend::wallet::{AccountId, SpendableNote};
use zcash_client_backend::wallet::SpendableNote;
use crate::{error::SqliteClientError, WalletDb};
@ -59,7 +60,10 @@ fn to_spendable_note(row: &Row) -> Result<SpendableNote, SqliteClientError> {
})
}
pub fn get_spendable_notes<P>(
#[deprecated(
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletRead::get_spendable_sapling_notes instead."
)]
pub fn get_spendable_sapling_notes<P>(
wdb: &WalletDb<P>,
account: AccountId,
anchor_height: BlockHeight,
@ -87,7 +91,10 @@ pub fn get_spendable_notes<P>(
notes.collect::<Result<_, _>>()
}
pub fn select_spendable_notes<P>(
#[deprecated(
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletRead::select_spendable_sapling_notes instead."
)]
pub fn select_spendable_sapling_notes<P>(
wdb: &WalletDb<P>,
account: AccountId,
target_value: Amount,
@ -147,29 +154,34 @@ pub fn select_spendable_notes<P>(
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use rusqlite::Connection;
use tempfile::NamedTempFile;
use zcash_proofs::prover::LocalTxProver;
use zcash_primitives::{
block::BlockHash,
consensus::{BlockHeight, BranchId},
consensus::{BlockHeight, BranchId, Parameters},
legacy::TransparentAddress,
sapling::{note_encryption::try_sapling_output_recovery, prover::TxProver},
transaction::{components::Amount, Transaction},
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
};
use zcash_proofs::prover::LocalTxProver;
#[cfg(feature = "transparent-inputs")]
use zcash_primitives::legacy::keys as transparent;
use zcash_client_backend::{
data_api::{chain::scan_cached_blocks, wallet::create_spend_to_address, WalletRead},
keys::{sapling, UnifiedFullViewingKey},
wallet::OvkPolicy,
};
use crate::{
chain::init::init_cache_database,
tests::{self, fake_compact_block, insert_into_cache, sapling_activation_height},
tests::{self, fake_compact_block, insert_into_cache, network, sapling_activation_height},
wallet::{
get_balance, get_balance_at,
init::{init_accounts_table, init_blocks_table, init_wallet_db},
@ -193,13 +205,39 @@ mod tests {
init_wallet_db(&db_data).unwrap();
// Add two accounts to the wallet
let extsk0 = ExtendedSpendingKey::master(&[]);
let extsk1 = ExtendedSpendingKey::master(&[0]);
let extfvks = [
ExtendedFullViewingKey::from(&extsk0),
ExtendedFullViewingKey::from(&extsk1),
let extsk0 = sapling::spending_key(&[0u8; 32], network().coin_type(), AccountId(0));
let extsk1 = sapling::spending_key(&[1u8; 32], network().coin_type(), AccountId(1));
let extfvk0 = ExtendedFullViewingKey::from(&extsk0);
let extfvk1 = ExtendedFullViewingKey::from(&extsk1);
#[cfg(feature = "transparent-inputs")]
let ufvks = {
let tsk0 = transparent::AccountPrivKey::from_seed(&network(), &[0u8; 32], AccountId(0))
.unwrap();
let tsk1 = transparent::AccountPrivKey::from_seed(&network(), &[1u8; 32], AccountId(1))
.unwrap();
[
UnifiedFullViewingKey::new(
AccountId(0),
Some(tsk0.to_account_pubkey()),
Some(extfvk0),
)
.unwrap(),
UnifiedFullViewingKey::new(
AccountId(1),
Some(tsk1.to_account_pubkey()),
Some(extfvk1),
)
.unwrap(),
]
};
#[cfg(not(feature = "transparent-inputs"))]
let ufvks = [
UnifiedFullViewingKey::new(AccountId(0), Some(extfvk0)).unwrap(),
UnifiedFullViewingKey::new(AccountId(1), Some(extfvk1)).unwrap(),
];
init_accounts_table(&db_data, &extfvks).unwrap();
init_accounts_table(&db_data, &ufvks).unwrap();
let to = extsk0.default_address().1.into();
// Invalid extsk for the given account should cause an error
@ -214,6 +252,7 @@ mod tests {
Amount::from_u64(1).unwrap(),
None,
OvkPolicy::Sender,
10,
) {
Ok(_) => panic!("Should have failed"),
Err(e) => assert_eq!(e.to_string(), "Incorrect ExtendedSpendingKey for account 0"),
@ -229,6 +268,7 @@ mod tests {
Amount::from_u64(1).unwrap(),
None,
OvkPolicy::Sender,
10,
) {
Ok(_) => panic!("Should have failed"),
Err(e) => assert_eq!(e.to_string(), "Incorrect ExtendedSpendingKey for account 1"),
@ -242,9 +282,14 @@ mod tests {
init_wallet_db(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvks = [ExtendedFullViewingKey::from(&extsk)];
init_accounts_table(&db_data, &extfvks).unwrap();
let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), AccountId(0));
let extfvk = ExtendedFullViewingKey::from(&extsk);
#[cfg(feature = "transparent-inputs")]
let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk)).unwrap();
#[cfg(not(feature = "transparent-inputs"))]
let ufvk = UnifiedFullViewingKey::new(AccountId(0), Some(extfvk)).unwrap();
init_accounts_table(&db_data, &[ufvk]).unwrap();
let to = extsk.default_address().1.into();
// We cannot do anything if we aren't synchronised
@ -259,6 +304,7 @@ mod tests {
Amount::from_u64(1).unwrap(),
None,
OvkPolicy::Sender,
10,
) {
Ok(_) => panic!("Should have failed"),
Err(e) => assert_eq!(e.to_string(), "Must scan blocks first"),
@ -280,9 +326,13 @@ mod tests {
.unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvks = [ExtendedFullViewingKey::from(&extsk)];
init_accounts_table(&db_data, &extfvks).unwrap();
let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), AccountId(0));
let extfvk = ExtendedFullViewingKey::from(&extsk);
#[cfg(feature = "transparent-inputs")]
let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk)).unwrap();
#[cfg(not(feature = "transparent-inputs"))]
let ufvk = UnifiedFullViewingKey::new(AccountId(0), Some(extfvk)).unwrap();
init_accounts_table(&db_data, &[ufvk]).unwrap();
let to = extsk.default_address().1.into();
// Account balance should be zero
@ -300,6 +350,7 @@ mod tests {
Amount::from_u64(1).unwrap(),
None,
OvkPolicy::Sender,
10,
) {
Ok(_) => panic!("Should have failed"),
Err(e) => assert_eq!(
@ -320,9 +371,13 @@ mod tests {
init_wallet_db(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), AccountId(0));
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap();
#[cfg(feature = "transparent-inputs")]
let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk.clone())).unwrap();
#[cfg(not(feature = "transparent-inputs"))]
let ufvk = UnifiedFullViewingKey::new(AccountId(0), Some(extfvk.clone())).unwrap();
init_accounts_table(&db_data, &[ufvk]).unwrap();
// Add funds to the wallet in a single note
let value = Amount::from_u64(50000).unwrap();
@ -337,7 +392,10 @@ mod tests {
scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap();
// Verified balance matches total balance
let (_, anchor_height) = (&db_data).get_target_and_anchor_heights().unwrap().unwrap();
let (_, anchor_height) = (&db_data)
.get_target_and_anchor_heights(10)
.unwrap()
.unwrap();
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), value);
assert_eq!(
get_balance_at(&db_data, AccountId(0), anchor_height).unwrap(),
@ -355,7 +413,10 @@ mod tests {
scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap();
// Verified balance does not include the second note
let (_, anchor_height2) = (&db_data).get_target_and_anchor_heights().unwrap().unwrap();
let (_, anchor_height2) = (&db_data)
.get_target_and_anchor_heights(10)
.unwrap()
.unwrap();
assert_eq!(
get_balance(&db_data, AccountId(0)).unwrap(),
(value + value).unwrap()
@ -378,6 +439,7 @@ mod tests {
Amount::from_u64(70000).unwrap(),
None,
OvkPolicy::Sender,
10,
) {
Ok(_) => panic!("Should have failed"),
Err(e) => assert_eq!(
@ -410,6 +472,7 @@ mod tests {
Amount::from_u64(70000).unwrap(),
None,
OvkPolicy::Sender,
10,
) {
Ok(_) => panic!("Should have failed"),
Err(e) => assert_eq!(
@ -435,6 +498,7 @@ mod tests {
Amount::from_u64(70000).unwrap(),
None,
OvkPolicy::Sender,
10,
)
.unwrap();
}
@ -450,9 +514,13 @@ mod tests {
init_wallet_db(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), AccountId(0));
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap();
#[cfg(feature = "transparent-inputs")]
let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk.clone())).unwrap();
#[cfg(not(feature = "transparent-inputs"))]
let ufvk = UnifiedFullViewingKey::new(AccountId(0), Some(extfvk.clone())).unwrap();
init_accounts_table(&db_data, &[ufvk]).unwrap();
// Add funds to the wallet in a single note
let value = Amount::from_u64(50000).unwrap();
@ -480,6 +548,7 @@ mod tests {
Amount::from_u64(15000).unwrap(),
None,
OvkPolicy::Sender,
10,
)
.unwrap();
@ -494,6 +563,7 @@ mod tests {
Amount::from_u64(2000).unwrap(),
None,
OvkPolicy::Sender,
10,
) {
Ok(_) => panic!("Should have failed"),
Err(e) => assert_eq!(
@ -526,6 +596,7 @@ mod tests {
Amount::from_u64(2000).unwrap(),
None,
OvkPolicy::Sender,
10,
) {
Ok(_) => panic!("Should have failed"),
Err(e) => assert_eq!(
@ -555,6 +626,7 @@ mod tests {
Amount::from_u64(2000).unwrap(),
None,
OvkPolicy::Sender,
10,
)
.unwrap();
}
@ -571,9 +643,13 @@ mod tests {
init_wallet_db(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extsk = sapling::spending_key(&[0u8; 32], network.coin_type(), AccountId(0));
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap();
#[cfg(feature = "transparent-inputs")]
let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk.clone())).unwrap();
#[cfg(not(feature = "transparent-inputs"))]
let ufvk = UnifiedFullViewingKey::new(AccountId(0), Some(extfvk.clone())).unwrap();
init_accounts_table(&db_data, &[ufvk]).unwrap();
// Add funds to the wallet in a single note
let value = Amount::from_u64(50000).unwrap();
@ -603,6 +679,7 @@ mod tests {
Amount::from_u64(15000).unwrap(),
None,
ovk_policy,
10,
)
.unwrap();
@ -676,9 +753,13 @@ mod tests {
init_wallet_db(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), AccountId(0));
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap();
#[cfg(feature = "transparent-inputs")]
let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk.clone())).unwrap();
#[cfg(not(feature = "transparent-inputs"))]
let ufvk = UnifiedFullViewingKey::new(AccountId(0), Some(extfvk.clone())).unwrap();
init_accounts_table(&db_data, &[ufvk]).unwrap();
// Add funds to the wallet in a single note
let value = Amount::from_u64(51000).unwrap();
@ -693,7 +774,10 @@ mod tests {
scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap();
// Verified balance matches total balance
let (_, anchor_height) = (&db_data).get_target_and_anchor_heights().unwrap().unwrap();
let (_, anchor_height) = (&db_data)
.get_target_and_anchor_heights(10)
.unwrap()
.unwrap();
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), value);
assert_eq!(
get_balance_at(&db_data, AccountId(0), anchor_height).unwrap(),
@ -711,6 +795,7 @@ mod tests {
Amount::from_u64(50000).unwrap(),
None,
OvkPolicy::Sender,
10,
)
.unwrap();
}

View File

@ -82,12 +82,21 @@ and this library adheres to Rust's notion of
corresponding to the associated full viewing key as specified in
[ZIP 32](https://zips.z.cash/zip-0032#deriving-a-sapling-internal-spending-key).
- `zcash_primitives::zip32::sapling_derive_internal_fvk` provides the
internal implementation of `ExtendedFullViewingKey.derive_internal`
but does not require a complete extended full viewing key, just
the full viewing key and the diversifier key. In the future, this
function will likely be refactored to become a member function of
a new `DiversifiableFullViewingKey` type, which represents the ability
to derive IVKs, OVKs, and addresses, but not child viewing keys.
internal implementation of `ExtendedFullViewingKey.derive_internal` but does
not require a complete extended full viewing key, just the full viewing key
and the diversifier key. In the future, this function will likely be
refactored to become a member function of a new `DiversifiableFullViewingKey`
type, which represents the ability to derive IVKs, OVKs, and addresses, but
not child viewing keys.
- A new module `zcash_primitives::legacy::keys` has been added under the
`transparent-inputs` feature flag to support types related to supporting
transparent components of unified addresses and derivation of OVKs for
shielding funds from the transparent pool.
- A `zcash_primitives::transaction::components::amount::Amount::sum`
convenience method has been added to facilitate bounds-checked summation of
account values.
- The `zcash_primitives::zip32::AccountId`, a type-safe wrapper for ZIP 32
account indices.
### Changed
- MSRV is now 1.51.0.
@ -96,6 +105,9 @@ and this library adheres to Rust's notion of
`zcash_primitives::sapling`:
- `zcash_primitives::group_hash`
- `zcash_primitives::keys`
- `zcash_primitives::sapling::keys::{prf_expand, prf_expand_vec, OutgoingViewingKey}`
have all been moved into to the this module to reflect the fact that they
are used outside of the Sapling protocol.
- `zcash_primitives::pedersen_hash`
- `zcash_primitives::primitives::*` (moved into `zcash_primitives::sapling`)
- `zcash_primitives::prover`
@ -137,6 +149,10 @@ and this library adheres to Rust's notion of
`jubjub::ExtendedPoint` to `zcash_note_encryption::EphemeralKeyBytes`.
- The `epk: jubjub::ExtendedPoint` field of `CompactOutputDescription ` has been
replaced by `ephemeral_key: zcash_note_encryption::EphemeralKeyBytes`.
- The `zcash_primitives::transaction::Builder::add_sapling_output` method
now takes its `MemoBytes` argument as a required field rather than an
optional one. If the empty memo is desired, use
`MemoBytes::from(Memo::Empty)` explicitly.
## [0.5.0] - 2021-03-26
### Added

View File

@ -22,12 +22,14 @@ bip0039 = { version = "0.9", features = ["std", "all-languages"] }
blake2b_simd = "1"
blake2s_simd = "1"
bls12_381 = "0.6"
bs58 = { version = "0.4", features = ["check"], optional = true }
byteorder = "1"
chacha20poly1305 = "0.9"
equihash = { version = "0.1", path = "../components/equihash" }
ff = "0.11"
fpe = "0.5"
group = "0.11"
hdwallet = { version = "0.3.0", optional = true }
hex = "0.4"
incrementalmerkletree = "0.2"
jubjub = "0.8"
@ -38,7 +40,7 @@ orchard = "=0.1.0-beta.1"
proptest = { version = "1.0.0", optional = true }
rand = "0.8"
rand_core = "0.6"
ripemd160 = { version = "0.9", optional = true }
ripemd = { version = "0.1", optional = true }
secp256k1 = { version = "0.20", optional = true }
sha2 = "0.9"
subtle = "2.2.3"
@ -59,8 +61,8 @@ orchard = { version = "=0.1.0-beta.1", features = ["test-dependencies"] }
pprof = { version = "=0.6.1", features = ["criterion", "flamegraph"] }
[features]
transparent-inputs = ["ripemd160", "secp256k1"]
test-dependencies = ["proptest"]
transparent-inputs = ["bs58", "hdwallet", "ripemd", "secp256k1"]
test-dependencies = ["proptest", "orchard/test-dependencies"]
zfuture = []
[lib]

View File

@ -0,0 +1,24 @@
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
pub const PRF_EXPAND_PERSONALIZATION: &[u8; 16] = b"Zcash_ExpandSeed";
/// PRF^expand(sk, t) := BLAKE2b-512("Zcash_ExpandSeed", sk || t)
pub fn prf_expand(sk: &[u8], t: &[u8]) -> Blake2bHash {
prf_expand_vec(sk, &[t])
}
pub fn prf_expand_vec(sk: &[u8], ts: &[&[u8]]) -> Blake2bHash {
let mut h = Blake2bParams::new()
.hash_length(64)
.personal(PRF_EXPAND_PERSONALIZATION)
.to_state();
h.update(sk);
for t in ts {
h.update(t);
}
h.finalize()
}
/// An outgoing viewing key
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct OutgoingViewingKey(pub [u8; 32]);

View File

@ -6,6 +6,9 @@ use std::ops::Shl;
use zcash_encoding::Vector;
#[cfg(feature = "transparent-inputs")]
pub mod keys;
/// Minimal subset of script opcodes.
enum OpCode {
// push value

View File

@ -0,0 +1,505 @@
use hdwallet::{ExtendedPrivKey, ExtendedPubKey, KeyIndex};
use ripemd::Digest as RipemdDigest;
use secp256k1::PublicKey;
use sha2::{Digest as Sha2Digest, Sha256};
use std::convert::TryInto;
use crate::{consensus, keys::prf_expand_vec, zip32::AccountId};
use super::TransparentAddress;
const MAX_TRANSPARENT_CHILD_INDEX: u32 = 0x7FFFFFFF;
/// A type representing a BIP-44 private key at the account path level
/// `m/44'/<coin_type>'/<account>'
#[derive(Clone, Debug)]
pub struct AccountPrivKey(ExtendedPrivKey);
impl AccountPrivKey {
/// Performs derivation of the extended private key for the BIP-44 path:
/// `m/44'/<coin_type>'/<account>'`.
///
/// This produces the root of the derivation tree for transparent
/// viewing keys and addresses for the for the provided account.
pub fn from_seed<P: consensus::Parameters>(
params: &P,
seed: &[u8],
account: AccountId,
) -> Result<AccountPrivKey, hdwallet::error::Error> {
ExtendedPrivKey::with_seed(&seed)?
.derive_private_key(KeyIndex::hardened_from_normalize_index(44)?)?
.derive_private_key(KeyIndex::hardened_from_normalize_index(params.coin_type())?)?
.derive_private_key(KeyIndex::hardened_from_normalize_index(account.0)?)
.map(AccountPrivKey)
}
pub fn from_extended_privkey(extprivkey: ExtendedPrivKey) -> Self {
AccountPrivKey(extprivkey)
}
pub fn to_account_pubkey(&self) -> AccountPubKey {
AccountPubKey(ExtendedPubKey::from_private_key(&self.0))
}
/// Derives the BIP-44 private spending key for the external (incoming payment) child path
/// `m/44'/<coin_type>'/<account>'/0/<child_index>`.
pub fn derive_external_secret_key(
&self,
child_index: u32,
) -> Result<secp256k1::key::SecretKey, hdwallet::error::Error> {
self.0
.derive_private_key(KeyIndex::Normal(0))?
.derive_private_key(KeyIndex::Normal(child_index))
.map(|k| k.private_key)
}
/// Derives the BIP-44 private spending key for the internal (change) child path
/// `m/44'/<coin_type>'/<account>'/1/<child_index>`.
pub fn derive_internal_secret_key(
&self,
child_index: u32,
) -> Result<secp256k1::key::SecretKey, hdwallet::error::Error> {
self.0
.derive_private_key(KeyIndex::Normal(1))?
.derive_private_key(KeyIndex::Normal(child_index))
.map(|k| k.private_key)
}
}
/// A type representing a BIP-44 public key at the account path level
/// `m/44'/<coin_type>'/<account>'`.
///
/// This provides the necessary derivation capability for the transparent component of a unified
/// full viewing key.
#[derive(Clone, Debug)]
pub struct AccountPubKey(ExtendedPubKey);
impl AccountPubKey {
/// Derives the BIP-44 public key at the external "change level" path
/// `m/44'/<coin_type>'/<account>'/0`.
pub fn derive_external_ivk(&self) -> Result<ExternalIvk, hdwallet::error::Error> {
self.0
.derive_public_key(KeyIndex::Normal(0))
.map(ExternalIvk)
}
/// Derives the BIP-44 public key at the internal "change level" path
/// `m/44'/<coin_type>'/<account>'/1`.
pub fn derive_internal_ivk(&self) -> Result<InternalIvk, hdwallet::error::Error> {
self.0
.derive_public_key(KeyIndex::Normal(1))
.map(InternalIvk)
}
/// Derives the internal ovk and external ovk corresponding to this
/// transparent fvk. As specified in [ZIP 316][transparent-ovk].
///
/// [transparent-ovk]: https://zips.z.cash/zip-0316#deriving-internal-keys
pub fn ovks_for_shielding(&self) -> (InternalOvk, ExternalOvk) {
let i_ovk = prf_expand_vec(
&self.0.chain_code,
&[&[0xd0], &self.0.public_key.serialize()],
);
let i_ovk = i_ovk.as_bytes();
let ovk_external = ExternalOvk(i_ovk[..32].try_into().unwrap());
let ovk_internal = InternalOvk(i_ovk[32..].try_into().unwrap());
(ovk_internal, ovk_external)
}
/// Derives the internal ovk corresponding to this transparent fvk.
pub fn internal_ovk(&self) -> InternalOvk {
self.ovks_for_shielding().0
}
/// Derives the external ovk corresponding to this transparent fvk.
pub fn external_ovk(&self) -> ExternalOvk {
self.ovks_for_shielding().1
}
pub fn serialize(&self) -> Vec<u8> {
let mut buf = self.0.chain_code.clone();
buf.extend(self.0.public_key.serialize().to_vec());
buf
}
pub fn deserialize(data: &[u8; 65]) -> Result<Self, hdwallet::error::Error> {
let chain_code = data[..32].to_vec();
let public_key = PublicKey::from_slice(&data[32..])?;
Ok(AccountPubKey(ExtendedPubKey {
public_key,
chain_code,
}))
}
}
/// Derives the P2PKH transparent address corresponding to the given pubkey.
#[deprecated(note = "This function will be removed from the public API in an upcoming refactor.")]
pub fn pubkey_to_address(pubkey: &secp256k1::key::PublicKey) -> TransparentAddress {
TransparentAddress::PublicKey(
*ripemd::Ripemd160::digest(Sha256::digest(&pubkey.serialize())).as_ref(),
)
}
pub(crate) mod private {
use hdwallet::ExtendedPubKey;
pub trait SealedChangeLevelKey {
fn extended_pubkey(&self) -> &ExtendedPubKey;
fn from_extended_pubkey(key: ExtendedPubKey) -> Self;
}
}
pub trait IncomingViewingKey: private::SealedChangeLevelKey + std::marker::Sized {
/// Derives a transparent address at the provided child index.
#[allow(deprecated)]
fn derive_address(
&self,
child_index: u32,
) -> Result<TransparentAddress, hdwallet::error::Error> {
let child_key = self
.extended_pubkey()
.derive_public_key(KeyIndex::Normal(child_index))?;
Ok(pubkey_to_address(&child_key.public_key))
}
/// Searches the space of child indexes for an index that will
/// generate a valid transparent address, and returns the resulting
/// address and the index at which it was generated.
fn default_address(&self) -> (TransparentAddress, u32) {
let mut child_index = 0;
while child_index <= MAX_TRANSPARENT_CHILD_INDEX {
match self.derive_address(child_index) {
Ok(addr) => {
return (addr, child_index);
}
Err(_) => {
child_index += 1;
}
}
}
panic!("Exhausted child index space attempting to find a default address.");
}
fn serialize(&self) -> Vec<u8> {
let extpubkey = self.extended_pubkey();
let mut buf = extpubkey.chain_code.clone();
buf.extend(extpubkey.public_key.serialize().to_vec());
buf
}
fn deserialize(data: &[u8; 65]) -> Result<Self, hdwallet::error::Error> {
let chain_code = data[..32].to_vec();
let public_key = PublicKey::from_slice(&data[32..])?;
Ok(Self::from_extended_pubkey(ExtendedPubKey {
public_key,
chain_code,
}))
}
}
/// A type representing an incoming viewing key at the BIP-44 "external"
/// path `m/44'/<coin_type>'/<account>'/0`. This allows derivation
/// of child addresses that may be provided to external parties.
#[derive(Clone, Debug)]
pub struct ExternalIvk(ExtendedPubKey);
impl private::SealedChangeLevelKey for ExternalIvk {
fn extended_pubkey(&self) -> &ExtendedPubKey {
&self.0
}
fn from_extended_pubkey(key: ExtendedPubKey) -> Self {
ExternalIvk(key)
}
}
impl IncomingViewingKey for ExternalIvk {}
/// A type representing an incoming viewing key at the BIP-44 "internal"
/// path `m/44'/<coin_type>'/<account>'/1`. This allows derivation
/// of change addresses for use within the wallet, but which should
/// not be shared with external parties.
#[derive(Clone, Debug)]
pub struct InternalIvk(ExtendedPubKey);
impl private::SealedChangeLevelKey for InternalIvk {
fn extended_pubkey(&self) -> &ExtendedPubKey {
&self.0
}
fn from_extended_pubkey(key: ExtendedPubKey) -> Self {
InternalIvk(key)
}
}
impl IncomingViewingKey for InternalIvk {}
/// Internal ovk used for autoshielding.
pub struct InternalOvk([u8; 32]);
impl InternalOvk {
pub fn as_bytes(&self) -> [u8; 32] {
self.0
}
}
/// External ovk used by zcashd for transparent -> shielded spends to
/// external receivers.
pub struct ExternalOvk([u8; 32]);
impl ExternalOvk {
pub fn as_bytes(&self) -> [u8; 32] {
self.0
}
}
#[cfg(test)]
mod tests {
use super::AccountPubKey;
#[test]
fn check_ovk_test_vectors() {
struct TestVector {
c: [u8; 32],
pk: [u8; 33],
external_ovk: [u8; 32],
internal_ovk: [u8; 32],
}
// From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/zip_0316.py
let test_vectors = vec![
TestVector {
c: [
0x5d, 0x7a, 0x8f, 0x73, 0x9a, 0x2d, 0x9e, 0x94, 0x5b, 0x0c, 0xe1, 0x52, 0xa8,
0x04, 0x9e, 0x29, 0x4c, 0x4d, 0x6e, 0x66, 0xb1, 0x64, 0x93, 0x9d, 0xaf, 0xfa,
0x2e, 0xf6, 0xee, 0x69, 0x21, 0x48,
],
pk: [
0x02, 0x16, 0x88, 0x4f, 0x1d, 0xbc, 0x92, 0x90, 0x89, 0xa4, 0x17, 0x6e, 0x84,
0x0b, 0xb5, 0x81, 0xc8, 0x0e, 0x16, 0xe9, 0xb1, 0xab, 0xd6, 0x54, 0xe6, 0x2c,
0x8b, 0x0b, 0x95, 0x70, 0x20, 0xb7, 0x48,
],
external_ovk: [
0xdc, 0xe7, 0xfb, 0x7f, 0x20, 0xeb, 0x77, 0x64, 0xd5, 0x12, 0x4f, 0xbd, 0x23,
0xc4, 0xd7, 0xca, 0x8c, 0x32, 0x19, 0xec, 0x1d, 0xb3, 0xff, 0x1e, 0x08, 0x13,
0x50, 0xad, 0x03, 0x9b, 0x40, 0x79,
],
internal_ovk: [
0x4d, 0x46, 0xc7, 0x14, 0xed, 0xda, 0xd9, 0x4a, 0x40, 0xac, 0x21, 0x28, 0x6a,
0xff, 0x32, 0x7d, 0x7e, 0xbf, 0x11, 0x9e, 0x86, 0x85, 0x10, 0x9b, 0x44, 0xe8,
0x02, 0x83, 0xd8, 0xc8, 0xa4, 0x00,
],
},
TestVector {
c: [
0xbf, 0x69, 0xb8, 0x25, 0x0c, 0x18, 0xef, 0x41, 0x29, 0x4c, 0xa9, 0x79, 0x93,
0xdb, 0x54, 0x6c, 0x1f, 0xe0, 0x1f, 0x7e, 0x9c, 0x8e, 0x36, 0xd6, 0xa5, 0xe2,
0x9d, 0x4e, 0x30, 0xa7, 0x35, 0x94,
],
pk: [
0x03, 0x72, 0x73, 0xb6, 0x57, 0xd9, 0x71, 0xa4, 0x5e, 0x72, 0x24, 0x0c, 0x7a,
0xaa, 0xa7, 0xd0, 0x68, 0x5d, 0x06, 0xd7, 0x99, 0x9b, 0x0a, 0x19, 0xc4, 0xce,
0xa3, 0x27, 0x88, 0xa6, 0xab, 0x51, 0x3d,
],
external_ovk: [
0x8d, 0x31, 0x53, 0x7b, 0x38, 0x8f, 0x40, 0x23, 0xe6, 0x48, 0x70, 0x8b, 0xfb,
0xde, 0x2b, 0xa1, 0xff, 0x1a, 0x4e, 0xe1, 0x12, 0xea, 0x67, 0x0a, 0xd1, 0x67,
0x44, 0xf4, 0x58, 0x3e, 0x95, 0x52,
],
internal_ovk: [
0x16, 0x77, 0x49, 0x00, 0x76, 0x9d, 0x9c, 0x03, 0xbe, 0x06, 0x32, 0x45, 0xcf,
0x1c, 0x22, 0x44, 0xa9, 0x2e, 0x48, 0x51, 0x01, 0x54, 0x73, 0x61, 0x3f, 0xbf,
0x38, 0xd2, 0x42, 0xd7, 0x54, 0xf6,
],
},
TestVector {
c: [
0x3d, 0xc1, 0x66, 0xd5, 0x6a, 0x1d, 0x62, 0xf5, 0xa8, 0xd7, 0x55, 0x1d, 0xb5,
0xfd, 0x93, 0x13, 0xe8, 0xc7, 0x20, 0x3d, 0x99, 0x6a, 0xf7, 0xd4, 0x77, 0x08,
0x37, 0x56, 0xd5, 0x9a, 0xf8, 0x0d,
],
pk: [
0x03, 0xec, 0x05, 0xbb, 0x7f, 0x06, 0x5e, 0x25, 0x6f, 0xf4, 0x54, 0xf8, 0xa8,
0xdf, 0x6f, 0x2f, 0x9b, 0x8a, 0x8c, 0x95, 0x08, 0xca, 0xac, 0xfe, 0xe9, 0x52,
0x1c, 0xbe, 0x68, 0x9d, 0xd1, 0x12, 0x0f,
],
external_ovk: [
0xdb, 0x97, 0x52, 0x0e, 0x2f, 0xe3, 0x68, 0xad, 0x50, 0x2d, 0xef, 0xf8, 0x42,
0xf0, 0xc0, 0xee, 0x5d, 0x20, 0x3b, 0x48, 0x33, 0x7a, 0x0f, 0xff, 0x75, 0xbe,
0x24, 0x52, 0x59, 0x77, 0xf3, 0x7e,
],
internal_ovk: [
0xbc, 0x4a, 0xcb, 0x5f, 0x52, 0xb8, 0xae, 0x21, 0xe3, 0x32, 0xb1, 0x7c, 0x29,
0x63, 0x1f, 0x68, 0xe9, 0x68, 0x2a, 0x46, 0xc4, 0xa7, 0xab, 0xc8, 0xed, 0xf9,
0x0d, 0x37, 0xae, 0xea, 0xd3, 0x6c,
],
},
TestVector {
c: [
0x49, 0x5c, 0x22, 0x2f, 0x7f, 0xba, 0x1e, 0x31, 0xde, 0xfa, 0x3d, 0x5a, 0x57,
0xef, 0xc2, 0xe1, 0xe9, 0xb0, 0x1a, 0x03, 0x55, 0x87, 0xd5, 0xfb, 0x1a, 0x38,
0xe0, 0x1d, 0x94, 0x90, 0x3d, 0x3c,
],
pk: [
0x02, 0x81, 0x8f, 0x50, 0xce, 0x47, 0x10, 0xf4, 0xeb, 0x11, 0xe7, 0x43, 0xe6,
0x40, 0x85, 0x44, 0xaa, 0x3c, 0x12, 0x3c, 0x7f, 0x07, 0xe2, 0xaa, 0xbb, 0x91,
0xaf, 0xc4, 0xec, 0x48, 0x78, 0x8d, 0xe9,
],
external_ovk: [
0xb8, 0xa3, 0x6d, 0x62, 0xa6, 0x3f, 0x69, 0x36, 0x7b, 0xe3, 0xf4, 0xbe, 0xd4,
0x20, 0x26, 0x4a, 0xdb, 0x63, 0x7b, 0xbb, 0x47, 0x0e, 0x1f, 0x56, 0xe0, 0x33,
0x8b, 0x38, 0xe2, 0xa6, 0x90, 0x97,
],
internal_ovk: [
0x4f, 0xf6, 0xfa, 0xf2, 0x06, 0x63, 0x1e, 0xcb, 0x01, 0xf9, 0x57, 0x30, 0xf7,
0xe5, 0x5b, 0xfc, 0xff, 0x8b, 0x02, 0xa3, 0x14, 0x88, 0x5a, 0x6d, 0x24, 0x8e,
0x6e, 0xbe, 0xb7, 0x4d, 0x3e, 0x50,
],
},
TestVector {
c: [
0xa7, 0xaf, 0x9d, 0xb6, 0x99, 0x0e, 0xd8, 0x3d, 0xd6, 0x4a, 0xf3, 0x59, 0x7c,
0x04, 0x32, 0x3e, 0xa5, 0x1b, 0x00, 0x52, 0xad, 0x80, 0x84, 0xa8, 0xb9, 0xda,
0x94, 0x8d, 0x32, 0x0d, 0xad, 0xd6,
],
pk: [
0x02, 0xae, 0x36, 0xb6, 0x1a, 0x3d, 0x10, 0xf1, 0xaa, 0x75, 0x2a, 0xb1, 0xdc,
0x16, 0xe3, 0xe4, 0x9b, 0x6a, 0xc0, 0xd2, 0xae, 0x19, 0x07, 0xd2, 0xe6, 0x94,
0x25, 0xec, 0x12, 0xc9, 0x3a, 0xae, 0xbc,
],
external_ovk: [
0xda, 0x6f, 0x47, 0x0f, 0x42, 0x5b, 0x3d, 0x27, 0xf4, 0x28, 0x6e, 0xf0, 0x3b,
0x7e, 0x87, 0x01, 0x7c, 0x20, 0xa7, 0x10, 0xb3, 0xff, 0xb9, 0xc1, 0xb6, 0x6c,
0x71, 0x60, 0x92, 0xe3, 0xd9, 0xbc,
],
internal_ovk: [
0x09, 0xb5, 0x4f, 0x75, 0xcb, 0x70, 0x32, 0x67, 0x1d, 0xc6, 0x8a, 0xaa, 0x07,
0x30, 0x5f, 0x38, 0xcd, 0xbc, 0x87, 0x9e, 0xe1, 0x5b, 0xec, 0x04, 0x71, 0x3c,
0x24, 0xdc, 0xe3, 0xca, 0x70, 0x26,
],
},
TestVector {
c: [
0xe0, 0x0c, 0x7a, 0x1d, 0x48, 0xaf, 0x04, 0x68, 0x27, 0x59, 0x1e, 0x97, 0x33,
0xa9, 0x7f, 0xa6, 0xb6, 0x79, 0xf3, 0xdc, 0x60, 0x1d, 0x00, 0x82, 0x85, 0xed,
0xcb, 0xda, 0xe6, 0x9c, 0xe8, 0xfc,
],
pk: [
0x02, 0x49, 0x26, 0x53, 0x80, 0xd2, 0xb0, 0x2e, 0x0a, 0x1d, 0x98, 0x8f, 0x3d,
0xe3, 0x45, 0x8b, 0x6e, 0x00, 0x29, 0x1d, 0xb0, 0xe6, 0x2e, 0x17, 0x47, 0x91,
0xd0, 0x09, 0x29, 0x9f, 0x61, 0xfe, 0xc4,
],
external_ovk: [
0x60, 0xa7, 0xa0, 0x8e, 0xef, 0xa2, 0x4e, 0x75, 0xcc, 0xbb, 0x29, 0xdc, 0x84,
0x94, 0x67, 0x2d, 0x73, 0x0f, 0xb3, 0x88, 0x7c, 0xb2, 0x6e, 0xf5, 0x1c, 0x6a,
0x1a, 0x78, 0xe8, 0x8a, 0x78, 0x39,
],
internal_ovk: [
0x3b, 0xab, 0x40, 0x98, 0x08, 0x10, 0x8b, 0xa9, 0xe5, 0xa1, 0xbb, 0x6a, 0x42,
0x24, 0x59, 0x9d, 0x62, 0xcc, 0xee, 0x63, 0xff, 0x2f, 0x38, 0x15, 0x4c, 0x7f,
0xb0, 0xc9, 0xa9, 0xa5, 0x79, 0x0f,
],
},
TestVector {
c: [
0xe2, 0x88, 0x53, 0x15, 0xeb, 0x46, 0x71, 0x09, 0x8b, 0x79, 0x53, 0x5e, 0x79,
0x0f, 0xe5, 0x3e, 0x29, 0xfe, 0xf2, 0xb3, 0x76, 0x66, 0x97, 0xac, 0x32, 0xb4,
0xf4, 0x73, 0xf4, 0x68, 0xa0, 0x08,
],
pk: [
0x03, 0x9a, 0x0e, 0x46, 0x39, 0xb4, 0x69, 0x1f, 0x02, 0x7c, 0x0d, 0xb7, 0xfe,
0xf1, 0xbb, 0x5e, 0xf9, 0x0a, 0xcd, 0xb7, 0x08, 0x62, 0x6d, 0x2e, 0x1f, 0x3e,
0x38, 0x3e, 0xe7, 0x5b, 0x31, 0xcf, 0x57,
],
external_ovk: [
0xbb, 0x47, 0x87, 0x2c, 0x25, 0x09, 0xbf, 0x3c, 0x72, 0xde, 0xdf, 0x4f, 0xc1,
0x77, 0x0f, 0x91, 0x93, 0xe2, 0xc1, 0x90, 0xd7, 0xaa, 0x8e, 0x9e, 0x88, 0x1a,
0xd2, 0xf1, 0x73, 0x48, 0x4e, 0xf2,
],
internal_ovk: [
0x5f, 0x36, 0xdf, 0xa3, 0x6c, 0xa7, 0x65, 0x74, 0x50, 0x29, 0x4e, 0xaa, 0xdd,
0xad, 0x78, 0xaf, 0xf2, 0xb3, 0xdc, 0x38, 0x5a, 0x57, 0x73, 0x5a, 0xc0, 0x0d,
0x3d, 0x9a, 0x29, 0x2b, 0x8c, 0x77,
],
},
TestVector {
c: [
0xed, 0x94, 0x94, 0xc6, 0xac, 0x89, 0x3c, 0x49, 0x72, 0x38, 0x33, 0xec, 0x89,
0x26, 0xc1, 0x03, 0x95, 0x86, 0xa7, 0xaf, 0xcf, 0x4a, 0x0d, 0x9c, 0x73, 0x1e,
0x98, 0x5d, 0x99, 0x58, 0x9c, 0x8b,
],
pk: [
0x03, 0xbb, 0xf4, 0x49, 0x82, 0xf1, 0xba, 0x3a, 0x2b, 0x9d, 0xd3, 0xc1, 0x77,
0x4d, 0x71, 0xce, 0x33, 0x60, 0x59, 0x9b, 0x07, 0xf2, 0x11, 0xc8, 0x16, 0xb8,
0xc4, 0x3b, 0x98, 0x42, 0x23, 0x09, 0x24,
],
external_ovk: [
0xed, 0xe8, 0xfb, 0x11, 0x37, 0x9b, 0x15, 0xae, 0xc4, 0xfa, 0x4e, 0xc5, 0x12,
0x4c, 0x95, 0x00, 0xad, 0xf4, 0x0e, 0xb6, 0xf7, 0xca, 0xa5, 0xe9, 0xce, 0x80,
0xf6, 0xbd, 0x9e, 0x73, 0xd0, 0xe7,
],
internal_ovk: [
0x25, 0x0b, 0x4d, 0xfc, 0x34, 0xdd, 0x57, 0x76, 0x74, 0x51, 0x57, 0xf3, 0x82,
0xce, 0x6d, 0xe4, 0xf6, 0xfe, 0x22, 0xd7, 0x98, 0x02, 0xf3, 0x9f, 0xe1, 0x34,
0x77, 0x8b, 0x79, 0x40, 0x42, 0xd3,
],
},
TestVector {
c: [
0x92, 0x47, 0x69, 0x30, 0xd0, 0x69, 0x89, 0x6c, 0xff, 0x30, 0xeb, 0x41, 0x4f,
0x72, 0x7b, 0x89, 0xe0, 0x01, 0xaf, 0xa2, 0xfb, 0x8d, 0xc3, 0x43, 0x6d, 0x75,
0xa4, 0xa6, 0xf2, 0x65, 0x72, 0x50,
],
pk: [
0x03, 0xff, 0x63, 0xc7, 0x89, 0x25, 0x1c, 0x10, 0x43, 0xc6, 0xf9, 0x6c, 0x66,
0xbf, 0x5b, 0x0f, 0x61, 0xc9, 0xd6, 0x5f, 0xef, 0x5a, 0xaf, 0x42, 0x84, 0xa6,
0xa5, 0x69, 0x94, 0x94, 0x1c, 0x05, 0xfa,
],
external_ovk: [
0xb3, 0x11, 0x52, 0x06, 0x42, 0x71, 0x01, 0x01, 0xbb, 0xc8, 0x1b, 0xbe, 0x92,
0x85, 0x1f, 0x9e, 0x65, 0x36, 0x22, 0x3e, 0xd6, 0xe6, 0xa1, 0x28, 0x59, 0x06,
0x62, 0x1e, 0xfa, 0xe6, 0x41, 0x10,
],
internal_ovk: [
0xf4, 0x46, 0xc0, 0xc1, 0x74, 0x1c, 0x94, 0x42, 0x56, 0x8e, 0x12, 0xf0, 0x55,
0xef, 0xd5, 0x0c, 0x1e, 0xfe, 0x4d, 0x71, 0x53, 0x3d, 0x97, 0x6b, 0x08, 0xe9,
0x94, 0x41, 0x44, 0x49, 0xc4, 0xac,
],
},
TestVector {
c: [
0x7d, 0x41, 0x7a, 0xdb, 0x3d, 0x15, 0xcc, 0x54, 0xdc, 0xb1, 0xfc, 0xe4, 0x67,
0x50, 0x0c, 0x6b, 0x8f, 0xb8, 0x6b, 0x12, 0xb5, 0x6d, 0xa9, 0xc3, 0x82, 0x85,
0x7d, 0xee, 0xcc, 0x40, 0xa9, 0x8d,
],
pk: [
0x02, 0xbf, 0x39, 0x20, 0xce, 0x2e, 0x9e, 0x95, 0xb0, 0xee, 0xce, 0x13, 0x0a,
0x50, 0xba, 0x7d, 0xcc, 0x6f, 0x26, 0x51, 0x2a, 0x9f, 0xc7, 0xb8, 0x04, 0xaf,
0xf0, 0x89, 0xf5, 0x0c, 0xbc, 0xff, 0xf7,
],
external_ovk: [
0xae, 0x63, 0x84, 0xf8, 0x07, 0x72, 0x1c, 0x5f, 0x46, 0xc8, 0xaa, 0x83, 0x3b,
0x66, 0x9b, 0x01, 0xc4, 0x22, 0x7c, 0x00, 0x18, 0xcb, 0x27, 0x29, 0xa9, 0x79,
0x91, 0x01, 0xea, 0xb8, 0x5a, 0xb9,
],
internal_ovk: [
0xef, 0x70, 0x8e, 0xb8, 0x26, 0xd8, 0xbf, 0xcd, 0x7f, 0xaa, 0x4f, 0x90, 0xdf,
0x46, 0x1d, 0xed, 0x08, 0xd1, 0x6e, 0x19, 0x1b, 0x4e, 0x51, 0xb8, 0xa3, 0xa9,
0x1c, 0x02, 0x0b, 0x32, 0xcc, 0x07,
],
},
];
for tv in test_vectors {
let mut key_bytes = [0u8; 65];
key_bytes[..32].copy_from_slice(&tv.c);
key_bytes[32..].copy_from_slice(&tv.pk);
let account_key = AccountPubKey::deserialize(&key_bytes).unwrap();
let (internal, external) = account_key.ovks_for_shielding();
assert_eq!(tv.internal_ovk, internal.as_bytes());
assert_eq!(tv.external_ovk, external.as_bytes());
}
}
}

View File

@ -12,6 +12,7 @@
pub mod block;
pub mod consensus;
pub mod constants;
pub mod keys;
pub mod legacy;
pub mod memo;
pub mod merkle_tree;

View File

@ -23,13 +23,13 @@ use subtle::{Choice, ConstantTimeEq};
use crate::{
constants::{self, SPENDING_KEY_GENERATOR},
keys::prf_expand,
merkle_tree::{HashSer, Hashable},
transaction::components::amount::MAX_MONEY,
};
use self::{
group_hash::group_hash,
keys::prf_expand,
pedersen_hash::{pedersen_hash, Personalization},
redjubjub::{PrivateKey, PublicKey, Signature},
};

View File

@ -6,37 +6,14 @@
use crate::{
constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR},
keys::{prf_expand, OutgoingViewingKey},
sapling::{ProofGenerationKey, ViewingKey},
};
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
use ff::PrimeField;
use group::{Group, GroupEncoding};
use std::io::{self, Read, Write};
use subtle::CtOption;
pub const PRF_EXPAND_PERSONALIZATION: &[u8; 16] = b"Zcash_ExpandSeed";
/// PRF^expand(sk, t) := BLAKE2b-512("Zcash_ExpandSeed", sk || t)
pub fn prf_expand(sk: &[u8], t: &[u8]) -> Blake2bHash {
prf_expand_vec(sk, &[t])
}
pub fn prf_expand_vec(sk: &[u8], ts: &[&[u8]]) -> Blake2bHash {
let mut h = Blake2bParams::new()
.hash_length(64)
.personal(PRF_EXPAND_PERSONALIZATION)
.to_state();
h.update(sk);
for t in ts {
h.update(t);
}
h.finalize()
}
/// An outgoing viewing key
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct OutgoingViewingKey(pub [u8; 32]);
/// A Sapling expanded spending key
#[derive(Clone)]
pub struct ExpandedSpendingKey {

View File

@ -16,8 +16,9 @@ use zcash_note_encryption::{
use crate::{
consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD},
keys::OutgoingViewingKey,
memo::MemoBytes,
sapling::{keys::OutgoingViewingKey, Diversifier, Note, PaymentAddress, Rseed, SaplingIvk},
sapling::{Diversifier, Note, PaymentAddress, Rseed, SaplingIvk},
transaction::components::{
amount::Amount,
sapling::{self, OutputDescription},
@ -492,12 +493,10 @@ mod tests {
NetworkUpgrade::{Canopy, Sapling},
Parameters, TestNetwork, TEST_NETWORK, ZIP212_GRACE_PERIOD,
},
keys::OutgoingViewingKey,
memo::MemoBytes,
sapling::util::generate_random_rseed,
sapling::{
keys::OutgoingViewingKey, Diversifier, PaymentAddress, Rseed, SaplingIvk,
ValueCommitment,
},
sapling::{Diversifier, PaymentAddress, Rseed, SaplingIvk, ValueCommitment},
transaction::components::{
amount::Amount,
sapling::{self, CompactOutputDescription, OutputDescription},

View File

@ -12,12 +12,11 @@ use rand::{rngs::OsRng, CryptoRng, RngCore};
use crate::{
consensus::{self, BlockHeight, BranchId},
keys::OutgoingViewingKey,
legacy::TransparentAddress,
memo::MemoBytes,
merkle_tree::MerklePath,
sapling::{
keys::OutgoingViewingKey, prover::TxProver, Diversifier, Node, Note, PaymentAddress,
},
sapling::{prover::TxProver, Diversifier, Node, Note, PaymentAddress},
transaction::{
components::{
amount::{Amount, DEFAULT_FEE},
@ -205,7 +204,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
ovk: Option<OutgoingViewingKey>,
to: PaymentAddress,
value: Amount,
memo: Option<MemoBytes>,
memo: MemoBytes,
) -> Result<(), Error> {
self.sapling_builder
.add_output(&mut self.rng, ovk, to, value, memo)
@ -302,16 +301,18 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
//
if change.is_positive() {
// Send change to the specified change address. If no change address
// was set, send change to the first Sapling address given as input.
match self.change_address.take() {
Some(ChangeAddress::SaplingChangeAddress(ovk, addr)) => {
self.add_sapling_output(Some(ovk), addr, change, None)?;
self.add_sapling_output(Some(ovk), addr, change, MemoBytes::empty())?;
}
None => {
let (ovk, addr) = self
.sapling_builder
.get_candidate_change_address()
.ok_or(Error::NoChangeAddress)?;
self.add_sapling_output(Some(ovk), addr, change, None)?;
self.add_sapling_output(Some(ovk), addr, change, MemoBytes::empty())?;
}
}
}
@ -469,6 +470,7 @@ mod tests {
use crate::{
consensus::{NetworkUpgrade, Parameters, TEST_NETWORK},
legacy::TransparentAddress,
memo::MemoBytes,
merkle_tree::{CommitmentTree, IncrementalWitness},
sapling::{prover::mock::MockTxProver, Node, Rseed},
transaction::components::{
@ -500,7 +502,12 @@ mod tests {
let mut builder = Builder::new(TEST_NETWORK, sapling_activation_height);
assert_eq!(
builder.add_sapling_output(Some(ovk), to, Amount::from_i64(-1).unwrap(), None),
builder.add_sapling_output(
Some(ovk),
to,
Amount::from_i64(-1).unwrap(),
MemoBytes::empty()
),
Err(Error::SaplingBuild(build_s::Error::InvalidAmount))
);
}
@ -625,7 +632,12 @@ mod tests {
{
let mut builder = Builder::new(TEST_NETWORK, tx_height);
builder
.add_sapling_output(ovk, to.clone(), Amount::from_u64(50000).unwrap(), None)
.add_sapling_output(
ovk,
to.clone(),
Amount::from_u64(50000).unwrap(),
MemoBytes::empty(),
)
.unwrap();
assert_eq!(
builder.build(&MockTxProver),
@ -674,7 +686,12 @@ mod tests {
)
.unwrap();
builder
.add_sapling_output(ovk, to.clone(), Amount::from_u64(30000).unwrap(), None)
.add_sapling_output(
ovk,
to.clone(),
Amount::from_u64(30000).unwrap(),
MemoBytes::empty(),
)
.unwrap();
builder
.add_transparent_output(
@ -715,7 +732,12 @@ mod tests {
.add_sapling_spend(extsk, *to.diversifier(), note2, witness2.path().unwrap())
.unwrap();
builder
.add_sapling_output(ovk, to, Amount::from_u64(30000).unwrap(), None)
.add_sapling_output(
ovk,
to,
Amount::from_u64(30000).unwrap(),
MemoBytes::empty(),
)
.unwrap();
builder
.add_transparent_output(

View File

@ -114,6 +114,14 @@ impl Amount {
pub const fn is_negative(self) -> bool {
self.0.is_negative()
}
pub fn sum<I: IntoIterator<Item = Amount>>(values: I) -> Option<Amount> {
let mut result = Amount::zero();
for value in values {
result = (result + value)?;
}
Some(result)
}
}
impl TryFrom<i64> for Amount {

View File

@ -9,10 +9,10 @@ use rand::{seq::SliceRandom, RngCore};
use crate::{
consensus::{self, BlockHeight},
keys::OutgoingViewingKey,
memo::MemoBytes,
merkle_tree::MerklePath,
sapling::{
keys::OutgoingViewingKey,
note_encryption::sapling_note_encryption,
prover::TxProver,
redjubjub::{PrivateKey, Signature},
@ -86,7 +86,7 @@ impl SaplingOutput {
ovk: Option<OutgoingViewingKey>,
to: PaymentAddress,
value: Amount,
memo: Option<MemoBytes>,
memo: MemoBytes,
) -> Result<Self, Error> {
let g_d = to.g_d().ok_or(Error::InvalidAddress)?;
if value.is_negative() {
@ -106,7 +106,7 @@ impl SaplingOutput {
ovk,
to,
note,
memo: memo.unwrap_or_else(MemoBytes::empty),
memo,
})
}
@ -276,7 +276,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
ovk: Option<OutgoingViewingKey>,
to: PaymentAddress,
value: Amount,
memo: Option<MemoBytes>,
memo: MemoBytes,
) -> Result<(), Error> {
let output = SaplingOutput::new_internal(
&self.params,

View File

@ -2,9 +2,6 @@
use std::fmt;
#[cfg(feature = "transparent-inputs")]
use blake2b_simd::Hash as Blake2bHash;
use crate::{
legacy::{Script, TransparentAddress},
transaction::{
@ -17,11 +14,15 @@ use crate::{
};
#[cfg(feature = "transparent-inputs")]
use crate::transaction::{
self as tx,
components::OutPoint,
sighash::{signature_hash, SignableInput, SIGHASH_ALL},
TransactionData, TxDigests,
use {
crate::transaction::{
self as tx,
components::OutPoint,
sighash::{signature_hash, SignableInput, SIGHASH_ALL},
TransactionData, TxDigests,
},
blake2b_simd::Hash as Blake2bHash,
ripemd::Digest,
};
#[derive(Debug, PartialEq)]
@ -96,7 +97,7 @@ impl TransparentBuilder {
let pubkey = secp256k1::PublicKey::from_secret_key(&self.secp, &sk).serialize();
match coin.script_pubkey.address() {
Some(TransparentAddress::PublicKey(hash)) => {
use ripemd160::Ripemd160;
use ripemd::Ripemd160;
use sha2::{Digest, Sha256};
if hash[..] != Ripemd160::digest(&Sha256::digest(&pubkey))[..] {

View File

@ -8,6 +8,7 @@ use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt};
use fpe::ff1::{BinaryNumeralString, FF1};
use std::convert::TryInto;
use std::ops::AddAssign;
use subtle::{Choice, ConditionallySelectable};
use crate::{
constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR},
@ -15,14 +16,37 @@ use crate::{
};
use std::io::{self, Read, Write};
use crate::sapling::keys::{
prf_expand, prf_expand_vec, ExpandedSpendingKey, FullViewingKey, OutgoingViewingKey,
use crate::{
keys::{prf_expand, prf_expand_vec, OutgoingViewingKey},
sapling::keys::{ExpandedSpendingKey, FullViewingKey},
};
pub const ZIP32_SAPLING_MASTER_PERSONALIZATION: &[u8; 16] = b"ZcashIP32Sapling";
pub const ZIP32_SAPLING_FVFP_PERSONALIZATION: &[u8; 16] = b"ZcashSaplingFVFP";
pub const ZIP32_SAPLING_INT_PERSONALIZATION: &[u8; 16] = b"Zcash_SaplingInt";
/// A type-safe wrapper for account identifiers.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct AccountId(pub u32);
impl From<u32> for AccountId {
fn from(id: u32) -> Self {
Self(id)
}
}
impl Default for AccountId {
fn default() -> Self {
AccountId(0)
}
}
impl ConditionallySelectable for AccountId {
fn conditional_select(a0: &Self, a1: &Self, c: Choice) -> Self {
AccountId(u32::conditional_select(&a0.0, &a1.0, c))
}
}
// Common helper functions
fn derive_child_ovk(parent: &OutgoingViewingKey, i_l: &[u8]) -> OutgoingViewingKey {
@ -94,9 +118,9 @@ impl ChildIndex {
}
}
/// A chain code
/// A BIP-32 chain code
#[derive(Clone, Copy, Debug, PartialEq)]
struct ChainCode([u8; 32]);
pub struct ChainCode([u8; 32]);
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DiversifierIndex(pub [u8; 11]);
@ -799,6 +823,14 @@ mod tests {
d1: Option<[u8; 11]>,
d2: Option<[u8; 11]>,
dmax: Option<[u8; 11]>,
internal_nsk: Option<[u8; 32]>,
internal_ovk: [u8; 32],
internal_dk: [u8; 32],
internal_nk: [u8; 32],
internal_ivk: [u8; 32],
internal_xsk: Option<[u8; 169]>,
internal_xfvk: [u8; 169],
internal_fp: [u8; 32],
}
// From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_zip32.py
@ -887,6 +919,66 @@ mod tests {
]),
d2: None,
dmax: None,
internal_nsk: Some([
0x51, 0x12, 0x33, 0x63, 0x6b, 0x95, 0xfd, 0x0a, 0xfb, 0x6b, 0xf8, 0x19, 0x3a,
0x7d, 0x8f, 0x49, 0xef, 0xd7, 0x36, 0xa9, 0x88, 0x77, 0x5c, 0x54, 0xf9, 0x56,
0x68, 0x76, 0x46, 0xea, 0xab, 0x07,
]),
internal_ovk: [
0x9d, 0xc4, 0x77, 0xfe, 0x1e, 0x7d, 0x28, 0x29, 0x13, 0xf6, 0x51, 0x65, 0x4d,
0x39, 0x85, 0xf0, 0x9d, 0x53, 0xc2, 0xd3, 0xb5, 0x76, 0x3d, 0x7a, 0x72, 0x3b,
0xcb, 0xd6, 0xee, 0x05, 0x3d, 0x5a,
],
internal_dk: [
0x40, 0xdd, 0xc5, 0x6e, 0x69, 0x75, 0x13, 0x8c, 0x08, 0x39, 0xe5, 0x80, 0xb5,
0x4d, 0x6d, 0x99, 0x9d, 0xc6, 0x16, 0x84, 0x3c, 0xfe, 0x04, 0x1e, 0x8f, 0x38,
0x8b, 0x12, 0x4e, 0xf7, 0xb5, 0xed,
],
internal_nk: [
0xa3, 0x83, 0x1a, 0x5c, 0x69, 0x33, 0xf8, 0xec, 0x6a, 0xa5, 0xce, 0x31, 0x6c,
0x50, 0x8b, 0x79, 0x91, 0xcd, 0x94, 0xd3, 0xbd, 0xb7, 0x00, 0xa1, 0xc4, 0x27,
0xa6, 0xae, 0x15, 0xe7, 0x2f, 0xb5,
],
internal_ivk: [
0x79, 0x05, 0x77, 0x32, 0x1c, 0x51, 0x18, 0x04, 0x63, 0x6e, 0xe6, 0xba, 0xa4,
0xee, 0xa7, 0x79, 0xb4, 0xa4, 0x6a, 0x5a, 0x12, 0xf8, 0x5d, 0x36, 0x50, 0x74,
0xa0, 0x9d, 0x05, 0x4f, 0x34, 0x01,
],
internal_xsk: Some([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x94, 0x7c, 0x4b,
0x03, 0xbf, 0x72, 0xa3, 0x7a, 0xb4, 0x4f, 0x72, 0x27, 0x6d, 0x1c, 0xf3, 0xfd,
0xcd, 0x7e, 0xbf, 0x3e, 0x73, 0x34, 0x8b, 0x7e, 0x55, 0x0d, 0x75, 0x20, 0x18,
0x66, 0x8e, 0xb6, 0xc0, 0x0c, 0x93, 0xd3, 0x60, 0x32, 0xb9, 0xa2, 0x68, 0xe9,
0x9e, 0x86, 0xa8, 0x60, 0x77, 0x65, 0x60, 0xbf, 0x0e, 0x83, 0xc1, 0xa1, 0x0b,
0x51, 0xf6, 0x07, 0xc9, 0x54, 0x74, 0x25, 0x06, 0x51, 0x12, 0x33, 0x63, 0x6b,
0x95, 0xfd, 0x0a, 0xfb, 0x6b, 0xf8, 0x19, 0x3a, 0x7d, 0x8f, 0x49, 0xef, 0xd7,
0x36, 0xa9, 0x88, 0x77, 0x5c, 0x54, 0xf9, 0x56, 0x68, 0x76, 0x46, 0xea, 0xab,
0x07, 0x9d, 0xc4, 0x77, 0xfe, 0x1e, 0x7d, 0x28, 0x29, 0x13, 0xf6, 0x51, 0x65,
0x4d, 0x39, 0x85, 0xf0, 0x9d, 0x53, 0xc2, 0xd3, 0xb5, 0x76, 0x3d, 0x7a, 0x72,
0x3b, 0xcb, 0xd6, 0xee, 0x05, 0x3d, 0x5a, 0x40, 0xdd, 0xc5, 0x6e, 0x69, 0x75,
0x13, 0x8c, 0x08, 0x39, 0xe5, 0x80, 0xb5, 0x4d, 0x6d, 0x99, 0x9d, 0xc6, 0x16,
0x84, 0x3c, 0xfe, 0x04, 0x1e, 0x8f, 0x38, 0x8b, 0x12, 0x4e, 0xf7, 0xb5, 0xed,
]),
internal_xfvk: [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x94, 0x7c, 0x4b,
0x03, 0xbf, 0x72, 0xa3, 0x7a, 0xb4, 0x4f, 0x72, 0x27, 0x6d, 0x1c, 0xf3, 0xfd,
0xcd, 0x7e, 0xbf, 0x3e, 0x73, 0x34, 0x8b, 0x7e, 0x55, 0x0d, 0x75, 0x20, 0x18,
0x66, 0x8e, 0x93, 0x44, 0x2e, 0x5f, 0xef, 0xfb, 0xff, 0x16, 0xe7, 0x21, 0x72,
0x02, 0xdc, 0x73, 0x06, 0x72, 0x9f, 0xff, 0xfe, 0x85, 0xaf, 0x56, 0x83, 0xbc,
0xe2, 0x64, 0x2e, 0x3e, 0xeb, 0x5d, 0x38, 0x71, 0xa3, 0x83, 0x1a, 0x5c, 0x69,
0x33, 0xf8, 0xec, 0x6a, 0xa5, 0xce, 0x31, 0x6c, 0x50, 0x8b, 0x79, 0x91, 0xcd,
0x94, 0xd3, 0xbd, 0xb7, 0x00, 0xa1, 0xc4, 0x27, 0xa6, 0xae, 0x15, 0xe7, 0x2f,
0xb5, 0x9d, 0xc4, 0x77, 0xfe, 0x1e, 0x7d, 0x28, 0x29, 0x13, 0xf6, 0x51, 0x65,
0x4d, 0x39, 0x85, 0xf0, 0x9d, 0x53, 0xc2, 0xd3, 0xb5, 0x76, 0x3d, 0x7a, 0x72,
0x3b, 0xcb, 0xd6, 0xee, 0x05, 0x3d, 0x5a, 0x40, 0xdd, 0xc5, 0x6e, 0x69, 0x75,
0x13, 0x8c, 0x08, 0x39, 0xe5, 0x80, 0xb5, 0x4d, 0x6d, 0x99, 0x9d, 0xc6, 0x16,
0x84, 0x3c, 0xfe, 0x04, 0x1e, 0x8f, 0x38, 0x8b, 0x12, 0x4e, 0xf7, 0xb5, 0xed,
],
internal_fp: [
0x82, 0x64, 0xed, 0xec, 0x63, 0xb1, 0x55, 0x00, 0x1d, 0x84, 0x96, 0x68, 0x5c,
0xc7, 0xc2, 0x1e, 0xa9, 0x57, 0xc6, 0xf5, 0x91, 0x09, 0x0a, 0x1c, 0x20, 0xe5,
0x2a, 0x41, 0x89, 0xb8, 0xbb, 0x96,
],
},
TestVector {
ask: Some([
@ -974,6 +1066,66 @@ mod tests {
dmax: Some([
0x63, 0x89, 0x57, 0x4c, 0xde, 0x0f, 0xbb, 0xc6, 0x36, 0x81, 0x31,
]),
internal_nsk: Some([
0x74, 0x92, 0x9f, 0x79, 0x0c, 0x11, 0xdc, 0xab, 0x3a, 0x2f, 0x93, 0x12, 0x35,
0xcd, 0xb2, 0x67, 0xf5, 0xa3, 0x1b, 0x9f, 0x13, 0x9f, 0x2c, 0x9f, 0xd8, 0x16,
0xb0, 0x44, 0x4f, 0xb8, 0x05, 0x05,
]),
internal_ovk: [
0x0c, 0xd4, 0xd7, 0xc5, 0xcc, 0x7f, 0x53, 0x4b, 0x96, 0xd2, 0x41, 0x82, 0xa3,
0x14, 0x65, 0xb4, 0x78, 0x11, 0x05, 0x48, 0x9c, 0xd1, 0x0d, 0x50, 0x0c, 0xf5,
0x29, 0x5a, 0x6f, 0xd8, 0x18, 0xcc,
],
internal_dk: [
0xd2, 0x78, 0xb7, 0x2c, 0x62, 0x1d, 0x19, 0xcb, 0x00, 0xf9, 0x70, 0x07, 0x9c,
0x89, 0x22, 0x76, 0x1c, 0xdd, 0x3a, 0xe7, 0xf2, 0x7b, 0x18, 0x47, 0xc5, 0x53,
0x60, 0xdb, 0xeb, 0xf6, 0x54, 0x92,
],
internal_nk: [
0x2b, 0x5c, 0x78, 0xa2, 0xfb, 0xa5, 0x01, 0x9c, 0x15, 0xa7, 0x51, 0x50, 0x2b,
0xa9, 0x91, 0x6f, 0xae, 0xda, 0xe1, 0xfc, 0x14, 0xdc, 0x81, 0xb0, 0xb8, 0x35,
0xf2, 0xbf, 0x95, 0xc0, 0x68, 0xe8,
],
internal_ivk: [
0xdf, 0x44, 0x54, 0xa6, 0x76, 0xd1, 0xde, 0x32, 0xe2, 0x0a, 0xe6, 0x28, 0x7a,
0x92, 0xfa, 0xfe, 0xfb, 0xbb, 0x3e, 0x54, 0xb5, 0x88, 0xc8, 0xda, 0x28, 0x07,
0xec, 0x43, 0x68, 0x2c, 0x85, 0x00,
],
internal_xsk: Some([
0x01, 0x14, 0xc2, 0x71, 0x3a, 0x01, 0x00, 0x00, 0x00, 0x01, 0x47, 0x11, 0x0c,
0x69, 0x1a, 0x03, 0xb9, 0xd9, 0xf0, 0xba, 0x90, 0x05, 0xc5, 0xe7, 0x90, 0xa5,
0x95, 0xb7, 0xf0, 0x4e, 0x33, 0x29, 0xd2, 0xfa, 0x43, 0x8a, 0x67, 0x05, 0xda,
0xbc, 0xe6, 0x28, 0x2b, 0xc1, 0x97, 0xa5, 0x16, 0x28, 0x7c, 0x8e, 0xa8, 0xf6,
0x8c, 0x42, 0x4a, 0xba, 0xd3, 0x02, 0xb4, 0x5c, 0xdf, 0x95, 0x40, 0x79, 0x61,
0xd7, 0xb8, 0xb4, 0x55, 0x26, 0x7a, 0x35, 0x0c, 0x74, 0x92, 0x9f, 0x79, 0x0c,
0x11, 0xdc, 0xab, 0x3a, 0x2f, 0x93, 0x12, 0x35, 0xcd, 0xb2, 0x67, 0xf5, 0xa3,
0x1b, 0x9f, 0x13, 0x9f, 0x2c, 0x9f, 0xd8, 0x16, 0xb0, 0x44, 0x4f, 0xb8, 0x05,
0x05, 0x0c, 0xd4, 0xd7, 0xc5, 0xcc, 0x7f, 0x53, 0x4b, 0x96, 0xd2, 0x41, 0x82,
0xa3, 0x14, 0x65, 0xb4, 0x78, 0x11, 0x05, 0x48, 0x9c, 0xd1, 0x0d, 0x50, 0x0c,
0xf5, 0x29, 0x5a, 0x6f, 0xd8, 0x18, 0xcc, 0xd2, 0x78, 0xb7, 0x2c, 0x62, 0x1d,
0x19, 0xcb, 0x00, 0xf9, 0x70, 0x07, 0x9c, 0x89, 0x22, 0x76, 0x1c, 0xdd, 0x3a,
0xe7, 0xf2, 0x7b, 0x18, 0x47, 0xc5, 0x53, 0x60, 0xdb, 0xeb, 0xf6, 0x54, 0x92,
]),
internal_xfvk: [
0x01, 0x14, 0xc2, 0x71, 0x3a, 0x01, 0x00, 0x00, 0x00, 0x01, 0x47, 0x11, 0x0c,
0x69, 0x1a, 0x03, 0xb9, 0xd9, 0xf0, 0xba, 0x90, 0x05, 0xc5, 0xe7, 0x90, 0xa5,
0x95, 0xb7, 0xf0, 0x4e, 0x33, 0x29, 0xd2, 0xfa, 0x43, 0x8a, 0x67, 0x05, 0xda,
0xbc, 0xe6, 0xdc, 0x14, 0xb5, 0x14, 0xd3, 0xa9, 0x25, 0x94, 0xc2, 0x19, 0x25,
0xaf, 0x2f, 0x77, 0x65, 0xa5, 0x47, 0xb3, 0x0e, 0x73, 0xfa, 0x7b, 0x70, 0x0e,
0xa1, 0xbf, 0xf2, 0xe5, 0xef, 0xaa, 0xa8, 0x8b, 0x2b, 0x5c, 0x78, 0xa2, 0xfb,
0xa5, 0x01, 0x9c, 0x15, 0xa7, 0x51, 0x50, 0x2b, 0xa9, 0x91, 0x6f, 0xae, 0xda,
0xe1, 0xfc, 0x14, 0xdc, 0x81, 0xb0, 0xb8, 0x35, 0xf2, 0xbf, 0x95, 0xc0, 0x68,
0xe8, 0x0c, 0xd4, 0xd7, 0xc5, 0xcc, 0x7f, 0x53, 0x4b, 0x96, 0xd2, 0x41, 0x82,
0xa3, 0x14, 0x65, 0xb4, 0x78, 0x11, 0x05, 0x48, 0x9c, 0xd1, 0x0d, 0x50, 0x0c,
0xf5, 0x29, 0x5a, 0x6f, 0xd8, 0x18, 0xcc, 0xd2, 0x78, 0xb7, 0x2c, 0x62, 0x1d,
0x19, 0xcb, 0x00, 0xf9, 0x70, 0x07, 0x9c, 0x89, 0x22, 0x76, 0x1c, 0xdd, 0x3a,
0xe7, 0xf2, 0x7b, 0x18, 0x47, 0xc5, 0x53, 0x60, 0xdb, 0xeb, 0xf6, 0x54, 0x92,
],
internal_fp: [
0x0d, 0xe5, 0x83, 0xca, 0x50, 0x2b, 0x1c, 0x4b, 0x87, 0xca, 0xc8, 0xc9, 0x78,
0x6c, 0x61, 0x9b, 0x79, 0xe1, 0x69, 0xb4, 0x15, 0x61, 0xf2, 0x44, 0xee, 0xec,
0x86, 0x86, 0xb8, 0xdb, 0xc4, 0xe1,
],
},
TestVector {
ask: Some([
@ -1059,6 +1211,66 @@ mod tests {
]),
d2: None,
dmax: None,
internal_nsk: Some([
0x34, 0x78, 0x65, 0xac, 0xf4, 0x7e, 0x50, 0x45, 0x38, 0xf5, 0xef, 0x8b, 0x04,
0x70, 0x20, 0x80, 0xe6, 0x09, 0x1c, 0xda, 0x57, 0x97, 0xcd, 0x7d, 0x23, 0x5a,
0x54, 0x6e, 0xb1, 0x0f, 0x55, 0x08,
]),
internal_ovk: [
0xdd, 0xba, 0xc2, 0xa4, 0x93, 0xf5, 0x3c, 0x3b, 0x09, 0x33, 0xd9, 0x13, 0xde,
0xf8, 0x88, 0x48, 0x65, 0x4c, 0x08, 0x7c, 0x12, 0x60, 0x9d, 0xf0, 0x1b, 0xaf,
0x94, 0x05, 0xce, 0x78, 0x04, 0xfd,
],
internal_dk: [
0x60, 0x2c, 0xd3, 0x17, 0xb7, 0xce, 0xa1, 0x1e, 0x8c, 0xc7, 0xae, 0x2e, 0xa4,
0x05, 0xb4, 0x0d, 0x46, 0xb1, 0x59, 0x2a, 0x30, 0xf0, 0xcb, 0x6e, 0x8c, 0x4f,
0x17, 0xd7, 0xf7, 0xc4, 0x7f, 0xeb,
],
internal_nk: [
0xf9, 0x12, 0x87, 0xc0, 0x6e, 0x30, 0xb6, 0x5e, 0xa1, 0xbd, 0xb7, 0x16, 0xb2,
0x31, 0xde, 0x67, 0x78, 0xa5, 0xd8, 0x0e, 0xe5, 0xcd, 0x9c, 0x06, 0x0d, 0x1a,
0xba, 0xca, 0xe0, 0xaa, 0xe2, 0x3b,
],
internal_ivk: [
0x1d, 0x59, 0xea, 0x20, 0x17, 0x88, 0x18, 0x64, 0xd2, 0x4e, 0xad, 0xb5, 0xcf,
0x34, 0x68, 0xa4, 0x1a, 0x1b, 0x2a, 0xaa, 0x0d, 0x1b, 0x3a, 0x72, 0xc6, 0xda,
0x9c, 0xe6, 0x50, 0x2a, 0x0a, 0x05,
],
internal_xsk: Some([
0x02, 0xdb, 0x99, 0x9e, 0x07, 0x02, 0x00, 0x00, 0x80, 0x97, 0xce, 0x15, 0xf4,
0xed, 0x1b, 0x97, 0x39, 0xb0, 0x26, 0x2a, 0x46, 0x3b, 0xcb, 0x3d, 0xc9, 0xb3,
0xbd, 0x23, 0x23, 0xa9, 0xba, 0xa4, 0x41, 0xca, 0x42, 0x77, 0x73, 0x83, 0xa8,
0xd4, 0x35, 0x8b, 0xe8, 0x11, 0x3c, 0xee, 0x34, 0x13, 0xa7, 0x1f, 0x82, 0xc4,
0x1f, 0xc8, 0xda, 0x51, 0x7b, 0xe1, 0x34, 0x04, 0x98, 0x32, 0xe6, 0x82, 0x5c,
0x92, 0xda, 0x6b, 0x84, 0xfe, 0xe4, 0xc6, 0x0d, 0x34, 0x78, 0x65, 0xac, 0xf4,
0x7e, 0x50, 0x45, 0x38, 0xf5, 0xef, 0x8b, 0x04, 0x70, 0x20, 0x80, 0xe6, 0x09,
0x1c, 0xda, 0x57, 0x97, 0xcd, 0x7d, 0x23, 0x5a, 0x54, 0x6e, 0xb1, 0x0f, 0x55,
0x08, 0xdd, 0xba, 0xc2, 0xa4, 0x93, 0xf5, 0x3c, 0x3b, 0x09, 0x33, 0xd9, 0x13,
0xde, 0xf8, 0x88, 0x48, 0x65, 0x4c, 0x08, 0x7c, 0x12, 0x60, 0x9d, 0xf0, 0x1b,
0xaf, 0x94, 0x05, 0xce, 0x78, 0x04, 0xfd, 0x60, 0x2c, 0xd3, 0x17, 0xb7, 0xce,
0xa1, 0x1e, 0x8c, 0xc7, 0xae, 0x2e, 0xa4, 0x05, 0xb4, 0x0d, 0x46, 0xb1, 0x59,
0x2a, 0x30, 0xf0, 0xcb, 0x6e, 0x8c, 0x4f, 0x17, 0xd7, 0xf7, 0xc4, 0x7f, 0xeb,
]),
internal_xfvk: [
0x02, 0xdb, 0x99, 0x9e, 0x07, 0x02, 0x00, 0x00, 0x80, 0x97, 0xce, 0x15, 0xf4,
0xed, 0x1b, 0x97, 0x39, 0xb0, 0x26, 0x2a, 0x46, 0x3b, 0xcb, 0x3d, 0xc9, 0xb3,
0xbd, 0x23, 0x23, 0xa9, 0xba, 0xa4, 0x41, 0xca, 0x42, 0x77, 0x73, 0x83, 0xa8,
0xd4, 0x35, 0xa6, 0xc5, 0x92, 0x5a, 0x0f, 0x85, 0xfa, 0x4f, 0x1e, 0x40, 0x5e,
0x3a, 0x49, 0x70, 0xd0, 0xc4, 0xa4, 0xb4, 0x81, 0x44, 0x38, 0xf4, 0xe9, 0xd4,
0x52, 0x0e, 0x20, 0xf7, 0xfd, 0xcf, 0x38, 0x41, 0xf9, 0x12, 0x87, 0xc0, 0x6e,
0x30, 0xb6, 0x5e, 0xa1, 0xbd, 0xb7, 0x16, 0xb2, 0x31, 0xde, 0x67, 0x78, 0xa5,
0xd8, 0x0e, 0xe5, 0xcd, 0x9c, 0x06, 0x0d, 0x1a, 0xba, 0xca, 0xe0, 0xaa, 0xe2,
0x3b, 0xdd, 0xba, 0xc2, 0xa4, 0x93, 0xf5, 0x3c, 0x3b, 0x09, 0x33, 0xd9, 0x13,
0xde, 0xf8, 0x88, 0x48, 0x65, 0x4c, 0x08, 0x7c, 0x12, 0x60, 0x9d, 0xf0, 0x1b,
0xaf, 0x94, 0x05, 0xce, 0x78, 0x04, 0xfd, 0x60, 0x2c, 0xd3, 0x17, 0xb7, 0xce,
0xa1, 0x1e, 0x8c, 0xc7, 0xae, 0x2e, 0xa4, 0x05, 0xb4, 0x0d, 0x46, 0xb1, 0x59,
0x2a, 0x30, 0xf0, 0xcb, 0x6e, 0x8c, 0x4f, 0x17, 0xd7, 0xf7, 0xc4, 0x7f, 0xeb,
],
internal_fp: [
0x30, 0xfe, 0x0d, 0x61, 0x0f, 0x94, 0x7b, 0x2c, 0x26, 0x0e, 0x7b, 0x29, 0xe7,
0x9e, 0x5c, 0x2e, 0x7d, 0x3e, 0x14, 0xab, 0xf9, 0x79, 0xf6, 0x40, 0x6d, 0x07,
0xba, 0xf8, 0xfa, 0xdd, 0xf4, 0x95,
],
},
TestVector {
ask: None,
@ -1122,6 +1334,48 @@ mod tests {
]),
d2: None,
dmax: None,
internal_nsk: None,
internal_ovk: [
0xdd, 0xba, 0xc2, 0xa4, 0x93, 0xf5, 0x3c, 0x3b, 0x09, 0x33, 0xd9, 0x13, 0xde,
0xf8, 0x88, 0x48, 0x65, 0x4c, 0x08, 0x7c, 0x12, 0x60, 0x9d, 0xf0, 0x1b, 0xaf,
0x94, 0x05, 0xce, 0x78, 0x04, 0xfd,
],
internal_dk: [
0x60, 0x2c, 0xd3, 0x17, 0xb7, 0xce, 0xa1, 0x1e, 0x8c, 0xc7, 0xae, 0x2e, 0xa4,
0x05, 0xb4, 0x0d, 0x46, 0xb1, 0x59, 0x2a, 0x30, 0xf0, 0xcb, 0x6e, 0x8c, 0x4f,
0x17, 0xd7, 0xf7, 0xc4, 0x7f, 0xeb,
],
internal_nk: [
0xf9, 0x12, 0x87, 0xc0, 0x6e, 0x30, 0xb6, 0x5e, 0xa1, 0xbd, 0xb7, 0x16, 0xb2,
0x31, 0xde, 0x67, 0x78, 0xa5, 0xd8, 0x0e, 0xe5, 0xcd, 0x9c, 0x06, 0x0d, 0x1a,
0xba, 0xca, 0xe0, 0xaa, 0xe2, 0x3b,
],
internal_ivk: [
0x1d, 0x59, 0xea, 0x20, 0x17, 0x88, 0x18, 0x64, 0xd2, 0x4e, 0xad, 0xb5, 0xcf,
0x34, 0x68, 0xa4, 0x1a, 0x1b, 0x2a, 0xaa, 0x0d, 0x1b, 0x3a, 0x72, 0xc6, 0xda,
0x9c, 0xe6, 0x50, 0x2a, 0x0a, 0x05,
],
internal_xsk: None,
internal_xfvk: [
0x02, 0xdb, 0x99, 0x9e, 0x07, 0x02, 0x00, 0x00, 0x80, 0x97, 0xce, 0x15, 0xf4,
0xed, 0x1b, 0x97, 0x39, 0xb0, 0x26, 0x2a, 0x46, 0x3b, 0xcb, 0x3d, 0xc9, 0xb3,
0xbd, 0x23, 0x23, 0xa9, 0xba, 0xa4, 0x41, 0xca, 0x42, 0x77, 0x73, 0x83, 0xa8,
0xd4, 0x35, 0xa6, 0xc5, 0x92, 0x5a, 0x0f, 0x85, 0xfa, 0x4f, 0x1e, 0x40, 0x5e,
0x3a, 0x49, 0x70, 0xd0, 0xc4, 0xa4, 0xb4, 0x81, 0x44, 0x38, 0xf4, 0xe9, 0xd4,
0x52, 0x0e, 0x20, 0xf7, 0xfd, 0xcf, 0x38, 0x41, 0xf9, 0x12, 0x87, 0xc0, 0x6e,
0x30, 0xb6, 0x5e, 0xa1, 0xbd, 0xb7, 0x16, 0xb2, 0x31, 0xde, 0x67, 0x78, 0xa5,
0xd8, 0x0e, 0xe5, 0xcd, 0x9c, 0x06, 0x0d, 0x1a, 0xba, 0xca, 0xe0, 0xaa, 0xe2,
0x3b, 0xdd, 0xba, 0xc2, 0xa4, 0x93, 0xf5, 0x3c, 0x3b, 0x09, 0x33, 0xd9, 0x13,
0xde, 0xf8, 0x88, 0x48, 0x65, 0x4c, 0x08, 0x7c, 0x12, 0x60, 0x9d, 0xf0, 0x1b,
0xaf, 0x94, 0x05, 0xce, 0x78, 0x04, 0xfd, 0x60, 0x2c, 0xd3, 0x17, 0xb7, 0xce,
0xa1, 0x1e, 0x8c, 0xc7, 0xae, 0x2e, 0xa4, 0x05, 0xb4, 0x0d, 0x46, 0xb1, 0x59,
0x2a, 0x30, 0xf0, 0xcb, 0x6e, 0x8c, 0x4f, 0x17, 0xd7, 0xf7, 0xc4, 0x7f, 0xeb,
],
internal_fp: [
0x30, 0xfe, 0x0d, 0x61, 0x0f, 0x94, 0x7b, 0x2c, 0x26, 0x0e, 0x7b, 0x29, 0xe7,
0x9e, 0x5c, 0x2e, 0x7d, 0x3e, 0x14, 0xab, 0xf9, 0x79, 0xf6, 0x40, 0x6d, 0x07,
0xba, 0xf8, 0xfa, 0xdd, 0xf4, 0x95,
],
},
TestVector {
ask: None,
@ -1187,6 +1441,48 @@ mod tests {
dmax: Some([
0x1a, 0x73, 0x0f, 0xeb, 0x00, 0x59, 0xcf, 0x1f, 0x5b, 0xde, 0xa8,
]),
internal_nsk: None,
internal_ovk: [
0xbf, 0x19, 0xe2, 0x57, 0xdd, 0x83, 0x3e, 0x02, 0x94, 0xec, 0x2a, 0xcb, 0xdf,
0xa4, 0x0e, 0x14, 0x52, 0xf8, 0xe6, 0xa1, 0xf0, 0xc7, 0xf6, 0xf3, 0xab, 0xe5,
0x6a, 0xfd, 0x5f, 0x6e, 0x26, 0x18,
],
internal_dk: [
0x1f, 0xfd, 0x6f, 0x81, 0xfe, 0x85, 0xc4, 0x9f, 0xe3, 0xe7, 0x3e, 0xf7, 0x3e,
0x50, 0x11, 0x38, 0x22, 0xca, 0x62, 0x67, 0x31, 0x2b, 0x7a, 0xce, 0xd0, 0xc1,
0x56, 0xa3, 0x2b, 0x3f, 0x24, 0x38,
],
internal_nk: [
0x82, 0x2f, 0x2f, 0x70, 0x96, 0x0f, 0x05, 0xd6, 0x96, 0x74, 0x58, 0xe3, 0x92,
0x10, 0xd5, 0x77, 0x1f, 0x98, 0x47, 0xae, 0xf9, 0xe3, 0x4d, 0x94, 0xb8, 0xaf,
0xbf, 0x95, 0xbb, 0xc4, 0xd2, 0x27,
],
internal_ivk: [
0xf9, 0x8a, 0x76, 0x09, 0x8e, 0x91, 0x05, 0x03, 0xe8, 0x02, 0x77, 0x52, 0x04,
0x2d, 0xe8, 0x7e, 0x7d, 0x89, 0x3a, 0xb0, 0x14, 0x5e, 0xbc, 0x3b, 0x05, 0x97,
0xc2, 0x39, 0x7f, 0x69, 0xd2, 0x01,
],
internal_xsk: None,
internal_xfvk: [
0x03, 0x48, 0xc1, 0x83, 0x75, 0x03, 0x00, 0x00, 0x00, 0x8d, 0x93, 0x7b, 0xcf,
0x81, 0xba, 0x43, 0x0d, 0x5b, 0x49, 0xaf, 0xc0, 0xa4, 0x03, 0x36, 0x7b, 0x1f,
0xd9, 0x98, 0x79, 0xec, 0xba, 0x41, 0xbe, 0x05, 0x1c, 0x5a, 0x4a, 0xa7, 0xd6,
0xe7, 0xe8, 0xb1, 0x85, 0xc5, 0x7b, 0x50, 0x9c, 0x25, 0x36, 0xc4, 0xf2, 0xd3,
0x26, 0xd7, 0x66, 0xc8, 0xfa, 0xb2, 0x54, 0x47, 0xde, 0x53, 0x75, 0xa9, 0x32,
0x8d, 0x64, 0x9d, 0xda, 0xbd, 0x97, 0xa6, 0xa3, 0x82, 0x2f, 0x2f, 0x70, 0x96,
0x0f, 0x05, 0xd6, 0x96, 0x74, 0x58, 0xe3, 0x92, 0x10, 0xd5, 0x77, 0x1f, 0x98,
0x47, 0xae, 0xf9, 0xe3, 0x4d, 0x94, 0xb8, 0xaf, 0xbf, 0x95, 0xbb, 0xc4, 0xd2,
0x27, 0xbf, 0x19, 0xe2, 0x57, 0xdd, 0x83, 0x3e, 0x02, 0x94, 0xec, 0x2a, 0xcb,
0xdf, 0xa4, 0x0e, 0x14, 0x52, 0xf8, 0xe6, 0xa1, 0xf0, 0xc7, 0xf6, 0xf3, 0xab,
0xe5, 0x6a, 0xfd, 0x5f, 0x6e, 0x26, 0x18, 0x1f, 0xfd, 0x6f, 0x81, 0xfe, 0x85,
0xc4, 0x9f, 0xe3, 0xe7, 0x3e, 0xf7, 0x3e, 0x50, 0x11, 0x38, 0x22, 0xca, 0x62,
0x67, 0x31, 0x2b, 0x7a, 0xce, 0xd0, 0xc1, 0x56, 0xa3, 0x2b, 0x3f, 0x24, 0x38,
],
internal_fp: [
0xba, 0x64, 0xe4, 0x0d, 0x08, 0x6d, 0x36, 0x2c, 0xa5, 0xa1, 0x7f, 0x5e, 0x3b,
0x1b, 0xee, 0x63, 0x24, 0xc8, 0x4f, 0x10, 0x12, 0x44, 0xa4, 0x00, 0x2a, 0x2e,
0xca, 0xaf, 0x05, 0xbd, 0xd9, 0x81,
],
},
];
@ -1216,10 +1512,7 @@ mod tests {
let xsks = [m, m_1, m_1_2h];
for j in 0..xsks.len() {
let xsk = &xsks[j];
let tv = &test_vectors[j];
for (xsk, tv) in xsks.iter().zip(test_vectors.iter()) {
assert_eq!(xsk.expsk.ask.to_repr().as_ref(), tv.ask.unwrap());
assert_eq!(xsk.expsk.nsk.to_repr().as_ref(), tv.nsk.unwrap());
@ -1230,12 +1523,24 @@ mod tests {
let mut ser = vec![];
xsk.write(&mut ser).unwrap();
assert_eq!(&ser[..], &tv.xsk.unwrap()[..]);
let internal_xsk = xsk.derive_internal();
assert_eq!(internal_xsk.expsk.ask.to_repr().as_ref(), tv.ask.unwrap());
assert_eq!(
internal_xsk.expsk.nsk.to_repr().as_ref(),
tv.internal_nsk.unwrap()
);
assert_eq!(internal_xsk.expsk.ovk.0, tv.internal_ovk);
assert_eq!(internal_xsk.dk.0, tv.internal_dk);
assert_eq!(internal_xsk.chain_code.0, tv.c);
let mut ser = vec![];
internal_xsk.write(&mut ser).unwrap();
assert_eq!(&ser[..], &tv.internal_xsk.unwrap()[..]);
}
for j in 0..xfvks.len() {
let xfvk = &xfvks[j];
let tv = &test_vectors[j];
for (xfvk, tv) in xfvks.iter().zip(test_vectors.iter()) {
assert_eq!(xfvk.fvk.vk.ak.to_bytes(), tv.ak);
assert_eq!(xfvk.fvk.vk.nk.to_bytes(), tv.nk);
@ -1278,6 +1583,24 @@ mod tests {
Some((_, _)) => panic!(),
None => assert!(tv.dmax.is_none()),
}
let internal_xfvk = xfvk.derive_internal();
assert_eq!(internal_xfvk.fvk.vk.ak.to_bytes(), tv.ak);
assert_eq!(internal_xfvk.fvk.vk.nk.to_bytes(), tv.internal_nk);
assert_eq!(internal_xfvk.fvk.ovk.0, tv.internal_ovk);
assert_eq!(internal_xfvk.dk.0, tv.internal_dk);
assert_eq!(internal_xfvk.chain_code.0, tv.c);
assert_eq!(
internal_xfvk.fvk.vk.ivk().to_repr().as_ref(),
tv.internal_ivk
);
let mut ser = vec![];
internal_xfvk.write(&mut ser).unwrap();
assert_eq!(&ser[..], &tv.internal_xfvk[..]);
assert_eq!(FvkFingerprint::from(&internal_xfvk.fvk).0, tv.internal_fp);
}
}
}