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:
commit
e63979e80a
|
@ -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" }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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(
|
||||
¶ms.b58_pubkey_address_prefix(),
|
||||
¶ms.b58_script_address_prefix(),
|
||||
addr,
|
||||
),
|
||||
RecipientAddress::Shielded(pa) => encode_payment_address_p(params, pa),
|
||||
RecipientAddress::Transparent(addr) => encode_transparent_address_p(params, addr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
¶ms.b58_pubkey_address_prefix(),
|
||||
¶ms.b58_script_address_prefix(),
|
||||
self,
|
||||
)
|
||||
}
|
||||
|
||||
fn decode(params: &P, address: &str) -> Result<TransparentAddress, TransparentCodecError> {
|
||||
decode_transparent_address(
|
||||
¶ms.b58_pubkey_address_prefix(),
|
||||
¶ms.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(
|
||||
¶ms.b58_pubkey_address_prefix(),
|
||||
¶ms.b58_script_address_prefix(),
|
||||
addr,
|
||||
)
|
||||
}
|
||||
|
||||
/// Decodes a [`TransparentAddress`] from a Base58Check-encoded string.
|
||||
///
|
||||
/// # Examples
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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(¶ms, &request.to_uri(¶ms).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(¤t, &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))
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]);
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))[..] {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue