From cff457ff15af74ca87a2922054413f7fa21afc69 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Tue, 22 Dec 2020 11:10:13 -0300 Subject: [PATCH 01/79] PoC Auto-Shielding Add retrieval of transparent UTXOs to WalletRead Co-authored-by: Kris Nuttycombe Co-authored-by: Kevin Gorham --- zcash_client_backend/Cargo.toml | 4 + zcash_client_backend/src/data_api.rs | 34 +++++-- zcash_client_backend/src/data_api/error.rs | 2 + zcash_client_backend/src/data_api/wallet.rs | 100 +++++++++++++++++- zcash_client_backend/src/encoding.rs | 57 +++++++++++ zcash_client_backend/src/keys.rs | 20 +++- zcash_client_backend/src/wallet.rs | 15 ++- zcash_client_sqlite/src/error.rs | 6 +- zcash_client_sqlite/src/lib.rs | 95 +++++++++++++----- zcash_client_sqlite/src/wallet.rs | 106 ++++++++++++++++++-- zcash_client_sqlite/src/wallet/init.rs | 15 +++ zcash_client_sqlite/src/wallet/transact.rs | 4 +- 12 files changed, 415 insertions(+), 43 deletions(-) diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index da0518aa7..1d7f8e9f2 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -26,6 +26,9 @@ percent-encoding = "2.1.0" proptest = { version = "0.10.1", optional = true } protobuf = "2.20" rand_core = "0.5.1" +ripemd160 = { version = "0.9.1", optional = true } +secp256k1 = { version = "0.20", optional = true } +sha2 = "0.9" subtle = "2.2.3" time = "0.2" zcash_note_encryption = { version = "0.0", path = "../components/zcash_note_encryption" } @@ -43,6 +46,7 @@ zcash_client_sqlite = { version = "0.3", path = "../zcash_client_sqlite" } zcash_proofs = { version = "0.5", path = "../zcash_proofs" } [features] +transparent-inputs = ["ripemd160", "secp256k1"] test-dependencies = ["proptest", "zcash_primitives/test-dependencies"] [badges] diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 166fd80cb..2d8a33eb1 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -7,10 +7,14 @@ use std::fmt::Debug; use zcash_primitives::{ block::BlockHash, consensus::BlockHeight, + legacy::TransparentAddress, memo::{Memo, MemoBytes}, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{Node, Nullifier, PaymentAddress}, - transaction::{components::Amount, Transaction, TxId}, + transaction::{ + components::{Amount, OutPoint}, + Transaction, TxId, + }, zip32::ExtendedFullViewingKey, }; @@ -19,7 +23,7 @@ use crate::{ data_api::wallet::ANCHOR_OFFSET, decrypt::DecryptedOutput, proto::compact_formats::CompactBlock, - wallet::{AccountId, SpendableNote, WalletTx}, + wallet::{AccountId, SpendableNote, WalletTransparentOutput, WalletTx}, }; pub mod chain; @@ -160,7 +164,7 @@ pub trait WalletRead { fn get_nullifiers(&self) -> Result, Self::Error>; /// Return all spendable notes. - fn get_spendable_notes( + fn get_spendable_sapling_notes( &self, account: AccountId, anchor_height: BlockHeight, @@ -168,12 +172,18 @@ pub trait WalletRead { /// Returns a list of spendable 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, anchor_height: BlockHeight, ) -> Result, Self::Error>; + + fn get_spendable_transparent_utxos( + &self, + address: &TransparentAddress, + anchor_height: BlockHeight, + ) -> Result, Self::Error>; } /// The subset of information that is relevant to this wallet that has been @@ -215,6 +225,7 @@ pub struct SentTransaction<'a> { pub recipient_address: &'a RecipientAddress, pub value: Amount, pub memo: Option, + pub utxos_spent: Vec, } /// This trait encapsulates the write capabilities required to update stored @@ -274,6 +285,7 @@ pub mod testing { use zcash_primitives::{ block::BlockHash, consensus::BlockHeight, + legacy::TransparentAddress, memo::Memo, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{Node, Nullifier, PaymentAddress}, @@ -283,7 +295,7 @@ pub mod testing { use crate::{ proto::compact_formats::CompactBlock, - wallet::{AccountId, SpendableNote}, + wallet::{AccountId, SpendableNote, WalletTransparentOutput}, }; use super::{ @@ -380,7 +392,7 @@ pub mod testing { Ok(Vec::new()) } - fn get_spendable_notes( + fn get_spendable_sapling_notes( &self, _account: AccountId, _anchor_height: BlockHeight, @@ -388,7 +400,7 @@ pub mod testing { Ok(Vec::new()) } - fn select_spendable_notes( + fn select_spendable_sapling_notes( &self, _account: AccountId, _target_value: Amount, @@ -396,6 +408,14 @@ pub mod testing { ) -> Result, Self::Error> { Ok(Vec::new()) } + + fn get_spendable_transparent_utxos( + &self, + _address: &TransparentAddress, + _anchor_height: BlockHeight, + ) -> Result, Self::Error> { + Ok(Vec::new()) + } } impl WalletWrite for MockWalletDb { diff --git a/zcash_client_backend/src/data_api/error.rs b/zcash_client_backend/src/data_api/error.rs index 1968af8f9..086daee40 100644 --- a/zcash_client_backend/src/data_api/error.rs +++ b/zcash_client_backend/src/data_api/error.rs @@ -25,6 +25,8 @@ pub enum ChainInvalid { #[derive(Debug)] pub enum Error { /// 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. diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 82a9a9a9b..be5760614 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -20,6 +20,12 @@ use crate::{ wallet::{AccountId, OvkPolicy}, }; +#[cfg(feature = "transparent-inputs")] +use zcash_primitives::{legacy::Script, transaction::components::TxOut}; + +#[cfg(feature = "transparent-inputs")] +use crate::keys::derive_transparent_address_from_secret_key; + pub const ANCHOR_OFFSET: u32 = 10; /// Scans a [`Transaction`] for any information that can be decrypted by the accounts in @@ -184,7 +190,8 @@ where .and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?; let target_value = value + DEFAULT_FEE; - 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.iter().map(|n| n.note_value).sum(); @@ -254,5 +261,96 @@ where recipient_address: to, value, memo, + utxos_spent: vec![], + }) +} + +#[cfg(feature = "transparent-inputs")] +pub fn shield_funds( + wallet_db: &mut D, + params: &P, + prover: impl TxProver, + account: AccountId, + sk: &secp256k1::SecretKey, + extsk: &ExtendedSpendingKey, + memo: &MemoBytes, +) -> Result +where + E: From>, + P: consensus::Parameters, + R: Copy + Debug, + D: WalletWrite, +{ + let (latest_scanned_height, latest_anchor) = wallet_db + .get_target_and_anchor_heights() + .and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?; + + // derive the corresponding t-address + let taddr = derive_transparent_address_from_secret_key(*sk); + + // derive own shielded address from the provided extended spending key + let z_address = extsk.default_address().unwrap().1; + + let exfvk = ExtendedFullViewingKey::from(extsk); + + let ovk = exfvk.fvk.ovk; + + // get UTXOs from DB + let utxos = wallet_db.get_spendable_transparent_utxos(&taddr, latest_anchor)?; + let total_amount = utxos.iter().map(|utxo| utxo.value).sum::(); + + let fee = DEFAULT_FEE; + if fee >= total_amount { + return Err(E::from(Error::InsufficientBalance(total_amount, fee))); + } + + let amount_to_shield = total_amount - fee; + + let mut builder = Builder::new(params.clone(), latest_scanned_height); + + for utxo in &utxos { + let coin = TxOut { + value: utxo.value, + script_pubkey: Script { + 0: utxo.script.clone(), + }, + }; + + builder + .add_transparent_input(*sk, utxo.outpoint.clone(), coin) + .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, + Some(memo.clone()), + ) + .map_err(Error::Builder)?; + + let consensus_branch_id = BranchId::for_height(params, latest_anchor); + + let (tx, tx_metadata) = builder + .build(consensus_branch_id, &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(), + output_index, + account, + recipient_address: &RecipientAddress::Shielded(z_address), + value: amount_to_shield, + memo: Some(memo.clone()), + utxos_spent: utxos.iter().map(|utxo| utxo.outpoint.clone()).collect(), }) } diff --git a/zcash_client_backend/src/encoding.rs b/zcash_client_backend/src/encoding.rs index 827b99bf0..18cda7292 100644 --- a/zcash_client_backend/src/encoding.rs +++ b/zcash_client_backend/src/encoding.rs @@ -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

+where + Self: std::marker::Sized, +{ + type Error; + + fn encode(&self, params: &P) -> String; + fn decode(params: &P, address: &str) -> Result; +} + +#[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 AddressCodec

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 { + 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 diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index c2fd72b34..c0de03693 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -1,6 +1,14 @@ //! Helper functions for managing light client key material. +#![cfg(feature = "transparent-inputs")] -use zcash_primitives::zip32::{ChildIndex, ExtendedSpendingKey}; +use zcash_primitives::{ + legacy::TransparentAddress, + zip32::{ChildIndex, ExtendedSpendingKey}, +}; + +use secp256k1::{key::PublicKey, Secp256k1}; + +use sha2::{Digest, Sha256}; /// Derives the ZIP 32 [`ExtendedSpendingKey`] for a given coin type and account from the /// given seed. @@ -33,6 +41,16 @@ pub fn spending_key(seed: &[u8], coin_type: u32, account: u32) -> ExtendedSpendi ) } +pub fn derive_transparent_address_from_secret_key( + secret_key: secp256k1::key::SecretKey, +) -> TransparentAddress { + let secp = Secp256k1::new(); + let pk = PublicKey::from_secret_key(&secp, &secret_key); + let mut hash160 = ripemd160::Ripemd160::new(); + hash160.update(Sha256::digest(&pk.serialize()[..].to_vec())); + TransparentAddress::PublicKey(*hash160.finalize().as_ref()) +} + #[cfg(test)] mod tests { use super::spending_key; diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index 220aaf6e3..39e3d9d2c 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -4,11 +4,16 @@ use subtle::{Choice, ConditionallySelectable}; use zcash_primitives::{ + consensus::BlockHeight, + legacy::TransparentAddress, merkle_tree::IncrementalWitness, sapling::{ keys::OutgoingViewingKey, Diversifier, Node, Note, Nullifier, PaymentAddress, Rseed, }, - transaction::{components::Amount, TxId}, + transaction::{ + components::{Amount, OutPoint}, + TxId, + }, }; /// A type-safe wrapper for account identifiers. @@ -39,6 +44,14 @@ pub struct WalletTx { pub shielded_outputs: Vec>, } +pub struct WalletTransparentOutput { + pub address: TransparentAddress, + pub outpoint: OutPoint, + pub script: Vec, + pub value: Amount, + pub height: BlockHeight, +} + /// A subset of a [`SpendDescription`] relevant to wallets and light clients. /// /// [`SpendDescription`]: zcash_primitives::transaction::components::SpendDescription diff --git a/zcash_client_sqlite/src/error.rs b/zcash_client_sqlite/src/error.rs index e83c20e5d..6c285fb4c 100644 --- a/zcash_client_sqlite/src/error.rs +++ b/zcash_client_sqlite/src/error.rs @@ -3,7 +3,7 @@ use std::error; use std::fmt; -use zcash_client_backend::data_api; +use zcash_client_backend::{data_api, encoding::TransparentCodecError}; use crate::NoteId; @@ -32,6 +32,9 @@ pub enum SqliteClientError { /// Base58 decoding error Base58(bs58::decode::Error), + /// Base58 decoding error + TransparentAddress(TransparentCodecError), + /// Wrapper for rusqlite errors. DbError(rusqlite::Error), @@ -68,6 +71,7 @@ impl fmt::Display for SqliteClientError { SqliteClientError::InvalidNoteId => write!(f, "The note ID associated with an inserted witness must correspond to a received note."), 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), diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 0d9047574..c9ba7cad8 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -41,6 +41,7 @@ use rusqlite::{Connection, Statement, NO_PARAMS}; use zcash_primitives::{ block::BlockHash, consensus::{self, BlockHeight}, + legacy::TransparentAddress, memo::Memo, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{Node, Nullifier, PaymentAddress}, @@ -54,7 +55,7 @@ use zcash_client_backend::{ }, encoding::encode_payment_address, proto::compact_formats::CompactBlock, - wallet::{AccountId, SpendableNote}, + wallet::{AccountId, SpendableNote, WalletTransparentOutput}, }; use crate::error::SqliteClientError; @@ -80,6 +81,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(i64); + /// A wrapper for the SQLite connection to the wallet database. pub struct WalletDb

{ conn: Connection, @@ -91,7 +97,9 @@ impl WalletDb

{ pub fn for_path>(path: F, params: P) -> Result { Connection::open(path).map(move |conn| WalletDb { conn, params }) } +} +impl WalletDb

{ /// Given a wallet database connection, obtain a handle for the write operations /// for that database. This operation may eagerly initialize and cache sqlite /// prepared statements that are used in write operations. @@ -122,9 +130,18 @@ impl WalletDb

{ 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 = ?" )?, + 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" + )?, + 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)" + )?, 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)", @@ -176,25 +193,25 @@ impl WalletRead for WalletDb

{ type TxRef = i64; fn block_height_extrema(&self) -> Result, Self::Error> { - wallet::block_height_extrema(self).map_err(SqliteClientError::from) + wallet::block_height_extrema(&self).map_err(SqliteClientError::from) } fn get_block_hash(&self, block_height: BlockHeight) -> Result, Self::Error> { - wallet::get_block_hash(self, block_height).map_err(SqliteClientError::from) + wallet::get_block_hash(&self, block_height).map_err(SqliteClientError::from) } fn get_tx_height(&self, txid: TxId) -> Result, Self::Error> { - wallet::get_tx_height(self, txid).map_err(SqliteClientError::from) + wallet::get_tx_height(&self, txid).map_err(SqliteClientError::from) } fn get_extended_full_viewing_keys( &self, ) -> Result, Self::Error> { - wallet::get_extended_full_viewing_keys(self) + wallet::get_extended_full_viewing_keys(&self) } fn get_address(&self, account: AccountId) -> Result, Self::Error> { - wallet::get_address(self, account) + wallet::get_address(&self, account) } fn is_valid_account_extfvk( @@ -202,7 +219,7 @@ impl WalletRead for WalletDb

{ account: AccountId, extfvk: &ExtendedFullViewingKey, ) -> Result { - wallet::is_valid_account_extfvk(self, account, extfvk) + wallet::is_valid_account_extfvk(&self, account, extfvk) } fn get_balance_at( @@ -210,7 +227,7 @@ impl WalletRead for WalletDb

{ account: AccountId, anchor_height: BlockHeight, ) -> Result { - wallet::get_balance_at(self, account, anchor_height) + wallet::get_balance_at(&self, account, anchor_height) } fn get_memo(&self, id_note: Self::NoteRef) -> Result { @@ -224,7 +241,7 @@ impl WalletRead for WalletDb

{ &self, block_height: BlockHeight, ) -> Result>, Self::Error> { - wallet::get_commitment_tree(self, block_height) + wallet::get_commitment_tree(&self, block_height) } #[allow(clippy::type_complexity)] @@ -232,28 +249,41 @@ impl WalletRead for WalletDb

{ &self, block_height: BlockHeight, ) -> Result)>, Self::Error> { - wallet::get_witnesses(self, block_height) + wallet::get_witnesses(&self, block_height) } fn get_nullifiers(&self) -> Result, Self::Error> { - wallet::get_nullifiers(self) + wallet::get_nullifiers(&self) } - fn get_spendable_notes( + fn get_spendable_sapling_notes( &self, account: AccountId, anchor_height: BlockHeight, ) -> Result, Self::Error> { - wallet::transact::get_spendable_notes(self, account, anchor_height) + 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, Self::Error> { - wallet::transact::select_spendable_notes(self, account, target_value, anchor_height) + wallet::transact::select_spendable_sapling_notes( + &self, + account, + target_value, + anchor_height, + ) + } + + fn get_spendable_transparent_utxos( + &self, + address: &TransparentAddress, + anchor_height: BlockHeight, + ) -> Result, Self::Error> { + wallet::get_spendable_transparent_utxos(&self, address, anchor_height) } } @@ -275,8 +305,10 @@ 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>, + stmt_mark_transparent_utxo_spent: Statement<'a>, + stmt_insert_received_transparent_utxo: Statement<'a>, stmt_insert_received_note: Statement<'a>, stmt_update_received_note: Statement<'a>, stmt_select_received_note: Statement<'a>, @@ -355,22 +387,32 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> { self.wallet_db.get_nullifiers() } - fn get_spendable_notes( + fn get_spendable_sapling_notes( &self, account: AccountId, anchor_height: BlockHeight, ) -> Result, 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, Self::Error> { self.wallet_db - .select_spendable_notes(account, target_value, anchor_height) + .select_spendable_sapling_notes(account, target_value, anchor_height) + } + + fn get_spendable_transparent_utxos( + &self, + address: &TransparentAddress, + anchor_height: BlockHeight, + ) -> Result, Self::Error> { + self.wallet_db + .get_spendable_transparent_utxos(address, anchor_height) } } @@ -426,9 +468,12 @@ 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)?; } + //TODO + //wallet::mark_transparent_utxo_spent(up, tx_ref, &utxo.outpoint)?; + for output in &tx.shielded_outputs { let received_note_id = wallet::put_received_note(up, output, tx_row)?; @@ -490,7 +535,11 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { // Assumes that create_spend_to_address() will never be called in parallel, which is a // reasonable assumption for a light client such as a mobile phone. for spend in &sent_tx.tx.shielded_spends { - wallet::mark_spent(up, tx_ref, &spend.nullifier)?; + wallet::mark_sapling_note_spent(up, tx_ref, &spend.nullifier)?; + } + + for utxo_outpoint in &sent_tx.utxos_spent { + wallet::mark_transparent_utxo_spent(up, tx_ref, &utxo_outpoint)?; } wallet::insert_sent_note( diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 9fbc41185..483f80e7f 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -15,10 +15,14 @@ use std::convert::TryFrom; use zcash_primitives::{ block::BlockHash, consensus::{self, BlockHeight, NetworkUpgrade}, + legacy::TransparentAddress, memo::{Memo, MemoBytes}, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{Node, Note, Nullifier, PaymentAddress}, - transaction::{components::Amount, Transaction, TxId}, + transaction::{ + components::{Amount, OutPoint}, + Transaction, TxId, + }, zip32::ExtendedFullViewingKey, }; @@ -27,13 +31,13 @@ use zcash_client_backend::{ data_api::error::Error, encoding::{ decode_extended_full_viewing_key, decode_payment_address, encode_extended_full_viewing_key, - encode_payment_address, + encode_payment_address, AddressCodec, }, - wallet::{AccountId, WalletShieldedOutput, WalletTx}, + wallet::{AccountId, WalletShieldedOutput, WalletTransparentOutput, WalletTx}, DecryptedOutput, }; -use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDb}; +use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, UtxoId, WalletDb}; pub mod init; pub mod transact; @@ -588,6 +592,57 @@ pub fn get_nullifiers

( Ok(res) } +pub fn get_spendable_transparent_utxos( + wdb: &WalletDb

, + address: &TransparentAddress, + anchor_height: BlockHeight, +) -> Result, SqliteClientError> { + let mut stmt_blocks = wdb.conn.prepare( + "SELECT address, prevout_txid, prevout_idx, script, value_zat, height + FROM utxos + WHERE address = ? + AND height <= ? + AND spent_in_tx IS NULL", + )?; + + let addr_str = address.encode(&wdb.params); + + let rows = stmt_blocks.query_map(params![addr_str, u32::from(anchor_height)], |row| { + let addr: String = row.get(0)?; + let address = TransparentAddress::decode(&wdb.params, &addr).map_err(|e| { + rusqlite::Error::FromSqlConversionFailure( + addr.len(), + rusqlite::types::Type::Text, + Box::new(e), + ) + })?; + + let id: Vec = row.get(1)?; + + let mut txid_bytes = [0u8; 32]; + txid_bytes.copy_from_slice(&id); + let index: i32 = row.get(2)?; + let script: Vec = row.get(3)?; + let value: i64 = row.get(4)?; + let height: u32 = row.get(5)?; + + Ok(WalletTransparentOutput { + address, + outpoint: OutPoint::new(txid_bytes, index as u32), + script, + value: Amount::from_i64(value).unwrap(), + height: BlockHeight::from(height), + }) + })?; + + let mut utxos = Vec::::new(); + + for utxo in rows { + utxos.push(utxo.unwrap()) + } + Ok(utxos) +} + /// Inserts information about a scanned block into the database. pub fn insert_block<'a, P>( stmts: &mut DataConnStmtCache<'a, P>, @@ -676,18 +731,56 @@ 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>( +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(()) } /// Records the specified shielded output as having been received. +pub 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(()) +} + +pub fn put_received_transparent_utxo<'a, P: consensus::Parameters>( + stmts: &mut DataConnStmtCache<'a, P>, + output: &WalletTransparentOutput, +) -> Result { + 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.script), + (&":value_zat", &i64::from(output.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())) +} + // Assumptions: // - A transaction will not contain more than 2^63 shielded outputs. // - A note value will never exceed 2^63 zatoshis. @@ -847,7 +940,6 @@ pub fn insert_sent_note<'a, P: consensus::Parameters>( Ok(()) } - #[cfg(test)] mod tests { use tempfile::NamedTempFile; diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index f21f53182..572e54b2b 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -106,6 +106,21 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { )", 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(()) } diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index 5fccb430f..35ed92f0c 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -59,7 +59,7 @@ fn to_spendable_note(row: &Row) -> Result { }) } -pub fn get_spendable_notes

( +pub fn get_spendable_sapling_notes

( wdb: &WalletDb

, account: AccountId, anchor_height: BlockHeight, @@ -87,7 +87,7 @@ pub fn get_spendable_notes

( notes.collect::>() } -pub fn select_spendable_notes

( +pub fn select_spendable_sapling_notes

( wdb: &WalletDb

, account: AccountId, target_value: Amount, From 862e221a9bf46098436e7ca895d8171b4cfb7b26 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 12 Feb 2021 14:08:31 -0700 Subject: [PATCH 02/79] Put transparent dependencies behind a feature flag. --- zcash_client_backend/Cargo.toml | 3 +- zcash_client_backend/src/data_api.rs | 7 ++- zcash_client_backend/src/keys.rs | 70 ++++++++++++++++++++++++---- zcash_client_backend/src/wallet.rs | 13 +++--- zcash_client_sqlite/Cargo.toml | 1 + zcash_client_sqlite/src/lib.rs | 16 +++++-- zcash_client_sqlite/src/wallet.rs | 16 +++++-- zcash_primitives/Cargo.toml | 2 +- 8 files changed, 103 insertions(+), 25 deletions(-) diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index 1d7f8e9f2..916c99535 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -20,6 +20,7 @@ base64 = "0.13" ff = "0.8" group = "0.8" hex = "0.4" +hdwallet = { version = "0.3.0", optional = true } jubjub = "0.5.1" nom = "6.1" percent-encoding = "2.1.0" @@ -27,7 +28,7 @@ proptest = { version = "0.10.1", optional = true } protobuf = "2.20" rand_core = "0.5.1" ripemd160 = { version = "0.9.1", optional = true } -secp256k1 = { version = "0.20", optional = true } +secp256k1 = { version = "0.19", optional = true } sha2 = "0.9" subtle = "2.2.3" time = "0.2" diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 2d8a33eb1..645691cf6 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -7,7 +7,6 @@ use std::fmt::Debug; use zcash_primitives::{ block::BlockHash, consensus::BlockHeight, - legacy::TransparentAddress, memo::{Memo, MemoBytes}, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{Node, Nullifier, PaymentAddress}, @@ -23,9 +22,12 @@ use crate::{ data_api::wallet::ANCHOR_OFFSET, decrypt::DecryptedOutput, proto::compact_formats::CompactBlock, - wallet::{AccountId, SpendableNote, WalletTransparentOutput, WalletTx}, + wallet::{AccountId, SpendableNote, WalletTx}, }; +#[cfg(feature = "transparent-inputs")] +use {crate::wallet::WalletTransparentOutput, zcash_primitives::legacy::TransparentAddress}; + pub mod chain; pub mod error; pub mod wallet; @@ -179,6 +181,7 @@ pub trait WalletRead { anchor_height: BlockHeight, ) -> Result, Self::Error>; + #[cfg(feature = "transparent-inputs")] fn get_spendable_transparent_utxos( &self, address: &TransparentAddress, diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index c0de03693..ba32975f1 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -1,15 +1,18 @@ //! Helper functions for managing light client key material. -#![cfg(feature = "transparent-inputs")] -use zcash_primitives::{ - legacy::TransparentAddress, - zip32::{ChildIndex, ExtendedSpendingKey}, +use zcash_primitives::zip32::{ChildIndex, ExtendedSpendingKey}; + +#[cfg(feature = "transparent-inputs")] +use { + crate::wallet::AccountId, + bs58::decode::Error as Bs58Error, + hdwallet::{ExtendedPrivKey, KeyIndex}, + secp256k1::{key::PublicKey, Secp256k1, SecretKey}, + sha2::{Digest, Sha256}, + std::convert::TryInto, + zcash_primitives::{consensus, legacy::TransparentAddress}, }; -use secp256k1::{key::PublicKey, Secp256k1}; - -use sha2::{Digest, Sha256}; - /// Derives the ZIP 32 [`ExtendedSpendingKey`] for a given coin type and account from the /// given seed. /// @@ -41,6 +44,7 @@ pub fn spending_key(seed: &[u8], coin_type: u32, account: u32) -> ExtendedSpendi ) } +#[cfg(feature = "transparent-inputs")] pub fn derive_transparent_address_from_secret_key( secret_key: secp256k1::key::SecretKey, ) -> TransparentAddress { @@ -51,6 +55,56 @@ pub fn derive_transparent_address_from_secret_key( TransparentAddress::PublicKey(*hash160.finalize().as_ref()) } +#[cfg(feature = "transparent-inputs")] +pub fn derive_secret_key_from_seed( + params: &P, + seed: &[u8], + account: AccountId, + index: u32, +) -> Result { + let ext_t_key = ExtendedPrivKey::with_seed(&seed)?; + let private_key = ext_t_key + .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)?)? + .derive_private_key(KeyIndex::Normal(0))? + .derive_private_key(KeyIndex::Normal(index))? + .private_key; + + Ok(private_key) +} + +#[cfg(feature = "transparent-inputs")] +pub struct Wif(pub String); + +#[cfg(feature = "transparent-inputs")] +impl Wif { + pub fn from_secret_key(sk: &SecretKey, compressed: bool) -> Self { + let secret_key = sk.as_ref(); + let mut wif = [0u8; 34]; + wif[0] = 0x80; + wif[1..33].copy_from_slice(secret_key); + if compressed { + wif[33] = 0x01; + Wif(bs58::encode(&wif[..]).with_check().into_string()) + } else { + Wif(bs58::encode(&wif[..]).with_check().into_string()) + } + } +} + +#[cfg(feature = "transparent-inputs")] +impl TryInto for Wif { + type Error = Bs58Error; + + fn try_into(self) -> Result { + bs58::decode(&self.0) + .with_check(None) + .into_vec() + .map(|decoded| SecretKey::from_slice(&decoded[1..33]).expect("wrong size key")) + } +} + #[cfg(test)] mod tests { use super::spending_key; diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index 39e3d9d2c..a2f5971b4 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -4,16 +4,16 @@ use subtle::{Choice, ConditionallySelectable}; use zcash_primitives::{ - consensus::BlockHeight, - legacy::TransparentAddress, merkle_tree::IncrementalWitness, sapling::{ keys::OutgoingViewingKey, Diversifier, Node, Note, Nullifier, PaymentAddress, Rseed, }, - transaction::{ - components::{Amount, OutPoint}, - TxId, - }, + transaction::{components::Amount, TxId}, +}; + +#[cfg(feature = "transparent-inputs")] +use zcash_primitives::{ + consensus::BlockHeight, legacy::TransparentAddress, transaction::components::OutPoint, }; /// A type-safe wrapper for account identifiers. @@ -44,6 +44,7 @@ pub struct WalletTx { pub shielded_outputs: Vec>, } +#[cfg(feature = "transparent-inputs")] pub struct WalletTransparentOutput { pub address: TransparentAddress, pub outpoint: OutPoint, diff --git a/zcash_client_sqlite/Cargo.toml b/zcash_client_sqlite/Cargo.toml index 5e1efe0f6..ef7fb0853 100644 --- a/zcash_client_sqlite/Cargo.toml +++ b/zcash_client_sqlite/Cargo.toml @@ -32,4 +32,5 @@ zcash_proofs = { version = "0.5", path = "../zcash_proofs" } [features] mainnet = [] +transparent-inputs = [] test-dependencies = ["zcash_client_backend/test-dependencies"] diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index c9ba7cad8..daddcba8e 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -41,7 +41,6 @@ use rusqlite::{Connection, Statement, NO_PARAMS}; use zcash_primitives::{ block::BlockHash, consensus::{self, BlockHeight}, - legacy::TransparentAddress, memo::Memo, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{Node, Nullifier, PaymentAddress}, @@ -55,11 +54,17 @@ use zcash_client_backend::{ }, encoding::encode_payment_address, proto::compact_formats::CompactBlock, - wallet::{AccountId, SpendableNote, WalletTransparentOutput}, + wallet::{AccountId, SpendableNote}, }; use crate::error::SqliteClientError; +#[cfg(feature = "transparent-inputs")] +use { + zcash_client_backend::wallet::WalletTransparentOutput, + zcash_primitives::legacy::TransparentAddress, +}; + pub mod chain; pub mod error; pub mod wallet; @@ -83,8 +88,9 @@ impl fmt::Display for NoteId { /// A newtype wrapper for sqlite primary key values for the utxos /// table. +#[cfg(feature = "transparent-inputs")] #[derive(Debug, Copy, Clone)] -pub struct UtxoId(i64); +pub struct UtxoId(pub i64); /// A wrapper for the SQLite connection to the wallet database. pub struct WalletDb

{ @@ -138,6 +144,7 @@ impl WalletDb

{ 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)" @@ -278,6 +285,7 @@ impl WalletRead for WalletDb

{ ) } + #[cfg(feature = "transparent-inputs")] fn get_spendable_transparent_utxos( &self, address: &TransparentAddress, @@ -308,6 +316,7 @@ pub struct DataConnStmtCache<'a, P> { stmt_mark_sapling_note_spent: Statement<'a>, stmt_mark_transparent_utxo_spent: Statement<'a>, + #[cfg(feature = "transparent-inputs")] stmt_insert_received_transparent_utxo: Statement<'a>, stmt_insert_received_note: Statement<'a>, stmt_update_received_note: Statement<'a>, @@ -406,6 +415,7 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> { .select_spendable_sapling_notes(account, target_value, anchor_height) } + #[cfg(feature = "transparent-inputs")] fn get_spendable_transparent_utxos( &self, address: &TransparentAddress, diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 483f80e7f..f95c6d426 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -15,7 +15,6 @@ use std::convert::TryFrom; use zcash_primitives::{ block::BlockHash, consensus::{self, BlockHeight, NetworkUpgrade}, - legacy::TransparentAddress, memo::{Memo, MemoBytes}, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{Node, Note, Nullifier, PaymentAddress}, @@ -31,13 +30,20 @@ use zcash_client_backend::{ data_api::error::Error, encoding::{ decode_extended_full_viewing_key, decode_payment_address, encode_extended_full_viewing_key, - encode_payment_address, AddressCodec, + encode_payment_address, }, - wallet::{AccountId, WalletShieldedOutput, WalletTransparentOutput, WalletTx}, + wallet::{AccountId, WalletShieldedOutput, WalletTx}, DecryptedOutput, }; -use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, UtxoId, WalletDb}; +use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDb}; + +#[cfg(feature = "transparent-inputs")] +use { + crate::UtxoId, + zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput}, + zcash_primitives::legacy::TransparentAddress, +}; pub mod init; pub mod transact; @@ -592,6 +598,7 @@ pub fn get_nullifiers

( Ok(res) } +#[cfg(feature = "transparent-inputs")] pub fn get_spendable_transparent_utxos( wdb: &WalletDb

, address: &TransparentAddress, @@ -761,6 +768,7 @@ pub fn mark_transparent_utxo_spent<'a, P>( Ok(()) } +#[cfg(feature = "transparent-inputs")] pub fn put_received_transparent_utxo<'a, P: consensus::Parameters>( stmts: &mut DataConnStmtCache<'a, P>, output: &WalletTransparentOutput, diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index a950331cf..a651df4ec 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -35,7 +35,7 @@ proptest = { version = "0.10.1", optional = true } rand = "0.7" rand_core = "0.5.1" ripemd160 = { version = "0.9", optional = true } -secp256k1 = { version = "0.20", optional = true } +secp256k1 = { version = "0.19", optional = true } sha2 = "0.9" subtle = "2.2.3" zcash_note_encryption = { version = "0.0", path = "../components/zcash_note_encryption" } From ca3e3a45950f4c9dad2fadb04efb294f182ec94e Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 12 Feb 2021 15:54:59 -0700 Subject: [PATCH 03/79] Add WIF derivation for transparent keys. With @gmale --- zcash_client_backend/Cargo.toml | 2 +- zcash_client_backend/src/data_api.rs | 1 + zcash_client_backend/src/data_api/wallet.rs | 1 + zcash_client_backend/src/keys.rs | 50 ++++++++++++++++++++- 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index 916c99535..e90e5e814 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -48,7 +48,7 @@ zcash_proofs = { version = "0.5", path = "../zcash_proofs" } [features] transparent-inputs = ["ripemd160", "secp256k1"] -test-dependencies = ["proptest", "zcash_primitives/test-dependencies"] +test-dependencies = ["proptest", "zcash_primitives/test-dependencies", "hdwallet"] [badges] maintenance = { status = "actively-developed" } diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 645691cf6..361a95d32 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -412,6 +412,7 @@ pub mod testing { Ok(Vec::new()) } + #[cfg(feature = "transparent-inputs")] fn get_spendable_transparent_utxos( &self, _address: &TransparentAddress, diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index be5760614..3632f336c 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -308,6 +308,7 @@ where let mut builder = Builder::new(params.clone(), latest_scanned_height); + #[cfg(feature = "transparent-inputs")] for utxo in &utxos { let coin = TxOut { value: utxo.value, diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index ba32975f1..502801945 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -5,9 +5,9 @@ use zcash_primitives::zip32::{ChildIndex, ExtendedSpendingKey}; #[cfg(feature = "transparent-inputs")] use { crate::wallet::AccountId, - bs58::decode::Error as Bs58Error, + bs58::{self, decode::Error as Bs58Error}, hdwallet::{ExtendedPrivKey, KeyIndex}, - secp256k1::{key::PublicKey, Secp256k1, SecretKey}, + secp256k1::{key::PublicKey, key::SecretKey, Secp256k1}, sha2::{Digest, Sha256}, std::convert::TryInto, zcash_primitives::{consensus, legacy::TransparentAddress}, @@ -109,9 +109,55 @@ impl TryInto for Wif { mod tests { use super::spending_key; + #[cfg(feature = "transparent-inputs")] + use { + super::{derive_secret_key_from_seed, derive_transparent_address_from_secret_key, Wif}, + crate::{encoding::AddressCodec, wallet::AccountId}, + secp256k1::key::SecretKey, + std::convert::TryInto, + zcash_primitives::consensus::MAIN_NETWORK, + }; + #[test] #[should_panic] fn spending_key_panics_on_short_seed() { let _ = spending_key(&[0; 31][..], 0, 0); } + + #[cfg(feature = "transparent-inputs")] + #[test] + fn sk_to_wif() { + let seed_hex = "6ef5f84def6f4b9d38f466586a8380a38593bd47c8cda77f091856176da47f26b5bd1c8d097486e5635df5a66e820d28e1d73346f499801c86228d43f390304f"; + let seed = hex::decode(&seed_hex).unwrap(); + let sk = derive_secret_key_from_seed(&MAIN_NETWORK, &seed, AccountId(0), 0).unwrap(); + assert_eq!( + Wif::from_secret_key(&sk, true).0, + "L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2".to_string() + ); + } + + #[cfg(feature = "transparent-inputs")] + #[test] + fn sk_to_taddr() { + let seed_hex = "6ef5f84def6f4b9d38f466586a8380a38593bd47c8cda77f091856176da47f26b5bd1c8d097486e5635df5a66e820d28e1d73346f499801c86228d43f390304f"; + let seed = hex::decode(&seed_hex).unwrap(); + let sk = derive_secret_key_from_seed(&MAIN_NETWORK, &seed, AccountId(0), 0).unwrap(); + let taddr = derive_transparent_address_from_secret_key(sk); + assert_eq!( + taddr.encode(&MAIN_NETWORK), + "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string() + ); + } + + #[cfg(feature = "transparent-inputs")] + #[test] + fn sk_wif_to_taddr() { + let sk_wif = Wif("L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2".to_string()); + let sk: SecretKey = sk_wif.try_into().expect("invalid wif"); + let taddr = derive_transparent_address_from_secret_key(sk); + assert_eq!( + taddr.encode(&MAIN_NETWORK), + "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string() + ); + } } From a3bc1e3e63e1baa1b188f69c01605bc39ab49e64 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 12 Feb 2021 16:57:44 -0700 Subject: [PATCH 04/79] Rename get_spendable -> get_unspent --- zcash_client_backend/src/data_api.rs | 16 ++++++------ zcash_client_backend/src/data_api/wallet.rs | 4 +-- zcash_client_backend/src/keys.rs | 5 ++-- zcash_client_sqlite/src/lib.rs | 29 +++++++++------------ zcash_client_sqlite/src/wallet.rs | 2 +- zcash_client_sqlite/src/wallet/transact.rs | 4 +-- 6 files changed, 28 insertions(+), 32 deletions(-) diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 361a95d32..4f2e2efa9 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -165,16 +165,16 @@ pub trait WalletRead { /// with which they are associated. fn get_nullifiers(&self) -> Result, Self::Error>; - /// Return all spendable notes. - fn get_spendable_sapling_notes( + /// Return all unspent notes. + fn get_unspent_sapling_notes( &self, account: AccountId, anchor_height: BlockHeight, ) -> Result, Self::Error>; - /// Returns a list of spendable notes sufficient to cover the specified + /// Returns a list of unspent notes sufficient to cover the specified /// target value, if possible. - fn select_spendable_sapling_notes( + fn select_unspent_sapling_notes( &self, account: AccountId, target_value: Amount, @@ -182,7 +182,7 @@ pub trait WalletRead { ) -> Result, Self::Error>; #[cfg(feature = "transparent-inputs")] - fn get_spendable_transparent_utxos( + fn get_unspent_transparent_utxos( &self, address: &TransparentAddress, anchor_height: BlockHeight, @@ -395,7 +395,7 @@ pub mod testing { Ok(Vec::new()) } - fn get_spendable_sapling_notes( + fn get_unspent_sapling_notes( &self, _account: AccountId, _anchor_height: BlockHeight, @@ -403,7 +403,7 @@ pub mod testing { Ok(Vec::new()) } - fn select_spendable_sapling_notes( + fn select_unspent_sapling_notes( &self, _account: AccountId, _target_value: Amount, @@ -413,7 +413,7 @@ pub mod testing { } #[cfg(feature = "transparent-inputs")] - fn get_spendable_transparent_utxos( + fn get_unspent_transparent_utxos( &self, _address: &TransparentAddress, _anchor_height: BlockHeight, diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 3632f336c..226887cc0 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -191,7 +191,7 @@ where let target_value = value + DEFAULT_FEE; let spendable_notes = - wallet_db.select_spendable_sapling_notes(account, target_value, anchor_height)?; + wallet_db.select_unspent_sapling_notes(account, target_value, anchor_height)?; // Confirm we were able to select sufficient value let selected_value = spendable_notes.iter().map(|n| n.note_value).sum(); @@ -296,7 +296,7 @@ where let ovk = exfvk.fvk.ovk; // get UTXOs from DB - let utxos = wallet_db.get_spendable_transparent_utxos(&taddr, latest_anchor)?; + let utxos = wallet_db.get_unspent_transparent_utxos(&taddr, latest_anchor)?; let total_amount = utxos.iter().map(|utxo| utxo.value).sum::(); let fee = DEFAULT_FEE; diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index 502801945..d6def4070 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -75,6 +75,7 @@ pub fn derive_secret_key_from_seed( } #[cfg(feature = "transparent-inputs")] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Wif(pub String); #[cfg(feature = "transparent-inputs")] @@ -94,7 +95,7 @@ impl Wif { } #[cfg(feature = "transparent-inputs")] -impl TryInto for Wif { +impl<'a> TryInto for &'a Wif { type Error = Bs58Error; fn try_into(self) -> Result { @@ -153,7 +154,7 @@ mod tests { #[test] fn sk_wif_to_taddr() { let sk_wif = Wif("L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2".to_string()); - let sk: SecretKey = sk_wif.try_into().expect("invalid wif"); + let sk: SecretKey = (&sk_wif).try_into().expect("invalid wif"); let taddr = derive_transparent_address_from_secret_key(sk); assert_eq!( taddr.encode(&MAIN_NETWORK), diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index daddcba8e..961953a8f 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -263,35 +263,30 @@ impl WalletRead for WalletDb

{ wallet::get_nullifiers(&self) } - fn get_spendable_sapling_notes( + fn get_unspent_sapling_notes( &self, account: AccountId, anchor_height: BlockHeight, ) -> Result, Self::Error> { - wallet::transact::get_spendable_sapling_notes(&self, account, anchor_height) + wallet::transact::get_unspent_sapling_notes(&self, account, anchor_height) } - fn select_spendable_sapling_notes( + fn select_unspent_sapling_notes( &self, account: AccountId, target_value: Amount, anchor_height: BlockHeight, ) -> Result, Self::Error> { - wallet::transact::select_spendable_sapling_notes( - &self, - account, - target_value, - anchor_height, - ) + wallet::transact::select_unspent_sapling_notes(&self, account, target_value, anchor_height) } #[cfg(feature = "transparent-inputs")] - fn get_spendable_transparent_utxos( + fn get_unspent_transparent_utxos( &self, address: &TransparentAddress, anchor_height: BlockHeight, ) -> Result, Self::Error> { - wallet::get_spendable_transparent_utxos(&self, address, anchor_height) + wallet::get_unspent_transparent_utxos(&self, address, anchor_height) } } @@ -396,33 +391,33 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> { self.wallet_db.get_nullifiers() } - fn get_spendable_sapling_notes( + fn get_unspent_sapling_notes( &self, account: AccountId, anchor_height: BlockHeight, ) -> Result, Self::Error> { self.wallet_db - .get_spendable_sapling_notes(account, anchor_height) + .get_unspent_sapling_notes(account, anchor_height) } - fn select_spendable_sapling_notes( + fn select_unspent_sapling_notes( &self, account: AccountId, target_value: Amount, anchor_height: BlockHeight, ) -> Result, Self::Error> { self.wallet_db - .select_spendable_sapling_notes(account, target_value, anchor_height) + .select_unspent_sapling_notes(account, target_value, anchor_height) } #[cfg(feature = "transparent-inputs")] - fn get_spendable_transparent_utxos( + fn get_unspent_transparent_utxos( &self, address: &TransparentAddress, anchor_height: BlockHeight, ) -> Result, Self::Error> { self.wallet_db - .get_spendable_transparent_utxos(address, anchor_height) + .get_unspent_transparent_utxos(address, anchor_height) } } diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index f95c6d426..0234f267f 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -599,7 +599,7 @@ pub fn get_nullifiers

( } #[cfg(feature = "transparent-inputs")] -pub fn get_spendable_transparent_utxos( +pub fn get_unspent_transparent_utxos( wdb: &WalletDb

, address: &TransparentAddress, anchor_height: BlockHeight, diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index 35ed92f0c..db313c73f 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -59,7 +59,7 @@ fn to_spendable_note(row: &Row) -> Result { }) } -pub fn get_spendable_sapling_notes

( +pub fn get_unspent_sapling_notes

( wdb: &WalletDb

, account: AccountId, anchor_height: BlockHeight, @@ -87,7 +87,7 @@ pub fn get_spendable_sapling_notes

( notes.collect::>() } -pub fn select_spendable_sapling_notes

( +pub fn select_unspent_sapling_notes

( wdb: &WalletDb

, account: AccountId, target_value: Amount, From b88ee47e36f20cc9d19e143877beb73f54275348 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 12 Feb 2021 19:09:54 -0700 Subject: [PATCH 05/79] Add confirmation depth to shield_funds --- zcash_client_backend/src/data_api/wallet.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 226887cc0..7d69c7203 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -266,6 +266,7 @@ where } #[cfg(feature = "transparent-inputs")] +#[allow(clippy::too_many_arguments)] pub fn shield_funds( wallet_db: &mut D, params: &P, @@ -274,6 +275,7 @@ pub fn shield_funds( sk: &secp256k1::SecretKey, extsk: &ExtendedSpendingKey, memo: &MemoBytes, + confirmations: u32, ) -> Result where E: From>, @@ -296,7 +298,7 @@ where let ovk = exfvk.fvk.ovk; // get UTXOs from DB - let utxos = wallet_db.get_unspent_transparent_utxos(&taddr, latest_anchor)?; + let utxos = wallet_db.get_unspent_transparent_utxos(&taddr, latest_anchor - confirmations)?; let total_amount = utxos.iter().map(|utxo| utxo.value).sum::(); let fee = DEFAULT_FEE; From 8828276361e02155f49da31d2cf9515231f4d26b Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 26 Mar 2021 18:39:43 -0600 Subject: [PATCH 06/79] Query for unspent utxos checks to ensure that spending tx is mined. Also make it an error to try to send a memo to a transparent address. --- zcash_client_backend/Cargo.toml | 7 +-- zcash_client_backend/src/data_api/error.rs | 8 +++ zcash_client_backend/src/data_api/wallet.rs | 58 ++++++++++++++------- zcash_client_backend/src/lib.rs | 3 ++ zcash_client_sqlite/src/lib.rs | 6 +++ zcash_client_sqlite/src/wallet.rs | 28 ++++++++-- zcash_primitives/src/transaction/builder.rs | 44 ++++++++++++---- 7 files changed, 119 insertions(+), 35 deletions(-) diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index e90e5e814..bc456e6a0 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -22,6 +22,7 @@ group = "0.8" hex = "0.4" hdwallet = { version = "0.3.0", optional = true } jubjub = "0.5.1" +log = "0.4" nom = "6.1" percent-encoding = "2.1.0" proptest = { version = "0.10.1", optional = true } @@ -29,7 +30,7 @@ protobuf = "2.20" rand_core = "0.5.1" ripemd160 = { version = "0.9.1", optional = true } secp256k1 = { version = "0.19", optional = true } -sha2 = "0.9" +sha2 = { version = "0.9", optional = true } subtle = "2.2.3" time = "0.2" zcash_note_encryption = { version = "0.0", path = "../components/zcash_note_encryption" } @@ -47,8 +48,8 @@ zcash_client_sqlite = { version = "0.3", path = "../zcash_client_sqlite" } zcash_proofs = { version = "0.5", path = "../zcash_proofs" } [features] -transparent-inputs = ["ripemd160", "secp256k1"] -test-dependencies = ["proptest", "zcash_primitives/test-dependencies", "hdwallet"] +transparent-inputs = ["ripemd160", "hdwallet", "sha2", "secp256k1"] +test-dependencies = ["proptest", "zcash_primitives/test-dependencies", "hdwallet", "sha2"] [badges] maintenance = { status = "actively-developed" } diff --git a/zcash_client_backend/src/data_api/error.rs b/zcash_client_backend/src/data_api/error.rs index 086daee40..e679eba7c 100644 --- a/zcash_client_backend/src/data_api/error.rs +++ b/zcash_client_backend/src/data_api/error.rs @@ -59,6 +59,12 @@ pub enum Error { /// The wallet attempted a sapling-only operation at a block /// height when Sapling was not yet active. SaplingNotActive, + + /// A memo is required when constructing a Sapling output + MemoRequired, + + /// It is forbidden to provide a memo when constructing a transparent output. + MemoForbidden, } impl ChainInvalid { @@ -99,6 +105,8 @@ impl fmt::Display for Error { Error::Builder(e) => write!(f, "{:?}", e), Error::Protobuf(e) => write!(f, "{}", e), Error::SaplingNotActive => write!(f, "Could not determine Sapling upgrade activation height."), + Error::MemoRequired => write!(f, "A memo is required when sending to a Sapling address."), + Error::MemoForbidden => write!(f, "It is not possible to send a memo to a transparent address."), } } } diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 7d69c7203..bcd31227e 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -1,6 +1,5 @@ -//! Functions for scanning the chain and extracting relevant information. +use std::convert::TryFrom; use std::fmt::Debug; - use zcash_primitives::{ consensus::{self, BranchId, NetworkUpgrade}, memo::MemoBytes, @@ -40,6 +39,8 @@ where P: consensus::Parameters, D: WalletWrite, { + debug!("decrypt_and_store: {:?}", tx); + // Fetch the ExtendedFullViewingKeys we are tracking let extfvks = data.get_extended_full_viewing_keys()?; @@ -54,16 +55,32 @@ where .ok_or(Error::SaplingNotActive)?; let outputs = decrypt_transaction(params, height, tx, &extfvks); - if outputs.is_empty() { - Ok(()) - } else { + if !outputs.is_empty() { data.store_received_tx(&ReceivedTransaction { tx, outputs: &outputs, })?; - - Ok(()) } + + // store z->t transactions in the same way the would be stored by create_spend_to_address + if !tx.vout.is_empty() { + // TODO: clarify with Kris the simplest way to determine account and iterate over outputs + // i.e. there are probably edge cases where we need to combine vouts into one "sent" transaction for the total value + data.store_sent_tx(&SentTransaction { + tx: &tx, + created: time::OffsetDateTime::now_utc(), + output_index: usize::try_from(0).unwrap(), + account: AccountId(0), + recipient_address: &RecipientAddress::Transparent( + tx.vout[0].script_pubkey.address().unwrap(), + ), + value: tx.vout[0].value, + memo: None, + utxos_spent: vec![], + })?; + } + + Ok(()) } #[allow(clippy::needless_doctest_main)] @@ -224,12 +241,22 @@ where match to { RecipientAddress::Shielded(to) => { - builder.add_sapling_output(ovk, to.clone(), value, memo.clone()) + memo.clone().ok_or(Error::MemoRequired).and_then(|memo| { + builder + .add_sapling_output(ovk, to.clone(), value, memo) + .map_err(Error::Builder) + }) } - - RecipientAddress::Transparent(to) => builder.add_transparent_output(&to, value), - } - .map_err(Error::Builder)?; + RecipientAddress::Transparent(to) => { + if memo.is_some() { + Err(Error::MemoForbidden) + } else { + builder + .add_transparent_output(&to, value) + .map_err(Error::Builder) + } + } + }?; let consensus_branch_id = BranchId::for_height(params, height); let (tx, tx_metadata) = builder @@ -329,12 +356,7 @@ where // add the sapling output to shield the funds builder - .add_sapling_output( - Some(ovk), - z_address.clone(), - amount_to_shield, - Some(memo.clone()), - ) + .add_sapling_output(Some(ovk), z_address.clone(), amount_to_shield, memo.clone()) .map_err(Error::Builder)?; let consensus_branch_id = BranchId::for_height(params, latest_anchor); diff --git a/zcash_client_backend/src/lib.rs b/zcash_client_backend/src/lib.rs index 085070c13..92fab32ec 100644 --- a/zcash_client_backend/src/lib.rs +++ b/zcash_client_backend/src/lib.rs @@ -8,6 +8,9 @@ // Temporary until we have addressed all Result cases. #![allow(clippy::result_unit_err)] +#[macro_use] +extern crate log; + pub mod address; pub mod data_api; mod decrypt; diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 961953a8f..f39f2e688 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -149,6 +149,10 @@ impl WalletDb

{ "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)", @@ -313,6 +317,8 @@ pub struct DataConnStmtCache<'a, P> { #[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>, diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 0234f267f..70fd7abf0 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -605,11 +605,13 @@ pub fn get_unspent_transparent_utxos( anchor_height: BlockHeight, ) -> Result, SqliteClientError> { let mut stmt_blocks = wdb.conn.prepare( - "SELECT address, prevout_txid, prevout_idx, script, value_zat, height - FROM utxos - WHERE address = ? - AND height <= ? - AND spent_in_tx IS NULL", + "SELECT u.address, 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); @@ -789,6 +791,22 @@ pub fn put_received_transparent_utxo<'a, P: consensus::Parameters>( Ok(UtxoId(stmts.wallet_db.conn.last_insert_rowid())) } +#[cfg(feature = "transparent-inputs")] +pub fn delete_utxos_above<'a, P: consensus::Parameters>( + stmts: &mut DataConnStmtCache<'a, P>, + taddr: &TransparentAddress, + height: BlockHeight, +) -> Result { + 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) +} + // Assumptions: // - A transaction will not contain more than 2^63 shielded outputs. // - A note value will never exceed 2^63 zatoshis. diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index db58da4ed..c2a9fc0a3 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -110,7 +110,7 @@ impl SaplingOutput

{ ovk: Option, to: PaymentAddress, value: Amount, - memo: Option, + memo: MemoBytes, ) -> Result { Self::new_internal(params, height, rng, ovk, to, value, memo) } @@ -122,7 +122,7 @@ impl SaplingOutput

{ ovk: Option, to: PaymentAddress, value: Amount, - memo: Option, + memo: MemoBytes, ) -> Result { let g_d = to.g_d().ok_or(Error::InvalidAddress)?; if value.is_negative() { @@ -142,7 +142,7 @@ impl SaplingOutput

{ ovk, to, note, - memo: memo.unwrap_or_else(MemoBytes::empty), + memo, _params: PhantomData::default(), }) } @@ -521,7 +521,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> { ovk: Option, to: PaymentAddress, value: Amount, - memo: Option, + memo: MemoBytes, ) -> Result<(), Error> { let output = SaplingOutput::new_internal( &self.params, @@ -645,7 +645,12 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> { return Err(Error::NoChangeAddress); }; - self.add_sapling_output(Some(change_address.0), change_address.1, change, None)?; + self.add_sapling_output( + Some(change_address.0), + change_address.1, + change, + MemoBytes::empty(), + )?; } // @@ -965,6 +970,7 @@ mod tests { use crate::{ consensus::{self, Parameters, H0, TEST_NETWORK}, legacy::TransparentAddress, + memo::MemoBytes, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{prover::mock::MockTxProver, Node, Rseed}, transaction::components::{amount::Amount, amount::DEFAULT_FEE}, @@ -985,7 +991,12 @@ mod tests { let mut builder = Builder::new(TEST_NETWORK, H0); 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::InvalidAmount) ); } @@ -1104,7 +1115,12 @@ mod tests { { let mut builder = Builder::new(TEST_NETWORK, H0); 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(consensus::BranchId::Sapling, &MockTxProver), @@ -1153,7 +1169,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( @@ -1194,7 +1215,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( From 13cd7498b7c7013389254f3c252f97bf34d24f7b Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 29 Mar 2021 14:35:18 -0600 Subject: [PATCH 07/79] Store vout as part of store_decrypted_tx --- zcash_client_backend/src/data_api.rs | 15 ++++---- zcash_client_backend/src/data_api/wallet.rs | 31 ++++----------- zcash_client_sqlite/src/lib.rs | 42 ++++++++++++++++++--- zcash_client_sqlite/src/wallet.rs | 37 ++++++------------ 4 files changed, 63 insertions(+), 62 deletions(-) diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 4f2e2efa9..9df7ada77 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -204,9 +204,10 @@ 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, + pub account_id: AccountId, + pub sapling_outputs: &'a Vec, } /// A transaction that was constructed and sent by the wallet. @@ -241,9 +242,9 @@ pub trait WalletWrite: WalletRead { updated_witnesses: &[(Self::NoteRef, IncrementalWitness)], ) -> Result)>, Self::Error>; - fn store_received_tx( + fn store_decrypted_tx( &mut self, - received_tx: &ReceivedTransaction, + received_tx: &DecryptedTransaction, ) -> Result; fn store_sent_tx(&mut self, sent_tx: &SentTransaction) -> Result; @@ -302,7 +303,7 @@ pub mod testing { }; use super::{ - error::Error, BlockSource, PrunedBlock, ReceivedTransaction, SentTransaction, WalletRead, + error::Error, BlockSource, DecryptedTransaction, PrunedBlock, SentTransaction, WalletRead, WalletWrite, }; @@ -432,9 +433,9 @@ pub mod testing { Ok(vec![]) } - fn store_received_tx( + fn store_decrypted_tx( &mut self, - _received_tx: &ReceivedTransaction, + _received_tx: &DecryptedTransaction, ) -> Result { Ok(TxId([0u8; 32])) } diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index bcd31227e..be92caeca 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -1,4 +1,3 @@ -use std::convert::TryFrom; use std::fmt::Debug; use zcash_primitives::{ consensus::{self, BranchId, NetworkUpgrade}, @@ -14,7 +13,7 @@ use zcash_primitives::{ use crate::{ address::RecipientAddress, - data_api::{error::Error, ReceivedTransaction, SentTransaction, WalletWrite}, + data_api::{error::Error, DecryptedTransaction, SentTransaction, WalletWrite}, decrypt_transaction, wallet::{AccountId, OvkPolicy}, }; @@ -54,29 +53,13 @@ where .or_else(|| params.activation_height(NetworkUpgrade::Sapling)) .ok_or(Error::SaplingNotActive)?; - let outputs = decrypt_transaction(params, height, tx, &extfvks); - if !outputs.is_empty() { - data.store_received_tx(&ReceivedTransaction { - tx, - outputs: &outputs, - })?; - } + let sapling_outputs = decrypt_transaction(params, height, tx, &extfvks); - // store z->t transactions in the same way the would be stored by create_spend_to_address - if !tx.vout.is_empty() { - // TODO: clarify with Kris the simplest way to determine account and iterate over outputs - // i.e. there are probably edge cases where we need to combine vouts into one "sent" transaction for the total value - data.store_sent_tx(&SentTransaction { - tx: &tx, - created: time::OffsetDateTime::now_utc(), - output_index: usize::try_from(0).unwrap(), - account: AccountId(0), - recipient_address: &RecipientAddress::Transparent( - tx.vout[0].script_pubkey.address().unwrap(), - ), - value: tx.vout[0].value, - memo: None, - utxos_spent: vec![], + if !(sapling_outputs.is_empty() && tx.vout.is_empty()) { + data.store_decrypted_tx(&DecryptedTransaction { + tx, + account_id: AccountId(0), //FIXME + sapling_outputs: &sapling_outputs, })?; } diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index f39f2e688..a004ee303 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -49,8 +49,9 @@ use zcash_primitives::{ }; 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, @@ -513,21 +514,50 @@ 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.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 { + 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, + RecipientAddress::Shielded(output.to.clone()), + Amount::from_u64(output.note.value).map_err(|_| { + SqliteClientError::CorruptedData("Note value invalid.".to_string()) + })?, + Some(&output.memo), + )?; } else { wallet::put_received_note(up, output, tx_ref)?; } } + // store received z->t transactions in the same way they would be stored by + // create_spend_to_address If there are any of our shielded inputs, we interpret this + // as our z->t tx and store the vouts as our sent notes. + // FIXME this is a weird heuristic that is bound to trip us up somewhere. + if d_tx.sapling_outputs.iter().any(|output| !output.outgoing) { + for (i, txout) in d_tx.tx.vout.iter().enumerate() { + // FIXME: We shouldn't be confusing notes and transparent outputs. + wallet::insert_sent_note( + up, + tx_ref, + i, + d_tx.account_id, + &RecipientAddress::Transparent(txout.script_pubkey.address().unwrap()), + txout.value, + None, + )?; + } + } + Ok(tx_ref) }) } diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 70fd7abf0..468689f45 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -30,7 +30,6 @@ use zcash_client_backend::{ data_api::error::Error, encoding::{ decode_extended_full_viewing_key, decode_payment_address, encode_extended_full_viewing_key, - encode_payment_address, }, wallet::{AccountId, WalletShieldedOutput, WalletTx}, DecryptedOutput, @@ -899,38 +898,26 @@ pub fn update_expired_notes

( /// Records information about a note that your wallet created. 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: RecipientAddress, + 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, + to.encode(&stmts.wallet_db.params), + ivalue, + &memo.map(|m| m.as_slice()), tx_ref, - output_index + 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(()) From 665c4c7aff3743fce38e7527b553197c51ffa65e Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 29 Mar 2021 14:55:15 -0600 Subject: [PATCH 08/79] Figure out the account ID for z->t spends. --- zcash_client_backend/src/data_api.rs | 1 - zcash_client_backend/src/data_api/wallet.rs | 1 - zcash_client_sqlite/src/lib.rs | 40 +++++++++++++-------- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 9df7ada77..b2c3c0217 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -206,7 +206,6 @@ pub struct PrunedBlock<'a> { /// wallet database when transactions are successfully decrypted. pub struct DecryptedTransaction<'a> { pub tx: &'a Transaction, - pub account_id: AccountId, pub sapling_outputs: &'a Vec, } diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index be92caeca..9862225a4 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -58,7 +58,6 @@ where if !(sapling_outputs.is_empty() && tx.vout.is_empty()) { data.store_decrypted_tx(&DecryptedTransaction { tx, - account_id: AccountId(0), //FIXME sapling_outputs: &sapling_outputs, })?; } diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index a004ee303..cc71b03ec 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -521,6 +521,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { self.transactionally(|up| { let tx_ref = wallet::put_tx_data(up, d_tx.tx, None)?; + let mut spending_account_id: Option = None; for output in d_tx.sapling_outputs { if output.outgoing { wallet::put_sent_note( @@ -529,12 +530,21 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { output.index, output.account, RecipientAddress::Shielded(output.to.clone()), - Amount::from_u64(output.note.value).map_err(|_| { - SqliteClientError::CorruptedData("Note value invalid.".to_string()) - })?, + 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)?; } } @@ -544,17 +554,19 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { // as our z->t tx and store the vouts as our sent notes. // FIXME this is a weird heuristic that is bound to trip us up somewhere. if d_tx.sapling_outputs.iter().any(|output| !output.outgoing) { - for (i, txout) in d_tx.tx.vout.iter().enumerate() { - // FIXME: We shouldn't be confusing notes and transparent outputs. - wallet::insert_sent_note( - up, - tx_ref, - i, - d_tx.account_id, - &RecipientAddress::Transparent(txout.script_pubkey.address().unwrap()), - txout.value, - None, - )?; + if let Some(account_id) = spending_account_id { + for (i, txout) in d_tx.tx.vout.iter().enumerate() { + // FIXME: We shouldn't be confusing notes and transparent outputs. + wallet::put_sent_note( + up, + tx_ref, + i, + account_id, + RecipientAddress::Transparent(txout.script_pubkey.address().unwrap()), + txout.value, + None, + )?; + } } } From 5595ca225c7551bb87e37e346aded24745c30a74 Mon Sep 17 00:00:00 2001 From: Kevin Gorham Date: Wed, 31 Mar 2021 21:04:38 -0400 Subject: [PATCH 09/79] Add public key derivations and related tests. --- zcash_client_backend/src/keys.rs | 94 ++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 23 deletions(-) diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index d6def4070..09cf7eff7 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -6,7 +6,7 @@ use zcash_primitives::zip32::{ChildIndex, ExtendedSpendingKey}; use { crate::wallet::AccountId, bs58::{self, decode::Error as Bs58Error}, - hdwallet::{ExtendedPrivKey, KeyIndex}, + hdwallet::{ExtendedPrivKey, ExtendedPubKey, KeyIndex}, secp256k1::{key::PublicKey, key::SecretKey, Secp256k1}, sha2::{Digest, Sha256}, std::convert::TryInto, @@ -50,8 +50,15 @@ pub fn derive_transparent_address_from_secret_key( ) -> TransparentAddress { let secp = Secp256k1::new(); let pk = PublicKey::from_secret_key(&secp, &secret_key); + derive_transparent_address_from_public_key(pk) +} + +#[cfg(feature = "transparent-inputs")] +pub fn derive_transparent_address_from_public_key( + public_key: secp256k1::key::PublicKey, +) -> TransparentAddress { let mut hash160 = ripemd160::Ripemd160::new(); - hash160.update(Sha256::digest(&pk.serialize()[..].to_vec())); + hash160.update(Sha256::digest(&public_key.serialize()[..].to_vec())); TransparentAddress::PublicKey(*hash160.finalize().as_ref()) } @@ -62,15 +69,37 @@ pub fn derive_secret_key_from_seed( account: AccountId, index: u32, ) -> Result { - let ext_t_key = ExtendedPrivKey::with_seed(&seed)?; - let private_key = ext_t_key + let private_key = + derive_extended_private_key_from_seed(params, seed, account, index)?.private_key; + Ok(private_key) +} + +#[cfg(feature = "transparent-inputs")] +pub fn derive_public_key_from_seed( + params: &P, + seed: &[u8], + account: AccountId, + index: u32, +) -> Result { + let private_key = derive_extended_private_key_from_seed(params, seed, account, index)?; + let pub_key = ExtendedPubKey::from_private_key(&private_key); + Ok(pub_key.public_key) +} + +#[cfg(feature = "transparent-inputs")] +pub fn derive_extended_private_key_from_seed( + params: &P, + seed: &[u8], + account: AccountId, + index: u32, +) -> Result { + let pk = ExtendedPrivKey::with_seed(&seed)?; + let private_key = pk .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)?)? .derive_private_key(KeyIndex::Normal(0))? - .derive_private_key(KeyIndex::Normal(index))? - .private_key; - + .derive_private_key(KeyIndex::Normal(index))?; Ok(private_key) } @@ -112,13 +141,22 @@ mod tests { #[cfg(feature = "transparent-inputs")] use { - super::{derive_secret_key_from_seed, derive_transparent_address_from_secret_key, Wif}, + super::{ + derive_public_key_from_seed, derive_secret_key_from_seed, + derive_transparent_address_from_public_key, derive_transparent_address_from_secret_key, + Wif, + }, crate::{encoding::AddressCodec, wallet::AccountId}, secp256k1::key::SecretKey, std::convert::TryInto, zcash_primitives::consensus::MAIN_NETWORK, }; + fn seed() -> Vec { + let seed_hex = "6ef5f84def6f4b9d38f466586a8380a38593bd47c8cda77f091856176da47f26b5bd1c8d097486e5635df5a66e820d28e1d73346f499801c86228d43f390304f"; + hex::decode(&seed_hex).unwrap() + } + #[test] #[should_panic] fn spending_key_panics_on_short_seed() { @@ -128,11 +166,10 @@ mod tests { #[cfg(feature = "transparent-inputs")] #[test] fn sk_to_wif() { - let seed_hex = "6ef5f84def6f4b9d38f466586a8380a38593bd47c8cda77f091856176da47f26b5bd1c8d097486e5635df5a66e820d28e1d73346f499801c86228d43f390304f"; - let seed = hex::decode(&seed_hex).unwrap(); - let sk = derive_secret_key_from_seed(&MAIN_NETWORK, &seed, AccountId(0), 0).unwrap(); + let sk = derive_secret_key_from_seed(&MAIN_NETWORK, &seed(), AccountId(0), 0).unwrap(); + let wif = Wif::from_secret_key(&sk, true).0; assert_eq!( - Wif::from_secret_key(&sk, true).0, + wif, "L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2".to_string() ); } @@ -140,14 +177,9 @@ mod tests { #[cfg(feature = "transparent-inputs")] #[test] fn sk_to_taddr() { - let seed_hex = "6ef5f84def6f4b9d38f466586a8380a38593bd47c8cda77f091856176da47f26b5bd1c8d097486e5635df5a66e820d28e1d73346f499801c86228d43f390304f"; - let seed = hex::decode(&seed_hex).unwrap(); - let sk = derive_secret_key_from_seed(&MAIN_NETWORK, &seed, AccountId(0), 0).unwrap(); - let taddr = derive_transparent_address_from_secret_key(sk); - assert_eq!( - taddr.encode(&MAIN_NETWORK), - "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string() - ); + let sk = derive_secret_key_from_seed(&MAIN_NETWORK, &seed(), AccountId(0), 0).unwrap(); + let taddr = derive_transparent_address_from_secret_key(sk).encode(&MAIN_NETWORK); + assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string()); } #[cfg(feature = "transparent-inputs")] @@ -155,10 +187,26 @@ mod tests { fn sk_wif_to_taddr() { let sk_wif = Wif("L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2".to_string()); let sk: SecretKey = (&sk_wif).try_into().expect("invalid wif"); - let taddr = derive_transparent_address_from_secret_key(sk); + let taddr = derive_transparent_address_from_secret_key(sk).encode(&MAIN_NETWORK); + assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string()); + } + + #[cfg(feature = "transparent-inputs")] + #[test] + fn pk_from_seed() { + let pk = derive_public_key_from_seed(&MAIN_NETWORK, &seed(), AccountId(0), 0).unwrap(); + let hex_value = hex::encode(&pk.serialize()); assert_eq!( - taddr.encode(&MAIN_NETWORK), - "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string() + hex_value, + "03b1d7fb28d17c125b504d06b1530097e0a3c76ada184237e3bc0925041230a5af".to_string() ); } + + #[cfg(feature = "transparent-inputs")] + #[test] + fn pk_to_taddr() { + let pk = derive_public_key_from_seed(&MAIN_NETWORK, &seed(), AccountId(0), 0).unwrap(); + let taddr = derive_transparent_address_from_public_key(pk).encode(&MAIN_NETWORK); + assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string()); + } } From bdf56925e9633370bfaabf223b231f40d75548f7 Mon Sep 17 00:00:00 2001 From: Kevin Gorham Date: Thu, 1 Apr 2021 01:54:46 -0400 Subject: [PATCH 10/79] Insert into accounts table with taddrs. --- zcash_client_sqlite/src/wallet/init.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 572e54b2b..a55f8c609 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -2,13 +2,9 @@ use rusqlite::{types::ToSql, NO_PARAMS}; -use zcash_primitives::{ - block::BlockHash, - consensus::{self, BlockHeight}, - zip32::ExtendedFullViewingKey, -}; +use zcash_primitives::{block::BlockHash, consensus::{self, BlockHeight}, legacy::TransparentAddress, zip32::ExtendedFullViewingKey}; -use zcash_client_backend::encoding::encode_extended_full_viewing_key; +use zcash_client_backend::encoding::{encode_extended_full_viewing_key, AddressCodec}; use crate::{address_from_extfvk, error::SqliteClientError, WalletDb}; @@ -161,6 +157,7 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { pub fn init_accounts_table( wdb: &WalletDb

, extfvks: &[ExtendedFullViewingKey], + taddrs: &Vec, ) -> Result<(), SqliteClientError> { let mut empty_check = wdb.conn.prepare("SELECT * FROM accounts LIMIT 1")?; if empty_check.exists(NO_PARAMS)? { @@ -169,21 +166,23 @@ pub fn init_accounts_table( // Insert accounts atomically wdb.conn.execute("BEGIN IMMEDIATE", NO_PARAMS)?; - for (account, extfvk) in extfvks.iter().enumerate() { + for (account, (extfvk, taddr)) in extfvks.iter().zip(taddrs.iter()).enumerate() { let extfvk_str = encode_extended_full_viewing_key( wdb.params.hrp_sapling_extended_full_viewing_key(), extfvk, ); let address_str = address_from_extfvk(&wdb.params, extfvk); + let taddress_str: String = taddr.encode(&wdb.params); wdb.conn.execute( - "INSERT INTO accounts (account, extfvk, address) - VALUES (?, ?, ?)", + "INSERT INTO accounts (account, extfvk, address, transparent_address) + VALUES (?, ?, ?, ?)", &[ (account as u32).to_sql()?, extfvk_str.to_sql()?, address_str.to_sql()?, + taddress_str.to_sql()?, ], )?; } From 8e16d93f942f63bdb9e6d414fd16077f004544c2 Mon Sep 17 00:00:00 2001 From: Kevin Gorham Date: Wed, 31 Mar 2021 17:59:36 -0400 Subject: [PATCH 11/79] Update accounts table create statement. This PR makes the opinionated change that T-addrs are required to be supported when using the zcash_client_sqlite backend. --- zcash_client_backend/src/data_api/wallet.rs | 4 +- zcash_client_backend/src/encoding.rs | 6 +- zcash_client_backend/src/keys.rs | 29 +++++----- zcash_client_sqlite/Cargo.toml | 6 +- zcash_client_sqlite/src/chain.rs | 42 ++++---------- zcash_client_sqlite/src/lib.rs | 40 ++++++++++---- zcash_client_sqlite/src/wallet.rs | 19 +------ zcash_client_sqlite/src/wallet/init.rs | 61 ++++++++++++++------- zcash_client_sqlite/src/wallet/transact.rs | 50 +++++++++++------ 9 files changed, 140 insertions(+), 117 deletions(-) diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 9862225a4..e0a4a37ff 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -128,7 +128,7 @@ where /// }; /// /// let account = AccountId(0); -/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, account.0); +/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, account); /// let to = extsk.default_address().unwrap().1.into(); /// /// let data_file = NamedTempFile::new().unwrap(); @@ -297,7 +297,7 @@ where .and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?; // derive the corresponding t-address - let taddr = derive_transparent_address_from_secret_key(*sk); + let taddr = derive_transparent_address_from_secret_key(sk); // derive own shielded address from the provided extended spending key let z_address = extsk.default_address().unwrap().1; diff --git a/zcash_client_backend/src/encoding.rs b/zcash_client_backend/src/encoding.rs index 18cda7292..520e3e071 100644 --- a/zcash_client_backend/src/encoding.rs +++ b/zcash_client_backend/src/encoding.rs @@ -104,9 +104,10 @@ impl AddressCodec

for TransparentAddress { /// use zcash_client_backend::{ /// encoding::encode_extended_spending_key, /// keys::spending_key, +/// wallet::AccountId, /// }; /// -/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, 0); +/// let extsk = 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 @@ -135,10 +136,11 @@ pub fn decode_extended_spending_key( /// use zcash_client_backend::{ /// encoding::encode_extended_full_viewing_key, /// keys::spending_key, +/// wallet::AccountId, /// }; /// use zcash_primitives::zip32::ExtendedFullViewingKey; /// -/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, 0); +/// let extsk = 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); /// ``` diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index 09cf7eff7..152c12b0a 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -24,12 +24,15 @@ use { /// /// ``` /// use zcash_primitives::{constants::testnet::COIN_TYPE}; -/// use zcash_client_backend::{keys::spending_key}; +/// use zcash_client_backend::{ +/// keys::spending_key, +/// wallet::AccountId, +/// }; /// -/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, 0); +/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, AccountId(0)); /// ``` /// [`ExtendedSpendingKey`]: zcash_primitives::zip32::ExtendedSpendingKey -pub fn spending_key(seed: &[u8], coin_type: u32, account: u32) -> 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"); } @@ -39,26 +42,26 @@ pub fn spending_key(seed: &[u8], coin_type: u32, account: u32) -> ExtendedSpendi &[ ChildIndex::Hardened(32), ChildIndex::Hardened(coin_type), - ChildIndex::Hardened(account), + ChildIndex::Hardened(account.0), ], ) } #[cfg(feature = "transparent-inputs")] pub fn derive_transparent_address_from_secret_key( - secret_key: secp256k1::key::SecretKey, + secret_key: &secp256k1::key::SecretKey, ) -> TransparentAddress { let secp = Secp256k1::new(); - let pk = PublicKey::from_secret_key(&secp, &secret_key); - derive_transparent_address_from_public_key(pk) + let pk = PublicKey::from_secret_key(&secp, secret_key); + derive_transparent_address_from_public_key(&pk) } #[cfg(feature = "transparent-inputs")] pub fn derive_transparent_address_from_public_key( - public_key: secp256k1::key::PublicKey, + public_key: &secp256k1::key::PublicKey, ) -> TransparentAddress { let mut hash160 = ripemd160::Ripemd160::new(); - hash160.update(Sha256::digest(&public_key.serialize()[..].to_vec())); + hash160.update(Sha256::digest(&public_key.serialize())); TransparentAddress::PublicKey(*hash160.finalize().as_ref()) } @@ -160,7 +163,7 @@ mod tests { #[test] #[should_panic] fn spending_key_panics_on_short_seed() { - let _ = spending_key(&[0; 31][..], 0, 0); + let _ = spending_key(&[0; 31][..], 0, AccountId(0)); } #[cfg(feature = "transparent-inputs")] @@ -178,7 +181,7 @@ mod tests { #[test] fn sk_to_taddr() { let sk = derive_secret_key_from_seed(&MAIN_NETWORK, &seed(), AccountId(0), 0).unwrap(); - let taddr = derive_transparent_address_from_secret_key(sk).encode(&MAIN_NETWORK); + let taddr = derive_transparent_address_from_secret_key(&sk).encode(&MAIN_NETWORK); assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string()); } @@ -187,7 +190,7 @@ mod tests { fn sk_wif_to_taddr() { let sk_wif = Wif("L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2".to_string()); let sk: SecretKey = (&sk_wif).try_into().expect("invalid wif"); - let taddr = derive_transparent_address_from_secret_key(sk).encode(&MAIN_NETWORK); + let taddr = derive_transparent_address_from_secret_key(&sk).encode(&MAIN_NETWORK); assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string()); } @@ -206,7 +209,7 @@ mod tests { #[test] fn pk_to_taddr() { let pk = derive_public_key_from_seed(&MAIN_NETWORK, &seed(), AccountId(0), 0).unwrap(); - let taddr = derive_transparent_address_from_public_key(pk).encode(&MAIN_NETWORK); + let taddr = derive_transparent_address_from_public_key(&pk).encode(&MAIN_NETWORK); assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string()); } } diff --git a/zcash_client_sqlite/Cargo.toml b/zcash_client_sqlite/Cargo.toml index ef7fb0853..720ff385a 100644 --- a/zcash_client_sqlite/Cargo.toml +++ b/zcash_client_sqlite/Cargo.toml @@ -21,9 +21,10 @@ jubjub = "0.5.1" protobuf = "2.20" rand_core = "0.5.1" rusqlite = { version = "0.24", features = ["bundled", "time"] } +secp256k1 = { version = "0.19" } time = "0.2" -zcash_client_backend = { version = "0.5", path = "../zcash_client_backend" } -zcash_primitives = { version = "0.5", path = "../zcash_primitives" } +zcash_client_backend = { version = "0.5", path = "../zcash_client_backend", features = ["transparent-inputs"] } +zcash_primitives = { version = "0.5", path = "../zcash_primitives", features = ["transparent-inputs"] } [dev-dependencies] rand_core = "0.5.1" @@ -32,5 +33,4 @@ zcash_proofs = { version = "0.5", path = "../zcash_proofs" } [features] mainnet = [] -transparent-inputs = [] test-dependencies = ["zcash_client_backend/test-dependencies"] diff --git a/zcash_client_sqlite/src/chain.rs b/zcash_client_sqlite/src/chain.rs index b416ebe88..90bc8fde9 100644 --- a/zcash_client_sqlite/src/chain.rs +++ b/zcash_client_sqlite/src/chain.rs @@ -69,9 +69,7 @@ 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 +82,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 +100,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 +179,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 +249,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 +319,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()); @@ -390,9 +376,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(); @@ -451,9 +435,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()); @@ -499,9 +481,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()); diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index cc71b03ec..f902ea2fe 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -60,7 +60,6 @@ use zcash_client_backend::{ use crate::error::SqliteClientError; -#[cfg(feature = "transparent-inputs")] use { zcash_client_backend::wallet::WalletTransparentOutput, zcash_primitives::legacy::TransparentAddress, @@ -89,7 +88,6 @@ impl fmt::Display for NoteId { /// A newtype wrapper for sqlite primary key values for the utxos /// table. -#[cfg(feature = "transparent-inputs")] #[derive(Debug, Copy, Clone)] pub struct UtxoId(pub i64); @@ -145,12 +143,10 @@ impl WalletDb

{ 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" )?, @@ -285,7 +281,6 @@ impl WalletRead for WalletDb

{ wallet::transact::select_unspent_sapling_notes(&self, account, target_value, anchor_height) } - #[cfg(feature = "transparent-inputs")] fn get_unspent_transparent_utxos( &self, address: &TransparentAddress, @@ -316,9 +311,7 @@ pub struct DataConnStmtCache<'a, P> { stmt_mark_sapling_note_spent: Statement<'a>, 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>, @@ -417,7 +410,6 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> { .select_unspent_sapling_notes(account, target_value, anchor_height) } - #[cfg(feature = "transparent-inputs")] fn get_unspent_transparent_utxos( &self, address: &TransparentAddress, @@ -656,23 +648,30 @@ mod tests { use protobuf::Message; use rand_core::{OsRng, RngCore}; use rusqlite::params; + use secp256k1::key::SecretKey; - use zcash_client_backend::proto::compact_formats::{ - CompactBlock, CompactOutput, CompactSpend, CompactTx, + use zcash_client_backend::{ + keys::{ + derive_secret_key_from_seed, derive_transparent_address_from_secret_key, spending_key, + }, + proto::compact_formats::{CompactBlock, CompactOutput, CompactSpend, CompactTx}, }; 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, PaymentAddress, }, transaction::components::Amount, - zip32::ExtendedFullViewingKey, + zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, }; + use crate::{wallet::init::init_accounts_table, AccountId, WalletDb}; + use super::BlockDb; #[cfg(feature = "mainnet")] @@ -699,6 +698,25 @@ mod tests { .unwrap() } + pub(crate) fn derive_test_keys_from_seed( + seed: &[u8], + account: AccountId, + ) -> (ExtendedSpendingKey, SecretKey) { + let extsk = spending_key(seed, network().coin_type(), account); + let tsk = derive_secret_key_from_seed(&network(), seed, account, 0).unwrap(); + (extsk, tsk) + } + + pub(crate) fn init_test_accounts_table( + db_data: &WalletDb, + ) -> (ExtendedFullViewingKey, TransparentAddress) { + let (extsk, tsk) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); + let extfvk = ExtendedFullViewingKey::from(&extsk); + let taddr = derive_transparent_address_from_secret_key(&tsk); + init_accounts_table(db_data, &[extfvk.clone()], &[taddr.clone()]).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( diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 468689f45..44996372a 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -37,7 +37,6 @@ use zcash_client_backend::{ use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDb}; -#[cfg(feature = "transparent-inputs")] use { crate::UtxoId, zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput}, @@ -597,7 +596,6 @@ pub fn get_nullifiers

( Ok(res) } -#[cfg(feature = "transparent-inputs")] pub fn get_unspent_transparent_utxos( wdb: &WalletDb

, address: &TransparentAddress, @@ -769,7 +767,6 @@ pub fn mark_transparent_utxo_spent<'a, P>( Ok(()) } -#[cfg(feature = "transparent-inputs")] pub fn put_received_transparent_utxo<'a, P: consensus::Parameters>( stmts: &mut DataConnStmtCache<'a, P>, output: &WalletTransparentOutput, @@ -790,7 +787,6 @@ pub fn put_received_transparent_utxo<'a, P: consensus::Parameters>( Ok(UtxoId(stmts.wallet_db.conn.last_insert_rowid())) } -#[cfg(feature = "transparent-inputs")] pub fn delete_utxos_above<'a, P: consensus::Parameters>( stmts: &mut DataConnStmtCache<'a, P>, taddr: &TransparentAddress, @@ -957,18 +953,11 @@ pub fn insert_sent_note<'a, P: consensus::Parameters>( 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}; @@ -979,9 +968,7 @@ 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()); diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index a55f8c609..ac77d0286 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -2,7 +2,12 @@ use rusqlite::{types::ToSql, NO_PARAMS}; -use zcash_primitives::{block::BlockHash, consensus::{self, BlockHeight}, legacy::TransparentAddress, zip32::ExtendedFullViewingKey}; +use zcash_primitives::{ + block::BlockHash, + consensus::{self, BlockHeight}, + legacy::TransparentAddress, + zip32::ExtendedFullViewingKey, +}; use zcash_client_backend::encoding::{encode_extended_full_viewing_key, AddressCodec}; @@ -29,7 +34,8 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { "CREATE TABLE IF NOT EXISTS accounts ( account INTEGER PRIMARY KEY, extfvk TEXT NOT NULL, - address TEXT NOT NULL + address TEXT NOT NULL, + transparent_address TEXT NOT NULL )", NO_PARAMS, )?; @@ -133,10 +139,15 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { /// use tempfile::NamedTempFile; /// /// use zcash_primitives::{ -/// consensus::Network, +/// consensus::{Network, Parameters}, /// zip32::{ExtendedFullViewingKey, ExtendedSpendingKey} /// }; /// +/// use zcash_client_backend::{ +/// keys::{spending_key, derive_transparent_address_from_secret_key, derive_secret_key_from_seed}, +/// wallet::AccountId, +/// }; +/// /// use zcash_client_sqlite::{ /// WalletDb, /// wallet::init::{init_accounts_table, init_wallet_db} @@ -146,9 +157,13 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> 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]; +/// let account = AccountId(0); +/// let extsk = spending_key(&seed, Network::TestNetwork.coin_type(), account); +/// let tsk = derive_secret_key_from_seed(&Network::TestNetwork, &seed, account, 0).unwrap(); +/// let extfvk = ExtendedFullViewingKey::from(&extsk); +/// let taddr = derive_transparent_address_from_secret_key(&tsk); +/// init_accounts_table(&db_data, &[&extfvk], &[&taddr]).unwrap(); /// ``` /// /// [`get_address`]: crate::wallet::get_address @@ -157,8 +172,11 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { pub fn init_accounts_table( wdb: &WalletDb

, extfvks: &[ExtendedFullViewingKey], - taddrs: &Vec, + taddrs: &[TransparentAddress], ) -> Result<(), SqliteClientError> { + //TODO: make this a proper error? + assert!(extfvks.len() == taddrs.len()); + let mut empty_check = wdb.conn.prepare("SELECT * FROM accounts LIMIT 1")?; if empty_check.exists(NO_PARAMS)? { return Err(SqliteClientError::TableNotEmpty); @@ -253,10 +271,10 @@ pub fn init_blocks_table

( mod tests { use tempfile::NamedTempFile; + use zcash_client_backend::keys::derive_transparent_address_from_secret_key; + use zcash_primitives::{ - block::BlockHash, - consensus::BlockHeight, - zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, + block::BlockHash, consensus::BlockHeight, zip32::ExtendedFullViewingKey, }; use crate::{tests, wallet::get_address, AccountId, WalletDb}; @@ -270,18 +288,18 @@ mod tests { init_wallet_db(&db_data).unwrap(); // We can call the function as many times as we want with no data - init_accounts_table(&db_data, &[]).unwrap(); - init_accounts_table(&db_data, &[]).unwrap(); + init_accounts_table(&db_data, &[], &[]).unwrap(); + init_accounts_table(&db_data, &[], &[]).unwrap(); // First call with data should initialise the accounts table - let extfvks = [ExtendedFullViewingKey::from(&ExtendedSpendingKey::master( - &[], - ))]; - init_accounts_table(&db_data, &extfvks).unwrap(); + let (extsk, tsk) = tests::derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); + let extfvk = ExtendedFullViewingKey::from(&extsk); + let taddr = derive_transparent_address_from_secret_key(&tsk); + init_accounts_table(&db_data, &[extfvk.clone()], &[taddr.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, &[], &[]).unwrap_err(); + init_accounts_table(&db_data, &[extfvk], &[taddr]).unwrap_err(); } #[test] @@ -318,9 +336,10 @@ 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, tsk) = tests::derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); + let extfvk = ExtendedFullViewingKey::from(&extsk); + let taddr = derive_transparent_address_from_secret_key(&tsk); + init_accounts_table(&db_data, &[extfvk], &[taddr]).unwrap(); // The account's address should be in the data DB let pa = get_address(&db_data, AccountId(0)).unwrap(); diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index db313c73f..c897f0dd1 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -164,12 +164,16 @@ mod tests { use zcash_client_backend::{ data_api::{chain::scan_cached_blocks, wallet::create_spend_to_address, WalletRead}, + keys::derive_transparent_address_from_secret_key, wallet::OvkPolicy, }; use crate::{ chain::init::init_cache_database, - tests::{self, fake_compact_block, insert_into_cache, sapling_activation_height}, + tests::{ + self, derive_test_keys_from_seed, fake_compact_block, insert_into_cache, + sapling_activation_height, + }, wallet::{ get_balance, get_balance_at, init::{init_accounts_table, init_blocks_table, init_wallet_db}, @@ -193,13 +197,17 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add two accounts to the wallet - let extsk0 = ExtendedSpendingKey::master(&[]); - let extsk1 = ExtendedSpendingKey::master(&[0]); + let (extsk0, tsk0) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); + let (extsk1, tsk1) = derive_test_keys_from_seed(&[1u8; 32], AccountId(1)); let extfvks = [ ExtendedFullViewingKey::from(&extsk0), ExtendedFullViewingKey::from(&extsk1), ]; - init_accounts_table(&db_data, &extfvks).unwrap(); + let taddrs = [ + derive_transparent_address_from_secret_key(&tsk0), + derive_transparent_address_from_secret_key(&tsk1), + ]; + init_accounts_table(&db_data, &extfvks, &taddrs).unwrap(); let to = extsk0.default_address().unwrap().1.into(); // Invalid extsk for the given account should cause an error @@ -242,9 +250,10 @@ 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, tsk) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); + let extfvk = ExtendedFullViewingKey::from(&extsk); + let taddr = derive_transparent_address_from_secret_key(&tsk); + init_accounts_table(&db_data, &[extfvk], &[taddr]).unwrap(); let to = extsk.default_address().unwrap().1.into(); // We cannot do anything if we aren't synchronised @@ -280,9 +289,10 @@ 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, tsk) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); + let extfvk = ExtendedFullViewingKey::from(&extsk); + let taddr = derive_transparent_address_from_secret_key(&tsk); + init_accounts_table(&db_data, &[extfvk], &[taddr]).unwrap(); let to = extsk.default_address().unwrap().1.into(); // Account balance should be zero @@ -320,9 +330,10 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add an account to the wallet - let extsk = ExtendedSpendingKey::master(&[]); + let (extsk, tsk) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); - init_accounts_table(&db_data, &[extfvk.clone()]).unwrap(); + let taddr = derive_transparent_address_from_secret_key(&tsk); + init_accounts_table(&db_data, &[extfvk.clone()], &[taddr]).unwrap(); // Add funds to the wallet in a single note let value = Amount::from_u64(50000).unwrap(); @@ -447,9 +458,10 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add an account to the wallet - let extsk = ExtendedSpendingKey::master(&[]); + let (extsk, tsk) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); - init_accounts_table(&db_data, &[extfvk.clone()]).unwrap(); + let taddr = derive_transparent_address_from_secret_key(&tsk); + init_accounts_table(&db_data, &[extfvk.clone()], &[taddr]).unwrap(); // Add funds to the wallet in a single note let value = Amount::from_u64(50000).unwrap(); @@ -568,9 +580,10 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add an account to the wallet - let extsk = ExtendedSpendingKey::master(&[]); + let (extsk, tsk) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); - init_accounts_table(&db_data, &[extfvk.clone()]).unwrap(); + let taddr = derive_transparent_address_from_secret_key(&tsk); + init_accounts_table(&db_data, &[extfvk.clone()], &[taddr]).unwrap(); // Add funds to the wallet in a single note let value = Amount::from_u64(50000).unwrap(); @@ -673,9 +686,10 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add an account to the wallet - let extsk = ExtendedSpendingKey::master(&[]); + let (extsk, tsk) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); - init_accounts_table(&db_data, &[extfvk.clone()]).unwrap(); + let taddr = derive_transparent_address_from_secret_key(&tsk); + init_accounts_table(&db_data, &[extfvk.clone()], &[taddr]).unwrap(); // Add funds to the wallet in a single note let value = Amount::from_u64(51000).unwrap(); From 9b3025de4dc70237af60e2c9fe2fc9da9f12814b Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 9 Apr 2021 14:40:35 -0600 Subject: [PATCH 12/79] Add ZIP-321 request based sends to zcash_client_backend. --- zcash_client_backend/src/data_api.rs | 8 +- zcash_client_backend/src/data_api/wallet.rs | 129 +++++++++++++------- zcash_client_backend/src/zip321.rs | 2 +- zcash_client_sqlite/src/lib.rs | 20 +-- zcash_client_sqlite/src/wallet/init.rs | 2 +- 5 files changed, 104 insertions(+), 57 deletions(-) diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index b2c3c0217..2a3bd7b2b 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -217,6 +217,12 @@ pub struct DecryptedTransaction<'a> { pub struct SentTransaction<'a> { pub tx: &'a Transaction, pub created: time::OffsetDateTime, + pub account: AccountId, + pub outputs: Vec>, + pub utxos_spent: Vec, +} + +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 @@ -224,11 +230,9 @@ 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, - pub utxos_spent: Vec, } /// This trait encapsulates the write capabilities required to update stored diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index e0a4a37ff..38d85b8c5 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -13,9 +13,12 @@ use zcash_primitives::{ use crate::{ address::RecipientAddress, - data_api::{error::Error, DecryptedTransaction, SentTransaction, WalletWrite}, + data_api::{ + error::Error, DecryptedTransaction, SentTransaction, SentTransactionOutput, WalletWrite, + }, decrypt_transaction, wallet::{AccountId, OvkPolicy}, + zip321::{Payment, TransactionRequest}, }; #[cfg(feature = "transparent-inputs")] @@ -159,10 +162,39 @@ pub fn create_spend_to_address( account: AccountId, extsk: &ExtendedSpendingKey, to: &RecipientAddress, - value: Amount, + amount: Amount, memo: Option, ovk_policy: OvkPolicy, ) -> Result +where + E: From>, + P: consensus::Parameters + Clone, + R: Copy + Debug, + D: WalletWrite, +{ + let req = TransactionRequest { + payments: vec![Payment { + recipient_address: to.clone(), + amount, + memo, + label: None, + message: None, + other_params: vec![], + }], + }; + + spend(wallet_db, params, prover, account, extsk, &req, ovk_policy) +} + +pub fn spend( + wallet_db: &mut D, + params: &P, + prover: impl TxProver, + account: AccountId, + extsk: &ExtendedSpendingKey, + request: &TransactionRequest, + ovk_policy: OvkPolicy, +) -> Result where E: From>, P: consensus::Parameters + Clone, @@ -188,7 +220,7 @@ where .get_target_and_anchor_heights() .and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?; - let target_value = value + DEFAULT_FEE; + let target_value = request.payments.iter().map(|p| p.amount).sum::() + DEFAULT_FEE; let spendable_notes = wallet_db.select_unspent_sapling_notes(account, target_value, anchor_height)?; @@ -202,6 +234,7 @@ where } // Create the transaction + let consensus_branch_id = BranchId::for_height(params, height); let mut builder = Builder::new(params.clone(), height); for selected in spendable_notes { let from = extfvk @@ -221,55 +254,61 @@ where .map_err(Error::Builder)?; } - match to { - RecipientAddress::Shielded(to) => { - memo.clone().ok_or(Error::MemoRequired).and_then(|memo| { - builder - .add_sapling_output(ovk, to.clone(), value, memo) - .map_err(Error::Builder) - }) - } - RecipientAddress::Transparent(to) => { - if memo.is_some() { - Err(Error::MemoForbidden) - } else { - builder - .add_transparent_output(&to, value) - .map_err(Error::Builder) + 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) + } } - } - }?; + }? + } - let consensus_branch_id = BranchId::for_height(params, height); let (tx, tx_metadata) = builder .build(consensus_branch_id, &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.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.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, utxos_spent: vec![], }) } @@ -353,11 +392,13 @@ where wallet_db.store_sent_tx(&SentTransaction { tx: &tx, created: time::OffsetDateTime::now_utc(), - output_index, account, - recipient_address: &RecipientAddress::Shielded(z_address), - value: amount_to_shield, - memo: Some(memo.clone()), + 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(), }) } diff --git a/zcash_client_backend/src/zip321.rs b/zcash_client_backend/src/zip321.rs index bc2d8300b..7abbcc15e 100644 --- a/zcash_client_backend/src/zip321.rs +++ b/zcash_client_backend/src/zip321.rs @@ -106,7 +106,7 @@ impl Payment { /// payment value in the request. #[derive(Debug, PartialEq)] pub struct TransactionRequest { - payments: Vec, + pub payments: Vec, } impl TransactionRequest { diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index f902ea2fe..98c7efcdd 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -587,15 +587,17 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { wallet::mark_transparent_utxo_spent(up, tx_ref, &utxo_outpoint)?; } - 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(), - )?; + for output in &sent_tx.outputs { + wallet::insert_sent_note( + up, + tx_ref, + output.output_index, + sent_tx.account, + output.recipient_address, + output.value, + output.memo.as_ref(), + )?; + } // Return the row number of the transaction, so the caller can fetch it for sending. Ok(tx_ref) diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index ac77d0286..96e436300 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -163,7 +163,7 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { /// let tsk = derive_secret_key_from_seed(&Network::TestNetwork, &seed, account, 0).unwrap(); /// let extfvk = ExtendedFullViewingKey::from(&extsk); /// let taddr = derive_transparent_address_from_secret_key(&tsk); -/// init_accounts_table(&db_data, &[&extfvk], &[&taddr]).unwrap(); +/// init_accounts_table(&db_data, &[extfvk], &[taddr]).unwrap(); /// ``` /// /// [`get_address`]: crate::wallet::get_address From bb68744df123895272c36107d25127163d18ab1d Mon Sep 17 00:00:00 2001 From: Kevin Gorham Date: Tue, 13 Apr 2021 13:02:35 -0400 Subject: [PATCH 13/79] Ensure that rewinds go far enough to properly restore incremental witness state. --- zcash_client_backend/src/address.rs | 14 +- zcash_client_backend/src/data_api.rs | 14 +- zcash_client_backend/src/data_api/wallet.rs | 12 +- zcash_client_backend/src/encoding.rs | 18 +++ zcash_client_sqlite/src/error.rs | 13 +- zcash_client_sqlite/src/lib.rs | 70 ++++++---- zcash_client_sqlite/src/wallet.rs | 140 ++++++++++++++++---- zcash_client_sqlite/src/wallet/init.rs | 2 +- 8 files changed, 212 insertions(+), 71 deletions(-) diff --git a/zcash_client_backend/src/address.rs b/zcash_client_backend/src/address.rs index 8b144a3b7..472a00aad 100644 --- a/zcash_client_backend/src/address.rs +++ b/zcash_client_backend/src/address.rs @@ -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. @@ -43,14 +43,8 @@ impl RecipientAddress { pub fn encode(&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), } } } diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 2a3bd7b2b..e115cf299 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -143,11 +143,11 @@ pub trait WalletRead { ) -> Result; /// Returns the memo for a note. - /// - /// Implementations of this method must return an error if the note identifier - /// does not appear in the backing data store. fn get_memo(&self, id_note: Self::NoteRef) -> Result; + /// Returns a transaction. + fn get_transaction(&self, id_tx: Self::TxRef) -> Result; + /// Returns the note commitment tree at the specified block height. fn get_commitment_tree( &self, @@ -248,6 +248,7 @@ pub trait WalletWrite: WalletRead { fn store_decrypted_tx( &mut self, received_tx: &DecryptedTransaction, + nullifiers: &[(AccountId, Nullifier)], ) -> Result; fn store_sent_tx(&mut self, sent_tx: &SentTransaction) -> Result; @@ -296,7 +297,7 @@ pub mod testing { memo::Memo, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{Node, Nullifier, PaymentAddress}, - transaction::{components::Amount, TxId}, + transaction::{components::Amount, Transaction, TxId}, zip32::ExtendedFullViewingKey, }; @@ -380,6 +381,10 @@ pub mod testing { Ok(Memo::Empty) } + fn get_transaction(&self, _id_tx: Self::TxRef) -> Result { + Err(Error::ScanRequired) // wrong error but we'll fix it later. + } + fn get_commitment_tree( &self, _block_height: BlockHeight, @@ -439,6 +444,7 @@ pub mod testing { fn store_decrypted_tx( &mut self, _received_tx: &DecryptedTransaction, + _nullifiers: &[(AccountId, Nullifier)], ) -> Result { Ok(TxId([0u8; 32])) } diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 38d85b8c5..1fa0d3ae6 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -57,12 +57,16 @@ where .ok_or(Error::SaplingNotActive)?; let sapling_outputs = decrypt_transaction(params, height, tx, &extfvks); + let nullifiers = data.get_nullifiers()?; if !(sapling_outputs.is_empty() && tx.vout.is_empty()) { - data.store_decrypted_tx(&DecryptedTransaction { - tx, - sapling_outputs: &sapling_outputs, - })?; + data.store_decrypted_tx( + &DecryptedTransaction { + tx, + sapling_outputs: &sapling_outputs, + }, + &nullifiers, + )?; } Ok(()) diff --git a/zcash_client_backend/src/encoding.rs b/zcash_client_backend/src/encoding.rs index 520e3e071..f9d922144 100644 --- a/zcash_client_backend/src/encoding.rs +++ b/zcash_client_backend/src/encoding.rs @@ -197,6 +197,13 @@ pub fn encode_payment_address(hrp: &str, addr: &PaymentAddress) -> String { bech32_encode(hrp, |w| w.write_all(&addr.to_bytes())) } +pub fn encode_payment_address_p( + params: &P, + addr: &PaymentAddress, +) -> String { + encode_payment_address(params.hrp_sapling_payment_address(), addr) +} + /// Decodes a [`PaymentAddress`] from a Bech32-encoded string. /// /// # Examples @@ -300,6 +307,17 @@ pub fn encode_transparent_address( bs58::encode(decoded).with_check().into_string() } +pub fn encode_transparent_address_p( + 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 diff --git a/zcash_client_sqlite/src/error.rs b/zcash_client_sqlite/src/error.rs index 6c285fb4c..f487df7b2 100644 --- a/zcash_client_sqlite/src/error.rs +++ b/zcash_client_sqlite/src/error.rs @@ -4,8 +4,9 @@ use std::error; use std::fmt; 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)] @@ -44,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 + /// the safe rewind height. + RequestedRewindInvalid(BlockHeight), + /// Wrapper for errors from zcash_client_backend BackendError(data_api::error::Error), } @@ -68,7 +74,10 @@ 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) => + write!(f, "A rewind must be either of less than {} blocks, or at least back to block {} for your wallet.", PRUNING_HEIGHT, h), SqliteClientError::Bech32(e) => write!(f, "{}", e), SqliteClientError::Base58(e) => write!(f, "{}", e), SqliteClientError::TransparentAddress(e) => write!(f, "{}", e), diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 98c7efcdd..9a70dea96 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -44,7 +44,7 @@ use zcash_primitives::{ memo::Memo, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{Node, Nullifier, PaymentAddress}, - transaction::{components::Amount, TxId}, + transaction::{components::Amount, Transaction, TxId}, zip32::ExtendedFullViewingKey, }; @@ -69,6 +69,8 @@ pub mod chain; pub mod error; pub mod wallet; +pub const PRUNING_HEIGHT: u32 = 100; + /// A newtype wrapper for sqlite primary key values for the notes /// table. #[derive(Debug, Copy, Clone)] @@ -238,6 +240,10 @@ impl WalletRead for WalletDb

{ wallet::get_balance_at(&self, account, anchor_height) } + fn get_transaction(&self, id_tx: i64) -> Result { + wallet::get_transaction(&self, id_tx) + } + fn get_memo(&self, id_note: Self::NoteRef) -> Result { match id_note { NoteId::SentNoteId(id_note) => wallet::get_sent_memo(self, id_note), @@ -368,6 +374,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 { + self.wallet_db.get_transaction(id_tx) + } + fn get_memo(&self, id_note: Self::NoteRef) -> Result { self.wallet_db.get_memo(id_note) } @@ -497,7 +507,7 @@ 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)?; + 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)?; @@ -509,6 +519,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { fn store_decrypted_tx( &mut self, d_tx: &DecryptedTransaction, + nullifiers: &[(AccountId, Nullifier)], ) -> Result { self.transactionally(|up| { let tx_ref = wallet::put_tx_data(up, d_tx.tx, None)?; @@ -521,7 +532,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { tx_ref, output.index, output.account, - RecipientAddress::Shielded(output.to.clone()), + &output.to, Amount::from_u64(output.note.value) .map_err(|_| SqliteClientError::CorruptedData("Note value invalid.".to_string()))?, Some(&output.memo), @@ -541,22 +552,22 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { } } - // store received z->t transactions in the same way they would be stored by - // create_spend_to_address If there are any of our shielded inputs, we interpret this - // as our z->t tx and store the vouts as our sent notes. - // FIXME this is a weird heuristic that is bound to trip us up somewhere. - if d_tx.sapling_outputs.iter().any(|output| !output.outgoing) { - if let Some(account_id) = spending_account_id { - for (i, txout) in d_tx.tx.vout.iter().enumerate() { - // FIXME: We shouldn't be confusing notes and transparent outputs. - wallet::put_sent_note( + // if we have some transparent outputs yet no shielded outputs, then this is t2t and we + // can safely ignore it otherwise, this is z2t and it might have originated from our + // wallet + if !d_tx.tx.vout.is_empty() { + // store received z->t transactions in the same way they would be stored by + // create_spend_to_address If there are any of our shielded inputs, we interpret this + // as our z->t tx and store the vouts as our sent notes. + // FIXME this is a weird heuristic that is bound to trip us up somewhere. + if d_tx.tx.shielded_spends.iter().any(|input| nullifiers.iter().any(|(_, n)| *n == input.nullifier)) { + for (output_index, txout) in d_tx.tx.vout.iter().enumerate() { + wallet::put_sent_utxo( up, tx_ref, - i, - account_id, - RecipientAddress::Transparent(txout.script_pubkey.address().unwrap()), + output_index, + &txout.script_pubkey.address().unwrap(), txout.value, - None, )?; } } @@ -588,15 +599,24 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { } for output in &sent_tx.outputs { - wallet::insert_sent_note( - up, - tx_ref, - output.output_index, - sent_tx.account, - output.recipient_address, - output.value, - output.memo.as_ref(), - )?; + 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, + &addr, + output.value, + )?, + } } // Return the row number of the transaction, so the caller can fetch it for sending. diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 44996372a..ebfd40281 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -26,16 +26,16 @@ use zcash_primitives::{ }; 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_p, encode_transparent_address_p, }, wallet::{AccountId, WalletShieldedOutput, WalletTx}, DecryptedOutput, }; -use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDb}; +use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDb, PRUNING_HEIGHT}; use { crate::UtxoId, @@ -311,6 +311,17 @@ pub fn get_received_memo

(wdb: &WalletDb

, id_note: i64) -> Result(wdb: &WalletDb

, id_tx: i64) -> Result { + let tx_bytes: Vec<_> = wdb.conn.query_row( + "SELECT raw FROM transactions + WHERE id_tx = ?", + &[id_tx], + |row| row.get(0), + )?; + + Transaction::read(&tx_bytes[..]).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 @@ -445,6 +456,22 @@ pub fn get_block_hash

( .optional() } +/// Gets the height to which the database must be rewound if any rewind greater than the pruning +/// height is attempted. +pub fn get_rewind_height

(wdb: &WalletDb

) -> Result, 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(|h: u32| h.into()), + ) + .optional() + .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 @@ -469,30 +496,46 @@ pub fn rewind_to_height( .or(Ok(sapling_activation_height - 1)) })?; - // nothing to do if we're deleting back down to the max height - if block_height >= last_scanned_height { - Ok(()) + let rewind_height = if block_height >= (last_scanned_height - PRUNING_HEIGHT) { + Some(block_height) } else { - // Decrement witnesses. - wdb.conn.execute( - "DELETE FROM sapling_witnesses WHERE block > ?", - &[u32::from(block_height)], - )?; + match get_rewind_height(&wdb)? { + Some(h) => { + if block_height > h { + return Err(SqliteClientError::RequestedRewindInvalid(h)); + } else { + Some(block_height) + } + } + None => Some(block_height), + } + }; - // Un-mine transactions. - wdb.conn.execute( - "UPDATE transactions SET block = NULL, tx_index = NULL WHERE block > ?", - &[u32::from(block_height)], - )?; + // nothing to do if we're deleting back down to the max height - // Now that they aren't depended on, delete scanned blocks. - wdb.conn.execute( - "DELETE FROM blocks WHERE height > ?", - &[u32::from(block_height)], - )?; + if let Some(block_height) = rewind_height { + if block_height < last_scanned_height { + // Decrement witnesses. + wdb.conn.execute( + "DELETE FROM sapling_witnesses WHERE block > ?", + &[u32::from(block_height)], + )?; - Ok(()) + // Un-mine transactions. + wdb.conn.execute( + "UPDATE transactions SET block = NULL, tx_index = NULL WHERE block > ?", + &[u32::from(block_height)], + )?; + + // Now that they aren't depended on, delete scanned blocks. + wdb.conn.execute( + "DELETE FROM blocks WHERE height > ?", + &[u32::from(block_height)], + )?; + } } + + Ok(()) } /// Returns the commitment tree for the block at the specified height, @@ -897,7 +940,7 @@ pub fn put_sent_note<'a, P: consensus::Parameters>( tx_ref: i64, output_index: usize, account: AccountId, - to: RecipientAddress, + to: &PaymentAddress, value: Amount, memo: Option<&MemoBytes>, ) -> Result<(), SqliteClientError> { @@ -905,7 +948,7 @@ pub fn put_sent_note<'a, P: consensus::Parameters>( // Try updating an existing sent note. if stmts.stmt_update_sent_note.execute(params![ account.0 as i64, - to.encode(&stmts.wallet_db.params), + encode_payment_address_p(&stmts.wallet_db.params, &to), ivalue, &memo.map(|m| m.as_slice()), tx_ref, @@ -919,6 +962,31 @@ pub fn put_sent_note<'a, P: consensus::Parameters>( Ok(()) } +pub fn put_sent_utxo<'a, P: consensus::Parameters>( + stmts: &mut DataConnStmtCache<'a, P>, + tx_ref: i64, + output_index: usize, + to: &TransparentAddress, + value: Amount, +) -> Result<(), SqliteClientError> { + let ivalue: i64 = value.into(); + // Try updating an existing sent note. + if stmts.stmt_update_sent_note.execute(params![ + (None::), + encode_transparent_address_p(&stmts.wallet_db.params, &to), + ivalue, + (None::<&[u8]>), + tx_ref, + output_index as i64 + ])? == 0 + { + // It isn't there, so insert. + insert_sent_utxo(stmts, tx_ref, output_index, &to, value)? + } + + Ok(()) +} + /// Inserts a sent note into the wallet database. /// /// `output_index` is the index within the transaction that contains the recipient output: @@ -932,11 +1000,11 @@ pub fn insert_sent_note<'a, P: consensus::Parameters>( 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, @@ -949,6 +1017,28 @@ pub fn insert_sent_note<'a, P: consensus::Parameters>( Ok(()) } + +pub fn insert_sent_utxo<'a, P: consensus::Parameters>( + stmts: &mut DataConnStmtCache<'a, P>, + tx_ref: i64, + output_index: usize, + 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, + (output_index as i64), + (None::), + to_str, + ivalue, + (None::<&[u8]>) + ])?; + + Ok(()) +} + #[cfg(test)] mod tests { use tempfile::NamedTempFile; diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 96e436300..82388dc0d 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -98,7 +98,7 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { id_note INTEGER PRIMARY KEY, tx INTEGER NOT NULL, output_index INTEGER NOT NULL, - from_account INTEGER NOT NULL, + from_account INTEGER, address TEXT NOT NULL, value INTEGER NOT NULL, memo BLOB, From c1bc06964fd5c3b765859a5aca2524a8fb78595a Mon Sep 17 00:00:00 2001 From: Kevin Gorham Date: Wed, 14 Apr 2021 13:20:56 -0400 Subject: [PATCH 14/79] Add get_all_nullifiers. --- zcash_client_backend/src/data_api.rs | 2 ++ zcash_client_backend/src/data_api/wallet.rs | 4 ++-- zcash_client_sqlite/src/lib.rs | 11 +++++++++-- zcash_client_sqlite/src/wallet.rs | 20 +++++++++++++++++++- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index e115cf299..245076623 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -165,6 +165,8 @@ pub trait WalletRead { /// with which they are associated. fn get_nullifiers(&self) -> Result, Self::Error>; + fn get_all_nullifiers(&self) -> Result, Self::Error>; + /// Return all unspent notes. fn get_unspent_sapling_notes( &self, diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 1fa0d3ae6..22a421705 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -57,9 +57,9 @@ where .ok_or(Error::SaplingNotActive)?; let sapling_outputs = decrypt_transaction(params, height, tx, &extfvks); - let nullifiers = data.get_nullifiers()?; - + if !(sapling_outputs.is_empty() && tx.vout.is_empty()) { + let nullifiers = data.get_all_nullifiers()?; data.store_decrypted_tx( &DecryptedTransaction { tx, diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 9a70dea96..4dd1d20a0 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -270,6 +270,10 @@ impl WalletRead for WalletDb

{ wallet::get_nullifiers(&self) } + fn get_all_nullifiers(&self) -> Result, Self::Error> { + wallet::get_all_nullifiers(&self) + } + fn get_unspent_sapling_notes( &self, account: AccountId, @@ -401,6 +405,10 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> { self.wallet_db.get_nullifiers() } + fn get_all_nullifiers(&self) -> Result, Self::Error> { + self.wallet_db.get_all_nullifiers() + } + fn get_unspent_sapling_notes( &self, account: AccountId, @@ -560,7 +568,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { // create_spend_to_address If there are any of our shielded inputs, we interpret this // as our z->t tx and store the vouts as our sent notes. // FIXME this is a weird heuristic that is bound to trip us up somewhere. - if d_tx.tx.shielded_spends.iter().any(|input| nullifiers.iter().any(|(_, n)| *n == input.nullifier)) { + if d_tx.tx.shielded_spends.iter().any(|input| nullifiers.iter().any(|(_, n)| { *n == input.nullifier } )) { for (output_index, txout) in d_tx.tx.vout.iter().enumerate() { wallet::put_sent_utxo( up, @@ -572,7 +580,6 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { } } } - Ok(tx_ref) }) } diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index ebfd40281..f134ef29e 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -639,6 +639,24 @@ pub fn get_nullifiers

( Ok(res) } +pub fn get_all_nullifiers

( + wdb: &WalletDb

, +) -> Result, 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 = row.get(2)?; + Ok((account, Nullifier::from_slice(&nf_bytes).unwrap())) + })?; + + let res: Vec<_> = nullifiers.collect::>()?; + Ok(res) +} + pub fn get_unspent_transparent_utxos( wdb: &WalletDb

, address: &TransparentAddress, @@ -972,7 +990,7 @@ pub fn put_sent_utxo<'a, P: consensus::Parameters>( let ivalue: i64 = value.into(); // Try updating an existing sent note. if stmts.stmt_update_sent_note.execute(params![ - (None::), + 0, encode_transparent_address_p(&stmts.wallet_db.params, &to), ivalue, (None::<&[u8]>), From 7fc2d9725c2ff0be6f5a63d0ec1cfeb72e2f1b65 Mon Sep 17 00:00:00 2001 From: Kevin Gorham Date: Tue, 20 Apr 2021 10:04:56 -0400 Subject: [PATCH 15/79] Placeholder commit: just get things working. Clean up later but for now don't allow nullable accountIds and also delete more data, during a rewind. --- zcash_client_sqlite/src/lib.rs | 27 +++++++++++------ zcash_client_sqlite/src/wallet.rs | 40 ++++++++++++++++++++++++-- zcash_client_sqlite/src/wallet/init.rs | 2 +- 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 4dd1d20a0..e8c408617 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -568,15 +568,23 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { // create_spend_to_address If there are any of our shielded inputs, we interpret this // as our z->t tx and store the vouts as our sent notes. // FIXME this is a weird heuristic that is bound to trip us up somewhere. - if d_tx.tx.shielded_spends.iter().any(|input| nullifiers.iter().any(|(_, n)| { *n == input.nullifier } )) { - for (output_index, txout) in d_tx.tx.vout.iter().enumerate() { - wallet::put_sent_utxo( - up, - tx_ref, - output_index, - &txout.script_pubkey.address().unwrap(), - txout.value, - )?; + + // d_tx.tx.shielded_spends.iter().find(|input| nullifiers.iter().any(|(_, n)| { *n == input.nullifier } )) { + + match nullifiers.iter().find(|(_, n)| d_tx.tx.shielded_spends.iter().any(|input| *n == input.nullifier )) { + Some(tx_input) => { + for (output_index, txout) in d_tx.tx.vout.iter().enumerate() { + wallet::put_sent_utxo( + up, + tx_ref, + output_index, + tx_input.0, + &txout.script_pubkey.address().unwrap(), + txout.value, + )?; + } + }, + None => { } } } @@ -620,6 +628,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { up, tx_ref, output.output_index, + sent_tx.account, &addr, output.value, )?, diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index f134ef29e..488486d6d 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -532,6 +532,38 @@ pub fn rewind_to_height( "DELETE FROM blocks WHERE height > ?", &[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 > ? + );", + &[u32::from(block_height)], + )?; + + // Rewind sent notes + wdb.conn.execute( + "DELETE FROM sent_notes + WHERE id_note IN ( + SELECT sn.id_note + FROM sent_notes sn + LEFT OUTER JOIN transactions tx + ON tx.id_tx = sn.tx + WHERE tx.block > ? + );", + &[u32::from(block_height)], + )?; + + // Rewind utxos + wdb.conn.execute( + "DELETE FROM utxos WHERE height > ?", + &[u32::from(block_height)], + )?; } } @@ -984,13 +1016,14 @@ 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 note. if stmts.stmt_update_sent_note.execute(params![ - 0, + account.0 as i64, encode_transparent_address_p(&stmts.wallet_db.params, &to), ivalue, (None::<&[u8]>), @@ -999,7 +1032,7 @@ pub fn put_sent_utxo<'a, P: consensus::Parameters>( ])? == 0 { // It isn't there, so insert. - insert_sent_utxo(stmts, tx_ref, output_index, &to, value)? + insert_sent_utxo(stmts, tx_ref, output_index, account, &to, value)? } Ok(()) @@ -1040,6 +1073,7 @@ 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> { @@ -1048,7 +1082,7 @@ pub fn insert_sent_utxo<'a, P: consensus::Parameters>( stmts.stmt_insert_sent_note.execute(params![ tx_ref, (output_index as i64), - (None::), + account.0, to_str, ivalue, (None::<&[u8]>) diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 82388dc0d..96e436300 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -98,7 +98,7 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { id_note INTEGER PRIMARY KEY, tx INTEGER NOT NULL, output_index INTEGER NOT NULL, - from_account INTEGER, + from_account INTEGER NOT NULL, address TEXT NOT NULL, value INTEGER NOT NULL, memo BLOB, From 74434f370cf474156b73bfb685cb62e8cd5bcd42 Mon Sep 17 00:00:00 2001 From: Kevin Gorham Date: Thu, 22 Apr 2021 16:26:57 -0400 Subject: [PATCH 16/79] Fix: get rewind height when there are no unspent notes. Previously, this function was not properly returning an optional. --- zcash_client_sqlite/src/wallet.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 488486d6d..0c8ab365a 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -466,9 +466,11 @@ pub fn get_rewind_height

(wdb: &WalletDb

) -> Result, Sq JOIN transactions tx ON tx.id_tx = n.tx WHERE n.spent IS NULL", NO_PARAMS, - |row| row.get(0).map(|h: u32| h.into()), + |row| { + row.get(0) + .map(|maybe_height: Option| maybe_height.map(|height| height.into())) + }, ) - .optional() .map_err(SqliteClientError::from) } @@ -557,13 +559,13 @@ pub fn rewind_to_height( WHERE tx.block > ? );", &[u32::from(block_height)], - )?; - + )?; + // Rewind utxos wdb.conn.execute( "DELETE FROM utxos WHERE height > ?", &[u32::from(block_height)], - )?; + )?; } } From 08a5cfa80bcb0bbaa9b03f8ed5acff6ad7db2b4b Mon Sep 17 00:00:00 2001 From: Kevin Gorham Date: Fri, 30 Apr 2021 21:55:18 -0400 Subject: [PATCH 17/79] Add clear function for the accounts table. --- zcash_client_sqlite/src/wallet/init.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 96e436300..f724a9c9f 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -209,6 +209,17 @@ pub fn init_accounts_table( Ok(()) } +pub fn clear_accounts_table( + wdb: &WalletDb

, +) -> Result<(), SqliteClientError> { + wdb.conn.execute("PRAGMA foreign_keys = OFF;", NO_PARAMS)?; + wdb.conn.execute( "DELETE FROM accounts;", NO_PARAMS, )?; + wdb.conn.execute("PRAGMA foreign_keys = ON;", NO_PARAMS)?; + + Ok(()) +} + + /// Initialises the data database with the given block. /// /// This enables a newly-created database to be immediately-usable, without needing to From 5a5100395a4d3580e817f09d94293c00edc5b24d Mon Sep 17 00:00:00 2001 From: Kevin Gorham Date: Fri, 30 Apr 2021 23:06:28 -0400 Subject: [PATCH 18/79] Drop accounts table instead. We need to recreate the table each time it is cleared to handle any migrations. This is mostly a stop-gap measure until the migrations and table creations are handled by the same code. --- zcash_client_sqlite/src/wallet/init.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index f724a9c9f..0e7c81df9 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -209,12 +209,10 @@ pub fn init_accounts_table( Ok(()) } -pub fn clear_accounts_table( +pub fn drop_accounts_table( wdb: &WalletDb

, ) -> Result<(), SqliteClientError> { - wdb.conn.execute("PRAGMA foreign_keys = OFF;", NO_PARAMS)?; - wdb.conn.execute( "DELETE FROM accounts;", NO_PARAMS, )?; - wdb.conn.execute("PRAGMA foreign_keys = ON;", NO_PARAMS)?; + wdb.conn.execute( "DROP TABLE accounts;", NO_PARAMS, )?; Ok(()) } From 3828a814b5d2f8d6ec9111cea8c6c91caf18d26d Mon Sep 17 00:00:00 2001 From: Kevin Gorham Date: Tue, 4 May 2021 17:37:17 -0400 Subject: [PATCH 19/79] Relax fk constraints. --- zcash_client_sqlite/src/wallet/init.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 0e7c81df9..211dfedc5 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -212,7 +212,9 @@ pub fn init_accounts_table( pub fn drop_accounts_table( wdb: &WalletDb

, ) -> Result<(), SqliteClientError> { - wdb.conn.execute( "DROP TABLE accounts;", NO_PARAMS, )?; + wdb.conn.execute_batch( + "PRAGMA foreign_keys = OFF; DROP TABLE accounts; PRAGMA foreign_keys = ON;" + )?; Ok(()) } From bd65b01eb3131f24baab54a7d8e0c05affbc621e Mon Sep 17 00:00:00 2001 From: Kevin Gorham Date: Fri, 7 May 2021 04:18:45 -0400 Subject: [PATCH 20/79] Remove drop accounts function. --- zcash_client_sqlite/src/wallet/init.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 211dfedc5..96e436300 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -209,17 +209,6 @@ pub fn init_accounts_table( Ok(()) } -pub fn drop_accounts_table( - wdb: &WalletDb

, -) -> Result<(), SqliteClientError> { - wdb.conn.execute_batch( - "PRAGMA foreign_keys = OFF; DROP TABLE accounts; PRAGMA foreign_keys = ON;" - )?; - - Ok(()) -} - - /// Initialises the data database with the given block. /// /// This enables a newly-created database to be immediately-usable, without needing to From 8e3e7de50c3c35bd7c18988bb629dd1baeb1a3d1 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 14 May 2021 14:16:10 -0600 Subject: [PATCH 21/79] Minor code cleanup. --- zcash_client_backend/src/data_api/wallet.rs | 2 +- zcash_client_sqlite/src/lib.rs | 28 +++++++++------------ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 22a421705..bc5e9c5a0 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -57,7 +57,7 @@ where .ok_or(Error::SaplingNotActive)?; let sapling_outputs = decrypt_transaction(params, height, tx, &extfvks); - + if !(sapling_outputs.is_empty() && tx.vout.is_empty()) { let nullifiers = data.get_all_nullifiers()?; data.store_decrypted_tx( diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index e8c408617..8cfcd2d9b 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -569,22 +569,18 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { // as our z->t tx and store the vouts as our sent notes. // FIXME this is a weird heuristic that is bound to trip us up somewhere. - // d_tx.tx.shielded_spends.iter().find(|input| nullifiers.iter().any(|(_, n)| { *n == input.nullifier } )) { - - match nullifiers.iter().find(|(_, n)| d_tx.tx.shielded_spends.iter().any(|input| *n == input.nullifier )) { - Some(tx_input) => { - for (output_index, txout) in d_tx.tx.vout.iter().enumerate() { - wallet::put_sent_utxo( - up, - tx_ref, - output_index, - tx_input.0, - &txout.script_pubkey.address().unwrap(), - txout.value, - )?; - } - }, - None => { + if let Some((account_id, _)) = nullifiers.iter().find(|(_, nf)| + d_tx.tx.shielded_spends.iter().any(|input| *nf == input.nullifier) + ) { + for (output_index, txout) in d_tx.tx.vout.iter().enumerate() { + wallet::put_sent_utxo( + up, + tx_ref, + output_index, + *account_id, + &txout.script_pubkey.address().unwrap(), + txout.value, + )?; } } } From fd47ee365286f1e24f2940a120fa0974d5865c00 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 14 May 2021 15:09:36 -0600 Subject: [PATCH 22/79] Fix missing method in in-memory wallet prototype. --- zcash_client_backend/src/data_api.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 245076623..95b09f400 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -406,6 +406,10 @@ pub mod testing { Ok(Vec::new()) } + fn get_all_nullifiers(&self) -> Result, Self::Error> { + Ok(Vec::new()) + } + fn get_unspent_sapling_notes( &self, _account: AccountId, From 2053d7f57beb807ad4643e30c6ad5fe733b4c2f5 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 27 Aug 2021 13:45:36 -0600 Subject: [PATCH 23/79] Always take number of confirmations as a parameter. --- zcash_client_backend/src/data_api.rs | 4 +-- zcash_client_backend/src/data_api/wallet.rs | 23 +++++++++++------ zcash_client_sqlite/src/wallet.rs | 2 +- zcash_client_sqlite/src/wallet/transact.rs | 28 ++++++++++++++++++--- 4 files changed, 44 insertions(+), 13 deletions(-) diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 58c367a63..52b99edf5 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -19,7 +19,6 @@ use zcash_primitives::{ use crate::{ address::RecipientAddress, - data_api::wallet::ANCHOR_OFFSET, decrypt::DecryptedOutput, proto::compact_formats::CompactBlock, wallet::{AccountId, SpendableNote, WalletTx}, @@ -68,6 +67,7 @@ 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, Self::Error> { self.block_height_extrema().map(|heights| { heights.map(|(min_height, max_height)| { @@ -76,7 +76,7 @@ pub trait WalletRead { // Select an anchor ANCHOR_OFFSET 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), )); diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 4829d23cd..5cfbe5438 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -27,8 +27,6 @@ use zcash_primitives::{legacy::Script, transaction::components::TxOut}; #[cfg(feature = "transparent-inputs")] use crate::keys::derive_transparent_address_from_secret_key; -pub const ANCHOR_OFFSET: u32 = 10; - /// Scans a [`Transaction`] for any information that can be decrypted by the accounts in /// the wallet, and saves it to the wallet. pub fn decrypt_and_store_transaction( @@ -169,6 +167,7 @@ pub fn create_spend_to_address( amount: Amount, memo: Option, ovk_policy: OvkPolicy, + min_confirmations: u32, ) -> Result where E: From>, @@ -187,7 +186,16 @@ where }], }; - spend(wallet_db, params, prover, account, extsk, &req, ovk_policy) + spend( + wallet_db, + params, + prover, + account, + extsk, + &req, + ovk_policy, + min_confirmations, + ) } pub fn spend( @@ -198,6 +206,7 @@ pub fn spend( extsk: &ExtendedSpendingKey, request: &TransactionRequest, ovk_policy: OvkPolicy, + min_confirmations: u32, ) -> Result where E: From>, @@ -221,7 +230,7 @@ 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 @@ -337,7 +346,7 @@ pub fn shield_funds( sk: &secp256k1::SecretKey, extsk: &ExtendedSpendingKey, memo: &MemoBytes, - confirmations: u32, + min_confirmations: u32, ) -> Result where E: From>, @@ -346,7 +355,7 @@ where D: WalletWrite, { let (latest_scanned_height, latest_anchor) = wallet_db - .get_target_and_anchor_heights() + .get_target_and_anchor_heights(min_confirmations) .and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?; // derive the corresponding t-address @@ -360,7 +369,7 @@ where let ovk = exfvk.fvk.ovk; // get UTXOs from DB - let utxos = wallet_db.get_unspent_transparent_utxos(&taddr, latest_anchor - confirmations)?; + let utxos = wallet_db.get_unspent_transparent_utxos(&taddr, latest_anchor)?; let total_amount = utxos .iter() .map(|utxo| utxo.value) diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index dacb1a064..4cbe0cb3f 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -1128,7 +1128,7 @@ mod tests { 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()); diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index ed1c75d3a..60125b216 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -222,6 +222,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"), @@ -237,6 +238,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"), @@ -268,6 +270,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"), @@ -310,6 +313,7 @@ mod tests { Amount::from_u64(1).unwrap(), None, OvkPolicy::Sender, + 10, ) { Ok(_) => panic!("Should have failed"), Err(e) => assert_eq!( @@ -348,7 +352,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(), @@ -366,7 +373,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() @@ -389,6 +399,7 @@ mod tests { Amount::from_u64(70000).unwrap(), None, OvkPolicy::Sender, + 10, ) { Ok(_) => panic!("Should have failed"), Err(e) => assert_eq!( @@ -421,6 +432,7 @@ mod tests { Amount::from_u64(70000).unwrap(), None, OvkPolicy::Sender, + 10, ) { Ok(_) => panic!("Should have failed"), Err(e) => assert_eq!( @@ -446,6 +458,7 @@ mod tests { Amount::from_u64(70000).unwrap(), None, OvkPolicy::Sender, + 10, ) .unwrap(); } @@ -492,6 +505,7 @@ mod tests { Amount::from_u64(15000).unwrap(), None, OvkPolicy::Sender, + 10, ) .unwrap(); @@ -506,6 +520,7 @@ mod tests { Amount::from_u64(2000).unwrap(), None, OvkPolicy::Sender, + 10, ) { Ok(_) => panic!("Should have failed"), Err(e) => assert_eq!( @@ -538,6 +553,7 @@ mod tests { Amount::from_u64(2000).unwrap(), None, OvkPolicy::Sender, + 10, ) { Ok(_) => panic!("Should have failed"), Err(e) => assert_eq!( @@ -567,6 +583,7 @@ mod tests { Amount::from_u64(2000).unwrap(), None, OvkPolicy::Sender, + 10, ) .unwrap(); } @@ -616,6 +633,7 @@ mod tests { Amount::from_u64(15000).unwrap(), None, ovk_policy, + 10, ) .unwrap(); @@ -707,7 +725,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(), @@ -725,6 +746,7 @@ mod tests { Amount::from_u64(50000).unwrap(), None, OvkPolicy::Sender, + 10, ) .unwrap(); } From db89569b90613984a8a8f8e31a8832faa443e5ab Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 3 Sep 2021 17:32:40 -0600 Subject: [PATCH 24/79] Address documentation & naming requests from code review. --- zcash_client_backend/src/data_api.rs | 32 +++++---- zcash_client_backend/src/data_api/wallet.rs | 73 +++++++++++++++++---- zcash_client_sqlite/src/lib.rs | 33 ++++++---- zcash_client_sqlite/src/wallet.rs | 6 +- zcash_client_sqlite/src/wallet/transact.rs | 4 +- 5 files changed, 104 insertions(+), 44 deletions(-) diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 52b99edf5..db498509f 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -33,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; @@ -143,6 +142,9 @@ pub trait WalletRead { ) -> Result; /// Returns the memo for a note. + /// + /// Implementations of this method must return an error if the note identifier + /// does not appear in the backing data store. fn get_memo(&self, id_note: Self::NoteRef) -> Result; /// Returns a transaction. @@ -165,18 +167,20 @@ pub trait WalletRead { /// with which they are associated. fn get_nullifiers(&self) -> Result, Self::Error>; + /// Returns all nullifiers (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, Self::Error>; - /// Return all unspent notes. - fn get_unspent_sapling_notes( + /// Return all unspent Sapling notes. + fn get_spendable_sapling_notes( &self, account: AccountId, anchor_height: BlockHeight, ) -> Result, Self::Error>; - /// Returns a list of unspent notes sufficient to cover the specified + /// Returns a list of spendable Sapling notes sufficient to cover the specified /// target value, if possible. - fn select_unspent_sapling_notes( + fn select_spendable_sapling_notes( &self, account: AccountId, target_value: Amount, @@ -184,10 +188,12 @@ pub trait WalletRead { ) -> Result, Self::Error>; #[cfg(feature = "transparent-inputs")] - fn get_unspent_transparent_utxos( + /// 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, - anchor_height: BlockHeight, + max_height: BlockHeight, ) -> Result, Self::Error>; } @@ -410,7 +416,7 @@ pub mod testing { Ok(Vec::new()) } - fn get_unspent_sapling_notes( + fn get_spendable_sapling_notes( &self, _account: AccountId, _anchor_height: BlockHeight, @@ -418,7 +424,7 @@ pub mod testing { Ok(Vec::new()) } - fn select_unspent_sapling_notes( + fn select_spendable_sapling_notes( &self, _account: AccountId, _target_value: Amount, @@ -428,7 +434,7 @@ pub mod testing { } #[cfg(feature = "transparent-inputs")] - fn get_unspent_transparent_utxos( + fn get_unspent_transparent_outputs( &self, _address: &TransparentAddress, _anchor_height: BlockHeight, diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 5cfbe5438..f65362fa9 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -190,20 +190,44 @@ where wallet_db, params, prover, - account, 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 the +/// identifier for the transaction. +/// +/// This procedure uses the wallet's underlying note selection algorithm to choose +/// inputs of sufficient value to satisfy the request, if possible. +/// +/// 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. +#[allow(clippy::too_many_arguments)] pub fn spend( wallet_db: &mut D, params: &P, prover: impl TxProver, - account: AccountId, extsk: &ExtendedSpendingKey, + account: AccountId, request: &TransactionRequest, ovk_policy: OvkPolicy, min_confirmations: u32, @@ -241,7 +265,7 @@ where .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_unspent_sapling_notes(account, target_value, anchor_height)?; + wallet_db.select_spendable_sapling_notes(account, target_value, anchor_height)?; // Confirm we were able to select sufficient value let selected_value = spendable_notes @@ -336,15 +360,37 @@ where }) } +/// 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`. +/// * `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_funds( +pub fn shield_transparent_funds( wallet_db: &mut D, params: &P, prover: impl TxProver, - account: AccountId, sk: &secp256k1::SecretKey, - extsk: &ExtendedSpendingKey, + extfvk: &ExtendedFullViewingKey, + account: AccountId, memo: &MemoBytes, min_confirmations: u32, ) -> Result @@ -354,6 +400,12 @@ where R: Copy + Debug, D: WalletWrite, { + // 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()))?; @@ -362,14 +414,11 @@ where let taddr = derive_transparent_address_from_secret_key(sk); // derive own shielded address from the provided extended spending key - let z_address = extsk.default_address().unwrap().1; - - let exfvk = ExtendedFullViewingKey::from(extsk); - - let ovk = exfvk.fvk.ovk; + let z_address = extfvk.default_address().unwrap().1; + let ovk = extfvk.fvk.ovk; // get UTXOs from DB - let utxos = wallet_db.get_unspent_transparent_utxos(&taddr, latest_anchor)?; + let utxos = wallet_db.get_unspent_transparent_outputs(&taddr, latest_anchor)?; let total_amount = utxos .iter() .map(|utxo| utxo.value) diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 0d5daedc9..967ffec54 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -274,29 +274,34 @@ impl WalletRead for WalletDb

{ wallet::get_all_nullifiers(&self) } - fn get_unspent_sapling_notes( + fn get_spendable_sapling_notes( &self, account: AccountId, anchor_height: BlockHeight, ) -> Result, Self::Error> { - wallet::transact::get_unspent_sapling_notes(&self, account, anchor_height) + wallet::transact::get_spendable_sapling_notes(&self, account, anchor_height) } - fn select_unspent_sapling_notes( + fn select_spendable_sapling_notes( &self, account: AccountId, target_value: Amount, anchor_height: BlockHeight, ) -> Result, Self::Error> { - wallet::transact::select_unspent_sapling_notes(&self, account, target_value, anchor_height) + wallet::transact::select_spendable_sapling_notes( + &self, + account, + target_value, + anchor_height, + ) } - fn get_unspent_transparent_utxos( + fn get_unspent_transparent_outputs( &self, address: &TransparentAddress, - anchor_height: BlockHeight, + max_height: BlockHeight, ) -> Result, Self::Error> { - wallet::get_unspent_transparent_utxos(&self, address, anchor_height) + wallet::get_unspent_transparent_outputs(&self, address, max_height) } } @@ -409,32 +414,32 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> { self.wallet_db.get_all_nullifiers() } - fn get_unspent_sapling_notes( + fn get_spendable_sapling_notes( &self, account: AccountId, anchor_height: BlockHeight, ) -> Result, Self::Error> { self.wallet_db - .get_unspent_sapling_notes(account, anchor_height) + .get_spendable_sapling_notes(account, anchor_height) } - fn select_unspent_sapling_notes( + fn select_spendable_sapling_notes( &self, account: AccountId, target_value: Amount, anchor_height: BlockHeight, ) -> Result, Self::Error> { self.wallet_db - .select_unspent_sapling_notes(account, target_value, anchor_height) + .select_spendable_sapling_notes(account, target_value, anchor_height) } - fn get_unspent_transparent_utxos( + fn get_unspent_transparent_outputs( &self, address: &TransparentAddress, - anchor_height: BlockHeight, + max_height: BlockHeight, ) -> Result, Self::Error> { self.wallet_db - .get_unspent_transparent_utxos(address, anchor_height) + .get_unspent_transparent_outputs(address, max_height) } } diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 4cbe0cb3f..8d2cbb464 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -701,10 +701,10 @@ pub fn get_all_nullifiers

( Ok(res) } -pub fn get_unspent_transparent_utxos( +pub fn get_unspent_transparent_outputs( wdb: &WalletDb

, address: &TransparentAddress, - anchor_height: BlockHeight, + max_height: BlockHeight, ) -> Result, SqliteClientError> { let mut stmt_blocks = wdb.conn.prepare( "SELECT u.address, u.prevout_txid, u.prevout_idx, u.script, u.value_zat, u.height, tx.block as block @@ -718,7 +718,7 @@ pub fn get_unspent_transparent_utxos( let addr_str = address.encode(&wdb.params); - let rows = stmt_blocks.query_map(params![addr_str, u32::from(anchor_height)], |row| { + let rows = stmt_blocks.query_map(params![addr_str, u32::from(max_height)], |row| { let addr: String = row.get(0)?; let address = TransparentAddress::decode(&wdb.params, &addr).map_err(|e| { rusqlite::Error::FromSqlConversionFailure( diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index 60125b216..c1a17e687 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -59,7 +59,7 @@ fn to_spendable_note(row: &Row) -> Result { }) } -pub fn get_unspent_sapling_notes

( +pub fn get_spendable_sapling_notes

( wdb: &WalletDb

, account: AccountId, anchor_height: BlockHeight, @@ -87,7 +87,7 @@ pub fn get_unspent_sapling_notes

( notes.collect::>() } -pub fn select_unspent_sapling_notes

( +pub fn select_spendable_sapling_notes

( wdb: &WalletDb

, account: AccountId, target_value: Amount, From d49a20e6d2387a4c797b71a20c29ec416d63d6ea Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 1 Oct 2021 11:42:04 -0600 Subject: [PATCH 25/79] Modify WalletTransparentOutput to wrap TxOut directly. --- zcash_client_backend/CHANGELOG.md | 2 + zcash_client_backend/src/data_api/wallet.rs | 14 +------ zcash_client_backend/src/wallet.rs | 15 ++++++-- zcash_client_sqlite/src/wallet.rs | 42 ++++++++++----------- 4 files changed, 35 insertions(+), 38 deletions(-) diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index f4f84fe77..95c850f8a 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -19,6 +19,8 @@ and this library adheres to Rust's notion of abbreviations (matching Rust naming conventions): - `error::Error::InvalidExtSK` to `Error::InvalidExtSk` - `testing::MockWalletDB` to `testing::MockWalletDb` +- Account identifier variables that were previously typed as `u32` have + been updated to use a bespoke `AccountId` type in several places. ## [0.5.0] - 2021-03-26 ### Added diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index f65362fa9..7b051d613 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -21,9 +21,6 @@ use crate::{ zip321::{Payment, TransactionRequest}, }; -#[cfg(feature = "transparent-inputs")] -use zcash_primitives::{legacy::Script, transaction::components::TxOut}; - #[cfg(feature = "transparent-inputs")] use crate::keys::derive_transparent_address_from_secret_key; @@ -421,7 +418,7 @@ where let utxos = wallet_db.get_unspent_transparent_outputs(&taddr, latest_anchor)?; let total_amount = utxos .iter() - .map(|utxo| utxo.value) + .map(|utxo| utxo.txout.value) .sum::>() .ok_or_else(|| E::from(Error::InvalidAmount))?; @@ -436,15 +433,8 @@ where #[cfg(feature = "transparent-inputs")] for utxo in &utxos { - let coin = TxOut { - value: utxo.value, - script_pubkey: Script { - 0: utxo.script.clone(), - }, - }; - builder - .add_transparent_input(*sk, utxo.outpoint.clone(), coin) + .add_transparent_input(*sk, utxo.outpoint.clone(), utxo.txout.clone()) .map_err(Error::Builder)?; } diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index 3fd16e41d..bbc383802 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -14,7 +14,9 @@ use zcash_primitives::{ #[cfg(feature = "transparent-inputs")] use zcash_primitives::{ - consensus::BlockHeight, legacy::TransparentAddress, transaction::components::OutPoint, + consensus::BlockHeight, + legacy::TransparentAddress, + transaction::components::{OutPoint, TxOut}, }; /// A type-safe wrapper for account identifiers. @@ -47,13 +49,18 @@ pub struct WalletTx { #[cfg(feature = "transparent-inputs")] pub struct WalletTransparentOutput { - pub address: TransparentAddress, pub outpoint: OutPoint, - pub script: Vec, - pub value: Amount, + 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 diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 8d2cbb464..d064eefba 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -40,7 +40,10 @@ use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDb, PRUNI use { crate::UtxoId, zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput}, - zcash_primitives::legacy::TransparentAddress, + zcash_primitives::{ + legacy::{Script, TransparentAddress}, + transaction::components::TxOut, + }, }; pub mod init; @@ -707,7 +710,7 @@ pub fn get_unspent_transparent_outputs( max_height: BlockHeight, ) -> Result, SqliteClientError> { let mut stmt_blocks = wdb.conn.prepare( - "SELECT u.address, u.prevout_txid, u.prevout_idx, u.script, u.value_zat, u.height, tx.block as block + "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 @@ -719,29 +722,21 @@ pub fn get_unspent_transparent_outputs( let addr_str = address.encode(&wdb.params); let rows = stmt_blocks.query_map(params![addr_str, u32::from(max_height)], |row| { - let addr: String = row.get(0)?; - let address = TransparentAddress::decode(&wdb.params, &addr).map_err(|e| { - rusqlite::Error::FromSqlConversionFailure( - addr.len(), - rusqlite::types::Type::Text, - Box::new(e), - ) - })?; - - let id: Vec = row.get(1)?; + let id: Vec = row.get(0)?; let mut txid_bytes = [0u8; 32]; txid_bytes.copy_from_slice(&id); - let index: i32 = row.get(2)?; - let script: Vec = row.get(3)?; - let value: i64 = row.get(4)?; - let height: u32 = row.get(5)?; + 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 { - address, outpoint: OutPoint::new(txid_bytes, index as u32), - script, - value: Amount::from_i64(value).unwrap(), + txout: TxOut { + value, + script_pubkey, + }, height: BlockHeight::from(height), }) })?; @@ -877,11 +872,14 @@ pub fn put_received_transparent_utxo<'a, P: consensus::Parameters>( output: &WalletTransparentOutput, ) -> Result { let sql_args: &[(&str, &dyn ToSql)] = &[ - (&":address", &output.address.encode(&stmts.wallet_db.params)), + ( + &":address", + &output.address().encode(&stmts.wallet_db.params), + ), (&":prevout_txid", &output.outpoint.hash().to_vec()), (&":prevout_idx", &output.outpoint.n()), - (&":script", &output.script), - (&":value_zat", &i64::from(output.value)), + (&":script", &output.txout.script_pubkey.0), + (&":value_zat", &i64::from(output.txout.value)), (&":height", &u32::from(output.height)), ]; From d43a893c72f8cbd382825ea472f7ca702c157c9c Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 1 Oct 2021 11:58:23 -0600 Subject: [PATCH 26/79] Apply suggestions from code review & update changelog Co-authored-by: str4d Co-authored-by: Daira Hopwood --- zcash_client_backend/CHANGELOG.md | 8 +++++++- zcash_client_backend/src/data_api/error.rs | 4 ++-- zcash_client_backend/src/data_api/wallet.rs | 1 - zcash_client_backend/src/encoding.rs | 6 ++++++ zcash_client_sqlite/src/lib.rs | 5 ++++- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index 95c850f8a..d2a2f6a75 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -21,7 +21,13 @@ and this library adheres to Rust's notion of - `testing::MockWalletDB` to `testing::MockWalletDb` - Account identifier variables that were previously typed as `u32` have been updated to use a bespoke `AccountId` type in several places. - +- A new error constructor SqliteClientError::TransparentAddress has been added + to support handling of errors in transparent address decoding. +- The `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. +- The `SaplingBuiilder::add_output` method has now similarly been changed to take + its `MemoBytes` argument as a required field. ## [0.5.0] - 2021-03-26 ### Added - `zcash_client_backend::address::RecipientAddress` diff --git a/zcash_client_backend/src/data_api/error.rs b/zcash_client_backend/src/data_api/error.rs index 85d9f0983..8a856b0d6 100644 --- a/zcash_client_backend/src/data_api/error.rs +++ b/zcash_client_backend/src/data_api/error.rs @@ -63,7 +63,7 @@ pub enum Error { /// height when Sapling was not yet active. SaplingNotActive, - /// A memo is required when constructing a Sapling output + /// A memo is required when constructing a shielded output. MemoRequired, /// It is forbidden to provide a memo when constructing a transparent output. @@ -112,7 +112,7 @@ impl fmt::Display for Error { Error::Builder(e) => write!(f, "{:?}", e), Error::Protobuf(e) => write!(f, "{}", e), Error::SaplingNotActive => write!(f, "Could not determine Sapling upgrade activation height."), - Error::MemoRequired => write!(f, "A memo is required when sending to a Sapling address."), + Error::MemoRequired => write!(f, "A memo is required when sending to a shielded address."), Error::MemoForbidden => write!(f, "It is not possible to send a memo to a transparent address."), } } diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 7b051d613..64c7d03e9 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -431,7 +431,6 @@ where let mut builder = Builder::new(params.clone(), latest_scanned_height); - #[cfg(feature = "transparent-inputs")] for utxo in &utxos { builder .add_transparent_input(*sk, utxo.outpoint.clone(), utxo.txout.clone()) diff --git a/zcash_client_backend/src/encoding.rs b/zcash_client_backend/src/encoding.rs index f9d922144..9ddc2f453 100644 --- a/zcash_client_backend/src/encoding.rs +++ b/zcash_client_backend/src/encoding.rs @@ -197,6 +197,9 @@ 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( params: &P, addr: &PaymentAddress, @@ -307,6 +310,9 @@ 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( params: &P, addr: &TransparentAddress, diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 967ffec54..a7ccaaac1 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -69,7 +69,10 @@ pub mod chain; pub mod error; pub mod wallet; -pub const PRUNING_HEIGHT: u32 = 100; +/// 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. From e30c5cd6287fa33831e36915400d141df0b5a508 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 1 Oct 2021 12:22:20 -0600 Subject: [PATCH 27/79] Enforce maximum zip321 payment count in TransactionRequest constructor. --- zcash_client_backend/src/data_api/wallet.rs | 25 +++++++++---------- zcash_client_backend/src/zip321.rs | 27 ++++++++++++++++----- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 64c7d03e9..7dfb86c1f 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -172,16 +172,15 @@ where R: Copy + Debug, D: WalletWrite, { - let req = TransactionRequest { - payments: vec![Payment { - recipient_address: to.clone(), - amount, - memo, - label: None, - message: None, - other_params: vec![], - }], - }; + let req = TransactionRequest::new(vec![Payment { + recipient_address: to.clone(), + amount, + memo, + label: None, + message: None, + other_params: vec![], + }]) + .unwrap(); spend( wallet_db, @@ -255,7 +254,7 @@ where .and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?; let value = request - .payments + .payments() .iter() .map(|p| p.amount) .sum::>() @@ -297,7 +296,7 @@ where .map_err(Error::Builder)?; } - for payment in &request.payments { + for payment in request.payments() { match &payment.recipient_address { RecipientAddress::Shielded(to) => builder .add_sapling_output( @@ -321,7 +320,7 @@ where let (tx, tx_metadata) = builder.build(&prover).map_err(Error::Builder)?; - let sent_outputs = request.payments.iter().enumerate().map(|(i, payment)| { + 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(_) => diff --git a/zcash_client_backend/src/zip321.rs b/zcash_client_backend/src/zip321.rs index 7abbcc15e..5cfd51d6b 100644 --- a/zcash_client_backend/src/zip321.rs +++ b/zcash_client_backend/src/zip321.rs @@ -22,11 +22,12 @@ 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), } /// Converts a [`MemoBytes`] value to a ZIP 321 compatible base64-encoded string. @@ -39,10 +40,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 { +pub fn memo_from_base64(s: &str) -> Result { 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. @@ -106,10 +107,24 @@ impl Payment { /// payment value in the request. #[derive(Debug, PartialEq)] pub struct TransactionRequest { - pub payments: Vec, + payments: Vec, } impl TransactionRequest { + /// Constructs a new transaction request that obeys the ZIP-321 invariants + pub fn new(payments: Vec) -> Result { + if payments.len() > 2109 { + Err(Zip321Error::TooManyPayments(payments.len())) + } else { + Ok(TransactionRequest { payments }) + } + } + + /// 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(&mut self, params: &P) { From da3c84ff31beec2c114729044d05bec3211a03d3 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 1 Oct 2021 12:52:19 -0600 Subject: [PATCH 28/79] Update hdwallet to depend upon secp256k1-v0.20 --- Cargo.toml | 1 + zcash_client_backend/Cargo.toml | 2 +- zcash_client_sqlite/Cargo.toml | 2 +- zcash_primitives/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0d23a678f..01cb77356 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,4 @@ orchard = { git = "https://github.com/zcash/orchard.git", rev = "2c8241f25b943aa incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree.git", rev = "b7bd6246122a6e9ace8edb51553fbf5228906cbb" } zcash_encoding = { path = "components/zcash_encoding" } zcash_note_encryption = { path = "components/zcash_note_encryption" } +hdwallet = { git = "https://github.com/nuttycom/hdwallet", rev = "576683b9f2865f1118c309017ff36e01f84420c9" } diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index c9cf48b64..dbbe84dea 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -29,7 +29,7 @@ proptest = { version = "1.0.0", optional = true } protobuf = "2.20" rand_core = "0.6" ripemd160 = { version = "0.9.1", optional = true } -secp256k1 = { version = "0.19", optional = true } +secp256k1 = { version = "0.20", optional = true } sha2 = { version = "0.9", optional = true } subtle = "2.2.3" time = "0.2" diff --git a/zcash_client_sqlite/Cargo.toml b/zcash_client_sqlite/Cargo.toml index b890a0e1c..e9186e956 100644 --- a/zcash_client_sqlite/Cargo.toml +++ b/zcash_client_sqlite/Cargo.toml @@ -21,7 +21,7 @@ jubjub = "0.8" protobuf = "2.20" rand_core = "0.6" rusqlite = { version = "0.24", features = ["bundled", "time"] } -secp256k1 = { version = "0.19" } +secp256k1 = { version = "0.20" } time = "0.2" zcash_client_backend = { version = "0.5", path = "../zcash_client_backend", features = ["transparent-inputs"] } zcash_primitives = { version = "0.5", path = "../zcash_primitives", features = ["transparent-inputs"] } diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index 8a02f22d8..41529dbd8 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -41,7 +41,7 @@ proptest = { version = "1.0.0", optional = true } rand = "0.8" rand_core = "0.6" ripemd160 = { version = "0.9", optional = true } -secp256k1 = { version = "0.19", optional = true } +secp256k1 = { version = "0.20", optional = true } sha2 = "0.9" subtle = "2.2.3" zcash_encoding = { version = "0.0", path = "../components/zcash_encoding" } From 74b0c502940801b32d752426a3b3c06c5e9f9b59 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 1 Oct 2021 15:20:01 -0600 Subject: [PATCH 29/79] Fix transitivity of the test-dependencies feature. --- zcash_client_backend/Cargo.toml | 2 +- zcash_primitives/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index dbbe84dea..a7a1d6a31 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -48,7 +48,7 @@ zcash_proofs = { version = "0.5", path = "../zcash_proofs" } [features] transparent-inputs = ["ripemd160", "hdwallet", "sha2", "secp256k1"] -test-dependencies = ["proptest", "zcash_primitives/test-dependencies", "hdwallet", "sha2"] +test-dependencies = ["proptest", "zcash_primitives/test-dependencies"] [lib] bench = false diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index 41529dbd8..5152e0ad1 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -59,7 +59,7 @@ pprof = { version = "0.5", features = ["criterion", "flamegraph"] } [features] transparent-inputs = ["ripemd160", "secp256k1"] -test-dependencies = ["proptest"] +test-dependencies = ["proptest", "orchard/test-dependencies"] zfuture = [] [lib] From 86da9434adc950c57ebd8dba5e8432772052bdf7 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 4 Oct 2021 09:12:28 -0600 Subject: [PATCH 30/79] Fix zcash_client_backend doctest --- zcash_client_backend/src/data_api/wallet.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 7dfb86c1f..4ff77f23b 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -148,6 +148,7 @@ where /// Amount::from_u64(1).unwrap(), /// None, /// OvkPolicy::Sender, +/// 10 /// )?; /// /// # Ok(()) From cc58a21ad752fe7d314c4ec43d5a7dca1ffbe2c5 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 4 Oct 2021 14:09:02 -0600 Subject: [PATCH 31/79] Feature-flag transparent functionality in zcash_client_sqlite This fixes the wasm32-wasi build issues by excluding the hdwallet dependencies which are not wasm32-wasi compatible. --- zcash_client_backend/src/keys.rs | 58 +++++++++- zcash_client_sqlite/Cargo.toml | 5 +- zcash_client_sqlite/src/lib.rs | 47 ++++---- zcash_client_sqlite/src/wallet.rs | 12 ++- zcash_client_sqlite/src/wallet/init.rs | 119 +++++++++++++-------- zcash_client_sqlite/src/wallet/transact.rs | 92 ++++++++++------ 6 files changed, 225 insertions(+), 108 deletions(-) diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index 152c12b0a..c43a420ef 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -2,15 +2,18 @@ use zcash_primitives::zip32::{ChildIndex, ExtendedSpendingKey}; +use crate::wallet::AccountId; + +use zcash_primitives::{legacy::TransparentAddress, zip32::ExtendedFullViewingKey}; + #[cfg(feature = "transparent-inputs")] use { - crate::wallet::AccountId, bs58::{self, decode::Error as Bs58Error}, hdwallet::{ExtendedPrivKey, ExtendedPubKey, KeyIndex}, secp256k1::{key::PublicKey, key::SecretKey, Secp256k1}, sha2::{Digest, Sha256}, std::convert::TryInto, - zcash_primitives::{consensus, legacy::TransparentAddress}, + zcash_primitives::consensus, }; /// Derives the ZIP 32 [`ExtendedSpendingKey`] for a given coin type and account from the @@ -138,9 +141,57 @@ impl<'a> TryInto for &'a Wif { } } +/// A set of viewing keys that are all associated with a single +/// ZIP-0032 account identifier. +#[derive(Clone, Debug)] +pub struct UnifiedFullViewingKey { + account: AccountId, + transparent: Option, + sapling: Option, +} + +impl UnifiedFullViewingKey { + /// Construct a new unified full viewing key, if the required components are present. + pub fn new( + account: AccountId, + transparent: Option, + sapling: Option, + ) -> Option { + if sapling.is_none() { + None + } else { + Some(UnifiedFullViewingKey { + account, + 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. + // TODO: make this the pubkey rather than the address to + // permit child derivation + pub fn transparent(&self) -> Option<&TransparentAddress> { + self.transparent.as_ref() + } + + /// Returns the Sapling extended full viewing key component of this + /// unified key. + pub fn sapling(&self) -> Option<&ExtendedFullViewingKey> { + self.sapling.as_ref() + } +} + #[cfg(test)] mod tests { use super::spending_key; + use crate::wallet::AccountId; #[cfg(feature = "transparent-inputs")] use { @@ -149,12 +200,13 @@ mod tests { derive_transparent_address_from_public_key, derive_transparent_address_from_secret_key, Wif, }, - crate::{encoding::AddressCodec, wallet::AccountId}, + crate::encoding::AddressCodec, secp256k1::key::SecretKey, std::convert::TryInto, zcash_primitives::consensus::MAIN_NETWORK, }; + #[cfg(feature = "transparent-inputs")] fn seed() -> Vec { let seed_hex = "6ef5f84def6f4b9d38f466586a8380a38593bd47c8cda77f091856176da47f26b5bd1c8d097486e5635df5a66e820d28e1d73346f499801c86228d43f390304f"; hex::decode(&seed_hex).unwrap() diff --git a/zcash_client_sqlite/Cargo.toml b/zcash_client_sqlite/Cargo.toml index e9186e956..eaf346dff 100644 --- a/zcash_client_sqlite/Cargo.toml +++ b/zcash_client_sqlite/Cargo.toml @@ -23,8 +23,8 @@ 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", features = ["transparent-inputs"] } -zcash_primitives = { version = "0.5", path = "../zcash_primitives", features = ["transparent-inputs"] } +zcash_client_backend = { version = "0.5", path = "../zcash_client_backend"} +zcash_primitives = { version = "0.5", path = "../zcash_primitives"} [dev-dependencies] tempfile = "3" @@ -33,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", "zcash_primitives/transparent-inputs"] [lib] bench = false diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index a7ccaaac1..bfba66ab9 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -60,6 +60,7 @@ use zcash_client_backend::{ use crate::error::SqliteClientError; +#[cfg(feature = "transparent-inputs")] use { zcash_client_backend::wallet::WalletTransparentOutput, zcash_primitives::legacy::TransparentAddress, @@ -148,6 +149,7 @@ impl WalletDb

{ 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)" @@ -299,6 +301,7 @@ impl WalletRead for WalletDb

{ ) } + #[cfg(feature = "transparent-inputs")] fn get_unspent_transparent_outputs( &self, address: &TransparentAddress, @@ -329,6 +332,7 @@ pub struct DataConnStmtCache<'a, P> { stmt_mark_sapling_note_spent: Statement<'a>, stmt_mark_transparent_utxo_spent: Statement<'a>, + #[cfg(feature = "transparent-inputs")] stmt_insert_received_transparent_utxo: Statement<'a>, stmt_delete_utxos: Statement<'a>, stmt_insert_received_note: Statement<'a>, @@ -436,6 +440,7 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> { .select_spendable_sapling_notes(account, target_value, anchor_height) } + #[cfg(feature = "transparent-inputs")] fn get_unspent_transparent_outputs( &self, address: &TransparentAddress, @@ -694,15 +699,17 @@ mod tests { use protobuf::Message; use rand_core::{OsRng, RngCore}; use rusqlite::params; - use secp256k1::key::SecretKey; use zcash_client_backend::{ - keys::{ - derive_secret_key_from_seed, derive_transparent_address_from_secret_key, spending_key, - }, + keys::{spending_key, UnifiedFullViewingKey}, proto::compact_formats::{CompactBlock, CompactOutput, CompactSpend, CompactTx}, }; + #[cfg(feature = "transparent-inputs")] + use zcash_client_backend::keys::{ + derive_secret_key_from_seed, derive_transparent_address_from_secret_key, + }; + use zcash_primitives::{ block::BlockHash, consensus::{BlockHeight, Network, NetworkUpgrade, Parameters}, @@ -713,7 +720,7 @@ mod tests { PaymentAddress, }, transaction::components::Amount, - zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, + zip32::ExtendedFullViewingKey, }; use crate::{wallet::init::init_accounts_table, AccountId, WalletDb}; @@ -744,22 +751,26 @@ mod tests { .unwrap() } - pub(crate) fn derive_test_keys_from_seed( - seed: &[u8], - account: AccountId, - ) -> (ExtendedSpendingKey, SecretKey) { - let extsk = spending_key(seed, network().coin_type(), account); - let tsk = derive_secret_key_from_seed(&network(), seed, account, 0).unwrap(); - (extsk, tsk) - } - pub(crate) fn init_test_accounts_table( db_data: &WalletDb, - ) -> (ExtendedFullViewingKey, TransparentAddress) { - let (extsk, tsk) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); + ) -> (ExtendedFullViewingKey, Option) { + let seed = [0u8; 32]; + + let extsk = spending_key(&seed, network().coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); - let taddr = derive_transparent_address_from_secret_key(&tsk); - init_accounts_table(db_data, &[extfvk.clone()], &[taddr.clone()]).unwrap(); + + #[cfg(feature = "transparent-inputs")] + let taddr = { + let tsk = derive_secret_key_from_seed(&network(), &seed, AccountId(0), 0).unwrap(); + Some(derive_transparent_address_from_secret_key(&tsk)) + }; + + #[cfg(not(feature = "transparent-inputs"))] + let taddr = None; + + let ufvk = + UnifiedFullViewingKey::new(AccountId(0), taddr.clone(), Some(extfvk.clone())).unwrap(); + init_accounts_table(db_data, &[ufvk]).unwrap(); (extfvk, taddr) } diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index d064eefba..6c0f87125 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -37,13 +37,13 @@ use zcash_client_backend::{ use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDb, PRUNING_HEIGHT}; +use {zcash_client_backend::encoding::AddressCodec, zcash_primitives::legacy::TransparentAddress}; + +#[cfg(feature = "transparent-inputs")] use { crate::UtxoId, - zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput}, - zcash_primitives::{ - legacy::{Script, TransparentAddress}, - transaction::components::TxOut, - }, + zcash_client_backend::wallet::WalletTransparentOutput, + zcash_primitives::{legacy::Script, transaction::components::TxOut}, }; pub mod init; @@ -704,6 +704,7 @@ pub fn get_all_nullifiers

( Ok(res) } +#[cfg(feature = "transparent-inputs")] pub fn get_unspent_transparent_outputs( wdb: &WalletDb

, address: &TransparentAddress, @@ -867,6 +868,7 @@ pub fn mark_transparent_utxo_spent<'a, P>( Ok(()) } +#[cfg(feature = "transparent-inputs")] pub fn put_received_transparent_utxo<'a, P: consensus::Parameters>( stmts: &mut DataConnStmtCache<'a, P>, output: &WalletTransparentOutput, diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 96e436300..eeb8be965 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -1,15 +1,16 @@ //! 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}, - legacy::TransparentAddress, - zip32::ExtendedFullViewingKey, }; -use zcash_client_backend::encoding::{encode_extended_full_viewing_key, AddressCodec}; +use zcash_client_backend::{ + encoding::{encode_extended_full_viewing_key, AddressCodec}, + keys::UnifiedFullViewingKey, +}; use crate::{address_from_extfvk, error::SqliteClientError, WalletDb}; @@ -33,9 +34,9 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { wdb.conn.execute( "CREATE TABLE IF NOT EXISTS accounts ( account INTEGER PRIMARY KEY, - extfvk TEXT NOT NULL, - address TEXT NOT NULL, - transparent_address TEXT NOT NULL + extfvk TEXT, + address TEXT, + transparent_address TEXT )", NO_PARAMS, )?; @@ -126,11 +127,11 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { 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 @@ -144,7 +145,10 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { /// }; /// /// use zcash_client_backend::{ -/// keys::{spending_key, derive_transparent_address_from_secret_key, derive_secret_key_from_seed}, +/// keys::{ +/// spending_key, +/// UnifiedFullViewingKey +/// }, /// wallet::AccountId, /// }; /// @@ -160,10 +164,9 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { /// let seed = [0u8; 32]; /// let account = AccountId(0); /// let extsk = spending_key(&seed, Network::TestNetwork.coin_type(), account); -/// let tsk = derive_secret_key_from_seed(&Network::TestNetwork, &seed, account, 0).unwrap(); /// let extfvk = ExtendedFullViewingKey::from(&extsk); -/// let taddr = derive_transparent_address_from_secret_key(&tsk); -/// init_accounts_table(&db_data, &[extfvk], &[taddr]).unwrap(); +/// let ufvk = UnifiedFullViewingKey::new(account, None, Some(extfvk)).unwrap(); +/// init_accounts_table(&db_data, &[ufvk]).unwrap(); /// ``` /// /// [`get_address`]: crate::wallet::get_address @@ -171,12 +174,8 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { /// [`create_spend_to_address`]: zcash_client_backend::data_api::wallet::create_spend_to_address pub fn init_accounts_table( wdb: &WalletDb

, - extfvks: &[ExtendedFullViewingKey], - taddrs: &[TransparentAddress], + keys: &[UnifiedFullViewingKey], ) -> Result<(), SqliteClientError> { - //TODO: make this a proper error? - assert!(extfvks.len() == taddrs.len()); - let mut empty_check = wdb.conn.prepare("SELECT * FROM accounts LIMIT 1")?; if empty_check.exists(NO_PARAMS)? { return Err(SqliteClientError::TableNotEmpty); @@ -184,24 +183,23 @@ pub fn init_accounts_table( // Insert accounts atomically wdb.conn.execute("BEGIN IMMEDIATE", NO_PARAMS)?; - for (account, (extfvk, taddr)) in extfvks.iter().zip(taddrs.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 = 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 taddress_str: String = taddr.encode(&wdb.params); + let address_str: Option = key + .sapling() + .map(|extfvk| address_from_extfvk(&wdb.params, extfvk)); + let taddress_str: Option = key.transparent().map(|taddr| taddr.encode(&wdb.params)); wdb.conn.execute( "INSERT INTO accounts (account, extfvk, address, transparent_address) VALUES (?, ?, ?, ?)", - &[ - (account as u32).to_sql()?, - extfvk_str.to_sql()?, - address_str.to_sql()?, - taddress_str.to_sql()?, - ], + params![key.account().0, extfvk_str, address_str, taddress_str,], )?; } wdb.conn.execute("COMMIT", NO_PARAMS)?; @@ -271,13 +269,24 @@ pub fn init_blocks_table

( mod tests { use tempfile::NamedTempFile; - use zcash_client_backend::keys::derive_transparent_address_from_secret_key; + use zcash_client_backend::keys::{spending_key, UnifiedFullViewingKey}; - use zcash_primitives::{ - block::BlockHash, consensus::BlockHeight, zip32::ExtendedFullViewingKey, + #[cfg(feature = "transparent-inputs")] + use zcash_client_backend::keys::{ + derive_secret_key_from_seed, derive_transparent_address_from_secret_key, }; - use crate::{tests, wallet::get_address, AccountId, WalletDb}; + use zcash_primitives::{ + block::BlockHash, + consensus::{BlockHeight, Parameters}, + zip32::ExtendedFullViewingKey, + }; + + use crate::{ + tests::{self, network}, + wallet::get_address, + AccountId, WalletDb, + }; use super::{init_accounts_table, init_blocks_table, init_wallet_db}; @@ -288,18 +297,34 @@ mod tests { init_wallet_db(&db_data).unwrap(); // We can call the function as many times as we want with no data - init_accounts_table(&db_data, &[], &[]).unwrap(); - init_accounts_table(&db_data, &[], &[]).unwrap(); + 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 (extsk, tsk) = tests::derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); + let extsk = spending_key(&seed, network().coin_type(), account); let extfvk = ExtendedFullViewingKey::from(&extsk); - let taddr = derive_transparent_address_from_secret_key(&tsk); - init_accounts_table(&db_data, &[extfvk.clone()], &[taddr.clone()]).unwrap(); + + #[cfg(feature = "transparent-inputs")] + let ufvk = { + let tsk = derive_secret_key_from_seed(&network(), &seed, account, 0).unwrap(); + UnifiedFullViewingKey::new( + account, + Some(derive_transparent_address_from_secret_key(&tsk)), + Some(extfvk), + ) + .unwrap() + }; + #[cfg(not(feature = "transparent-inputs"))] + let ufvk = UnifiedFullViewingKey::new(account, None, 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, &[extfvk], &[taddr]).unwrap_err(); + init_accounts_table(&db_data, &[]).unwrap_err(); + init_accounts_table(&db_data, &[ufvk]).unwrap_err(); } #[test] @@ -335,11 +360,13 @@ 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, tsk) = tests::derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); + let extsk = spending_key(&seed, network().coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); - let taddr = derive_transparent_address_from_secret_key(&tsk); - init_accounts_table(&db_data, &[extfvk], &[taddr]).unwrap(); + let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk)).unwrap(); + 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(); diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index 25f680efb..d766a4a63 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -153,7 +153,7 @@ mod tests { 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}, @@ -164,16 +164,18 @@ mod tests { use zcash_client_backend::{ data_api::{chain::scan_cached_blocks, wallet::create_spend_to_address, WalletRead}, - keys::derive_transparent_address_from_secret_key, + keys::{spending_key, UnifiedFullViewingKey}, wallet::OvkPolicy, }; + #[cfg(feature = "transparent-inputs")] + use zcash_client_backend::keys::{ + derive_secret_key_from_seed, derive_transparent_address_from_secret_key, + }; + use crate::{ chain::init::init_cache_database, - tests::{ - self, derive_test_keys_from_seed, 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}, @@ -197,17 +199,39 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add two accounts to the wallet - let (extsk0, tsk0) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); - let (extsk1, tsk1) = derive_test_keys_from_seed(&[1u8; 32], AccountId(1)); - let extfvks = [ - ExtendedFullViewingKey::from(&extsk0), - ExtendedFullViewingKey::from(&extsk1), + let extsk0 = spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); + let extsk1 = 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 = + derive_secret_key_from_seed(&network(), &[0u8; 32], AccountId(0), 0).unwrap(); + let tsk1 = + derive_secret_key_from_seed(&network(), &[1u8; 32], AccountId(1), 0).unwrap(); + [ + UnifiedFullViewingKey::new( + AccountId(0), + Some(derive_transparent_address_from_secret_key(&tsk0)), + Some(extfvk0), + ) + .unwrap(), + UnifiedFullViewingKey::new( + AccountId(1), + Some(derive_transparent_address_from_secret_key(&tsk1)), + Some(extfvk1), + ) + .unwrap(), + ] + }; + #[cfg(not(feature = "transparent-inputs"))] + let ufvks = [ + UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk0)).unwrap(), + UnifiedFullViewingKey::new(AccountId(1), None, Some(extfvk1)).unwrap(), ]; - let taddrs = [ - derive_transparent_address_from_secret_key(&tsk0), - derive_transparent_address_from_secret_key(&tsk1), - ]; - init_accounts_table(&db_data, &extfvks, &taddrs).unwrap(); + + init_accounts_table(&db_data, &ufvks).unwrap(); let to = extsk0.default_address().unwrap().1.into(); // Invalid extsk for the given account should cause an error @@ -252,10 +276,10 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add an account to the wallet - let (extsk, tsk) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); + let extsk = spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); - let taddr = derive_transparent_address_from_secret_key(&tsk); - init_accounts_table(&db_data, &[extfvk], &[taddr]).unwrap(); + let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk)).unwrap(); + init_accounts_table(&db_data, &[ufvk]).unwrap(); let to = extsk.default_address().unwrap().1.into(); // We cannot do anything if we aren't synchronised @@ -292,10 +316,10 @@ mod tests { .unwrap(); // Add an account to the wallet - let (extsk, tsk) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); + let extsk = spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); - let taddr = derive_transparent_address_from_secret_key(&tsk); - init_accounts_table(&db_data, &[extfvk], &[taddr]).unwrap(); + let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk)).unwrap(); + init_accounts_table(&db_data, &[ufvk]).unwrap(); let to = extsk.default_address().unwrap().1.into(); // Account balance should be zero @@ -334,10 +358,10 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add an account to the wallet - let (extsk, tsk) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); + let extsk = spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); - let taddr = derive_transparent_address_from_secret_key(&tsk); - init_accounts_table(&db_data, &[extfvk.clone()], &[taddr]).unwrap(); + let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, 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(); @@ -474,10 +498,10 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add an account to the wallet - let (extsk, tsk) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); + let extsk = spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); - let taddr = derive_transparent_address_from_secret_key(&tsk); - init_accounts_table(&db_data, &[extfvk.clone()], &[taddr]).unwrap(); + let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, 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(); @@ -600,10 +624,10 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add an account to the wallet - let (extsk, tsk) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); + let extsk = spending_key(&[0u8; 32], network.coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); - let taddr = derive_transparent_address_from_secret_key(&tsk); - init_accounts_table(&db_data, &[extfvk.clone()], &[taddr]).unwrap(); + let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, 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(); @@ -707,10 +731,10 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add an account to the wallet - let (extsk, tsk) = derive_test_keys_from_seed(&[0u8; 32], AccountId(0)); + let extsk = spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); - let taddr = derive_transparent_address_from_secret_key(&tsk); - init_accounts_table(&db_data, &[extfvk.clone()], &[taddr]).unwrap(); + let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, 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(); From 37e6d3a2bcdbbccb2e579f5bf1c18eb00b09b616 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 20 Jan 2022 14:33:29 -0700 Subject: [PATCH 32/79] Apply suggestions from code review Co-authored-by: Daira Hopwood Co-authored-by: ying tong --- zcash_client_backend/Cargo.toml | 8 ++-- zcash_client_backend/src/data_api.rs | 11 +++-- zcash_client_backend/src/data_api/error.rs | 4 -- zcash_client_backend/src/data_api/wallet.rs | 5 +- zcash_client_backend/src/keys.rs | 53 +++++++++++++++------ zcash_client_backend/src/lib.rs | 3 -- zcash_client_sqlite/src/lib.rs | 22 +++++---- zcash_client_sqlite/src/wallet.rs | 43 ++++++++++++----- zcash_client_sqlite/src/wallet/init.rs | 2 +- zcash_extensions/src/transparent/demo.rs | 4 ++ zcash_primitives/src/consensus.rs | 19 ++++++++ zcash_primitives/src/constants/mainnet.rs | 5 ++ zcash_primitives/src/constants/testnet.rs | 5 ++ zcash_primitives/src/zip32.rs | 4 +- 14 files changed, 130 insertions(+), 58 deletions(-) diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index 50628c357..355ee3767 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -28,9 +28,9 @@ percent-encoding = "2.1.0" proptest = { version = "1.0.0", optional = true } protobuf = "2.20" rand_core = "0.6" -ripemd160 = { version = "0.9.1", optional = true } +ripemd = { version = "0.1", optional = true } secp256k1 = { version = "0.20", optional = true } -sha2 = { version = "0.9", 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" } @@ -43,11 +43,11 @@ protobuf-codegen-pure = "2.20" gumdrop = "0.8" rand_xorshift = "0.3" tempfile = "3.1.0" -zcash_client_sqlite = { version = "0.3", path = "../zcash_client_sqlite" } +zcash_client_sqlite = { version = "0.3", path = "../zcash_client_sqlite", features = ["transparent-inputs"] } zcash_proofs = { version = "0.5", path = "../zcash_proofs" } [features] -transparent-inputs = ["ripemd160", "hdwallet", "sha2", "secp256k1"] +transparent-inputs = ["ripemd", "hdwallet", "sha2", "secp256k1", "zcash_primitives/transparent-inputs"] test-dependencies = ["proptest", "zcash_primitives/test-dependencies"] [lib] diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index db498509f..fe9275537 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -10,10 +10,7 @@ use zcash_primitives::{ memo::{Memo, MemoBytes}, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{Node, Nullifier, PaymentAddress}, - transaction::{ - components::{Amount, OutPoint}, - Transaction, TxId, - }, + transaction::{components::Amount, Transaction, TxId}, zip32::ExtendedFullViewingKey, }; @@ -25,7 +22,10 @@ use crate::{ }; #[cfg(feature = "transparent-inputs")] -use {crate::wallet::WalletTransparentOutput, zcash_primitives::legacy::TransparentAddress}; +use { + crate::wallet::WalletTransparentOutput, + zcash_primitives::{legacy::TransparentAddress, transaction::components::OutPoint}, +}; pub mod chain; pub mod error; @@ -227,6 +227,7 @@ pub struct SentTransaction<'a> { pub created: time::OffsetDateTime, pub account: AccountId, pub outputs: Vec>, + #[cfg(feature = "transparent-inputs")] pub utxos_spent: Vec, } diff --git a/zcash_client_backend/src/data_api/error.rs b/zcash_client_backend/src/data_api/error.rs index 8a856b0d6..af903dd1c 100644 --- a/zcash_client_backend/src/data_api/error.rs +++ b/zcash_client_backend/src/data_api/error.rs @@ -63,9 +63,6 @@ pub enum Error { /// height when Sapling was not yet active. SaplingNotActive, - /// A memo is required when constructing a shielded output. - MemoRequired, - /// It is forbidden to provide a memo when constructing a transparent output. MemoForbidden, } @@ -112,7 +109,6 @@ impl fmt::Display for Error { Error::Builder(e) => write!(f, "{:?}", e), Error::Protobuf(e) => write!(f, "{}", e), Error::SaplingNotActive => write!(f, "Could not determine Sapling upgrade activation height."), - Error::MemoRequired => write!(f, "A memo is required when sending to a shielded address."), Error::MemoForbidden => write!(f, "It is not possible to send a memo to a transparent address."), } } diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 250ea0cf3..6ce54ad7b 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -36,8 +36,6 @@ where P: consensus::Parameters, D: WalletWrite, { - debug!("decrypt_and_store: {:?}", tx); - // Fetch the ExtendedFullViewingKeys we are tracking let extfvks = data.get_extended_full_viewing_keys()?; @@ -209,7 +207,7 @@ where /// * `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 ] +/// 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. @@ -353,6 +351,7 @@ where created: time::OffsetDateTime::now_utc(), outputs: sent_outputs, account, + #[cfg(feature = "transparent-inputs")] utxos_spent: vec![], }) } diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index c43a420ef..231ff5b8e 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -12,7 +12,6 @@ use { hdwallet::{ExtendedPrivKey, ExtendedPubKey, KeyIndex}, secp256k1::{key::PublicKey, key::SecretKey, Secp256k1}, sha2::{Digest, Sha256}, - std::convert::TryInto, zcash_primitives::consensus, }; @@ -63,7 +62,7 @@ pub fn derive_transparent_address_from_secret_key( pub fn derive_transparent_address_from_public_key( public_key: &secp256k1::key::PublicKey, ) -> TransparentAddress { - let mut hash160 = ripemd160::Ripemd160::new(); + let mut hash160 = ripemd::Ripemd160::new(); hash160.update(Sha256::digest(&public_key.serialize())); TransparentAddress::PublicKey(*hash160.finalize().as_ref()) } @@ -92,6 +91,11 @@ pub fn derive_public_key_from_seed( Ok(pub_key.public_key) } +/// Perform derivation of the extended private key for the BIP-44 path: +/// `m/44'/'/' +/// +/// This produces the extended private key for the external (non-change) +/// address at the specified index for the provided account. #[cfg(feature = "transparent-inputs")] pub fn derive_extended_private_key_from_seed( params: &P, @@ -109,16 +113,31 @@ pub fn derive_extended_private_key_from_seed( Ok(private_key) } +/// Wallet Import Format encoded transparent private key. #[cfg(feature = "transparent-inputs")] #[derive(Clone, Debug, Eq, PartialEq)] pub struct Wif(pub String); +#[cfg(feature = "transparent-inputs")] +#[derive(Debug)] +pub enum WifError { + Base58(Bs58Error), + InvalidLeadByte(u8), + InvalidTrailingByte(u8), + Secp256k1(secp256k1::Error), +} + #[cfg(feature = "transparent-inputs")] impl Wif { - pub fn from_secret_key(sk: &SecretKey, compressed: bool) -> Self { + /// Encode the provided secret key in Wallet Import Format. + pub fn from_secret_key( + params: &P, + sk: &SecretKey, + compressed: bool, + ) -> Self { let secret_key = sk.as_ref(); let mut wif = [0u8; 34]; - wif[0] = 0x80; + wif[0] = params.wif_lead_byte(); wif[1..33].copy_from_slice(secret_key); if compressed { wif[33] = 0x01; @@ -127,17 +146,24 @@ impl Wif { Wif(bs58::encode(&wif[..]).with_check().into_string()) } } -} -#[cfg(feature = "transparent-inputs")] -impl<'a> TryInto for &'a Wif { - type Error = Bs58Error; - - fn try_into(self) -> Result { + pub fn to_secret_key( + &self, + params: &P, + ) -> Result { bs58::decode(&self.0) .with_check(None) .into_vec() - .map(|decoded| SecretKey::from_slice(&decoded[1..33]).expect("wrong size key")) + .map_err(WifError::Base58) + .and_then(|decoded| { + if decoded[0] != params.wif_lead_byte() { + Err(WifError::InvalidLeadByte(decoded[0])) + } else if decoded[33] != 0x01 { + Err(WifError::InvalidTrailingByte(decoded[33])) + } else { + SecretKey::from_slice(&decoded[1..33]).map_err(WifError::Secp256k1) + } + }) } } @@ -202,7 +228,6 @@ mod tests { }, crate::encoding::AddressCodec, secp256k1::key::SecretKey, - std::convert::TryInto, zcash_primitives::consensus::MAIN_NETWORK, }; @@ -222,7 +247,7 @@ mod tests { #[test] fn sk_to_wif() { let sk = derive_secret_key_from_seed(&MAIN_NETWORK, &seed(), AccountId(0), 0).unwrap(); - let wif = Wif::from_secret_key(&sk, true).0; + let wif = Wif::from_secret_key(&MAIN_NETWORK, &sk, true).0; assert_eq!( wif, "L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2".to_string() @@ -241,7 +266,7 @@ mod tests { #[test] fn sk_wif_to_taddr() { let sk_wif = Wif("L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2".to_string()); - let sk: SecretKey = (&sk_wif).try_into().expect("invalid wif"); + let sk: SecretKey = (&sk_wif).to_secret_key(&MAIN_NETWORK).expect("invalid wif"); let taddr = derive_transparent_address_from_secret_key(&sk).encode(&MAIN_NETWORK); assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string()); } diff --git a/zcash_client_backend/src/lib.rs b/zcash_client_backend/src/lib.rs index 92fab32ec..085070c13 100644 --- a/zcash_client_backend/src/lib.rs +++ b/zcash_client_backend/src/lib.rs @@ -8,9 +8,6 @@ // Temporary until we have addressed all Result cases. #![allow(clippy::result_unit_err)] -#[macro_use] -extern crate log; - pub mod address; pub mod data_api; mod decrypt; diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index c2d6d936a..ed6d948d1 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -144,6 +144,7 @@ impl WalletDb

{ 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 @@ -154,6 +155,7 @@ impl WalletDb

{ "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" )?, @@ -330,10 +332,12 @@ pub struct DataConnStmtCache<'a, P> { stmt_select_tx_ref: 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>, @@ -506,9 +510,6 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { wallet::mark_sapling_note_spent(up, tx_row, &spend.nf)?; } - //TODO - //wallet::mark_transparent_utxo_spent(up, tx_ref, &utxo.outpoint)?; - for output in &tx.shielded_outputs { let received_note_id = wallet::put_received_note(up, output, tx_row)?; @@ -573,15 +574,14 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { } } - // if we have some transparent outputs yet no shielded outputs, then this is t2t and we - // can safely ignore it otherwise, this is z2t and it might have originated from our - // wallet + // If we have some transparent outputs: if !d_tx.tx.transparent_bundle().iter().any(|b| b.vout.is_empty()) { - // store received z->t transactions in the same way they would be stored by - // create_spend_to_address If there are any of our shielded inputs, we interpret this - // as our z->t tx and store the vouts as our sent notes. + // If there are no shielded spends, then this is t2t and we can safely ignore it + // otherwise, this is z2t and it might have originated from our wallet. + // Store received z->t transactions in the same way they would be stored by + // create_spend_to_address. If there are any of our shielded inputs, we interpret + // this as our z->t tx and store the vouts as our sent notes. // FIXME this is a weird heuristic that is bound to trip us up somewhere. - if let Some((account_id, _)) = nullifiers.iter().find( |(_, nf)| d_tx.tx.sapling_bundle().iter().flat_map(|b| b.shielded_spends.iter()) @@ -622,6 +622,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { } } + #[cfg(feature = "transparent-inputs")] for utxo_outpoint in &sent_tx.utxos_spent { wallet::mark_transparent_utxo_spent(up, tx_ref, &utxo_outpoint)?; } @@ -751,6 +752,7 @@ mod tests { .unwrap() } + #[cfg(test)] pub(crate) fn init_test_accounts_table( db_data: &WalletDb, ) -> (ExtendedFullViewingKey, Option) { diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 6c0f87125..869e7329f 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -18,10 +18,7 @@ use zcash_primitives::{ memo::{Memo, MemoBytes}, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{Node, Note, Nullifier, PaymentAddress}, - transaction::{ - components::{Amount, OutPoint}, - Transaction, TxId, - }, + transaction::{components::Amount, Transaction, TxId}, zip32::ExtendedFullViewingKey, }; @@ -37,13 +34,16 @@ use zcash_client_backend::{ use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDb, PRUNING_HEIGHT}; -use {zcash_client_backend::encoding::AddressCodec, zcash_primitives::legacy::TransparentAddress}; +use zcash_primitives::legacy::TransparentAddress; #[cfg(feature = "transparent-inputs")] use { crate::UtxoId, - zcash_client_backend::wallet::WalletTransparentOutput, - zcash_primitives::{legacy::Script, transaction::components::TxOut}, + zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput}, + zcash_primitives::{ + legacy::Script, + transaction::components::{OutPoint, TxOut}, + }, }; pub mod init; @@ -686,6 +686,7 @@ pub fn get_nullifiers

( Ok(res) } +/// Returns the nullifiers for the notes that this wallet is tracking. pub fn get_all_nullifiers

( wdb: &WalletDb

, ) -> Result, SqliteClientError> { @@ -704,6 +705,9 @@ pub fn get_all_nullifiers

( 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 fn get_unspent_transparent_outputs( wdb: &WalletDb

, @@ -849,7 +853,8 @@ pub fn mark_sapling_note_spent<'a, P>( Ok(()) } -/// Records the specified shielded output as having been received. +/// Marks the given UTXO as having been spent. +#[cfg(feature = "transparent-inputs")] pub fn mark_transparent_utxo_spent<'a, P>( stmts: &mut DataConnStmtCache<'a, P>, tx_ref: i64, @@ -868,6 +873,7 @@ pub fn mark_transparent_utxo_spent<'a, P>( Ok(()) } +/// Adds the given received UTXO to the datastore. #[cfg(feature = "transparent-inputs")] pub fn put_received_transparent_utxo<'a, P: consensus::Parameters>( stmts: &mut DataConnStmtCache<'a, P>, @@ -892,6 +898,10 @@ pub fn put_received_transparent_utxo<'a, P: consensus::Parameters>( 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. Used in the case of chain +/// rollback. +#[cfg(feature = "transparent-inputs")] pub fn delete_utxos_above<'a, P: consensus::Parameters>( stmts: &mut DataConnStmtCache<'a, P>, taddr: &TransparentAddress, @@ -907,9 +917,11 @@ pub fn delete_utxos_above<'a, P: consensus::Parameters>( Ok(rows) } -// Assumptions: -// - A transaction will not contain more than 2^63 shielded outputs. -// - A note value will never exceed 2^63 zatoshis. +/// Records the specified shielded output as having been received. +/// +/// This implementation assumes: +/// - A transaction will not contain more than 2^63 shielded outputs. +/// - A note value will never exceed 2^63 zatoshis. pub fn put_received_note<'a, P, T: ShieldedOutput>( stmts: &mut DataConnStmtCache<'a, P>, output: &T, @@ -1024,6 +1036,10 @@ pub fn put_sent_note<'a, P: consensus::Parameters>( 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. pub fn put_sent_utxo<'a, P: consensus::Parameters>( stmts: &mut DataConnStmtCache<'a, P>, tx_ref: i64, @@ -1033,7 +1049,7 @@ pub fn put_sent_utxo<'a, P: consensus::Parameters>( value: Amount, ) -> Result<(), SqliteClientError> { let ivalue: i64 = value.into(); - // Try updating an existing sent note. + // 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), @@ -1081,6 +1097,9 @@ 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. pub fn insert_sent_utxo<'a, P: consensus::Parameters>( stmts: &mut DataConnStmtCache<'a, P>, tx_ref: i64, diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 0d87a087c..f7a76a851 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -161,7 +161,7 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { /// let db_data = WalletDb::for_path(data_file.path(), Network::TestNetwork).unwrap(); /// init_wallet_db(&db_data).unwrap(); /// -/// let seed = [0u8; 32]; +/// let seed = [0u8; 32]; // insecure; replace with a strong random seed /// let account = AccountId(0); /// let extsk = spending_key(&seed, Network::TestNetwork.coin_type(), account); /// let extfvk = ExtendedFullViewingKey::from(&extsk); diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index 274e7b5d5..127e8d268 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -543,6 +543,10 @@ mod tests { fn b58_script_address_prefix(&self) -> [u8; 2] { constants::testnet::B58_SCRIPT_ADDRESS_PREFIX } + + fn wif_lead_byte(&self) -> u8 { + constants::testnet::WIF_LEAD_BYTE + } } fn demo_hashes(preimage_1: &[u8; 32], preimage_2: &[u8; 32]) -> ([u8; 32], [u8; 32]) { let hash_2 = { diff --git a/zcash_primitives/src/consensus.rs b/zcash_primitives/src/consensus.rs index f66f568bf..c4e0838c1 100644 --- a/zcash_primitives/src/consensus.rs +++ b/zcash_primitives/src/consensus.rs @@ -179,6 +179,10 @@ pub trait Parameters: Clone { /// /// [`TransparentAddress::Script`]: zcash_primitives::legacy::TransparentAddress::Script fn b58_script_address_prefix(&self) -> [u8; 2]; + + /// Returns the lead byte for the transparent Wallet Interchange Format encoding + /// of secp256k1 secret keys. + fn wif_lead_byte(&self) -> u8; } /// Marker struct for the production network. @@ -224,6 +228,10 @@ impl Parameters for MainNetwork { fn b58_script_address_prefix(&self) -> [u8; 2] { constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX } + + fn wif_lead_byte(&self) -> u8 { + constants::mainnet::WIF_LEAD_BYTE + } } /// Marker struct for the test network. @@ -269,6 +277,10 @@ impl Parameters for TestNetwork { fn b58_script_address_prefix(&self) -> [u8; 2] { constants::testnet::B58_SCRIPT_ADDRESS_PREFIX } + + fn wif_lead_byte(&self) -> u8 { + constants::testnet::WIF_LEAD_BYTE + } } #[derive(PartialEq, Copy, Clone, Debug)] @@ -326,6 +338,13 @@ impl Parameters for Network { Network::TestNetwork => TEST_NETWORK.b58_script_address_prefix(), } } + + fn wif_lead_byte(&self) -> u8 { + match self { + Network::MainNetwork => MAIN_NETWORK.wif_lead_byte(), + Network::TestNetwork => TEST_NETWORK.wif_lead_byte(), + } + } } /// An event that occurs at a specified height on the Zcash chain, at which point the diff --git a/zcash_primitives/src/constants/mainnet.rs b/zcash_primitives/src/constants/mainnet.rs index bd0e473f4..5b7a3387d 100644 --- a/zcash_primitives/src/constants/mainnet.rs +++ b/zcash_primitives/src/constants/mainnet.rs @@ -5,6 +5,11 @@ /// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md pub const COIN_TYPE: u32 = 133; +/// The mainnet Wallet Import Format (WIF) lead byte. +/// +/// [WIF]: https://en.bitcoin.it/wiki/Wallet_import_format +pub const WIF_LEAD_BYTE: u8 = 0x80; + /// The HRP for a Bech32-encoded mainnet [`ExtendedSpendingKey`]. /// /// Defined in [ZIP 32]. diff --git a/zcash_primitives/src/constants/testnet.rs b/zcash_primitives/src/constants/testnet.rs index d11c0e983..ab137a199 100644 --- a/zcash_primitives/src/constants/testnet.rs +++ b/zcash_primitives/src/constants/testnet.rs @@ -5,6 +5,11 @@ /// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md pub const COIN_TYPE: u32 = 1; +/// The mainnet Wallet Import Format (WIF) lead byte. +/// +/// [WIF]: https://en.bitcoin.it/wiki/Wallet_import_format +pub const WIF_LEAD_BYTE: u8 = 0xEF; + /// The HRP for a Bech32-encoded testnet [`ExtendedSpendingKey`]. /// /// Defined in [ZIP 32]. diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index 406fb9271..c83263fdd 100644 --- a/zcash_primitives/src/zip32.rs +++ b/zcash_primitives/src/zip32.rs @@ -92,9 +92,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]); From ffc4d0cefbf53b846a5e7ce3dd5f88c45b107861 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 20 Jan 2022 20:01:33 -0700 Subject: [PATCH 33/79] Add newtypes for transparent keys at the account & external levels. This updates UnifiedFullViewingKey to conform to ZIP 316, and adds types that facilitate this support. These types should likely be factored out from `zcash_client_backend` into `zcash_primitives` along with the remainder of the existing unified address support. --- zcash_client_backend/src/data_api/wallet.rs | 16 +- zcash_client_backend/src/encoding.rs | 8 +- zcash_client_backend/src/keys.rs | 395 +++++++++++--------- zcash_client_sqlite/src/lib.rs | 23 +- zcash_client_sqlite/src/wallet/init.rs | 38 +- zcash_client_sqlite/src/wallet/transact.rs | 34 +- 6 files changed, 271 insertions(+), 243 deletions(-) diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 6ce54ad7b..5940c2baa 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -22,7 +22,7 @@ use crate::{ }; #[cfg(feature = "transparent-inputs")] -use crate::keys::derive_transparent_address_from_secret_key; +use crate::keys::transparent; /// Scans a [`Transaction`] for any information that can be decrypted by the accounts in /// the wallet, and saves it to the wallet. @@ -103,7 +103,7 @@ where /// }; /// use zcash_proofs::prover::LocalTxProver; /// use zcash_client_backend::{ -/// keys::spending_key, +/// keys::sapling, /// data_api::wallet::create_spend_to_address, /// wallet::{AccountId, OvkPolicy}, /// }; @@ -128,7 +128,7 @@ where /// }; /// /// let account = AccountId(0); -/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, account); +/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, account); /// let to = extsk.default_address().1.into(); /// /// let data_file = NamedTempFile::new().unwrap(); @@ -384,7 +384,7 @@ pub fn shield_transparent_funds( wallet_db: &mut D, params: &P, prover: impl TxProver, - sk: &secp256k1::SecretKey, + sk: &transparent::ExternalPrivKey, extfvk: &ExtendedFullViewingKey, account: AccountId, memo: &MemoBytes, @@ -406,10 +406,12 @@ where .get_target_and_anchor_heights(min_confirmations) .and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?; - // derive the corresponding t-address - let taddr = derive_transparent_address_from_secret_key(sk); + // derive the t-address for the extpubkey at child index 0 + let taddr = sk.to_external_pubkey().to_address(); // derive own shielded address from the provided extended spending key + // TODO: this should become the internal change address derived from + // the wallet's UFVK let z_address = extfvk.default_address().1; let ovk = extfvk.fvk.ovk; @@ -432,7 +434,7 @@ where for utxo in &utxos { builder - .add_transparent_input(*sk, utxo.outpoint.clone(), utxo.txout.clone()) + .add_transparent_input(*sk.secret_key(), utxo.outpoint.clone(), utxo.txout.clone()) .map_err(Error::Builder)?; } diff --git a/zcash_client_backend/src/encoding.rs b/zcash_client_backend/src/encoding.rs index 9ddc2f453..203e0df4f 100644 --- a/zcash_client_backend/src/encoding.rs +++ b/zcash_client_backend/src/encoding.rs @@ -103,11 +103,11 @@ impl AddressCodec

for TransparentAddress { /// }; /// use zcash_client_backend::{ /// encoding::encode_extended_spending_key, -/// keys::spending_key, +/// keys::sapling, /// wallet::AccountId, /// }; /// -/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, AccountId(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 @@ -135,12 +135,12 @@ pub fn decode_extended_spending_key( /// }; /// use zcash_client_backend::{ /// encoding::encode_extended_full_viewing_key, -/// keys::spending_key, +/// keys::sapling, /// wallet::AccountId, /// }; /// use zcash_primitives::zip32::ExtendedFullViewingKey; /// -/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, AccountId(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); /// ``` diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index 231ff5b8e..4bdd8da01 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -1,169 +1,199 @@ //! Helper functions for managing light client key material. - -use zcash_primitives::zip32::{ChildIndex, ExtendedSpendingKey}; - use crate::wallet::AccountId; -use zcash_primitives::{legacy::TransparentAddress, zip32::ExtendedFullViewingKey}; +pub mod sapling { + pub use zcash_primitives::zip32::ExtendedFullViewingKey; + use zcash_primitives::zip32::{ChildIndex, ExtendedSpendingKey}; -#[cfg(feature = "transparent-inputs")] -use { - bs58::{self, decode::Error as Bs58Error}, - hdwallet::{ExtendedPrivKey, ExtendedPubKey, KeyIndex}, - secp256k1::{key::PublicKey, key::SecretKey, Secp256k1}, - sha2::{Digest, Sha256}, - zcash_primitives::consensus, -}; + use crate::wallet::AccountId; -/// 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, -/// wallet::AccountId, -/// }; -/// -/// let extsk = 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"); + /// 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::sapling, + /// wallet::AccountId, + /// }; + /// + /// 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), + ], + ) } - - ExtendedSpendingKey::from_path( - &ExtendedSpendingKey::master(&seed), - &[ - ChildIndex::Hardened(32), - ChildIndex::Hardened(coin_type), - ChildIndex::Hardened(account.0), - ], - ) } #[cfg(feature = "transparent-inputs")] -pub fn derive_transparent_address_from_secret_key( - secret_key: &secp256k1::key::SecretKey, -) -> TransparentAddress { - let secp = Secp256k1::new(); - let pk = PublicKey::from_secret_key(&secp, secret_key); - derive_transparent_address_from_public_key(&pk) -} +pub mod transparent { + use bs58::{self, decode::Error as Bs58Error}; + use hdwallet::{ExtendedPrivKey, ExtendedPubKey, KeyIndex}; + use secp256k1::key::SecretKey; + use sha2::{Digest, Sha256}; -#[cfg(feature = "transparent-inputs")] -pub fn derive_transparent_address_from_public_key( - public_key: &secp256k1::key::PublicKey, -) -> TransparentAddress { - let mut hash160 = ripemd::Ripemd160::new(); - hash160.update(Sha256::digest(&public_key.serialize())); - TransparentAddress::PublicKey(*hash160.finalize().as_ref()) -} + use crate::wallet::AccountId; + use zcash_primitives::{consensus, legacy::TransparentAddress}; -#[cfg(feature = "transparent-inputs")] -pub fn derive_secret_key_from_seed( - params: &P, - seed: &[u8], - account: AccountId, - index: u32, -) -> Result { - let private_key = - derive_extended_private_key_from_seed(params, seed, account, index)?.private_key; - Ok(private_key) -} + /// A type representing a BIP-44 private key at the account path level + /// `m/44'/'/' + #[derive(Clone, Debug)] + pub struct AccountPrivKey(ExtendedPrivKey); -#[cfg(feature = "transparent-inputs")] -pub fn derive_public_key_from_seed( - params: &P, - seed: &[u8], - account: AccountId, - index: u32, -) -> Result { - let private_key = derive_extended_private_key_from_seed(params, seed, account, index)?; - let pub_key = ExtendedPubKey::from_private_key(&private_key); - Ok(pub_key.public_key) -} + impl AccountPrivKey { + /// Perform derivation of the extended private key for the BIP-44 path: + /// `m/44'/'/' + /// + /// This produces the extended private key for the external (non-change) + /// address at the specified index for the provided account. + pub fn from_seed( + params: &P, + seed: &[u8], + account: AccountId, + ) -> Result { + 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) + } -/// Perform derivation of the extended private key for the BIP-44 path: -/// `m/44'/'/' -/// -/// This produces the extended private key for the external (non-change) -/// address at the specified index for the provided account. -#[cfg(feature = "transparent-inputs")] -pub fn derive_extended_private_key_from_seed( - params: &P, - seed: &[u8], - account: AccountId, - index: u32, -) -> Result { - let pk = ExtendedPrivKey::with_seed(&seed)?; - let private_key = pk - .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)?)? - .derive_private_key(KeyIndex::Normal(0))? - .derive_private_key(KeyIndex::Normal(index))?; - Ok(private_key) -} + pub fn to_account_pubkey(&self) -> AccountPubKey { + AccountPubKey(ExtendedPubKey::from_private_key(&self.0)) + } -/// Wallet Import Format encoded transparent private key. -#[cfg(feature = "transparent-inputs")] -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Wif(pub String); - -#[cfg(feature = "transparent-inputs")] -#[derive(Debug)] -pub enum WifError { - Base58(Bs58Error), - InvalidLeadByte(u8), - InvalidTrailingByte(u8), - Secp256k1(secp256k1::Error), -} - -#[cfg(feature = "transparent-inputs")] -impl Wif { - /// Encode the provided secret key in Wallet Import Format. - pub fn from_secret_key( - params: &P, - sk: &SecretKey, - compressed: bool, - ) -> Self { - let secret_key = sk.as_ref(); - let mut wif = [0u8; 34]; - wif[0] = params.wif_lead_byte(); - wif[1..33].copy_from_slice(secret_key); - if compressed { - wif[33] = 0x01; - Wif(bs58::encode(&wif[..]).with_check().into_string()) - } else { - Wif(bs58::encode(&wif[..]).with_check().into_string()) + /// Derive BIP-44 private key at the external child path + /// `m/44'/'/'/0/ + pub fn derive_external_secret_key( + &self, + child_index: u32, + ) -> Result { + self.0 + .derive_private_key(KeyIndex::Normal(0))? + .derive_private_key(KeyIndex::Normal(child_index)) + .map(ExternalPrivKey) } } - pub fn to_secret_key( - &self, - params: &P, - ) -> Result { - bs58::decode(&self.0) - .with_check(None) - .into_vec() - .map_err(WifError::Base58) - .and_then(|decoded| { - if decoded[0] != params.wif_lead_byte() { - Err(WifError::InvalidLeadByte(decoded[0])) - } else if decoded[33] != 0x01 { - Err(WifError::InvalidTrailingByte(decoded[33])) - } else { - SecretKey::from_slice(&decoded[1..33]).map_err(WifError::Secp256k1) - } - }) + /// A type representing a BIP-44 public key at the account path level + /// `m/44'/'/' + #[derive(Clone, Debug)] + pub struct AccountPubKey(ExtendedPubKey); + + impl AccountPubKey { + pub fn to_external_pubkey( + &self, + child_index: u32, + ) -> Result { + self.0 + .derive_public_key(KeyIndex::Normal(0))? + .derive_public_key(KeyIndex::Normal(child_index)) + .map(ExternalPubKey) + } + } + + /// A type representing a private key at the BIP-44 external child + /// level `m/44'/'/'/0/ + #[derive(Clone, Debug)] + pub struct ExternalPrivKey(ExtendedPrivKey); + + impl ExternalPrivKey { + pub fn to_external_pubkey(&self) -> ExternalPubKey { + ExternalPubKey(ExtendedPubKey::from_private_key(&self.0)) + } + + pub fn secret_key(&self) -> &secp256k1::key::SecretKey { + &self.0.private_key + } + } + + pub(crate) fn pubkey_to_address(pubkey: &secp256k1::key::PublicKey) -> TransparentAddress { + let mut hash160 = ripemd::Ripemd160::new(); + hash160.update(Sha256::digest(pubkey.serialize())); + TransparentAddress::PublicKey(*hash160.finalize().as_ref()) + } + + /// A type representing a public key at the BIP-44 external child + /// level `m/44'/'/'/0/ + #[derive(Clone, Debug)] + pub struct ExternalPubKey(ExtendedPubKey); + + impl ExternalPubKey { + pub fn to_address(&self) -> TransparentAddress { + pubkey_to_address(&self.0.public_key) + } + + pub fn public_key(&self) -> &secp256k1::key::PublicKey { + &self.0.public_key + } + } + + /// Wallet Import Format encoded transparent private key. + #[derive(Clone, Debug, Eq, PartialEq)] + pub struct Wif(pub String); + + #[derive(Debug)] + pub enum WifError { + Base58(Bs58Error), + InvalidLeadByte(u8), + InvalidTrailingByte(u8), + Secp256k1(secp256k1::Error), + } + + impl Wif { + /// Encode the provided secret key in Wallet Import Format. + pub fn from_secret_key( + params: &P, + sk: &SecretKey, + compressed: bool, + ) -> Self { + let secret_key = sk.as_ref(); + let mut wif = [0u8; 34]; + wif[0] = params.wif_lead_byte(); + wif[1..33].copy_from_slice(secret_key); + if compressed { + wif[33] = 0x01; + Wif(bs58::encode(&wif[..]).with_check().into_string()) + } else { + Wif(bs58::encode(&wif[..]).with_check().into_string()) + } + } + + pub fn to_secret_key( + &self, + params: &P, + ) -> Result { + bs58::decode(&self.0) + .with_check(None) + .into_vec() + .map_err(WifError::Base58) + .and_then(|decoded| { + if decoded[0] != params.wif_lead_byte() { + Err(WifError::InvalidLeadByte(decoded[0])) + } else if decoded[33] != 0x01 { + Err(WifError::InvalidTrailingByte(decoded[33])) + } else { + SecretKey::from_slice(&decoded[1..33]).map_err(WifError::Secp256k1) + } + }) + } } } @@ -172,16 +202,16 @@ impl Wif { #[derive(Clone, Debug)] pub struct UnifiedFullViewingKey { account: AccountId, - transparent: Option, - sapling: Option, + transparent: Option, + sapling: Option, } impl UnifiedFullViewingKey { /// Construct a new unified full viewing key, if the required components are present. pub fn new( account: AccountId, - transparent: Option, - sapling: Option, + transparent: Option, + sapling: Option, ) -> Option { if sapling.is_none() { None @@ -200,34 +230,27 @@ impl UnifiedFullViewingKey { self.account } - /// Returns the transparent component of the unified key. - // TODO: make this the pubkey rather than the address to - // permit child derivation - pub fn transparent(&self) -> Option<&TransparentAddress> { + /// Returns the transparent component of the unified key at the + /// BIP44 path `m/44'/'/'`. + pub fn transparent(&self) -> Option<&transparent::AccountPubKey> { self.transparent.as_ref() } /// Returns the Sapling extended full viewing key component of this /// unified key. - pub fn sapling(&self) -> Option<&ExtendedFullViewingKey> { + pub fn sapling(&self) -> Option<&sapling::ExtendedFullViewingKey> { self.sapling.as_ref() } } #[cfg(test)] mod tests { - use super::spending_key; + use super::sapling; use crate::wallet::AccountId; #[cfg(feature = "transparent-inputs")] use { - super::{ - derive_public_key_from_seed, derive_secret_key_from_seed, - derive_transparent_address_from_public_key, derive_transparent_address_from_secret_key, - Wif, - }, - crate::encoding::AddressCodec, - secp256k1::key::SecretKey, + super::transparent, crate::encoding::AddressCodec, secp256k1::key::SecretKey, zcash_primitives::consensus::MAIN_NETWORK, }; @@ -240,42 +263,44 @@ mod tests { #[test] #[should_panic] fn spending_key_panics_on_short_seed() { - let _ = spending_key(&[0; 31][..], 0, AccountId(0)); + let _ = sapling::spending_key(&[0; 31][..], 0, AccountId(0)); } #[cfg(feature = "transparent-inputs")] #[test] fn sk_to_wif() { - let sk = derive_secret_key_from_seed(&MAIN_NETWORK, &seed(), AccountId(0), 0).unwrap(); - let wif = Wif::from_secret_key(&MAIN_NETWORK, &sk, true).0; + let sk = transparent::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId(0)) + .unwrap() + .derive_external_secret_key(0) + .unwrap(); + let wif = transparent::Wif::from_secret_key(&MAIN_NETWORK, &sk.secret_key(), true).0; assert_eq!( wif, "L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2".to_string() ); } - #[cfg(feature = "transparent-inputs")] - #[test] - fn sk_to_taddr() { - let sk = derive_secret_key_from_seed(&MAIN_NETWORK, &seed(), AccountId(0), 0).unwrap(); - let taddr = derive_transparent_address_from_secret_key(&sk).encode(&MAIN_NETWORK); - assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string()); - } - #[cfg(feature = "transparent-inputs")] #[test] fn sk_wif_to_taddr() { - let sk_wif = Wif("L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2".to_string()); + let sk_wif = + transparent::Wif("L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2".to_string()); let sk: SecretKey = (&sk_wif).to_secret_key(&MAIN_NETWORK).expect("invalid wif"); - let taddr = derive_transparent_address_from_secret_key(&sk).encode(&MAIN_NETWORK); + let secp = secp256k1::Secp256k1::new(); + let pubkey = secp256k1::key::PublicKey::from_secret_key(&secp, &sk); + let taddr = transparent::pubkey_to_address(&pubkey).encode(&MAIN_NETWORK); assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string()); } #[cfg(feature = "transparent-inputs")] #[test] fn pk_from_seed() { - let pk = derive_public_key_from_seed(&MAIN_NETWORK, &seed(), AccountId(0), 0).unwrap(); - let hex_value = hex::encode(&pk.serialize()); + let pk = transparent::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId(0)) + .unwrap() + .derive_external_secret_key(0) + .unwrap() + .to_external_pubkey(); + let hex_value = hex::encode(&pk.public_key().serialize()); assert_eq!( hex_value, "03b1d7fb28d17c125b504d06b1530097e0a3c76ada184237e3bc0925041230a5af".to_string() @@ -285,8 +310,12 @@ mod tests { #[cfg(feature = "transparent-inputs")] #[test] fn pk_to_taddr() { - let pk = derive_public_key_from_seed(&MAIN_NETWORK, &seed(), AccountId(0), 0).unwrap(); - let taddr = derive_transparent_address_from_public_key(&pk).encode(&MAIN_NETWORK); + let pk = transparent::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId(0)) + .unwrap() + .derive_external_secret_key(0) + .unwrap() + .to_external_pubkey(); + let taddr = pk.to_address().encode(&MAIN_NETWORK); assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string()); } } diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index ed6d948d1..e5eeb57cb 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -702,14 +702,12 @@ mod tests { use rusqlite::params; use zcash_client_backend::{ - keys::{spending_key, UnifiedFullViewingKey}, + keys::{sapling, UnifiedFullViewingKey}, proto::compact_formats::{CompactBlock, CompactOutput, CompactSpend, CompactTx}, }; #[cfg(feature = "transparent-inputs")] - use zcash_client_backend::keys::{ - derive_secret_key_from_seed, derive_transparent_address_from_secret_key, - }; + use zcash_client_backend::keys::transparent; use zcash_primitives::{ block::BlockHash, @@ -758,22 +756,23 @@ mod tests { ) -> (ExtendedFullViewingKey, Option) { let seed = [0u8; 32]; - let extsk = spending_key(&seed, network().coin_type(), AccountId(0)); + let extsk = sapling::spending_key(&seed, network().coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); #[cfg(feature = "transparent-inputs")] - let taddr = { - let tsk = derive_secret_key_from_seed(&network(), &seed, AccountId(0), 0).unwrap(); - Some(derive_transparent_address_from_secret_key(&tsk)) - }; + let tkey = Some( + transparent::AccountPrivKey::from_seed(&network(), &seed, AccountId(0)) + .unwrap() + .to_account_pubkey() + ); #[cfg(not(feature = "transparent-inputs"))] - let taddr = None; + let tkey = None; let ufvk = - UnifiedFullViewingKey::new(AccountId(0), taddr.clone(), Some(extfvk.clone())).unwrap(); + UnifiedFullViewingKey::new(AccountId(0), tkey.clone(), Some(extfvk.clone())).unwrap(); init_accounts_table(db_data, &[ufvk]).unwrap(); - (extfvk, taddr) + (extfvk, tkey.map(|k| k.to_external_pubkey(0).unwrap().to_address())) } /// Create a fake CompactBlock at the given height, containing a single output paying diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index f7a76a851..e8dae82e8 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -146,7 +146,7 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { /// /// use zcash_client_backend::{ /// keys::{ -/// spending_key, +/// sapling, /// UnifiedFullViewingKey /// }, /// wallet::AccountId, @@ -163,7 +163,7 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { /// /// let seed = [0u8; 32]; // insecure; replace with a strong random seed /// let account = AccountId(0); -/// let extsk = spending_key(&seed, Network::TestNetwork.coin_type(), account); +/// 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(); @@ -194,7 +194,11 @@ pub fn init_accounts_table( let address_str: Option = key .sapling() .map(|extfvk| address_from_extfvk(&wdb.params, extfvk)); - let taddress_str: Option = key.transparent().map(|taddr| taddr.encode(&wdb.params)); + let taddress_str: Option = key.transparent().and_then(|k| { + k.to_external_pubkey(0) + .ok() + .map(|k| k.to_address().encode(&wdb.params)) + }); wdb.conn.execute( "INSERT INTO accounts (account, extfvk, address, transparent_address) @@ -269,12 +273,10 @@ pub fn init_blocks_table

( mod tests { use tempfile::NamedTempFile; - use zcash_client_backend::keys::{spending_key, UnifiedFullViewingKey}; + use zcash_client_backend::keys::{sapling, UnifiedFullViewingKey}; #[cfg(feature = "transparent-inputs")] - use zcash_client_backend::keys::{ - derive_secret_key_from_seed, derive_transparent_address_from_secret_key, - }; + use zcash_client_backend::keys::transparent; use zcash_primitives::{ block::BlockHash, @@ -304,21 +306,19 @@ mod tests { let account = AccountId(0); // First call with data should initialise the accounts table - let extsk = spending_key(&seed, network().coin_type(), account); + let extsk = sapling::spending_key(&seed, network().coin_type(), account); let extfvk = ExtendedFullViewingKey::from(&extsk); #[cfg(feature = "transparent-inputs")] - let ufvk = { - let tsk = derive_secret_key_from_seed(&network(), &seed, account, 0).unwrap(); - UnifiedFullViewingKey::new( - account, - Some(derive_transparent_address_from_secret_key(&tsk)), - Some(extfvk), - ) - .unwrap() - }; + let tkey = Some( + transparent::AccountPrivKey::from_seed(&network(), &seed, account) + .unwrap() + .to_account_pubkey(), + ); #[cfg(not(feature = "transparent-inputs"))] - let ufvk = UnifiedFullViewingKey::new(account, None, Some(extfvk)).unwrap(); + let tkey = None; + + let ufvk = UnifiedFullViewingKey::new(account, tkey, Some(extfvk)).unwrap(); init_accounts_table(&db_data, &[ufvk.clone()]).unwrap(); @@ -363,7 +363,7 @@ mod tests { let seed = [0u8; 32]; // Add an account to the wallet - let extsk = spending_key(&seed, network().coin_type(), AccountId(0)); + let extsk = sapling::spending_key(&seed, network().coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk)).unwrap(); init_accounts_table(&db_data, &[ufvk]).unwrap(); diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index 25fd5767f..c63b78330 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -164,14 +164,12 @@ mod tests { use zcash_client_backend::{ data_api::{chain::scan_cached_blocks, wallet::create_spend_to_address, WalletRead}, - keys::{spending_key, UnifiedFullViewingKey}, + keys::{sapling, UnifiedFullViewingKey}, wallet::OvkPolicy, }; #[cfg(feature = "transparent-inputs")] - use zcash_client_backend::keys::{ - derive_secret_key_from_seed, derive_transparent_address_from_secret_key, - }; + use zcash_client_backend::keys::transparent; use crate::{ chain::init::init_cache_database, @@ -199,27 +197,27 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add two accounts to the wallet - let extsk0 = spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); - let extsk1 = spending_key(&[1u8; 32], network().coin_type(), AccountId(1)); + 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 = - derive_secret_key_from_seed(&network(), &[0u8; 32], AccountId(0), 0).unwrap(); - let tsk1 = - derive_secret_key_from_seed(&network(), &[1u8; 32], AccountId(1), 0).unwrap(); + 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(derive_transparent_address_from_secret_key(&tsk0)), + Some(tsk0.to_account_pubkey()), Some(extfvk0), ) .unwrap(), UnifiedFullViewingKey::new( AccountId(1), - Some(derive_transparent_address_from_secret_key(&tsk1)), + Some(tsk1.to_account_pubkey()), Some(extfvk1), ) .unwrap(), @@ -276,7 +274,7 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add an account to the wallet - let extsk = spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); + let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk)).unwrap(); init_accounts_table(&db_data, &[ufvk]).unwrap(); @@ -316,7 +314,7 @@ mod tests { .unwrap(); // Add an account to the wallet - let extsk = spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); + let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk)).unwrap(); init_accounts_table(&db_data, &[ufvk]).unwrap(); @@ -358,7 +356,7 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add an account to the wallet - let extsk = spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); + let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk.clone())).unwrap(); init_accounts_table(&db_data, &[ufvk]).unwrap(); @@ -498,7 +496,7 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add an account to the wallet - let extsk = spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); + let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk.clone())).unwrap(); init_accounts_table(&db_data, &[ufvk]).unwrap(); @@ -624,7 +622,7 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add an account to the wallet - let extsk = spending_key(&[0u8; 32], network.coin_type(), AccountId(0)); + let extsk = sapling::spending_key(&[0u8; 32], network.coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk.clone())).unwrap(); init_accounts_table(&db_data, &[ufvk]).unwrap(); @@ -731,7 +729,7 @@ mod tests { init_wallet_db(&db_data).unwrap(); // Add an account to the wallet - let extsk = spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); + let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), AccountId(0)); let extfvk = ExtendedFullViewingKey::from(&extsk); let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk.clone())).unwrap(); init_accounts_table(&db_data, &[ufvk]).unwrap(); From 79bd2f77334b407a780344987ff1ba4e00db9835 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 20 Jan 2022 20:14:44 -0700 Subject: [PATCH 34/79] Add missing documentation & rustfmt. --- zcash_client_backend/src/keys.rs | 10 ++++++++++ zcash_client_sqlite/src/lib.rs | 7 +++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index 4bdd8da01..2101237fa 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -98,6 +98,8 @@ pub mod transparent { pub struct AccountPubKey(ExtendedPubKey); impl AccountPubKey { + /// Derive BIP-44 public key at the external child path + /// `m/44'/'/'/0/ pub fn to_external_pubkey( &self, child_index: u32, @@ -115,10 +117,12 @@ pub mod transparent { pub struct ExternalPrivKey(ExtendedPrivKey); impl ExternalPrivKey { + /// Returns the external public key corresponding to this private key pub fn to_external_pubkey(&self) -> ExternalPubKey { ExternalPubKey(ExtendedPubKey::from_private_key(&self.0)) } + /// Extracts the secp256k1 secret key component pub fn secret_key(&self) -> &secp256k1::key::SecretKey { &self.0.private_key } @@ -136,10 +140,14 @@ pub mod transparent { pub struct ExternalPubKey(ExtendedPubKey); impl ExternalPubKey { + /// Returns the transparent address corresponding to + /// this public key. pub fn to_address(&self) -> TransparentAddress { pubkey_to_address(&self.0.public_key) } + /// Returns the secp256k1::key::PublicKey component of + /// this public key. pub fn public_key(&self) -> &secp256k1::key::PublicKey { &self.0.public_key } @@ -149,6 +157,7 @@ pub mod transparent { #[derive(Clone, Debug, Eq, PartialEq)] pub struct Wif(pub String); + /// Errors that may occur in WIF key decoding. #[derive(Debug)] pub enum WifError { Base58(Bs58Error), @@ -176,6 +185,7 @@ pub mod transparent { } } + /// Decode this Wif value to obtain the encoded secret key pub fn to_secret_key( &self, params: &P, diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index e5eeb57cb..2eeacd014 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -763,7 +763,7 @@ mod tests { let tkey = Some( transparent::AccountPrivKey::from_seed(&network(), &seed, AccountId(0)) .unwrap() - .to_account_pubkey() + .to_account_pubkey(), ); #[cfg(not(feature = "transparent-inputs"))] @@ -772,7 +772,10 @@ mod tests { let ufvk = UnifiedFullViewingKey::new(AccountId(0), tkey.clone(), Some(extfvk.clone())).unwrap(); init_accounts_table(db_data, &[ufvk]).unwrap(); - (extfvk, tkey.map(|k| k.to_external_pubkey(0).unwrap().to_address())) + ( + extfvk, + tkey.map(|k| k.to_external_pubkey(0).unwrap().to_address()), + ) } /// Create a fake CompactBlock at the given height, containing a single output paying From f75ffb0eaf3cbb37a6b06cb8b0865c550159f9e8 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 20 Jan 2022 20:20:58 -0700 Subject: [PATCH 35/79] Document wallet database initialization. --- zcash_client_sqlite/src/wallet/init.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index e8dae82e8..aababd4cd 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -16,6 +16,13 @@ use crate::{address_from_extfvk, error::SqliteClientError, WalletDb}; /// Sets up the internal structure of the data database. /// +/// The database structure is the same irrespective of whether or +/// not transparent spends are enabled, and it is safe to use a +/// wallet database created without the ability to create transparent +/// spends with a build that enables transparent spends (though not +/// the reverse, as wallet balance calculations would ignore the +/// prior transparent inputs controlled by the wallet). +/// /// # Examples /// /// ``` From 8f408354b97c93bb0283c3e98f707eba1b65dee6 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 20 Jan 2022 20:55:34 -0700 Subject: [PATCH 36/79] Remove cyclic dev dependency between zcash_client_backend and zcash_client_sqlite. --- zcash_client_backend/Cargo.toml | 1 - zcash_client_backend/src/data_api/chain.rs | 72 ++++----------------- zcash_client_backend/src/data_api/wallet.rs | 27 +++----- zcash_client_backend/src/keys.rs | 5 +- zcash_client_sqlite/src/wallet/init.rs | 9 ++- 5 files changed, 32 insertions(+), 82 deletions(-) diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index 355ee3767..de89c4900 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -43,7 +43,6 @@ protobuf-codegen-pure = "2.20" gumdrop = "0.8" rand_xorshift = "0.3" tempfile = "3.1.0" -zcash_client_sqlite = { version = "0.3", path = "../zcash_client_sqlite", features = ["transparent-inputs"] } zcash_proofs = { version = "0.5", path = "../zcash_proofs" } [features] diff --git a/zcash_client_backend/src/data_api/chain.rs b/zcash_client_backend/src/data_api/chain.rs index 38a47007f..b70b08705 100644 --- a/zcash_client_backend/src/data_api/chain.rs +++ b/zcash_client_backend/src/data_api/chain.rs @@ -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> { //! 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; @@ -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( params: &P, cache: &C, diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 5940c2baa..d2b0a42ea 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -95,31 +95,27 @@ where /// # 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}, /// }; /// use zcash_proofs::prover::LocalTxProver; /// use zcash_client_backend::{ /// keys::sapling, -/// data_api::wallet::create_spend_to_address, +/// data_api::{wallet::create_spend_to_address, error::Error, testing}, /// wallet::{AccountId, OvkPolicy}, /// }; -/// use zcash_client_sqlite::{ -/// 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> { +/// # fn test() -> Result> { +/// /// let tx_prover = match LocalTxProver::with_default_location() { /// Some(tx_prover) => tx_prover, /// None => { @@ -131,13 +127,10 @@ where /// 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, @@ -147,9 +140,9 @@ where /// None, /// OvkPolicy::Sender, /// 10 -/// )?; +/// ) /// -/// # Ok(()) +/// # } /// # } /// ``` #[allow(clippy::too_many_arguments)] diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index 2101237fa..a9696d7d4 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -212,6 +212,7 @@ pub mod transparent { #[derive(Clone, Debug)] pub struct UnifiedFullViewingKey { account: AccountId, + #[cfg(feature = "transparent-inputs")] transparent: Option, sapling: Option, } @@ -220,7 +221,7 @@ impl UnifiedFullViewingKey { /// Construct a new unified full viewing key, if the required components are present. pub fn new( account: AccountId, - transparent: Option, + #[cfg(feature = "transparent-inputs")] transparent: Option, sapling: Option, ) -> Option { if sapling.is_none() { @@ -228,6 +229,7 @@ impl UnifiedFullViewingKey { } else { Some(UnifiedFullViewingKey { account, + #[cfg(feature = "transparent-inputs")] transparent, sapling, }) @@ -240,6 +242,7 @@ impl UnifiedFullViewingKey { self.account } + #[cfg(feature = "transparent-inputs")] /// Returns the transparent component of the unified key at the /// BIP44 path `m/44'/'/'`. pub fn transparent(&self) -> Option<&transparent::AccountPubKey> { diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index aababd4cd..25e7e4d2e 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -8,12 +8,14 @@ use zcash_primitives::{ }; use zcash_client_backend::{ - encoding::{encode_extended_full_viewing_key, AddressCodec}, - keys::UnifiedFullViewingKey, + 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; + /// Sets up the internal structure of the data database. /// /// The database structure is the same irrespective of whether or @@ -201,11 +203,14 @@ pub fn init_accounts_table( let address_str: Option = key .sapling() .map(|extfvk| address_from_extfvk(&wdb.params, extfvk)); + #[cfg(feature = "transparent-inputs")] let taddress_str: Option = key.transparent().and_then(|k| { k.to_external_pubkey(0) .ok() .map(|k| k.to_address().encode(&wdb.params)) }); + #[cfg(not(feature = "transparent-inputs"))] + let taddress_str: Option = None; wdb.conn.execute( "INSERT INTO accounts (account, extfvk, address, transparent_address) From dec395a5b0c3bd5ee1fb2661ded06005ce00b81f Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 21 Jan 2022 13:47:03 -0700 Subject: [PATCH 37/79] Add unified spending keys. --- zcash_client_backend/src/keys.rs | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index a9696d7d4..711f72b8f 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -1,5 +1,9 @@ //! Helper functions for managing light client key material. use crate::wallet::AccountId; +use zcash_primitives::{ + consensus, + zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, +}; pub mod sapling { pub use zcash_primitives::zip32::ExtendedFullViewingKey; @@ -207,6 +211,53 @@ pub mod transparent { } } +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)] +pub struct UnifiedSpendingKey { + account: AccountId, + #[cfg(feature = "transparent-inputs")] + transparent: transparent::AccountPrivKey, + sapling: ExtendedSpendingKey, +} + +impl UnifiedSpendingKey { + pub fn from_seed( + params: &P, + seed: &[u8], + account: AccountId, + ) -> Result { + if seed.len() < 32 { + panic!("ZIP 32 seeds MUST be at least 32 bytes"); + } + + #[cfg(feature = "transparent-inputs")] + let transparent = transparent::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), + }) + } + + 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(ExtendedFullViewingKey::from(&self.sapling)), + } + } +} + /// A set of viewing keys that are all associated with a single /// ZIP-0032 account identifier. #[derive(Clone, Debug)] From 00aee09662f607f53eb0467818c398460ee74245 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 21 Jan 2022 14:02:51 -0700 Subject: [PATCH 38/79] Add accessors for the ExtendedPubKey wrapped by AccountPubKey --- zcash_client_backend/src/keys.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index 711f72b8f..6f8324fd4 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -113,6 +113,14 @@ pub mod transparent { .derive_public_key(KeyIndex::Normal(child_index)) .map(ExternalPubKey) } + + pub fn from_extended_pubkey(extpubkey: ExtendedPubKey) -> Self { + AccountPubKey(extpubkey) + } + + pub fn extended_pubkey(&self) -> &ExtendedPubKey { + &self.0 + } } /// A type representing a private key at the BIP-44 external child From 7c03dbdc954440c90959e48fa32159a2d713f77b Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 21 Jan 2022 16:44:53 -0700 Subject: [PATCH 39/79] Add convenience method for amount sums. --- zcash_primitives/src/transaction/components/amount.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zcash_primitives/src/transaction/components/amount.rs b/zcash_primitives/src/transaction/components/amount.rs index 4b20a1cb1..7d86f9121 100644 --- a/zcash_primitives/src/transaction/components/amount.rs +++ b/zcash_primitives/src/transaction/components/amount.rs @@ -114,6 +114,14 @@ impl Amount { pub const fn is_negative(self) -> bool { self.0.is_negative() } + + pub fn sum>(values: I) -> Option { + let mut result = Amount::zero(); + for value in values { + result = (result + value)?; + } + Some(result) + } } impl TryFrom for Amount { From 281a4d5c1600fecd60700b4364b759d09b2b6860 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 21 Jan 2022 18:07:23 -0700 Subject: [PATCH 40/79] Add accessors for the ExtendedPrivKey wrapped by AccountPrivKey --- zcash_client_backend/src/keys.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index 6f8324fd4..263d42f47 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -79,6 +79,14 @@ pub mod transparent { .map(AccountPrivKey) } + pub fn from_extended_privkey(extprivkey: ExtendedPrivKey) -> Self { + AccountPrivKey(extprivkey) + } + + pub fn extended_privkey(&self) -> &ExtendedPrivKey { + &self.0 + } + pub fn to_account_pubkey(&self) -> AccountPubKey { AccountPubKey(ExtendedPubKey::from_private_key(&self.0)) } From 574ca4e180ea216cb855ad823fe8ca267c2766cd Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 21 Jan 2022 18:20:12 -0700 Subject: [PATCH 41/79] Add accessors to UnifiedSpendingKey --- zcash_client_backend/src/keys.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index 263d42f47..b159ad101 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -1,13 +1,10 @@ //! Helper functions for managing light client key material. use crate::wallet::AccountId; -use zcash_primitives::{ - consensus, - zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, -}; +use zcash_primitives::consensus; pub mod sapling { - pub use zcash_primitives::zip32::ExtendedFullViewingKey; - use zcash_primitives::zip32::{ChildIndex, ExtendedSpendingKey}; + use zcash_primitives::zip32::ChildIndex; + pub use zcash_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; use crate::wallet::AccountId; @@ -239,7 +236,7 @@ pub struct UnifiedSpendingKey { account: AccountId, #[cfg(feature = "transparent-inputs")] transparent: transparent::AccountPrivKey, - sapling: ExtendedSpendingKey, + sapling: sapling::ExtendedSpendingKey, } impl UnifiedSpendingKey { @@ -269,9 +266,26 @@ impl UnifiedSpendingKey { account: self.account, #[cfg(feature = "transparent-inputs")] transparent: Some(self.transparent.to_account_pubkey()), - sapling: Some(ExtendedFullViewingKey::from(&self.sapling)), + 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'/'/'`. + #[cfg(feature = "transparent-inputs")] + pub fn transparent(&self) -> Option<&transparent::AccountPrivKey> { + Some(&self.transparent) + } + + /// Returns the Sapling extended full viewing key component of this + /// unified key. + pub fn sapling(&self) -> Option<&sapling::ExtendedSpendingKey> { + Some(&self.sapling) + } } /// A set of viewing keys that are all associated with a single From 7d873e9d79df508d4995368e6f16eeba0314cfb7 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 21 Jan 2022 19:01:32 -0700 Subject: [PATCH 42/79] Fix test compilation errors related to UFVK construction. --- zcash_client_backend/src/keys.rs | 9 +++--- zcash_client_sqlite/src/lib.rs | 33 +++++++++++++--------- zcash_client_sqlite/src/wallet/init.rs | 31 +++++++++++--------- zcash_client_sqlite/src/wallet/transact.rs | 23 +++++++++++++-- 4 files changed, 63 insertions(+), 33 deletions(-) diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index b159ad101..3c5741d87 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -224,6 +224,7 @@ pub mod transparent { } } +#[derive(Debug)] pub enum DerivationError { #[cfg(feature = "transparent-inputs")] Transparent(hdwallet::error::Error), @@ -277,14 +278,14 @@ impl UnifiedSpendingKey { /// Returns the transparent component of the unified key at the /// BIP44 path `m/44'/'/'`. #[cfg(feature = "transparent-inputs")] - pub fn transparent(&self) -> Option<&transparent::AccountPrivKey> { - Some(&self.transparent) + pub fn transparent(&self) -> &transparent::AccountPrivKey { + &self.transparent } /// Returns the Sapling extended full viewing key component of this /// unified key. - pub fn sapling(&self) -> Option<&sapling::ExtendedSpendingKey> { - Some(&self.sapling) + pub fn sapling(&self) -> &sapling::ExtendedSpendingKey { + &self.sapling } } diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 2eeacd014..3c2240823 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -760,22 +760,27 @@ mod tests { let extfvk = ExtendedFullViewingKey::from(&extsk); #[cfg(feature = "transparent-inputs")] - let tkey = Some( - transparent::AccountPrivKey::from_seed(&network(), &seed, AccountId(0)) - .unwrap() - .to_account_pubkey(), - ); + { + let tkey = Some( + transparent::AccountPrivKey::from_seed(&network(), &seed, AccountId(0)) + .unwrap() + .to_account_pubkey(), + ); + let ufvk = UnifiedFullViewingKey::new(AccountId(0), tkey.clone(), Some(extfvk.clone())) + .unwrap(); + init_accounts_table(db_data, &[ufvk]).unwrap(); + ( + extfvk, + tkey.map(|k| k.to_external_pubkey(0).unwrap().to_address()), + ) + } #[cfg(not(feature = "transparent-inputs"))] - let tkey = None; - - let ufvk = - UnifiedFullViewingKey::new(AccountId(0), tkey.clone(), Some(extfvk.clone())).unwrap(); - init_accounts_table(db_data, &[ufvk]).unwrap(); - ( - extfvk, - tkey.map(|k| k.to_external_pubkey(0).unwrap().to_address()), - ) + { + let ufvk = UnifiedFullViewingKey::new(AccountId(0), Some(extfvk.clone())).unwrap(); + init_accounts_table(db_data, &[ufvk]).unwrap(); + (extfvk, None) + } } /// Create a fake CompactBlock at the given height, containing a single output paying diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 25e7e4d2e..4623ab69d 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -285,7 +285,7 @@ pub fn init_blocks_table

( mod tests { use tempfile::NamedTempFile; - use zcash_client_backend::keys::{sapling, UnifiedFullViewingKey}; + use zcash_client_backend::keys::{sapling, UnifiedFullViewingKey, UnifiedSpendingKey}; #[cfg(feature = "transparent-inputs")] use zcash_client_backend::keys::transparent; @@ -322,15 +322,19 @@ mod tests { let extfvk = ExtendedFullViewingKey::from(&extsk); #[cfg(feature = "transparent-inputs")] - let tkey = Some( - transparent::AccountPrivKey::from_seed(&network(), &seed, account) - .unwrap() - .to_account_pubkey(), - ); - #[cfg(not(feature = "transparent-inputs"))] - let tkey = None; + let ufvk = UnifiedFullViewingKey::new( + account, + Some( + transparent::AccountPrivKey::from_seed(&network(), &seed, account) + .unwrap() + .to_account_pubkey(), + ), + Some(extfvk), + ) + .unwrap(); - let ufvk = UnifiedFullViewingKey::new(account, tkey, Some(extfvk)).unwrap(); + #[cfg(not(feature = "transparent-inputs"))] + let ufvk = UnifiedFullViewingKey::new(account, Some(extfvk)).unwrap(); init_accounts_table(&db_data, &[ufvk.clone()]).unwrap(); @@ -375,13 +379,14 @@ mod tests { let seed = [0u8; 32]; // Add an account to the wallet - let extsk = sapling::spending_key(&seed, network().coin_type(), AccountId(0)); - let extfvk = ExtendedFullViewingKey::from(&extsk); - let ufvk = UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk)).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); } } diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index c63b78330..28bea0e3a 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -225,8 +225,8 @@ mod tests { }; #[cfg(not(feature = "transparent-inputs"))] let ufvks = [ - UnifiedFullViewingKey::new(AccountId(0), None, Some(extfvk0)).unwrap(), - UnifiedFullViewingKey::new(AccountId(1), None, Some(extfvk1)).unwrap(), + UnifiedFullViewingKey::new(AccountId(0), Some(extfvk0)).unwrap(), + UnifiedFullViewingKey::new(AccountId(1), Some(extfvk1)).unwrap(), ]; init_accounts_table(&db_data, &ufvks).unwrap(); @@ -276,7 +276,11 @@ mod tests { // Add an account to the wallet 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(); @@ -316,7 +320,10 @@ mod tests { // Add an account to the wallet 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(); @@ -358,7 +365,10 @@ mod tests { // Add an account to the wallet 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.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 @@ -498,7 +508,10 @@ mod tests { // Add an account to the wallet 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.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 @@ -624,7 +637,10 @@ mod tests { // Add an account to the wallet 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.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 @@ -731,7 +747,10 @@ mod tests { // Add an account to the wallet 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.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 From 15eb5aab503190d3dae4cc62c76cbdc5e8699ab1 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 21 Jan 2022 19:08:26 -0700 Subject: [PATCH 43/79] Fix a minor naming error in AccountPubKey --- zcash_client_backend/src/keys.rs | 2 +- zcash_client_sqlite/src/lib.rs | 2 +- zcash_client_sqlite/src/wallet/init.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index 3c5741d87..69d77a8e8 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -109,7 +109,7 @@ pub mod transparent { impl AccountPubKey { /// Derive BIP-44 public key at the external child path /// `m/44'/'/'/0/ - pub fn to_external_pubkey( + pub fn derive_external_pubkey( &self, child_index: u32, ) -> Result { diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 3c2240823..3ffadac1d 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -771,7 +771,7 @@ mod tests { init_accounts_table(db_data, &[ufvk]).unwrap(); ( extfvk, - tkey.map(|k| k.to_external_pubkey(0).unwrap().to_address()), + tkey.map(|k| k.derive_external_pubkey(0).unwrap().to_address()), ) } diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 4623ab69d..fc6f53833 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -205,7 +205,7 @@ pub fn init_accounts_table( .map(|extfvk| address_from_extfvk(&wdb.params, extfvk)); #[cfg(feature = "transparent-inputs")] let taddress_str: Option = key.transparent().and_then(|k| { - k.to_external_pubkey(0) + k.derive_external_pubkey(0) .ok() .map(|k| k.to_address().encode(&wdb.params)) }); From 1f9b9fc1475437dd61190ab9ba9986f14e2f4aa8 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 13 Jan 2022 13:00:29 +0800 Subject: [PATCH 44/79] zcash_primitives: Do not gate secp256k1 on transparent-inputs feature flag. --- zcash_primitives/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index bcdd65436..885094a85 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -39,7 +39,7 @@ proptest = { version = "1.0.0", optional = true } rand = "0.8" rand_core = "0.6" ripemd160 = { version = "0.9", optional = true } -secp256k1 = { version = "0.20", optional = true } +secp256k1 = "0.20" sha2 = "0.9" subtle = "2.2.3" zcash_encoding = { version = "0.0", path = "../components/zcash_encoding" } @@ -59,7 +59,7 @@ orchard = { version = "=0.1.0-beta.1", features = ["test-dependencies"] } pprof = { version = "=0.6.1", features = ["criterion", "flamegraph"] } [features] -transparent-inputs = ["ripemd160", "secp256k1"] +transparent-inputs = ["ripemd160"] test-dependencies = ["proptest", "orchard/test-dependencies"] zfuture = [] From a4c9f53a3a0b234b3d29603ce516d179c80f48fe Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Fri, 21 Jan 2022 16:27:17 +0800 Subject: [PATCH 45/79] Move ExternalPrivKey, ExternalPubKey to zcash_primitives. --- zcash_client_backend/src/data_api/wallet.rs | 4 +- zcash_client_backend/src/keys.rs | 50 +++------------------ zcash_client_backend/src/wallet.rs | 6 +++ zcash_primitives/Cargo.toml | 1 + zcash_primitives/src/lib.rs | 1 + zcash_primitives/src/transparent.rs | 45 +++++++++++++++++++ 6 files changed, 59 insertions(+), 48 deletions(-) create mode 100644 zcash_primitives/src/transparent.rs diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index d2b0a42ea..1b11ccea7 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -8,6 +8,7 @@ use zcash_primitives::{ components::{amount::DEFAULT_FEE, Amount}, Transaction, }, + transparent, zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, }; @@ -21,9 +22,6 @@ use crate::{ zip321::{Payment, TransactionRequest}, }; -#[cfg(feature = "transparent-inputs")] -use crate::keys::transparent; - /// Scans a [`Transaction`] for any information that can be decrypted by the accounts in /// the wallet, and saves it to the wallet. pub fn decrypt_and_store_transaction( diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index 69d77a8e8..b83b9813f 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -48,10 +48,12 @@ pub mod transparent { use bs58::{self, decode::Error as Bs58Error}; use hdwallet::{ExtendedPrivKey, ExtendedPubKey, KeyIndex}; use secp256k1::key::SecretKey; - use sha2::{Digest, Sha256}; use crate::wallet::AccountId; - use zcash_primitives::{consensus, legacy::TransparentAddress}; + use zcash_primitives::{ + consensus, + transparent::{ExternalPrivKey, ExternalPubKey}, + }; /// A type representing a BIP-44 private key at the account path level /// `m/44'/'/' @@ -128,48 +130,6 @@ pub mod transparent { } } - /// A type representing a private key at the BIP-44 external child - /// level `m/44'/'/'/0/ - #[derive(Clone, Debug)] - pub struct ExternalPrivKey(ExtendedPrivKey); - - impl ExternalPrivKey { - /// Returns the external public key corresponding to this private key - pub fn to_external_pubkey(&self) -> ExternalPubKey { - ExternalPubKey(ExtendedPubKey::from_private_key(&self.0)) - } - - /// Extracts the secp256k1 secret key component - pub fn secret_key(&self) -> &secp256k1::key::SecretKey { - &self.0.private_key - } - } - - pub(crate) fn pubkey_to_address(pubkey: &secp256k1::key::PublicKey) -> TransparentAddress { - let mut hash160 = ripemd::Ripemd160::new(); - hash160.update(Sha256::digest(pubkey.serialize())); - TransparentAddress::PublicKey(*hash160.finalize().as_ref()) - } - - /// A type representing a public key at the BIP-44 external child - /// level `m/44'/'/'/0/ - #[derive(Clone, Debug)] - pub struct ExternalPubKey(ExtendedPubKey); - - impl ExternalPubKey { - /// Returns the transparent address corresponding to - /// this public key. - pub fn to_address(&self) -> TransparentAddress { - pubkey_to_address(&self.0.public_key) - } - - /// Returns the secp256k1::key::PublicKey component of - /// this public key. - pub fn public_key(&self) -> &secp256k1::key::PublicKey { - &self.0.public_key - } - } - /// Wallet Import Format encoded transparent private key. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Wif(pub String); @@ -383,7 +343,7 @@ mod tests { let sk: SecretKey = (&sk_wif).to_secret_key(&MAIN_NETWORK).expect("invalid wif"); let secp = secp256k1::Secp256k1::new(); let pubkey = secp256k1::key::PublicKey::from_secret_key(&secp, &sk); - let taddr = transparent::pubkey_to_address(&pubkey).encode(&MAIN_NETWORK); + let taddr = zcash_primitives::transparent::pubkey_to_address(&pubkey).encode(&MAIN_NETWORK); assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string()); } diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index bbc383802..12b54d840 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -23,6 +23,12 @@ use zcash_primitives::{ #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct AccountId(pub u32); +impl From for AccountId { + fn from(id: u32) -> Self { + Self(id) + } +} + impl Default for AccountId { fn default() -> Self { AccountId(0) diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index 885094a85..df510c128 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -28,6 +28,7 @@ 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" diff --git a/zcash_primitives/src/lib.rs b/zcash_primitives/src/lib.rs index fde323e6a..f29970316 100644 --- a/zcash_primitives/src/lib.rs +++ b/zcash_primitives/src/lib.rs @@ -17,6 +17,7 @@ pub mod memo; pub mod merkle_tree; pub mod sapling; pub mod transaction; +pub mod transparent; pub mod zip32; pub mod zip339; diff --git a/zcash_primitives/src/transparent.rs b/zcash_primitives/src/transparent.rs new file mode 100644 index 000000000..4e03b9fc7 --- /dev/null +++ b/zcash_primitives/src/transparent.rs @@ -0,0 +1,45 @@ +use crate::legacy::TransparentAddress; +use hdwallet::{ExtendedPrivKey, ExtendedPubKey}; +use sha2::{Digest, Sha256}; + +/// A type representing a private key at the BIP-44 external child +/// level `m/44'/'/'/0/ +#[derive(Clone, Debug)] +pub struct ExternalPrivKey(pub ExtendedPrivKey); + +impl ExternalPrivKey { + /// Returns the external public key corresponding to this private key + pub fn to_external_pubkey(&self) -> ExternalPubKey { + ExternalPubKey(ExtendedPubKey::from_private_key(&self.0)) + } + + /// Extracts the secp256k1 secret key component + pub fn secret_key(&self) -> &secp256k1::key::SecretKey { + &self.0.private_key + } +} + +pub fn pubkey_to_address(pubkey: &secp256k1::key::PublicKey) -> TransparentAddress { + let mut hash160 = ripemd160::Ripemd160::new(); + hash160.update(Sha256::digest(&pubkey.serialize())); + TransparentAddress::PublicKey(*hash160.finalize().as_ref()) +} + +/// A type representing a public key at the BIP-44 external child +/// level `m/44'/'/'/0/ +#[derive(Clone, Debug)] +pub struct ExternalPubKey(pub ExtendedPubKey); + +impl ExternalPubKey { + /// Returns the transparent address corresponding to + /// this public key. + pub fn to_address(&self) -> TransparentAddress { + pubkey_to_address(&self.0.public_key) + } + + /// Returns the secp256k1::key::PublicKey component of + /// this public key. + pub fn public_key(&self) -> &secp256k1::key::PublicKey { + &self.0.public_key + } +} From 5033d29d2f46adb70e1c1eddad843981534d4155 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 11 Jan 2022 08:52:28 +0800 Subject: [PATCH 46/79] zip316::transparent: Implement ZIP 316 transparent internal ovk. --- zcash_primitives/src/transparent.rs | 53 ++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/zcash_primitives/src/transparent.rs b/zcash_primitives/src/transparent.rs index 4e03b9fc7..21551008b 100644 --- a/zcash_primitives/src/transparent.rs +++ b/zcash_primitives/src/transparent.rs @@ -1,6 +1,7 @@ -use crate::legacy::TransparentAddress; +use crate::{legacy::TransparentAddress, sapling::keys::prf_expand_vec}; use hdwallet::{ExtendedPrivKey, ExtendedPubKey}; use sha2::{Digest, Sha256}; +use std::convert::TryInto; /// A type representing a private key at the BIP-44 external child /// level `m/44'/'/'/0/ @@ -42,4 +43,54 @@ impl ExternalPubKey { pub fn public_key(&self) -> &secp256k1::key::PublicKey { &self.0.public_key } + + /// Returns the chain code component of this public key. + pub fn chain_code(&self) -> &[u8] { + &self.0.chain_code + } + + /// 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 + fn ovk_for_shielding(&self) -> (InternalOvk, ExternalOvk) { + let i_ovk = prf_expand_vec( + &self.chain_code(), + &[&[0xd0], &self.public_key().serialize()], + ); + let i_ovk = i_ovk.as_bytes(); + let ovk_internal = InternalOvk(i_ovk[..32].try_into().unwrap()); + let ovk_external = ExternalOvk(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.ovk_for_shielding().0 + } + + /// Derives the external ovk corresponding to this transparent fvk. + pub fn external_ovk(&self) -> ExternalOvk { + self.ovk_for_shielding().1 + } +} + +/// 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 + } } From c9fe8402e2c34a5407395764e6ff8f310dd8fcf1 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 11 Jan 2022 19:28:16 +0800 Subject: [PATCH 47/79] Use transparent internal ovk in shield_transparent_funds(). --- zcash_client_backend/src/data_api/wallet.rs | 7 ++++--- zcash_primitives/src/zip32.rs | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 1b11ccea7..7a8816d5a 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use zcash_primitives::{ consensus::{self, NetworkUpgrade}, memo::MemoBytes, - sapling::prover::TxProver, + sapling::{keys::OutgoingViewingKey, prover::TxProver}, transaction::{ builder::Builder, components::{amount::DEFAULT_FEE, Amount}, @@ -398,13 +398,14 @@ where .and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?; // derive the t-address for the extpubkey at child index 0 - let taddr = sk.to_external_pubkey().to_address(); + let t_ext_pubkey = sk.to_external_pubkey(); + let taddr = t_ext_pubkey.to_address(); + let ovk = OutgoingViewingKey(t_ext_pubkey.internal_ovk().as_bytes()); // derive own shielded address from the provided extended spending key // TODO: this should become the internal change address derived from // the wallet's UFVK let z_address = extfvk.default_address().1; - let ovk = extfvk.fvk.ovk; // get UTXOs from DB let utxos = wallet_db.get_unspent_transparent_outputs(&taddr, latest_anchor)?; diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index c83263fdd..46bdd02fe 100644 --- a/zcash_primitives/src/zip32.rs +++ b/zcash_primitives/src/zip32.rs @@ -513,6 +513,11 @@ impl ExtendedFullViewingKey { pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) { sapling_default_address(&self.fvk, &self.dk) } + + /// Returns the chain code. + pub fn chain_code(&self) -> ChainCode { + self.chain_code + } } #[cfg(test)] From 6f776aacc38511979db71fbdf82cf32e43cf30ce Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Fri, 14 Jan 2022 13:01:04 +0800 Subject: [PATCH 48/79] zcash_primitives::zip316::transparent: Parse Ufvk from zcash_address. --- zcash_client_sqlite/Cargo.toml | 1 + zcash_primitives/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/zcash_client_sqlite/Cargo.toml b/zcash_client_sqlite/Cargo.toml index eaf346dff..132ed849c 100644 --- a/zcash_client_sqlite/Cargo.toml +++ b/zcash_client_sqlite/Cargo.toml @@ -23,6 +23,7 @@ rand_core = "0.6" rusqlite = { version = "0.24", features = ["bundled", "time"] } secp256k1 = { version = "0.20" } time = "0.2" +zcash_address = { version = "0.0", path = "../components/zcash_address"} zcash_client_backend = { version = "0.5", path = "../zcash_client_backend"} zcash_primitives = { version = "0.5", path = "../zcash_primitives"} diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index df510c128..6e114da00 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -43,6 +43,7 @@ ripemd160 = { version = "0.9", optional = true } secp256k1 = "0.20" sha2 = "0.9" subtle = "2.2.3" +zcash_address = { version = "0.0", path = "../components/zcash_address" } zcash_encoding = { version = "0.0", path = "../components/zcash_encoding" } [dependencies.zcash_note_encryption] From a7ea5f0bc161b31d73d7e27726632f73348a52b8 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Fri, 21 Jan 2022 12:25:28 +0800 Subject: [PATCH 49/79] Implement TryFrom<&[u8] for ExternalPubKey. --- zcash_primitives/src/transparent.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/zcash_primitives/src/transparent.rs b/zcash_primitives/src/transparent.rs index 21551008b..7771303f8 100644 --- a/zcash_primitives/src/transparent.rs +++ b/zcash_primitives/src/transparent.rs @@ -1,5 +1,5 @@ use crate::{legacy::TransparentAddress, sapling::keys::prf_expand_vec}; -use hdwallet::{ExtendedPrivKey, ExtendedPubKey}; +use hdwallet::{traits::Deserialize, ExtendedPrivKey, ExtendedPubKey}; use sha2::{Digest, Sha256}; use std::convert::TryInto; @@ -31,6 +31,15 @@ pub fn pubkey_to_address(pubkey: &secp256k1::key::PublicKey) -> TransparentAddre #[derive(Clone, Debug)] pub struct ExternalPubKey(pub ExtendedPubKey); +impl std::convert::TryFrom<&[u8]> for ExternalPubKey { + type Error = hdwallet::error::Error; + + fn try_from(data: &[u8]) -> Result { + let ext_pub_key = ExtendedPubKey::deserialize(data)?; + Ok(Self(ext_pub_key)) + } +} + impl ExternalPubKey { /// Returns the transparent address corresponding to /// this public key. From 4dac37ffde9b055b27d5a38723a3a822cb7f4cba Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Sat, 22 Jan 2022 22:19:25 -0700 Subject: [PATCH 50/79] Ensure that transparent input functionality is correctly feature-flagged. --- zcash_client_backend/src/data_api/wallet.rs | 6 ++++-- zcash_primitives/Cargo.toml | 4 ++-- zcash_primitives/src/lib.rs | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 7a8816d5a..6bfbdbf62 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -2,16 +2,18 @@ use std::fmt::Debug; use zcash_primitives::{ consensus::{self, NetworkUpgrade}, memo::MemoBytes, - sapling::{keys::OutgoingViewingKey, prover::TxProver}, + sapling::prover::TxProver, transaction::{ builder::Builder, components::{amount::DEFAULT_FEE, Amount}, Transaction, }, - transparent, zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, }; +#[cfg(feature = "transparent-inputs")] +use zcash_primitives::{sapling::keys::OutgoingViewingKey, transparent}; + use crate::{ address::RecipientAddress, data_api::{ diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index 6e114da00..fb4410ee3 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -40,7 +40,7 @@ proptest = { version = "1.0.0", optional = true } rand = "0.8" rand_core = "0.6" ripemd160 = { version = "0.9", optional = true } -secp256k1 = "0.20" +secp256k1 = { version = "0.20", optional = true } sha2 = "0.9" subtle = "2.2.3" zcash_address = { version = "0.0", path = "../components/zcash_address" } @@ -61,7 +61,7 @@ orchard = { version = "=0.1.0-beta.1", features = ["test-dependencies"] } pprof = { version = "=0.6.1", features = ["criterion", "flamegraph"] } [features] -transparent-inputs = ["ripemd160"] +transparent-inputs = ["ripemd160", "hdwallet", "secp256k1"] test-dependencies = ["proptest", "orchard/test-dependencies"] zfuture = [] diff --git a/zcash_primitives/src/lib.rs b/zcash_primitives/src/lib.rs index f29970316..1cf836b73 100644 --- a/zcash_primitives/src/lib.rs +++ b/zcash_primitives/src/lib.rs @@ -17,6 +17,7 @@ pub mod memo; pub mod merkle_tree; pub mod sapling; pub mod transaction; +#[cfg(feature = "transparent-inputs")] pub mod transparent; pub mod zip32; pub mod zip339; From 8b0c1c4ab27ff6bda12c5a37c11733605d509a9d Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 24 Jan 2022 14:41:40 +0800 Subject: [PATCH 51/79] transparent::ExternalPubKey: impl TryFrom for &[u8; 65]. --- zcash_primitives/src/transparent.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zcash_primitives/src/transparent.rs b/zcash_primitives/src/transparent.rs index 7771303f8..686b598c1 100644 --- a/zcash_primitives/src/transparent.rs +++ b/zcash_primitives/src/transparent.rs @@ -31,10 +31,10 @@ pub fn pubkey_to_address(pubkey: &secp256k1::key::PublicKey) -> TransparentAddre #[derive(Clone, Debug)] pub struct ExternalPubKey(pub ExtendedPubKey); -impl std::convert::TryFrom<&[u8]> for ExternalPubKey { +impl std::convert::TryFrom<&[u8; 65]> for ExternalPubKey { type Error = hdwallet::error::Error; - fn try_from(data: &[u8]) -> Result { + fn try_from(data: &[u8; 65]) -> Result { let ext_pub_key = ExtendedPubKey::deserialize(data)?; Ok(Self(ext_pub_key)) } From 72c2e54a7bfca23cc5c8f22173ccc53d98853a05 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 24 Jan 2022 16:16:55 -0700 Subject: [PATCH 52/79] Add explicit serialize and deserialize methods to ExternalPubKey The serialization defined by HDWallet for the fields of ExtendedPubKey is in the opposite field order from what is defined in ZIP 316. --- zcash_primitives/src/transparent.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/zcash_primitives/src/transparent.rs b/zcash_primitives/src/transparent.rs index 686b598c1..a2caa6ca8 100644 --- a/zcash_primitives/src/transparent.rs +++ b/zcash_primitives/src/transparent.rs @@ -1,5 +1,6 @@ use crate::{legacy::TransparentAddress, sapling::keys::prf_expand_vec}; -use hdwallet::{traits::Deserialize, ExtendedPrivKey, ExtendedPubKey}; +use hdwallet::{ExtendedPrivKey, ExtendedPubKey}; +use secp256k1::PublicKey; use sha2::{Digest, Sha256}; use std::convert::TryInto; @@ -35,8 +36,7 @@ impl std::convert::TryFrom<&[u8; 65]> for ExternalPubKey { type Error = hdwallet::error::Error; fn try_from(data: &[u8; 65]) -> Result { - let ext_pub_key = ExtendedPubKey::deserialize(data)?; - Ok(Self(ext_pub_key)) + ExternalPubKey::deserialize(data) } } @@ -83,6 +83,21 @@ impl ExternalPubKey { pub fn external_ovk(&self) -> ExternalOvk { self.ovk_for_shielding().1 } + + pub fn serialize(&self) -> Vec { + 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 { + let chain_code = data[..32].to_vec(); + let public_key = PublicKey::from_slice(&data[32..])?; + Ok(ExternalPubKey(ExtendedPubKey { + public_key, + chain_code, + })) + } } /// Internal ovk used for autoshielding. From f58d191439ae8027a5934f8b24b1d9b9b3b9ef40 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 24 Jan 2022 18:02:25 -0700 Subject: [PATCH 53/79] Move transparent account keys to zcash_primitives. --- zcash_client_backend/src/data_api.rs | 8 +- zcash_client_backend/src/data_api/chain.rs | 4 +- zcash_client_backend/src/data_api/error.rs | 3 +- zcash_client_backend/src/data_api/wallet.rs | 9 +- zcash_client_backend/src/decrypt.rs | 4 +- zcash_client_backend/src/encoding.rs | 4 +- zcash_client_backend/src/keys.rs | 129 ++++-------------- zcash_client_backend/src/wallet.rs | 25 +--- zcash_client_backend/src/welding_rig.rs | 7 +- zcash_client_sqlite/src/lib.rs | 8 +- zcash_client_sqlite/src/wallet.rs | 4 +- zcash_client_sqlite/src/wallet/init.rs | 2 +- zcash_client_sqlite/src/wallet/transact.rs | 5 +- zcash_primitives/Cargo.toml | 3 +- zcash_primitives/src/keys.rs | 20 +++ zcash_primitives/src/legacy.rs | 3 + .../src/{transparent.rs => legacy/keys.rs} | 92 ++++++++++++- zcash_primitives/src/lib.rs | 3 +- zcash_primitives/src/sapling/keys.rs | 20 +-- zcash_primitives/src/zip32.rs | 23 ++++ 20 files changed, 190 insertions(+), 186 deletions(-) create mode 100644 zcash_primitives/src/keys.rs rename zcash_primitives/src/{transparent.rs => legacy/keys.rs} (54%) diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index fe9275537..9d13d642b 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -11,14 +11,14 @@ 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, decrypt::DecryptedOutput, proto::compact_formats::CompactBlock, - wallet::{AccountId, SpendableNote, WalletTx}, + wallet::{SpendableNote, WalletTx}, }; #[cfg(feature = "transparent-inputs")] @@ -307,12 +307,12 @@ pub mod testing { merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{Node, Nullifier, PaymentAddress}, transaction::{components::Amount, Transaction, TxId}, - zip32::ExtendedFullViewingKey, + zip32::{AccountId, ExtendedFullViewingKey}, }; use crate::{ proto::compact_formats::CompactBlock, - wallet::{AccountId, SpendableNote, WalletTransparentOutput}, + wallet::{SpendableNote, WalletTransparentOutput}, }; use super::{ diff --git a/zcash_client_backend/src/data_api/chain.rs b/zcash_client_backend/src/data_api/chain.rs index b70b08705..076912737 100644 --- a/zcash_client_backend/src/data_api/chain.rs +++ b/zcash_client_backend/src/data_api/chain.rs @@ -84,7 +84,7 @@ use zcash_primitives::{ consensus::{self, BlockHeight, NetworkUpgrade}, merkle_tree::CommitmentTree, sapling::Nullifier, - zip32::ExtendedFullViewingKey, + zip32::{AccountId, ExtendedFullViewingKey}, }; use crate::{ @@ -93,7 +93,7 @@ use crate::{ BlockSource, PrunedBlock, WalletWrite, }, proto::compact_formats::CompactBlock, - wallet::{AccountId, WalletTx}, + wallet::WalletTx, welding_rig::scan_block, }; diff --git a/zcash_client_backend/src/data_api/error.rs b/zcash_client_backend/src/data_api/error.rs index af903dd1c..bd1dc9559 100644 --- a/zcash_client_backend/src/data_api/error.rs +++ b/zcash_client_backend/src/data_api/error.rs @@ -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 diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 6bfbdbf62..c92a32f2e 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -8,11 +8,11 @@ use zcash_primitives::{ components::{amount::DEFAULT_FEE, Amount}, Transaction, }, - zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, + zip32::{AccountId, ExtendedFullViewingKey, ExtendedSpendingKey}, }; #[cfg(feature = "transparent-inputs")] -use zcash_primitives::{sapling::keys::OutgoingViewingKey, transparent}; +use zcash_primitives::{legacy::keys as transparent, sapling::keys::OutgoingViewingKey}; use crate::{ address::RecipientAddress, @@ -20,7 +20,7 @@ use crate::{ error::Error, DecryptedTransaction, SentTransaction, SentTransactionOutput, WalletWrite, }, decrypt_transaction, - wallet::{AccountId, OvkPolicy}, + wallet::OvkPolicy, zip321::{Payment, TransactionRequest}, }; @@ -102,12 +102,13 @@ where /// consensus::{self, Network}, /// constants::testnet::COIN_TYPE, /// transaction::{TxId, components::Amount}, +/// zip32::AccountId, /// }; /// use zcash_proofs::prover::LocalTxProver; /// use zcash_client_backend::{ /// keys::sapling, /// data_api::{wallet::create_spend_to_address, error::Error, testing}, -/// wallet::{AccountId, OvkPolicy}, +/// wallet::OvkPolicy, /// }; /// /// # fn main() { diff --git a/zcash_client_backend/src/decrypt.rs b/zcash_client_backend/src/decrypt.rs index 418db6708..2d6c0219f 100644 --- a/zcash_client_backend/src/decrypt.rs +++ b/zcash_client_backend/src/decrypt.rs @@ -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`]. diff --git a/zcash_client_backend/src/encoding.rs b/zcash_client_backend/src/encoding.rs index 203e0df4f..14e552efc 100644 --- a/zcash_client_backend/src/encoding.rs +++ b/zcash_client_backend/src/encoding.rs @@ -100,11 +100,11 @@ impl AddressCodec

for TransparentAddress { /// ``` /// use zcash_primitives::{ /// constants::testnet::{COIN_TYPE, HRP_SAPLING_EXTENDED_SPENDING_KEY}, +/// zip32::AccountId, /// }; /// use zcash_client_backend::{ /// encoding::encode_extended_spending_key, /// keys::sapling, -/// wallet::AccountId, /// }; /// /// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId(0)); @@ -132,11 +132,11 @@ 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::sapling, -/// wallet::AccountId, /// }; /// use zcash_primitives::zip32::ExtendedFullViewingKey; /// diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index b83b9813f..570ac3cf8 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -1,13 +1,13 @@ //! Helper functions for managing light client key material. -use crate::wallet::AccountId; -use zcash_primitives::consensus; +use zcash_primitives::{consensus, zip32::AccountId}; + +#[cfg(feature = "transparent-inputs")] +use zcash_primitives::legacy::keys as legacy; pub mod sapling { - use zcash_primitives::zip32::ChildIndex; + use zcash_primitives::zip32::{AccountId, ChildIndex}; pub use zcash_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; - use crate::wallet::AccountId; - /// Derives the ZIP 32 [`ExtendedSpendingKey`] for a given coin type and account from the /// given seed. /// @@ -18,10 +18,12 @@ pub mod sapling { /// # Examples /// /// ``` - /// use zcash_primitives::{constants::testnet::COIN_TYPE}; + /// use zcash_primitives::{ + /// constants::testnet::COIN_TYPE, + /// zip32::AccountId, + /// }; /// use zcash_client_backend::{ /// keys::sapling, - /// wallet::AccountId, /// }; /// /// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId(0)); @@ -46,89 +48,8 @@ pub mod sapling { #[cfg(feature = "transparent-inputs")] pub mod transparent { use bs58::{self, decode::Error as Bs58Error}; - use hdwallet::{ExtendedPrivKey, ExtendedPubKey, KeyIndex}; - use secp256k1::key::SecretKey; - - use crate::wallet::AccountId; - use zcash_primitives::{ - consensus, - transparent::{ExternalPrivKey, ExternalPubKey}, - }; - - /// A type representing a BIP-44 private key at the account path level - /// `m/44'/'/' - #[derive(Clone, Debug)] - pub struct AccountPrivKey(ExtendedPrivKey); - - impl AccountPrivKey { - /// Perform derivation of the extended private key for the BIP-44 path: - /// `m/44'/'/' - /// - /// This produces the extended private key for the external (non-change) - /// address at the specified index for the provided account. - pub fn from_seed( - params: &P, - seed: &[u8], - account: AccountId, - ) -> Result { - 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 extended_privkey(&self) -> &ExtendedPrivKey { - &self.0 - } - - pub fn to_account_pubkey(&self) -> AccountPubKey { - AccountPubKey(ExtendedPubKey::from_private_key(&self.0)) - } - - /// Derive BIP-44 private key at the external child path - /// `m/44'/'/'/0/ - pub fn derive_external_secret_key( - &self, - child_index: u32, - ) -> Result { - self.0 - .derive_private_key(KeyIndex::Normal(0))? - .derive_private_key(KeyIndex::Normal(child_index)) - .map(ExternalPrivKey) - } - } - - /// A type representing a BIP-44 public key at the account path level - /// `m/44'/'/' - #[derive(Clone, Debug)] - pub struct AccountPubKey(ExtendedPubKey); - - impl AccountPubKey { - /// Derive BIP-44 public key at the external child path - /// `m/44'/'/'/0/ - pub fn derive_external_pubkey( - &self, - child_index: u32, - ) -> Result { - self.0 - .derive_public_key(KeyIndex::Normal(0))? - .derive_public_key(KeyIndex::Normal(child_index)) - .map(ExternalPubKey) - } - - pub fn from_extended_pubkey(extpubkey: ExtendedPubKey) -> Self { - AccountPubKey(extpubkey) - } - - pub fn extended_pubkey(&self) -> &ExtendedPubKey { - &self.0 - } - } + use secp256k1::SecretKey; + use zcash_primitives::consensus; /// Wallet Import Format encoded transparent private key. #[derive(Clone, Debug, Eq, PartialEq)] @@ -196,7 +117,7 @@ pub enum DerivationError { pub struct UnifiedSpendingKey { account: AccountId, #[cfg(feature = "transparent-inputs")] - transparent: transparent::AccountPrivKey, + transparent: legacy::AccountPrivKey, sapling: sapling::ExtendedSpendingKey, } @@ -211,7 +132,7 @@ impl UnifiedSpendingKey { } #[cfg(feature = "transparent-inputs")] - let transparent = transparent::AccountPrivKey::from_seed(params, seed, account) + let transparent = legacy::AccountPrivKey::from_seed(params, seed, account) .map_err(DerivationError::Transparent)?; Ok(UnifiedSpendingKey { @@ -238,7 +159,7 @@ impl UnifiedSpendingKey { /// Returns the transparent component of the unified key at the /// BIP44 path `m/44'/'/'`. #[cfg(feature = "transparent-inputs")] - pub fn transparent(&self) -> &transparent::AccountPrivKey { + pub fn transparent(&self) -> &legacy::AccountPrivKey { &self.transparent } @@ -255,7 +176,7 @@ impl UnifiedSpendingKey { pub struct UnifiedFullViewingKey { account: AccountId, #[cfg(feature = "transparent-inputs")] - transparent: Option, + transparent: Option, sapling: Option, } @@ -263,7 +184,7 @@ 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, + #[cfg(feature = "transparent-inputs")] transparent: Option, sapling: Option, ) -> Option { if sapling.is_none() { @@ -287,7 +208,7 @@ impl UnifiedFullViewingKey { #[cfg(feature = "transparent-inputs")] /// Returns the transparent component of the unified key at the /// BIP44 path `m/44'/'/'`. - pub fn transparent(&self) -> Option<&transparent::AccountPubKey> { + pub fn transparent(&self) -> Option<&legacy::AccountPubKey> { self.transparent.as_ref() } @@ -301,12 +222,14 @@ impl UnifiedFullViewingKey { #[cfg(test)] mod tests { use super::sapling; - use crate::wallet::AccountId; + use zcash_primitives::zip32::AccountId; #[cfg(feature = "transparent-inputs")] use { - super::transparent, crate::encoding::AddressCodec, secp256k1::key::SecretKey, - zcash_primitives::consensus::MAIN_NETWORK, + super::transparent, + crate::encoding::AddressCodec, + secp256k1::key::SecretKey, + zcash_primitives::{consensus::MAIN_NETWORK, legacy}, }; #[cfg(feature = "transparent-inputs")] @@ -324,7 +247,7 @@ mod tests { #[cfg(feature = "transparent-inputs")] #[test] fn sk_to_wif() { - let sk = transparent::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId(0)) + let sk = legacy::keys::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId(0)) .unwrap() .derive_external_secret_key(0) .unwrap(); @@ -343,14 +266,14 @@ mod tests { let sk: SecretKey = (&sk_wif).to_secret_key(&MAIN_NETWORK).expect("invalid wif"); let secp = secp256k1::Secp256k1::new(); let pubkey = secp256k1::key::PublicKey::from_secret_key(&secp, &sk); - let taddr = zcash_primitives::transparent::pubkey_to_address(&pubkey).encode(&MAIN_NETWORK); + let taddr = legacy::keys::pubkey_to_address(&pubkey).encode(&MAIN_NETWORK); assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string()); } #[cfg(feature = "transparent-inputs")] #[test] fn pk_from_seed() { - let pk = transparent::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId(0)) + let pk = legacy::keys::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId(0)) .unwrap() .derive_external_secret_key(0) .unwrap() @@ -365,7 +288,7 @@ mod tests { #[cfg(feature = "transparent-inputs")] #[test] fn pk_to_taddr() { - let pk = transparent::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId(0)) + let pk = legacy::keys::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId(0)) .unwrap() .derive_external_secret_key(0) .unwrap() diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index 12b54d840..1211d999f 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -1,8 +1,6 @@ //! 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::{ merkle_tree::IncrementalWitness, @@ -10,6 +8,7 @@ use zcash_primitives::{ keys::OutgoingViewingKey, Diversifier, Node, Note, Nullifier, PaymentAddress, Rseed, }, transaction::{components::Amount, TxId}, + zip32::AccountId, }; #[cfg(feature = "transparent-inputs")] @@ -19,28 +18,6 @@ use zcash_primitives::{ transaction::components::{OutPoint, TxOut}, }; -/// A type-safe wrapper for account identifiers. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct AccountId(pub u32); - -impl From 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)) - } -} - /// A subset of a [`Transaction`] relevant to wallets and light clients. /// /// [`Transaction`]: zcash_primitives::transaction::Transaction diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index 8eeed5822..fc943ac32 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -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 = { diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 3ffadac1d..9d87d828e 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -45,7 +45,7 @@ use zcash_primitives::{ merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{Node, Nullifier, PaymentAddress}, transaction::{components::Amount, Transaction, TxId}, - zip32::ExtendedFullViewingKey, + zip32::{AccountId, ExtendedFullViewingKey}, }; use zcash_client_backend::{ @@ -55,7 +55,7 @@ use zcash_client_backend::{ }, encoding::encode_payment_address, proto::compact_formats::CompactBlock, - wallet::{AccountId, SpendableNote}, + wallet::SpendableNote, }; use crate::error::SqliteClientError; @@ -707,7 +707,7 @@ mod tests { }; #[cfg(feature = "transparent-inputs")] - use zcash_client_backend::keys::transparent; + use zcash_primitives::legacy; use zcash_primitives::{ block::BlockHash, @@ -762,7 +762,7 @@ mod tests { #[cfg(feature = "transparent-inputs")] { let tkey = Some( - transparent::AccountPrivKey::from_seed(&network(), &seed, AccountId(0)) + legacy::keys::AccountPrivKey::from_seed(&network(), &seed, AccountId(0)) .unwrap() .to_account_pubkey(), ); diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 869e7329f..c75e3c744 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -19,7 +19,7 @@ use zcash_primitives::{ merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{Node, Note, Nullifier, PaymentAddress}, transaction::{components::Amount, Transaction, TxId}, - zip32::ExtendedFullViewingKey, + zip32::{AccountId, ExtendedFullViewingKey}, }; use zcash_client_backend::{ @@ -28,7 +28,7 @@ use zcash_client_backend::{ decode_extended_full_viewing_key, decode_payment_address, encode_extended_full_viewing_key, encode_payment_address_p, encode_transparent_address_p, }, - wallet::{AccountId, WalletShieldedOutput, WalletTx}, + wallet::{WalletShieldedOutput, WalletTx}, DecryptedOutput, }; diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index fc6f53833..e9729dd9f 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -288,7 +288,7 @@ mod tests { use zcash_client_backend::keys::{sapling, UnifiedFullViewingKey, UnifiedSpendingKey}; #[cfg(feature = "transparent-inputs")] - use zcash_client_backend::keys::transparent; + use zcash_primitives::legacy::keys as transparent; use zcash_primitives::{ block::BlockHash, diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index 28bea0e3a..1b1747938 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -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}; @@ -169,7 +170,7 @@ mod tests { }; #[cfg(feature = "transparent-inputs")] - use zcash_client_backend::keys::transparent; + use zcash_primitives::legacy::keys as transparent; use crate::{ chain::init::init_cache_database, diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index fb4410ee3..7f88a595d 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -22,6 +22,7 @@ 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" } @@ -61,7 +62,7 @@ orchard = { version = "=0.1.0-beta.1", features = ["test-dependencies"] } pprof = { version = "=0.6.1", features = ["criterion", "flamegraph"] } [features] -transparent-inputs = ["ripemd160", "hdwallet", "secp256k1"] +transparent-inputs = ["bs58", "hdwallet", "ripemd160", "secp256k1"] test-dependencies = ["proptest", "orchard/test-dependencies"] zfuture = [] diff --git a/zcash_primitives/src/keys.rs b/zcash_primitives/src/keys.rs new file mode 100644 index 000000000..41f097188 --- /dev/null +++ b/zcash_primitives/src/keys.rs @@ -0,0 +1,20 @@ +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() +} diff --git a/zcash_primitives/src/legacy.rs b/zcash_primitives/src/legacy.rs index 67c35d401..ef94fe49a 100644 --- a/zcash_primitives/src/legacy.rs +++ b/zcash_primitives/src/legacy.rs @@ -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 diff --git a/zcash_primitives/src/transparent.rs b/zcash_primitives/src/legacy/keys.rs similarity index 54% rename from zcash_primitives/src/transparent.rs rename to zcash_primitives/src/legacy/keys.rs index a2caa6ca8..665a1a975 100644 --- a/zcash_primitives/src/transparent.rs +++ b/zcash_primitives/src/legacy/keys.rs @@ -1,13 +1,91 @@ -use crate::{legacy::TransparentAddress, sapling::keys::prf_expand_vec}; -use hdwallet::{ExtendedPrivKey, ExtendedPubKey}; +use hdwallet::{ExtendedPrivKey, ExtendedPubKey, KeyIndex}; use secp256k1::PublicKey; use sha2::{Digest, Sha256}; use std::convert::TryInto; +use crate::{consensus, keys::prf_expand_vec, zip32::AccountId}; + +use super::TransparentAddress; + +/// A type representing a BIP-44 private key at the account path level +/// `m/44'/'/' +#[derive(Clone, Debug)] +pub struct AccountPrivKey(ExtendedPrivKey); + +impl AccountPrivKey { + /// Perform derivation of the extended private key for the BIP-44 path: + /// `m/44'/'/' + /// + /// This produces the extended private key for the external (non-change) + /// address at the specified index for the provided account. + pub fn from_seed( + params: &P, + seed: &[u8], + account: AccountId, + ) -> Result { + 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 extended_privkey(&self) -> &ExtendedPrivKey { + &self.0 + } + + pub fn to_account_pubkey(&self) -> AccountPubKey { + AccountPubKey(ExtendedPubKey::from_private_key(&self.0)) + } + + /// Derive BIP-44 private key at the external child path + /// `m/44'/'/'/0/ + pub fn derive_external_secret_key( + &self, + child_index: u32, + ) -> Result { + self.0 + .derive_private_key(KeyIndex::Normal(0))? + .derive_private_key(KeyIndex::Normal(child_index)) + .map(ExternalPrivKey) + } +} + +/// A type representing a BIP-44 public key at the account path level +/// `m/44'/'/' +#[derive(Clone, Debug)] +pub struct AccountPubKey(ExtendedPubKey); + +impl AccountPubKey { + /// Derive BIP-44 public key at the external child path + /// `m/44'/'/'/0/ + pub fn derive_external_pubkey( + &self, + child_index: u32, + ) -> Result { + self.0 + .derive_public_key(KeyIndex::Normal(0))? + .derive_public_key(KeyIndex::Normal(child_index)) + .map(ExternalPubKey) + } + + pub fn from_extended_pubkey(extpubkey: ExtendedPubKey) -> Self { + AccountPubKey(extpubkey) + } + + pub fn extended_pubkey(&self) -> &ExtendedPubKey { + &self.0 + } +} + /// A type representing a private key at the BIP-44 external child /// level `m/44'/'/'/0/ #[derive(Clone, Debug)] -pub struct ExternalPrivKey(pub ExtendedPrivKey); +pub struct ExternalPrivKey(ExtendedPrivKey); impl ExternalPrivKey { /// Returns the external public key corresponding to this private key @@ -30,7 +108,7 @@ pub fn pubkey_to_address(pubkey: &secp256k1::key::PublicKey) -> TransparentAddre /// A type representing a public key at the BIP-44 external child /// level `m/44'/'/'/0/ #[derive(Clone, Debug)] -pub struct ExternalPubKey(pub ExtendedPubKey); +pub struct ExternalPubKey(ExtendedPubKey); impl std::convert::TryFrom<&[u8; 65]> for ExternalPubKey { type Error = hdwallet::error::Error; @@ -62,7 +140,7 @@ impl ExternalPubKey { /// transparent fvk. As specified in [ZIP 316][transparent-ovk]. /// /// [transparent-ovk]: https://zips.z.cash/zip-0316#deriving-internal-keys - fn ovk_for_shielding(&self) -> (InternalOvk, ExternalOvk) { + pub fn ovks_for_shielding(&self) -> (InternalOvk, ExternalOvk) { let i_ovk = prf_expand_vec( &self.chain_code(), &[&[0xd0], &self.public_key().serialize()], @@ -76,12 +154,12 @@ impl ExternalPubKey { /// Derives the internal ovk corresponding to this transparent fvk. pub fn internal_ovk(&self) -> InternalOvk { - self.ovk_for_shielding().0 + self.ovks_for_shielding().0 } /// Derives the external ovk corresponding to this transparent fvk. pub fn external_ovk(&self) -> ExternalOvk { - self.ovk_for_shielding().1 + self.ovks_for_shielding().1 } pub fn serialize(&self) -> Vec { diff --git a/zcash_primitives/src/lib.rs b/zcash_primitives/src/lib.rs index 1cf836b73..5d107ecf9 100644 --- a/zcash_primitives/src/lib.rs +++ b/zcash_primitives/src/lib.rs @@ -12,13 +12,12 @@ pub mod block; pub mod consensus; pub mod constants; +pub mod keys; pub mod legacy; pub mod memo; pub mod merkle_tree; pub mod sapling; pub mod transaction; -#[cfg(feature = "transparent-inputs")] -pub mod transparent; pub mod zip32; pub mod zip339; diff --git a/zcash_primitives/src/sapling/keys.rs b/zcash_primitives/src/sapling/keys.rs index eec54a32b..5dda4e74c 100644 --- a/zcash_primitives/src/sapling/keys.rs +++ b/zcash_primitives/src/sapling/keys.rs @@ -8,30 +8,12 @@ use crate::{ constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, 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() -} +pub use crate::keys::{prf_expand, prf_expand_vec}; /// An outgoing viewing key #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index 4602fb28f..785c99de2 100644 --- a/zcash_primitives/src/zip32.rs +++ b/zcash_primitives/src/zip32.rs @@ -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}, @@ -23,6 +24,28 @@ 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 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 { From 0b435352033575595ab77617692e3da62c06ad1c Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 24 Jan 2022 18:09:19 -0700 Subject: [PATCH 54/79] Move OutgoingViewingKey to zcash_primitives::keys --- zcash_client_backend/src/data_api/wallet.rs | 2 +- zcash_primitives/src/keys.rs | 4 ++++ zcash_primitives/src/sapling/keys.rs | 6 +----- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index c92a32f2e..412868915 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -12,7 +12,7 @@ use zcash_primitives::{ }; #[cfg(feature = "transparent-inputs")] -use zcash_primitives::{legacy::keys as transparent, sapling::keys::OutgoingViewingKey}; +use zcash_primitives::{keys::OutgoingViewingKey, legacy::keys as transparent}; use crate::{ address::RecipientAddress, diff --git a/zcash_primitives/src/keys.rs b/zcash_primitives/src/keys.rs index 41f097188..319134f21 100644 --- a/zcash_primitives/src/keys.rs +++ b/zcash_primitives/src/keys.rs @@ -18,3 +18,7 @@ pub fn prf_expand_vec(sk: &[u8], ts: &[&[u8]]) -> Blake2bHash { } h.finalize() } + +/// An outgoing viewing key +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct OutgoingViewingKey(pub [u8; 32]); diff --git a/zcash_primitives/src/sapling/keys.rs b/zcash_primitives/src/sapling/keys.rs index 5dda4e74c..c97c15af3 100644 --- a/zcash_primitives/src/sapling/keys.rs +++ b/zcash_primitives/src/sapling/keys.rs @@ -13,11 +13,7 @@ use group::{Group, GroupEncoding}; use std::io::{self, Read, Write}; use subtle::CtOption; -pub use crate::keys::{prf_expand, prf_expand_vec}; - -/// An outgoing viewing key -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct OutgoingViewingKey(pub [u8; 32]); +pub use crate::keys::{prf_expand, prf_expand_vec, OutgoingViewingKey}; /// A Sapling expanded spending key #[derive(Clone)] From 47fc12704bfc474a2b13e00fde428218bd3f0d9c Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 25 Jan 2022 09:21:42 -0700 Subject: [PATCH 55/79] Fix doctest compilation. --- zcash_client_sqlite/src/wallet.rs | 14 +++++++++----- zcash_client_sqlite/src/wallet/init.rs | 6 ++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index c75e3c744..87fe423d8 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -117,8 +117,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, @@ -213,8 +213,10 @@ pub fn is_valid_account_extfvk( /// /// ``` /// 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, @@ -249,8 +251,10 @@ pub fn get_balance

(wdb: &WalletDb

, account: AccountId) -> Result(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { /// # Examples /// /// ``` +/// # #[cfg(feature = "transparent-inputs")] +/// # { /// use tempfile::NamedTempFile; /// /// use zcash_primitives::{ /// consensus::{Network, Parameters}, -/// zip32::{ExtendedFullViewingKey, ExtendedSpendingKey} +/// zip32::{AccountId, ExtendedFullViewingKey, ExtendedSpendingKey} /// }; /// /// use zcash_client_backend::{ @@ -158,7 +160,6 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { /// sapling, /// UnifiedFullViewingKey /// }, -/// wallet::AccountId, /// }; /// /// use zcash_client_sqlite::{ @@ -176,6 +177,7 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> Result<(), rusqlite::Error> { /// 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 From 6fcdfda69ec5935eb405bdf8180bf327a1e022fe Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 26 Jan 2022 13:24:56 -0700 Subject: [PATCH 56/79] Derive OVKs from transparent account-level key, not child keys. This also renames a number of legacy key types to better reflect their intended use. --- zcash_client_backend/src/data_api/wallet.rs | 18 +- zcash_client_backend/src/keys.rs | 29 +-- zcash_client_sqlite/src/lib.rs | 4 +- zcash_client_sqlite/src/wallet/init.rs | 9 +- zcash_primitives/src/legacy/keys.rs | 213 ++++++++++++-------- 5 files changed, 155 insertions(+), 118 deletions(-) diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 412868915..ab9ad9e8f 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -12,7 +12,9 @@ use zcash_primitives::{ }; #[cfg(feature = "transparent-inputs")] -use zcash_primitives::{keys::OutgoingViewingKey, legacy::keys as transparent}; +use zcash_primitives::{ + keys::OutgoingViewingKey, legacy::keys as transparent, legacy::keys::IncomingViewingKey, +}; use crate::{ address::RecipientAddress, @@ -378,7 +380,7 @@ pub fn shield_transparent_funds( wallet_db: &mut D, params: &P, prover: impl TxProver, - sk: &transparent::ExternalPrivKey, + sk: &transparent::AccountPrivKey, extfvk: &ExtendedFullViewingKey, account: AccountId, memo: &MemoBytes, @@ -401,9 +403,8 @@ where .and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?; // derive the t-address for the extpubkey at child index 0 - let t_ext_pubkey = sk.to_external_pubkey(); - let taddr = t_ext_pubkey.to_address(); - let ovk = OutgoingViewingKey(t_ext_pubkey.internal_ovk().as_bytes()); + let account_pubkey = sk.to_account_pubkey(); + let ovk = OutgoingViewingKey(account_pubkey.internal_ovk().as_bytes()); // derive own shielded address from the provided extended spending key // TODO: this should become the internal change address derived from @@ -411,6 +412,10 @@ where let z_address = extfvk.default_address().1; // get UTXOs from DB + let (taddr, child_index) = account_pubkey + .derive_external_ivk() + .unwrap() + .default_address(); let utxos = wallet_db.get_unspent_transparent_outputs(&taddr, latest_anchor)?; let total_amount = utxos .iter() @@ -427,9 +432,10 @@ where 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(*sk.secret_key(), utxo.outpoint.clone(), utxo.txout.clone()) + .add_transparent_input(secret_key, utxo.outpoint.clone(), utxo.txout.clone()) .map_err(Error::Builder)?; } diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index 570ac3cf8..bccff28c4 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -229,7 +229,7 @@ mod tests { super::transparent, crate::encoding::AddressCodec, secp256k1::key::SecretKey, - zcash_primitives::{consensus::MAIN_NETWORK, legacy}, + zcash_primitives::{consensus::MAIN_NETWORK, legacy, legacy::keys::IncomingViewingKey}, }; #[cfg(feature = "transparent-inputs")] @@ -251,7 +251,7 @@ mod tests { .unwrap() .derive_external_secret_key(0) .unwrap(); - let wif = transparent::Wif::from_secret_key(&MAIN_NETWORK, &sk.secret_key(), true).0; + let wif = transparent::Wif::from_secret_key(&MAIN_NETWORK, &sk, true).0; assert_eq!( wif, "L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2".to_string() @@ -270,30 +270,17 @@ mod tests { assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string()); } - #[cfg(feature = "transparent-inputs")] - #[test] - fn pk_from_seed() { - let pk = legacy::keys::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId(0)) - .unwrap() - .derive_external_secret_key(0) - .unwrap() - .to_external_pubkey(); - let hex_value = hex::encode(&pk.public_key().serialize()); - assert_eq!( - hex_value, - "03b1d7fb28d17c125b504d06b1530097e0a3c76ada184237e3bc0925041230a5af".to_string() - ); - } - #[cfg(feature = "transparent-inputs")] #[test] fn pk_to_taddr() { - let pk = legacy::keys::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId(0)) + let taddr = legacy::keys::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId(0)) .unwrap() - .derive_external_secret_key(0) + .to_account_pubkey() + .derive_external_ivk() .unwrap() - .to_external_pubkey(); - let taddr = pk.to_address().encode(&MAIN_NETWORK); + .derive_address(0) + .unwrap() + .encode(&MAIN_NETWORK); assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string()); } } diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 9d87d828e..73b9f657f 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -707,7 +707,7 @@ mod tests { }; #[cfg(feature = "transparent-inputs")] - use zcash_primitives::legacy; + use zcash_primitives::{legacy, legacy::keys::IncomingViewingKey}; use zcash_primitives::{ block::BlockHash, @@ -771,7 +771,7 @@ mod tests { init_accounts_table(db_data, &[ufvk]).unwrap(); ( extfvk, - tkey.map(|k| k.derive_external_pubkey(0).unwrap().to_address()), + tkey.map(|k| k.derive_external_ivk().unwrap().default_address().0), ) } diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 970f58196..41714b55e 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -14,7 +14,10 @@ use zcash_client_backend::{ use crate::{address_from_extfvk, error::SqliteClientError, WalletDb}; #[cfg(feature = "transparent-inputs")] -use zcash_client_backend::encoding::AddressCodec; +use { + zcash_client_backend::encoding::AddressCodec, + zcash_primitives::legacy::keys::IncomingViewingKey, +}; /// Sets up the internal structure of the data database. /// @@ -207,9 +210,9 @@ pub fn init_accounts_table( .map(|extfvk| address_from_extfvk(&wdb.params, extfvk)); #[cfg(feature = "transparent-inputs")] let taddress_str: Option = key.transparent().and_then(|k| { - k.derive_external_pubkey(0) + k.derive_external_ivk() .ok() - .map(|k| k.to_address().encode(&wdb.params)) + .map(|k| k.default_address().0.encode(&wdb.params)) }); #[cfg(not(feature = "transparent-inputs"))] let taddress_str: Option = None; diff --git a/zcash_primitives/src/legacy/keys.rs b/zcash_primitives/src/legacy/keys.rs index 665a1a975..990952f3f 100644 --- a/zcash_primitives/src/legacy/keys.rs +++ b/zcash_primitives/src/legacy/keys.rs @@ -7,17 +7,19 @@ 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'/'/' #[derive(Clone, Debug)] pub struct AccountPrivKey(ExtendedPrivKey); impl AccountPrivKey { - /// Perform derivation of the extended private key for the BIP-44 path: - /// `m/44'/'/' + /// Performs derivation of the extended private key for the BIP-44 path: + /// `m/44'/'/'`. /// - /// This produces the extended private key for the external (non-change) - /// address at the specified index for the provided 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( params: &P, seed: &[u8], @@ -42,98 +44,54 @@ impl AccountPrivKey { AccountPubKey(ExtendedPubKey::from_private_key(&self.0)) } - /// Derive BIP-44 private key at the external child path - /// `m/44'/'/'/0/ + /// Derives the BIP-44 private spending key for the external (incoming payment) child path + /// `m/44'/'/'/0/`. pub fn derive_external_secret_key( &self, child_index: u32, - ) -> Result { + ) -> Result { self.0 .derive_private_key(KeyIndex::Normal(0))? .derive_private_key(KeyIndex::Normal(child_index)) - .map(ExternalPrivKey) + .map(|k| k.private_key) + } + + /// Derives the BIP-44 private spending key for the internal (change) child path + /// `m/44'/'/'/1/`. + pub fn derive_internal_secret_key( + &self, + child_index: u32, + ) -> Result { + 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'/'/' +/// `m/44'/'/'`. +/// +/// This provides the necessary derivation capability for the for +/// the transparent component of a unified full viewing key. #[derive(Clone, Debug)] pub struct AccountPubKey(ExtendedPubKey); impl AccountPubKey { - /// Derive BIP-44 public key at the external child path - /// `m/44'/'/'/0/ - pub fn derive_external_pubkey( - &self, - child_index: u32, - ) -> Result { + /// Derives the BIP-44 public key at the external "change level" path + /// `m/44'/'/'/0`. + pub fn derive_external_ivk(&self) -> Result { self.0 - .derive_public_key(KeyIndex::Normal(0))? - .derive_public_key(KeyIndex::Normal(child_index)) - .map(ExternalPubKey) + .derive_public_key(KeyIndex::Normal(0)) + .map(ExternalIvk) } - pub fn from_extended_pubkey(extpubkey: ExtendedPubKey) -> Self { - AccountPubKey(extpubkey) - } - - pub fn extended_pubkey(&self) -> &ExtendedPubKey { - &self.0 - } -} - -/// A type representing a private key at the BIP-44 external child -/// level `m/44'/'/'/0/ -#[derive(Clone, Debug)] -pub struct ExternalPrivKey(ExtendedPrivKey); - -impl ExternalPrivKey { - /// Returns the external public key corresponding to this private key - pub fn to_external_pubkey(&self) -> ExternalPubKey { - ExternalPubKey(ExtendedPubKey::from_private_key(&self.0)) - } - - /// Extracts the secp256k1 secret key component - pub fn secret_key(&self) -> &secp256k1::key::SecretKey { - &self.0.private_key - } -} - -pub fn pubkey_to_address(pubkey: &secp256k1::key::PublicKey) -> TransparentAddress { - let mut hash160 = ripemd160::Ripemd160::new(); - hash160.update(Sha256::digest(&pubkey.serialize())); - TransparentAddress::PublicKey(*hash160.finalize().as_ref()) -} - -/// A type representing a public key at the BIP-44 external child -/// level `m/44'/'/'/0/ -#[derive(Clone, Debug)] -pub struct ExternalPubKey(ExtendedPubKey); - -impl std::convert::TryFrom<&[u8; 65]> for ExternalPubKey { - type Error = hdwallet::error::Error; - - fn try_from(data: &[u8; 65]) -> Result { - ExternalPubKey::deserialize(data) - } -} - -impl ExternalPubKey { - /// Returns the transparent address corresponding to - /// this public key. - pub fn to_address(&self) -> TransparentAddress { - pubkey_to_address(&self.0.public_key) - } - - /// Returns the secp256k1::key::PublicKey component of - /// this public key. - pub fn public_key(&self) -> &secp256k1::key::PublicKey { - &self.0.public_key - } - - /// Returns the chain code component of this public key. - pub fn chain_code(&self) -> &[u8] { - &self.0.chain_code + /// Derives the BIP-44 public key at the internal "change level" path + /// `m/44'/'/'/1`. + pub fn derive_internal_ivk(&self) -> Result { + self.0 + .derive_public_key(KeyIndex::Normal(1)) + .map(InternalIvk) } /// Derives the internal ovk and external ovk corresponding to this @@ -142,8 +100,8 @@ impl ExternalPubKey { /// [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.chain_code(), - &[&[0xd0], &self.public_key().serialize()], + &self.0.chain_code, + &[&[0xd0], &self.0.public_key.serialize()], ); let i_ovk = i_ovk.as_bytes(); let ovk_internal = InternalOvk(i_ovk[..32].try_into().unwrap()); @@ -161,23 +119,106 @@ impl ExternalPubKey { pub fn external_ovk(&self) -> ExternalOvk { self.ovks_for_shielding().1 } +} - pub fn serialize(&self) -> Vec { - let mut buf = self.0.chain_code.clone(); - buf.extend(self.0.public_key.serialize().to_vec()); +pub fn pubkey_to_address(pubkey: &secp256k1::key::PublicKey) -> TransparentAddress { + let mut hash160 = ripemd160::Ripemd160::new(); + hash160.update(Sha256::digest(&pubkey.serialize())); + TransparentAddress::PublicKey(*hash160.finalize().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. + fn derive_address( + &self, + child_index: u32, + ) -> Result { + 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 { + let extpubkey = self.extended_pubkey(); + let mut buf = extpubkey.chain_code.clone(); + buf.extend(extpubkey.public_key.serialize().to_vec()); buf } - pub fn deserialize(data: &[u8; 65]) -> Result { + fn deserialize(data: &[u8; 65]) -> Result { let chain_code = data[..32].to_vec(); let public_key = PublicKey::from_slice(&data[32..])?; - Ok(ExternalPubKey(ExtendedPubKey { + 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'/'/'/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'/'/'/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]); From 132df78a31e3456b4430e52c92906a5328c12c50 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 26 Jan 2022 14:11:27 -0700 Subject: [PATCH 57/79] Add serialization and deserialization for AccountPubKey --- zcash_primitives/src/legacy/keys.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/zcash_primitives/src/legacy/keys.rs b/zcash_primitives/src/legacy/keys.rs index 990952f3f..3ecd15101 100644 --- a/zcash_primitives/src/legacy/keys.rs +++ b/zcash_primitives/src/legacy/keys.rs @@ -36,10 +36,6 @@ impl AccountPrivKey { AccountPrivKey(extprivkey) } - pub fn extended_privkey(&self) -> &ExtendedPrivKey { - &self.0 - } - pub fn to_account_pubkey(&self) -> AccountPubKey { AccountPubKey(ExtendedPubKey::from_private_key(&self.0)) } @@ -119,8 +115,25 @@ impl AccountPubKey { pub fn external_ovk(&self) -> ExternalOvk { self.ovks_for_shielding().1 } + + pub fn serialize(&self) -> Vec { + 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 { + 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 { let mut hash160 = ripemd160::Ripemd160::new(); hash160.update(Sha256::digest(&pubkey.serialize())); From 3a2017609206a1b0928eb614f180c5fea9fc7312 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 27 Jan 2022 08:54:27 -0700 Subject: [PATCH 58/79] Apply suggestions from code review Co-authored-by: str4d --- components/zcash_note_encryption/src/lib.rs | 2 +- zcash_client_backend/CHANGELOG.md | 4 ++-- zcash_client_backend/src/data_api.rs | 2 +- zcash_client_backend/src/decrypt.rs | 2 +- zcash_client_backend/src/wallet.rs | 3 ++- zcash_client_sqlite/Cargo.toml | 6 +++--- zcash_primitives/Cargo.toml | 1 - zcash_primitives/src/constants/mainnet.rs | 4 +++- zcash_primitives/src/constants/testnet.rs | 4 +++- zcash_primitives/src/sapling.rs | 2 +- zcash_primitives/src/sapling/keys.rs | 2 +- zcash_primitives/src/sapling/note_encryption.rs | 6 ++++-- zcash_primitives/src/transaction/builder.rs | 3 ++- .../src/transaction/components/sapling/builder.rs | 2 +- zcash_primitives/src/zip32.rs | 5 +++-- 15 files changed, 28 insertions(+), 20 deletions(-) diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index 95870338f..ed3edd5c3 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -352,10 +352,10 @@ pub trait ShieldedOutput { /// 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 diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index d2a2f6a75..a28177eae 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -25,8 +25,8 @@ and this library adheres to Rust's notion of to support handling of errors in transparent address decoding. - The `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. -- The `SaplingBuiilder::add_output` method has now similarly been changed to take + `MemoBytes::from(Memo::Empty)` explicitly. +- The `SaplingBuilder::add_output` method has now similarly been changed to take its `MemoBytes` argument as a required field. ## [0.5.0] - 2021-03-26 ### Added diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 9d13d642b..03bb34f40 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -72,7 +72,7 @@ pub trait WalletRead { 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(min_confirmations), diff --git a/zcash_client_backend/src/decrypt.rs b/zcash_client_backend/src/decrypt.rs index 2d6c0219f..16e59ad93 100644 --- a/zcash_client_backend/src/decrypt.rs +++ b/zcash_client_backend/src/decrypt.rs @@ -28,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, } diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index 1211d999f..99b2825eb 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -3,9 +3,10 @@ use zcash_note_encryption::EphemeralKeyBytes; use zcash_primitives::{ + keys::OutgoingViewingKey, merkle_tree::IncrementalWitness, sapling::{ - keys::OutgoingViewingKey, Diversifier, Node, Note, Nullifier, PaymentAddress, Rseed, + Diversifier, Node, Note, Nullifier, PaymentAddress, Rseed, }, transaction::{components::Amount, TxId}, zip32::AccountId, diff --git a/zcash_client_sqlite/Cargo.toml b/zcash_client_sqlite/Cargo.toml index 132ed849c..2f4d513ea 100644 --- a/zcash_client_sqlite/Cargo.toml +++ b/zcash_client_sqlite/Cargo.toml @@ -24,8 +24,8 @@ rusqlite = { version = "0.24", features = ["bundled", "time"] } secp256k1 = { version = "0.20" } time = "0.2" zcash_address = { version = "0.0", path = "../components/zcash_address"} -zcash_client_backend = { version = "0.5", path = "../zcash_client_backend"} -zcash_primitives = { version = "0.5", path = "../zcash_primitives"} +zcash_client_backend = { version = "0.5", path = "../zcash_client_backend" } +zcash_primitives = { version = "0.5", path = "../zcash_primitives" } [dev-dependencies] tempfile = "3" @@ -34,7 +34,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", "zcash_primitives/transparent-inputs"] +transparent-inputs = ["zcash_client_backend/transparent-inputs"] [lib] bench = false diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index 7f88a595d..bd1392a50 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -44,7 +44,6 @@ ripemd160 = { version = "0.9", optional = true } secp256k1 = { version = "0.20", optional = true } sha2 = "0.9" subtle = "2.2.3" -zcash_address = { version = "0.0", path = "../components/zcash_address" } zcash_encoding = { version = "0.0", path = "../components/zcash_encoding" } [dependencies.zcash_note_encryption] diff --git a/zcash_primitives/src/constants/mainnet.rs b/zcash_primitives/src/constants/mainnet.rs index 5b7a3387d..f9b072f30 100644 --- a/zcash_primitives/src/constants/mainnet.rs +++ b/zcash_primitives/src/constants/mainnet.rs @@ -5,9 +5,11 @@ /// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md pub const COIN_TYPE: u32 = 133; -/// The mainnet Wallet Import Format (WIF) lead byte. +/// The mainnet Wallet Import Format ([WIF]) lead byte, as defined by [section 5.6.1.2] of +/// the Zcash protocol spec. /// /// [WIF]: https://en.bitcoin.it/wiki/Wallet_import_format +/// [section 5.6.1.2]: https://zips.z.cash/protocol/protocol.pdf#transparentkeyencoding pub const WIF_LEAD_BYTE: u8 = 0x80; /// The HRP for a Bech32-encoded mainnet [`ExtendedSpendingKey`]. diff --git a/zcash_primitives/src/constants/testnet.rs b/zcash_primitives/src/constants/testnet.rs index ab137a199..daccf21bb 100644 --- a/zcash_primitives/src/constants/testnet.rs +++ b/zcash_primitives/src/constants/testnet.rs @@ -5,9 +5,11 @@ /// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md pub const COIN_TYPE: u32 = 1; -/// The mainnet Wallet Import Format (WIF) lead byte. +/// The mainnet Wallet Import Format ([WIF]) lead byte, as defined by [section 5.6.1.2] of +/// the Zcash protocol spec. /// /// [WIF]: https://en.bitcoin.it/wiki/Wallet_import_format +/// [section 5.6.1.2]: https://zips.z.cash/protocol/protocol.pdf#transparentkeyencoding pub const WIF_LEAD_BYTE: u8 = 0xEF; /// The HRP for a Bech32-encoded testnet [`ExtendedSpendingKey`]. diff --git a/zcash_primitives/src/sapling.rs b/zcash_primitives/src/sapling.rs index 8fa0187c0..b5094c07f 100644 --- a/zcash_primitives/src/sapling.rs +++ b/zcash_primitives/src/sapling.rs @@ -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}, }; diff --git a/zcash_primitives/src/sapling/keys.rs b/zcash_primitives/src/sapling/keys.rs index c97c15af3..7a21ba769 100644 --- a/zcash_primitives/src/sapling/keys.rs +++ b/zcash_primitives/src/sapling/keys.rs @@ -6,6 +6,7 @@ use crate::{ constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, + keys::{prf_expand, OutgoingViewingKey}, sapling::{ProofGenerationKey, ViewingKey}, }; use ff::PrimeField; @@ -13,7 +14,6 @@ use group::{Group, GroupEncoding}; use std::io::{self, Read, Write}; use subtle::CtOption; -pub use crate::keys::{prf_expand, prf_expand_vec, OutgoingViewingKey}; /// A Sapling expanded spending key #[derive(Clone)] diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index 20f4e170b..cba1b7375 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -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,10 +493,11 @@ 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, + Diversifier, PaymentAddress, Rseed, SaplingIvk, ValueCommitment, }, transaction::components::{ diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index 203178cb7..a22903702 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -12,11 +12,12 @@ 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, + prover::TxProver, Diversifier, Node, Note, PaymentAddress, }, transaction::{ components::{ diff --git a/zcash_primitives/src/transaction/components/sapling/builder.rs b/zcash_primitives/src/transaction/components/sapling/builder.rs index 71fea6e22..c56dd0f02 100644 --- a/zcash_primitives/src/transaction/components/sapling/builder.rs +++ b/zcash_primitives/src/transaction/components/sapling/builder.rs @@ -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}, diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index 785c99de2..41ba26cee 100644 --- a/zcash_primitives/src/zip32.rs +++ b/zcash_primitives/src/zip32.rs @@ -16,8 +16,9 @@ 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"; From a1e693d15f21afc191d70112eacd6a2c224f3305 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 27 Jan 2022 09:07:16 -0700 Subject: [PATCH 59/79] Remove the Wif type; it should not be used. We should attempt to avoid passing spending keys back and forth across the FFI entirely, but in any case this is no longer the correct type to use at this boundary; we should use the encoding of the transparent component of a unified spending key instead. --- zcash_client_backend/src/keys.rs | 87 ----------------------- zcash_extensions/src/transparent/demo.rs | 4 -- zcash_primitives/src/consensus.rs | 19 ----- zcash_primitives/src/constants/mainnet.rs | 7 -- zcash_primitives/src/constants/testnet.rs | 7 -- 5 files changed, 124 deletions(-) diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index bccff28c4..b169c148a 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -45,66 +45,6 @@ pub mod sapling { } } -#[cfg(feature = "transparent-inputs")] -pub mod transparent { - use bs58::{self, decode::Error as Bs58Error}; - use secp256k1::SecretKey; - use zcash_primitives::consensus; - - /// Wallet Import Format encoded transparent private key. - #[derive(Clone, Debug, Eq, PartialEq)] - pub struct Wif(pub String); - - /// Errors that may occur in WIF key decoding. - #[derive(Debug)] - pub enum WifError { - Base58(Bs58Error), - InvalidLeadByte(u8), - InvalidTrailingByte(u8), - Secp256k1(secp256k1::Error), - } - - impl Wif { - /// Encode the provided secret key in Wallet Import Format. - pub fn from_secret_key( - params: &P, - sk: &SecretKey, - compressed: bool, - ) -> Self { - let secret_key = sk.as_ref(); - let mut wif = [0u8; 34]; - wif[0] = params.wif_lead_byte(); - wif[1..33].copy_from_slice(secret_key); - if compressed { - wif[33] = 0x01; - Wif(bs58::encode(&wif[..]).with_check().into_string()) - } else { - Wif(bs58::encode(&wif[..]).with_check().into_string()) - } - } - - /// Decode this Wif value to obtain the encoded secret key - pub fn to_secret_key( - &self, - params: &P, - ) -> Result { - bs58::decode(&self.0) - .with_check(None) - .into_vec() - .map_err(WifError::Base58) - .and_then(|decoded| { - if decoded[0] != params.wif_lead_byte() { - Err(WifError::InvalidLeadByte(decoded[0])) - } else if decoded[33] != 0x01 { - Err(WifError::InvalidTrailingByte(decoded[33])) - } else { - SecretKey::from_slice(&decoded[1..33]).map_err(WifError::Secp256k1) - } - }) - } - } -} - #[derive(Debug)] pub enum DerivationError { #[cfg(feature = "transparent-inputs")] @@ -226,7 +166,6 @@ mod tests { #[cfg(feature = "transparent-inputs")] use { - super::transparent, crate::encoding::AddressCodec, secp256k1::key::SecretKey, zcash_primitives::{consensus::MAIN_NETWORK, legacy, legacy::keys::IncomingViewingKey}, @@ -244,32 +183,6 @@ mod tests { let _ = sapling::spending_key(&[0; 31][..], 0, AccountId(0)); } - #[cfg(feature = "transparent-inputs")] - #[test] - fn sk_to_wif() { - let sk = legacy::keys::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId(0)) - .unwrap() - .derive_external_secret_key(0) - .unwrap(); - let wif = transparent::Wif::from_secret_key(&MAIN_NETWORK, &sk, true).0; - assert_eq!( - wif, - "L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2".to_string() - ); - } - - #[cfg(feature = "transparent-inputs")] - #[test] - fn sk_wif_to_taddr() { - let sk_wif = - transparent::Wif("L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2".to_string()); - let sk: SecretKey = (&sk_wif).to_secret_key(&MAIN_NETWORK).expect("invalid wif"); - let secp = secp256k1::Secp256k1::new(); - let pubkey = secp256k1::key::PublicKey::from_secret_key(&secp, &sk); - let taddr = legacy::keys::pubkey_to_address(&pubkey).encode(&MAIN_NETWORK); - assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string()); - } - #[cfg(feature = "transparent-inputs")] #[test] fn pk_to_taddr() { diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index 127e8d268..274e7b5d5 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -543,10 +543,6 @@ mod tests { fn b58_script_address_prefix(&self) -> [u8; 2] { constants::testnet::B58_SCRIPT_ADDRESS_PREFIX } - - fn wif_lead_byte(&self) -> u8 { - constants::testnet::WIF_LEAD_BYTE - } } fn demo_hashes(preimage_1: &[u8; 32], preimage_2: &[u8; 32]) -> ([u8; 32], [u8; 32]) { let hash_2 = { diff --git a/zcash_primitives/src/consensus.rs b/zcash_primitives/src/consensus.rs index c4e0838c1..f66f568bf 100644 --- a/zcash_primitives/src/consensus.rs +++ b/zcash_primitives/src/consensus.rs @@ -179,10 +179,6 @@ pub trait Parameters: Clone { /// /// [`TransparentAddress::Script`]: zcash_primitives::legacy::TransparentAddress::Script fn b58_script_address_prefix(&self) -> [u8; 2]; - - /// Returns the lead byte for the transparent Wallet Interchange Format encoding - /// of secp256k1 secret keys. - fn wif_lead_byte(&self) -> u8; } /// Marker struct for the production network. @@ -228,10 +224,6 @@ impl Parameters for MainNetwork { fn b58_script_address_prefix(&self) -> [u8; 2] { constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX } - - fn wif_lead_byte(&self) -> u8 { - constants::mainnet::WIF_LEAD_BYTE - } } /// Marker struct for the test network. @@ -277,10 +269,6 @@ impl Parameters for TestNetwork { fn b58_script_address_prefix(&self) -> [u8; 2] { constants::testnet::B58_SCRIPT_ADDRESS_PREFIX } - - fn wif_lead_byte(&self) -> u8 { - constants::testnet::WIF_LEAD_BYTE - } } #[derive(PartialEq, Copy, Clone, Debug)] @@ -338,13 +326,6 @@ impl Parameters for Network { Network::TestNetwork => TEST_NETWORK.b58_script_address_prefix(), } } - - fn wif_lead_byte(&self) -> u8 { - match self { - Network::MainNetwork => MAIN_NETWORK.wif_lead_byte(), - Network::TestNetwork => TEST_NETWORK.wif_lead_byte(), - } - } } /// An event that occurs at a specified height on the Zcash chain, at which point the diff --git a/zcash_primitives/src/constants/mainnet.rs b/zcash_primitives/src/constants/mainnet.rs index f9b072f30..bd0e473f4 100644 --- a/zcash_primitives/src/constants/mainnet.rs +++ b/zcash_primitives/src/constants/mainnet.rs @@ -5,13 +5,6 @@ /// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md pub const COIN_TYPE: u32 = 133; -/// The mainnet Wallet Import Format ([WIF]) lead byte, as defined by [section 5.6.1.2] of -/// the Zcash protocol spec. -/// -/// [WIF]: https://en.bitcoin.it/wiki/Wallet_import_format -/// [section 5.6.1.2]: https://zips.z.cash/protocol/protocol.pdf#transparentkeyencoding -pub const WIF_LEAD_BYTE: u8 = 0x80; - /// The HRP for a Bech32-encoded mainnet [`ExtendedSpendingKey`]. /// /// Defined in [ZIP 32]. diff --git a/zcash_primitives/src/constants/testnet.rs b/zcash_primitives/src/constants/testnet.rs index daccf21bb..d11c0e983 100644 --- a/zcash_primitives/src/constants/testnet.rs +++ b/zcash_primitives/src/constants/testnet.rs @@ -5,13 +5,6 @@ /// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md pub const COIN_TYPE: u32 = 1; -/// The mainnet Wallet Import Format ([WIF]) lead byte, as defined by [section 5.6.1.2] of -/// the Zcash protocol spec. -/// -/// [WIF]: https://en.bitcoin.it/wiki/Wallet_import_format -/// [section 5.6.1.2]: https://zips.z.cash/protocol/protocol.pdf#transparentkeyencoding -pub const WIF_LEAD_BYTE: u8 = 0xEF; - /// The HRP for a Bech32-encoded testnet [`ExtendedSpendingKey`]. /// /// Defined in [ZIP 32]. From 544c4ed6bb8a5874b482d5f97ffa659c13012c90 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 27 Jan 2022 09:10:45 -0700 Subject: [PATCH 60/79] Remove unused zcash_address dependency. --- zcash_client_sqlite/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/zcash_client_sqlite/Cargo.toml b/zcash_client_sqlite/Cargo.toml index 2f4d513ea..41494bf06 100644 --- a/zcash_client_sqlite/Cargo.toml +++ b/zcash_client_sqlite/Cargo.toml @@ -23,7 +23,6 @@ rand_core = "0.6" rusqlite = { version = "0.24", features = ["bundled", "time"] } secp256k1 = { version = "0.20" } time = "0.2" -zcash_address = { version = "0.0", path = "../components/zcash_address"} zcash_client_backend = { version = "0.5", path = "../zcash_client_backend" } zcash_primitives = { version = "0.5", path = "../zcash_primitives" } From 6e11f2d11a77dfe59ed589146ae7339acb38f9d9 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 27 Jan 2022 09:15:10 -0700 Subject: [PATCH 61/79] Validate ZIP 321 request by roundtrip through the URI format. Co-authored-by: str4d --- zcash_client_backend/src/keys.rs | 1 - zcash_client_backend/src/wallet.rs | 4 +- zcash_client_backend/src/zip321.rs | 43 +++++++++++-------- zcash_primitives/src/sapling/keys.rs | 1 - .../src/sapling/note_encryption.rs | 5 +-- zcash_primitives/src/transaction/builder.rs | 4 +- zcash_primitives/src/zip32.rs | 2 +- 7 files changed, 29 insertions(+), 31 deletions(-) diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index b169c148a..51eab4198 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -167,7 +167,6 @@ mod tests { #[cfg(feature = "transparent-inputs")] use { crate::encoding::AddressCodec, - secp256k1::key::SecretKey, zcash_primitives::{consensus::MAIN_NETWORK, legacy, legacy::keys::IncomingViewingKey}, }; diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index 99b2825eb..6f7eb441f 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -5,9 +5,7 @@ use zcash_note_encryption::EphemeralKeyBytes; use zcash_primitives::{ keys::OutgoingViewingKey, merkle_tree::IncrementalWitness, - sapling::{ - Diversifier, Node, Note, Nullifier, PaymentAddress, Rseed, - }, + sapling::{Diversifier, Node, Note, Nullifier, PaymentAddress, Rseed}, transaction::{components::Amount, TxId}, zip32::AccountId, }; diff --git a/zcash_client_backend/src/zip321.rs b/zcash_client_backend/src/zip321.rs index 5cfd51d6b..c6e3cdda2 100644 --- a/zcash_client_backend/src/zip321.rs +++ b/zcash_client_backend/src/zip321.rs @@ -28,6 +28,10 @@ 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. @@ -113,11 +117,17 @@ pub struct TransactionRequest { impl TransactionRequest { /// Constructs a new transaction request that obeys the ZIP-321 invariants pub fn new(payments: Vec) -> Result { - if payments.len() > 2109 { - Err(Zip321Error::TooManyPayments(payments.len())) - } else { - Ok(TransactionRequest { payments }) + 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. @@ -217,17 +227,17 @@ impl TransactionRequest { } /// Parse the provided URI to a payment request value. - pub fn from_uri(params: &P, uri: &str) -> Result { + pub fn from_uri(params: &P, uri: &str) -> Result { // Parse the leading zcash:

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> = HashMap::new(); @@ -246,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); } @@ -380,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. @@ -426,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, i: usize) -> Result { + pub fn to_payment(vs: Vec, i: usize) -> Result { 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, @@ -444,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)) } }, diff --git a/zcash_primitives/src/sapling/keys.rs b/zcash_primitives/src/sapling/keys.rs index 7a21ba769..80de1379f 100644 --- a/zcash_primitives/src/sapling/keys.rs +++ b/zcash_primitives/src/sapling/keys.rs @@ -14,7 +14,6 @@ use group::{Group, GroupEncoding}; use std::io::{self, Read, Write}; use subtle::CtOption; - /// A Sapling expanded spending key #[derive(Clone)] pub struct ExpandedSpendingKey { diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index cba1b7375..43831cdc3 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -496,10 +496,7 @@ mod tests { keys::OutgoingViewingKey, memo::MemoBytes, sapling::util::generate_random_rseed, - sapling::{ - Diversifier, PaymentAddress, Rseed, SaplingIvk, - ValueCommitment, - }, + sapling::{Diversifier, PaymentAddress, Rseed, SaplingIvk, ValueCommitment}, transaction::components::{ amount::Amount, sapling::{self, CompactOutputDescription, OutputDescription}, diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index a22903702..f402cd33a 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -16,9 +16,7 @@ use crate::{ legacy::TransparentAddress, memo::MemoBytes, merkle_tree::MerklePath, - sapling::{ - prover::TxProver, Diversifier, Node, Note, PaymentAddress, - }, + sapling::{prover::TxProver, Diversifier, Node, Note, PaymentAddress}, transaction::{ components::{ amount::{Amount, DEFAULT_FEE}, diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index 41ba26cee..15b3a431d 100644 --- a/zcash_primitives/src/zip32.rs +++ b/zcash_primitives/src/zip32.rs @@ -18,7 +18,7 @@ use std::io::{self, Read, Write}; use crate::{ keys::{prf_expand, prf_expand_vec, OutgoingViewingKey}, - sapling::keys::{ExpandedSpendingKey, FullViewingKey}, + sapling::keys::{ExpandedSpendingKey, FullViewingKey}, }; pub const ZIP32_SAPLING_MASTER_PERSONALIZATION: &[u8; 16] = b"ZcashIP32Sapling"; From 4057b066bd491fccef130d65b30c8a3852304688 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 27 Jan 2022 16:18:26 -0700 Subject: [PATCH 62/79] ExtendedFullViewingKey::chain_code should not be public. --- zcash_primitives/src/zip32.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index 15b3a431d..33ebbca94 100644 --- a/zcash_primitives/src/zip32.rs +++ b/zcash_primitives/src/zip32.rs @@ -613,11 +613,6 @@ impl ExtendedFullViewingKey { sapling_default_address(&self.fvk, &self.dk) } - /// Returns the chain code. - pub fn chain_code(&self) -> ChainCode { - self.chain_code - } - /// Derives an internal full viewing key used for internal operations such /// as change and auto-shielding. The internal FVK has the same spend authority /// (the private key corresponding to ak) as the original, but viewing authority From 2f1d3da26da2d24a20e27c4d00d7cb381a2c3101 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 27 Jan 2022 16:25:01 -0700 Subject: [PATCH 63/79] Update changelogs. --- zcash_client_backend/CHANGELOG.md | 75 +++++++++++++++++++++++++++---- zcash_client_sqlite/CHANGELOG.md | 8 ++++ zcash_primitives/CHANGELOG.md | 16 +++++++ 3 files changed, 90 insertions(+), 9 deletions(-) diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index a28177eae..1fdca7e14 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -6,6 +6,36 @@ and this library adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- A new feature flag, `transparent-inputs` is now available for use in + compilation of `zcash_primitives`, `zcash_client_backend`, and + `zcash_client_sqlite`. This flag must be enabled to provide access to the + functionality which enables the receiving and spending of transparent funds. +- 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. +- 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::WalletRead::get_transaction` has been added + to provide access to decoded transaction data. +- `zcash_client_backend::data_api::WalletRead::get_all_nullifiers` has been + added. This method provides access to all Sapling nullifiers, including + for notes that have been previously marked spent. +- `zcash_client_backend::data_api::WalletRead::get_unspent_transparent_outputs` + has been added under the `transparent-inputs` feature flag to provide access + to received transparent UTXOs. +- A new `zcash_client_backend::encoding::AddressCodec` trait has been added + to facilitate address encoding. This new API should be considered unstable + and is likely to be removed or changed in an upcoming release. +- `zcash_client_backend::encoding::encode_payment_address` has been added. + This API should be considered unstable. +- `zcash_client_backend::encoding::encode_transparent_address` has been added. + This API should be considered unstable. + ### Changed - MSRV is now 1.51.0. - Bumped dependencies to `ff 0.11`, `group 0.11`, `bls12_381 0.6`, `jubjub 0.8`. @@ -15,19 +45,46 @@ 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`. + - 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` -- Account identifier variables that were previously typed as `u32` have - been updated to use a bespoke `AccountId` type in several places. -- A new error constructor SqliteClientError::TransparentAddress has been added - to support handling of errors in transparent address decoding. -- The `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. -- The `SaplingBuilder::add_output` method has now similarly been changed to take - its `MemoBytes` argument as a required field. +- `data_api::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`. +- `data_api::WalletRead::get_spendable_notes` has been renamed to + `get_spendable_sapling_notes` +- `data_api::WalletRead::select_spendable_notes` has been renamed to + `select_spendable_sapling_notes` +- The `zcash_client_backend::data_api::SentTransaction` type has been + substantially modified to accommodate handling of transparent + inputs. +- `data_api::WalletWrite::store_received_tx` has been renamed to + `store_decrypted_tx` and it now takes an explicit list of + `(AccountId, Nullifier)` pairs that allows the library to + provide quick access to previously decrypted nullifiers. +- 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. +- The hardcoded `data_api::wallet::ANCHOR_OFFSET` constant has been removed. +- `zcash_client_backend::keys::spending_key` has been moved to the + `zcash_client_backend::keys::sapling` module. +- Two new types, `UnifiedSpendingKey` and `UnifiedFullViewingKey` + have been added to the `zcash_client_backend::keys` module. These + types should be considered unstable as they are likely to be changed + and/or extracted into a different crate in a future release. +- A `zcash_client_backend::wallet::WalletTransparentOutput` type + has been added under the `transparent-inputs` feature flag in support + of autoshielding functionality. +- `zcash_client_backend::zip321::MemoError` has been renamed and + expanded into a more comprehensive `Zip321Error` type, and + functions in hthe `zip321` module have been updated to use + this unified error type. +- The `zcash_client_backend::wallet::AccountId` type has been moved + to the `zcash_primitives::zip32` module. + ## [0.5.0] - 2021-03-26 ### Added - `zcash_client_backend::address::RecipientAddress` diff --git a/zcash_client_sqlite/CHANGELOG.md b/zcash_client_sqlite/CHANGELOG.md index 82cbf0d37..dc07d27e8 100644 --- a/zcash_client_sqlite/CHANGELOG.md +++ b/zcash_client_sqlite/CHANGELOG.md @@ -6,6 +6,7 @@ and this library adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added ### Changed - MSRV is now 1.51.0. - Bumped dependencies to `ff 0.11`, `group 0.11`, `jubjub 0.8`. @@ -15,6 +16,13 @@ and this library adheres to Rust's notion of - `zcash_client_sqlite::WalletDB` to `WalletDb` - `zcash_client_sqlite::error::SqliteClientError::IncorrectHRPExtFVK` to `IncorrectHrpExtFvk`. +- A new error constructor `SqliteClientError::TransparentAddress` has been added + to support handling of errors in transparent address decoding. +- A new error constructor `SqliteClientError::RequestedRewindInvalid` has been added + to report when requested rewinds exceed supported bounds. +- The sqlite implemenations of `zcash_client_backend::data_api::WalletRead` + and `WalletWrite` have been updated to reflect the changes to those + traits. ## [0.3.0] - 2021-03-26 This release contains a major refactor of the APIs to leverage the new Data diff --git a/zcash_primitives/CHANGELOG.md b/zcash_primitives/CHANGELOG.md index 57c35c4c9..35552a2d4 100644 --- a/zcash_primitives/CHANGELOG.md +++ b/zcash_primitives/CHANGELOG.md @@ -88,6 +88,19 @@ and this library adheres to Rust's notion of 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. +- 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. +- 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_client_backend::wallet::AccountId` type has been moved + to the `zcash_primitives::zip32` module. ### Changed - MSRV is now 1.51.0. @@ -137,6 +150,9 @@ 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`. +- `zcash_primitives::sapling::keys::{prf_expand, prf_expand_vec, OutgoingViewingKey}` have + all been moved to the `zcash_primitives::keys` module to reflect the fact + that they are used outside of the Sapling protocol. ## [0.5.0] - 2021-03-26 ### Added From b3fbf2410de314fe8b5c0b562cd9a3c947686152 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 27 Jan 2022 21:21:18 -0700 Subject: [PATCH 64/79] Allow use of internal deprecated pubkey_to_address method. --- zcash_primitives/src/legacy/keys.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zcash_primitives/src/legacy/keys.rs b/zcash_primitives/src/legacy/keys.rs index 3ecd15101..d14355073 100644 --- a/zcash_primitives/src/legacy/keys.rs +++ b/zcash_primitives/src/legacy/keys.rs @@ -150,6 +150,7 @@ pub(crate) mod private { 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, From e8e5d94ea6c6752b613cb1374ec91fe0cd70d763 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 2 Feb 2022 10:29:19 -0700 Subject: [PATCH 65/79] Mark functions that will be made crate-private as deprecated --- zcash_client_backend/src/data_api.rs | 11 ++- zcash_client_sqlite/src/chain.rs | 1 + zcash_client_sqlite/src/lib.rs | 16 ++++ zcash_client_sqlite/src/wallet.rs | 100 +++++++++++++++++++-- zcash_client_sqlite/src/wallet/init.rs | 1 + zcash_client_sqlite/src/wallet/transact.rs | 15 +++- 6 files changed, 130 insertions(+), 14 deletions(-) diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 03bb34f40..45103c6af 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -163,12 +163,15 @@ pub trait WalletRead { block_height: BlockHeight, ) -> Result)>, 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 have not yet + /// been confirmed as a consequence of the spending transaction + /// being included in a block. fn get_nullifiers(&self) -> Result, Self::Error>; - /// Returns all nullifiers (including those for notes that have been previously spent), along - /// with the account identifiers with which they are associated. + /// Returns all nullifiers for notes that the wallet is tracing + /// (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, Self::Error>; /// Return all unspent Sapling notes. diff --git a/zcash_client_sqlite/src/chain.rs b/zcash_client_sqlite/src/chain.rs index 6f4e49d42..2bc61ead7 100644 --- a/zcash_client_sqlite/src/chain.rs +++ b/zcash_client_sqlite/src/chain.rs @@ -65,6 +65,7 @@ where } #[cfg(test)] +#[allow(deprecated)] mod tests { use tempfile::NamedTempFile; diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 73b9f657f..f4771de91 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -210,24 +210,29 @@ impl WalletRead for WalletDb

{ type TxRef = i64; fn block_height_extrema(&self) -> Result, Self::Error> { + #[allow(deprecated)] wallet::block_height_extrema(&self).map_err(SqliteClientError::from) } fn get_block_hash(&self, block_height: BlockHeight) -> Result, Self::Error> { + #[allow(deprecated)] wallet::get_block_hash(&self, block_height).map_err(SqliteClientError::from) } fn get_tx_height(&self, txid: TxId) -> Result, Self::Error> { + #[allow(deprecated)] wallet::get_tx_height(&self, txid).map_err(SqliteClientError::from) } fn get_extended_full_viewing_keys( &self, ) -> Result, Self::Error> { + #[allow(deprecated)] wallet::get_extended_full_viewing_keys(&self) } fn get_address(&self, account: AccountId) -> Result, Self::Error> { + #[allow(deprecated)] wallet::get_address(&self, account) } @@ -236,6 +241,7 @@ impl WalletRead for WalletDb

{ account: AccountId, extfvk: &ExtendedFullViewingKey, ) -> Result { + #[allow(deprecated)] wallet::is_valid_account_extfvk(&self, account, extfvk) } @@ -244,14 +250,17 @@ impl WalletRead for WalletDb

{ account: AccountId, anchor_height: BlockHeight, ) -> Result { + #[allow(deprecated)] wallet::get_balance_at(&self, account, anchor_height) } fn get_transaction(&self, id_tx: i64) -> Result { + #[allow(deprecated)] wallet::get_transaction(&self, id_tx) } fn get_memo(&self, id_note: Self::NoteRef) -> Result { + #[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), @@ -262,6 +271,7 @@ impl WalletRead for WalletDb

{ &self, block_height: BlockHeight, ) -> Result>, Self::Error> { + #[allow(deprecated)] wallet::get_commitment_tree(&self, block_height) } @@ -270,14 +280,17 @@ impl WalletRead for WalletDb

{ &self, block_height: BlockHeight, ) -> Result)>, Self::Error> { + #[allow(deprecated)] wallet::get_witnesses(&self, block_height) } fn get_nullifiers(&self) -> Result, Self::Error> { + #[allow(deprecated)] wallet::get_nullifiers(&self) } fn get_all_nullifiers(&self) -> Result, Self::Error> { + #[allow(deprecated)] wallet::get_all_nullifiers(&self) } @@ -286,6 +299,7 @@ impl WalletRead for WalletDb

{ account: AccountId, anchor_height: BlockHeight, ) -> Result, Self::Error> { + #[allow(deprecated)] wallet::transact::get_spendable_sapling_notes(&self, account, anchor_height) } @@ -295,6 +309,7 @@ impl WalletRead for WalletDb

{ target_value: Amount, anchor_height: BlockHeight, ) -> Result, Self::Error> { + #[allow(deprecated)] wallet::transact::select_spendable_sapling_notes( &self, account, @@ -483,6 +498,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( diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 87fe423d8..2b3698b7b 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -50,6 +50,7 @@ pub mod init; pub mod transact; /// 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; @@ -60,6 +61,7 @@ pub trait ShieldedOutput { fn nullifier(&self) -> Option; } +#[allow(deprecated)] impl ShieldedOutput for WalletShieldedOutput { fn index(&self) -> usize { self.index @@ -85,6 +87,7 @@ impl ShieldedOutput for WalletShieldedOutput { } } +#[allow(deprecated)] impl ShieldedOutput for DecryptedOutput { fn index(&self) -> usize { self.index @@ -128,6 +131,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( wdb: &WalletDb

, account: AccountId, @@ -146,6 +152,9 @@ pub fn get_address( /// 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( wdb: &WalletDb

, ) -> Result, SqliteClientError> { @@ -183,6 +192,9 @@ pub fn get_extended_full_viewing_keys( /// 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( wdb: &WalletDb

, account: AccountId, @@ -226,6 +238,9 @@ pub fn is_valid_account_extfvk( /// 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

(wdb: &WalletDb

, account: AccountId) -> Result { let balance = wdb.conn.query_row( "SELECT SUM(value) FROM received_notes @@ -264,6 +279,9 @@ pub fn get_balance

(wdb: &WalletDb

, account: AccountId) -> Result( wdb: &WalletDb

, account: AccountId, @@ -305,6 +323,9 @@ pub fn get_balance_at

( /// 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

(wdb: &WalletDb

, id_note: i64) -> Result { let memo_bytes: Vec<_> = wdb.conn.query_row( "SELECT memo FROM received_notes @@ -318,7 +339,8 @@ pub fn get_received_memo

(wdb: &WalletDb

, id_note: i64) -> Result( +/// Looks up a transaction by its internal database identifier. +pub(crate) fn get_transaction( wdb: &WalletDb

, id_tx: i64, ) -> Result { @@ -359,6 +381,9 @@ pub fn get_transaction( /// 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

(wdb: &WalletDb

, id_note: i64) -> Result { let memo_bytes: Vec<_> = wdb.conn.query_row( "SELECT memo FROM sent_notes @@ -388,6 +413,9 @@ pub fn get_sent_memo

(wdb: &WalletDb

, id_note: i64) -> Result( wdb: &WalletDb

, ) -> Result, rusqlite::Error> { @@ -427,6 +455,9 @@ pub fn block_height_extrema

( /// 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

( wdb: &WalletDb

, txid: TxId, @@ -457,6 +488,9 @@ pub fn get_tx_height

( /// 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

( wdb: &WalletDb

, block_height: BlockHeight, @@ -475,6 +509,7 @@ pub fn get_block_hash

( /// 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

(wdb: &WalletDb

) -> Result, SqliteClientError> { wdb.conn .query_row( @@ -497,7 +532,7 @@ pub fn get_rewind_height

(wdb: &WalletDb

) -> Result, Sq /// block, this function does nothing. /// /// This should only be executed inside a transactional context. -pub fn rewind_to_height( +pub(crate) fn rewind_to_height( wdb: &WalletDb

, block_height: BlockHeight, ) -> Result<(), SqliteClientError> { @@ -518,6 +553,7 @@ pub fn rewind_to_height( let rewind_height = if block_height >= (last_scanned_height - PRUNING_HEIGHT) { Some(block_height) } else { + #[allow(deprecated)] match get_rewind_height(&wdb)? { Some(h) => { if block_height > h { @@ -606,6 +642,9 @@ pub fn rewind_to_height( /// 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

( wdb: &WalletDb

, block_height: BlockHeight, @@ -646,6 +685,9 @@ pub fn get_commitment_tree

( /// 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

( wdb: &WalletDb

, block_height: BlockHeight, @@ -669,6 +711,9 @@ pub fn get_witnesses

( /// 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

( wdb: &WalletDb

, ) -> Result, SqliteClientError> { @@ -691,7 +736,7 @@ pub fn get_nullifiers

( } /// Returns the nullifiers for the notes that this wallet is tracking. -pub fn get_all_nullifiers

( +pub(crate) fn get_all_nullifiers

( wdb: &WalletDb

, ) -> Result, SqliteClientError> { // Get the nullifiers for the notes we are tracking @@ -713,7 +758,7 @@ pub fn get_all_nullifiers

( /// 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 fn get_unspent_transparent_outputs( +pub(crate) fn get_unspent_transparent_outputs( wdb: &WalletDb

, address: &TransparentAddress, max_height: BlockHeight, @@ -759,6 +804,9 @@ pub fn get_unspent_transparent_outputs( } /// 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, @@ -781,6 +829,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, @@ -808,6 +859,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, @@ -846,6 +900,9 @@ pub fn put_tx_data<'a, P>( /// /// Marking a note spent in this fashion does NOT imply that the /// spending transaction has been mined. +#[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, @@ -859,7 +916,7 @@ pub fn mark_sapling_note_spent<'a, P>( /// Marks the given UTXO as having been spent. #[cfg(feature = "transparent-inputs")] -pub fn mark_transparent_utxo_spent<'a, P>( +pub(crate) fn mark_transparent_utxo_spent<'a, P>( stmts: &mut DataConnStmtCache<'a, P>, tx_ref: i64, outpoint: &OutPoint, @@ -879,7 +936,7 @@ pub fn mark_transparent_utxo_spent<'a, P>( /// Adds the given received UTXO to the datastore. #[cfg(feature = "transparent-inputs")] -pub fn put_received_transparent_utxo<'a, P: consensus::Parameters>( +pub(crate) fn put_received_transparent_utxo<'a, P: consensus::Parameters>( stmts: &mut DataConnStmtCache<'a, P>, output: &WalletTransparentOutput, ) -> Result { @@ -906,6 +963,9 @@ pub fn put_received_transparent_utxo<'a, P: consensus::Parameters>( /// at block heights greater than the given height. Used in the case of chain /// rollback. #[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, @@ -926,6 +986,10 @@ pub fn delete_utxos_above<'a, P: consensus::Parameters>( /// This implementation assumes: /// - 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, @@ -975,6 +1039,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, @@ -992,6 +1059,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

( stmts: &mut DataConnStmtCache<'_, P>, below_height: BlockHeight, @@ -1004,6 +1074,9 @@ pub fn prune_witnesses

( /// 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

( stmts: &mut DataConnStmtCache<'_, P>, height: BlockHeight, @@ -1013,6 +1086,10 @@ pub fn update_expired_notes

( } /// 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>, tx_ref: i64, @@ -1044,6 +1121,10 @@ pub fn put_sent_note<'a, P: consensus::Parameters>( /// 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, @@ -1078,6 +1159,9 @@ pub fn put_sent_utxo<'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, @@ -1104,6 +1188,9 @@ pub fn insert_sent_note<'a, P: consensus::Parameters>( /// 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, @@ -1127,6 +1214,7 @@ pub fn insert_sent_utxo<'a, P: consensus::Parameters>( } #[cfg(test)] +#[allow(deprecated)] mod tests { use tempfile::NamedTempFile; diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 41714b55e..9324889ea 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -287,6 +287,7 @@ pub fn init_blocks_table

( } #[cfg(test)] +#[allow(deprecated)] mod tests { use tempfile::NamedTempFile; diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index 1b1747938..34f77cd49 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -60,6 +60,9 @@ fn to_spendable_note(row: &Row) -> Result { }) } +#[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

( wdb: &WalletDb

, account: AccountId, @@ -88,6 +91,9 @@ pub fn get_spendable_sapling_notes

( notes.collect::>() } +#[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

( wdb: &WalletDb

, account: AccountId, @@ -148,10 +154,13 @@ pub fn select_spendable_sapling_notes

( } #[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, Parameters}, @@ -161,7 +170,8 @@ mod tests { 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}, @@ -169,9 +179,6 @@ mod tests { wallet::OvkPolicy, }; - #[cfg(feature = "transparent-inputs")] - use zcash_primitives::legacy::keys as transparent; - use crate::{ chain::init::init_cache_database, tests::{self, fake_compact_block, insert_into_cache, network, sapling_activation_height}, From 5a75c210f077dd10f1e00a142655b8dea19aae11 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 2 Feb 2022 10:30:07 -0700 Subject: [PATCH 66/79] Fix order of note deletion relative to transactions being un-mined in rewind. --- zcash_client_sqlite/src/wallet.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 2b3698b7b..10ac01ed5 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -576,18 +576,6 @@ pub(crate) fn rewind_to_height( &[u32::from(block_height)], )?; - // Un-mine transactions. - wdb.conn.execute( - "UPDATE transactions SET block = NULL, tx_index = NULL WHERE block > ?", - &[u32::from(block_height)], - )?; - - // Now that they aren't depended on, delete scanned blocks. - wdb.conn.execute( - "DELETE FROM blocks WHERE height > ?", - &[u32::from(block_height)], - )?; - // Rewind received notes wdb.conn.execute( "DELETE FROM received_notes @@ -619,6 +607,18 @@ pub(crate) fn rewind_to_height( "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 > ?", + &[u32::from(block_height)], + )?; + + // Now that they aren't depended on, delete scanned blocks. + wdb.conn.execute( + "DELETE FROM blocks WHERE height > ?", + &[u32::from(block_height)], + )?; } } From d0e1f984299bca93bc746d7bc12009e4f96993a9 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 31 Jan 2022 16:22:20 -0700 Subject: [PATCH 67/79] Use extension traits for transparent-related data api functionality. --- zcash_client_backend/src/data_api.rs | 21 +++++++++++++++-- zcash_client_backend/src/data_api/wallet.rs | 7 ++++-- zcash_client_sqlite/src/lib.rs | 25 ++++++++++++++++++--- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 45103c6af..c7c9b8619 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -189,8 +189,10 @@ pub trait WalletRead { target_value: Amount, anchor_height: BlockHeight, ) -> Result, Self::Error>; +} - #[cfg(feature = "transparent-inputs")] +#[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( @@ -281,6 +283,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; +} + /// This trait provides sequential access to raw blockchain data via a callback-oriented /// API. pub trait BlockSource { @@ -323,6 +335,9 @@ pub mod testing { WalletWrite, }; + #[cfg(feature = "transparent-inputs")] + use super::WalletReadTransparent; + pub struct MockBlockSource {} impl BlockSource for MockBlockSource { @@ -436,8 +451,10 @@ pub mod testing { ) -> Result, Self::Error> { Ok(Vec::new()) } + } - #[cfg(feature = "transparent-inputs")] + #[cfg(feature = "transparent-inputs")] + impl WalletReadTransparent for MockWalletDb { fn get_unspent_transparent_outputs( &self, _address: &TransparentAddress, diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index ab9ad9e8f..4642351ed 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -26,6 +26,9 @@ use crate::{ zip321::{Payment, TransactionRequest}, }; +#[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. pub fn decrypt_and_store_transaction( @@ -376,7 +379,7 @@ where /// spent. #[cfg(feature = "transparent-inputs")] #[allow(clippy::too_many_arguments)] -pub fn shield_transparent_funds( +pub fn shield_transparent_funds( wallet_db: &mut D, params: &P, prover: impl TxProver, @@ -390,7 +393,7 @@ where E: From>, P: consensus::Parameters, R: Copy + Debug, - D: WalletWrite, + D: WalletWrite + WalletWriteTransparent, { // Check that the ExtendedSpendingKey we have been given corresponds to the // ExtendedFullViewingKey for the account we are spending from. diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index f4771de91..fa09f82c9 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -62,7 +62,10 @@ use crate::error::SqliteClientError; #[cfg(feature = "transparent-inputs")] use { - zcash_client_backend::wallet::WalletTransparentOutput, + zcash_client_backend::{ + data_api::{WalletReadTransparent, WalletWriteTransparent}, + wallet::WalletTransparentOutput, + }, zcash_primitives::legacy::TransparentAddress, }; @@ -317,8 +320,10 @@ impl WalletRead for WalletDb

{ anchor_height, ) } +} - #[cfg(feature = "transparent-inputs")] +#[cfg(feature = "transparent-inputs")] +impl WalletReadTransparent for WalletDb

{ fn get_unspent_transparent_outputs( &self, address: &TransparentAddress, @@ -458,8 +463,10 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> { self.wallet_db .select_spendable_sapling_notes(account, target_value, anchor_height) } +} - #[cfg(feature = "transparent-inputs")] +#[cfg(feature = "transparent-inputs")] +impl<'a, P: consensus::Parameters> WalletReadTransparent for DataConnStmtCache<'a, P> { fn get_unspent_transparent_outputs( &self, address: &TransparentAddress, @@ -675,6 +682,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 { + wallet::put_received_transparent_utxo(self, output) + } +} + /// A wrapper for the SQLite connection to the block cache database. pub struct BlockDb(Connection); From 8916a16f383edc18da8afe7889b226a2f405a110 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 31 Jan 2022 16:24:03 -0700 Subject: [PATCH 68/79] Replace ripemd160 dependency with ripemd --- zcash_primitives/Cargo.toml | 4 ++-- zcash_primitives/src/legacy/keys.rs | 5 ++-- .../components/transparent/builder.rs | 23 ++++++++++--------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index bd1392a50..1a0eada2c 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -40,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" @@ -61,7 +61,7 @@ orchard = { version = "=0.1.0-beta.1", features = ["test-dependencies"] } pprof = { version = "=0.6.1", features = ["criterion", "flamegraph"] } [features] -transparent-inputs = ["bs58", "hdwallet", "ripemd160", "secp256k1"] +transparent-inputs = ["bs58", "hdwallet", "ripemd", "secp256k1"] test-dependencies = ["proptest", "orchard/test-dependencies"] zfuture = [] diff --git a/zcash_primitives/src/legacy/keys.rs b/zcash_primitives/src/legacy/keys.rs index d14355073..9bc8756ed 100644 --- a/zcash_primitives/src/legacy/keys.rs +++ b/zcash_primitives/src/legacy/keys.rs @@ -1,6 +1,7 @@ use hdwallet::{ExtendedPrivKey, ExtendedPubKey, KeyIndex}; +use ripemd::Digest as RipemdDigest; use secp256k1::PublicKey; -use sha2::{Digest, Sha256}; +use sha2::{Digest as Sha2Digest, Sha256}; use std::convert::TryInto; use crate::{consensus, keys::prf_expand_vec, zip32::AccountId}; @@ -135,7 +136,7 @@ impl AccountPubKey { /// 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 { - let mut hash160 = ripemd160::Ripemd160::new(); + let mut hash160 = ripemd::Ripemd160::new(); hash160.update(Sha256::digest(&pubkey.serialize())); TransparentAddress::PublicKey(*hash160.finalize().as_ref()) } diff --git a/zcash_primitives/src/transaction/components/transparent/builder.rs b/zcash_primitives/src/transaction/components/transparent/builder.rs index da202f690..b62b3f9fb 100644 --- a/zcash_primitives/src/transaction/components/transparent/builder.rs +++ b/zcash_primitives/src/transaction/components/transparent/builder.rs @@ -2,9 +2,6 @@ use std::fmt; -#[cfg(feature = "transparent-inputs")] -use blake2b_simd::Hash as Blake2bHash; - use crate::{ legacy::TransparentAddress, transaction::components::{ @@ -14,14 +11,18 @@ use crate::{ }; #[cfg(feature = "transparent-inputs")] -use crate::{ - legacy::Script, - transaction::{ - self as tx, - components::OutPoint, - sighash::{signature_hash, SignableInput, SIGHASH_ALL}, - TransactionData, TxDigests, +use { + crate::{ + legacy::Script, + 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))[..] { From 3d51c53d68c3b1cfa8b8d0cc781744275c31ce63 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 31 Jan 2022 16:36:09 -0700 Subject: [PATCH 69/79] Address comments from code review. Co-authored-by: Daira Hopwood --- zcash_client_backend/src/data_api/wallet.rs | 2 +- zcash_client_sqlite/src/error.rs | 8 +- zcash_client_sqlite/src/lib.rs | 2 +- zcash_client_sqlite/src/wallet.rs | 116 +++++++++----------- zcash_client_sqlite/src/wallet/init.rs | 13 ++- zcash_primitives/src/legacy/keys.rs | 4 +- 6 files changed, 68 insertions(+), 77 deletions(-) diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 4642351ed..8a75998c5 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -27,7 +27,7 @@ use crate::{ }; #[cfg(feature = "transparent-inputs")] -use crate::data_api::{WalletWriteTransparent}; +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. diff --git a/zcash_client_sqlite/src/error.rs b/zcash_client_sqlite/src/error.rs index f487df7b2..33e21c2bf 100644 --- a/zcash_client_sqlite/src/error.rs +++ b/zcash_client_sqlite/src/error.rs @@ -47,8 +47,8 @@ pub enum SqliteClientError { /// A requested rewind would violate invariants of the /// storage layer. The payload returned with this error is - /// the safe rewind height. - RequestedRewindInvalid(BlockHeight), + /// (safe rewind height, requested height). + RequestedRewindInvalid(BlockHeight, BlockHeight), /// Wrapper for errors from zcash_client_backend BackendError(data_api::error::Error), @@ -76,8 +76,8 @@ impl fmt::Display for SqliteClientError { 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::RequestedRewindInvalid(h) => - write!(f, "A rewind must be either of less than {} blocks, or at least back to block {} for your wallet.", PRUNING_HEIGHT, h), + 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), diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index fa09f82c9..a8907ec03 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -551,7 +551,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { } } - // Prune the stored witnesses (we only expect rollbacks of at most 100 blocks). + // 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. diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 10ac01ed5..b27d4d096 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -550,76 +550,66 @@ pub(crate) fn rewind_to_height( .or(Ok(sapling_activation_height - 1)) })?; - let rewind_height = if block_height >= (last_scanned_height - PRUNING_HEIGHT) { - Some(block_height) - } else { + if block_height < last_scanned_height - PRUNING_HEIGHT { #[allow(deprecated)] - match get_rewind_height(&wdb)? { - Some(h) => { - if block_height > h { - return Err(SqliteClientError::RequestedRewindInvalid(h)); - } else { - Some(block_height) - } + if let Some(h) = get_rewind_height(&wdb)? { + if block_height > h { + return Err(SqliteClientError::RequestedRewindInvalid(h, block_height)); } - None => Some(block_height), } - }; + } // nothing to do if we're deleting back down to the max height + if block_height < last_scanned_height { + // Decrement witnesses. + wdb.conn.execute( + "DELETE FROM sapling_witnesses WHERE block > ?", + &[u32::from(block_height)], + )?; - if let Some(block_height) = rewind_height { - 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 > ? + );", + &[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 > ? - );", - &[u32::from(block_height)], - )?; + // Rewind sent notes + wdb.conn.execute( + "DELETE FROM sent_notes + WHERE id_note IN ( + SELECT sn.id_note + FROM sent_notes sn + LEFT OUTER JOIN transactions tx + ON tx.id_tx = sn.tx + WHERE tx.block > ? + );", + &[u32::from(block_height)], + )?; - // Rewind sent notes - wdb.conn.execute( - "DELETE FROM sent_notes - WHERE id_note IN ( - SELECT sn.id_note - FROM sent_notes sn - LEFT OUTER JOIN transactions tx - ON tx.id_tx = sn.tx - WHERE tx.block > ? - );", - &[u32::from(block_height)], - )?; + // Rewind utxos + wdb.conn.execute( + "DELETE FROM utxos WHERE height > ?", + &[u32::from(block_height)], + )?; - // 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 > ?", + &[u32::from(block_height)], + )?; - // Un-mine transactions. - wdb.conn.execute( - "UPDATE transactions SET block = NULL, tx_index = NULL WHERE block > ?", - &[u32::from(block_height)], - )?; - - // Now that they aren't depended on, delete scanned blocks. - wdb.conn.execute( - "DELETE FROM blocks WHERE height > ?", - &[u32::from(block_height)], - )?; - } + // Now that they aren't depended on, delete scanned blocks. + wdb.conn.execute( + "DELETE FROM blocks WHERE height > ?", + &[u32::from(block_height)], + )?; } Ok(()) @@ -983,7 +973,7 @@ pub fn delete_utxos_above<'a, P: consensus::Parameters>( /// Records the specified shielded output as having been received. /// -/// This implementation assumes: +/// 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( @@ -1118,7 +1108,7 @@ pub fn put_sent_note<'a, P: consensus::Parameters>( } /// 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. +/// 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( @@ -1134,7 +1124,7 @@ pub fn put_sent_utxo<'a, P: consensus::Parameters>( value: Amount, ) -> Result<(), SqliteClientError> { let ivalue: i64 = value.into(); - // Try updating an existing sent utxo. + // 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), diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 9324889ea..1ca07ce91 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -21,12 +21,13 @@ use { /// Sets up the internal structure of the data database. /// -/// The database structure is the same irrespective of whether or -/// not transparent spends are enabled, and it is safe to use a -/// wallet database created without the ability to create transparent -/// spends with a build that enables transparent spends (though not -/// the reverse, as wallet balance calculations would ignore the -/// prior transparent inputs controlled by the wallet). +/// 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 /// diff --git a/zcash_primitives/src/legacy/keys.rs b/zcash_primitives/src/legacy/keys.rs index 9bc8756ed..60ff056c1 100644 --- a/zcash_primitives/src/legacy/keys.rs +++ b/zcash_primitives/src/legacy/keys.rs @@ -69,8 +69,8 @@ impl AccountPrivKey { /// A type representing a BIP-44 public key at the account path level /// `m/44'/'/'`. /// -/// This provides the necessary derivation capability for the for -/// the transparent component of a unified full viewing key. +/// This provides the necessary derivation capability for the transparent component of a unified +/// full viewing key. #[derive(Clone, Debug)] pub struct AccountPubKey(ExtendedPubKey); From 2dd03487920f0f5e3fd75cd05a42b13c52a9db6b Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 1 Feb 2022 11:05:51 -0700 Subject: [PATCH 70/79] Remove the `nullifiers` argument from `store_decrypted_tx` This value can be obtained internally within the implementation of `store_decrypted_tx` and does not need to be part of the public API. --- zcash_client_backend/src/data_api.rs | 7 +++++-- zcash_client_backend/src/data_api/wallet.rs | 12 ++++-------- zcash_client_sqlite/src/lib.rs | 11 ++++------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index c7c9b8619..1ca6e2e2f 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -252,6 +252,10 @@ pub struct SentTransactionOutput<'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, @@ -259,10 +263,10 @@ pub trait WalletWrite: WalletRead { updated_witnesses: &[(Self::NoteRef, IncrementalWitness)], ) -> Result)>, Self::Error>; + /// Caches a decrypted transaction in the persistent wallet store. fn store_decrypted_tx( &mut self, received_tx: &DecryptedTransaction, - nullifiers: &[(AccountId, Nullifier)], ) -> Result; fn store_sent_tx(&mut self, sent_tx: &SentTransaction) -> Result; @@ -477,7 +481,6 @@ pub mod testing { fn store_decrypted_tx( &mut self, _received_tx: &DecryptedTransaction, - _nullifiers: &[(AccountId, Nullifier)], ) -> Result { Ok(TxId::from_bytes([0u8; 32])) } diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 8a75998c5..2e7376782 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -57,14 +57,10 @@ where let sapling_outputs = decrypt_transaction(params, height, tx, &extfvks); if !(sapling_outputs.is_empty() && tx.transparent_bundle().iter().all(|b| b.vout.is_empty())) { - let nullifiers = data.get_all_nullifiers()?; - data.store_decrypted_tx( - &DecryptedTransaction { - tx, - sapling_outputs: &sapling_outputs, - }, - &nullifiers, - )?; + data.store_decrypted_tx(&DecryptedTransaction { + tx, + sapling_outputs: &sapling_outputs, + })?; } Ok(()) diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index a8907ec03..cbcd5fb47 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -564,8 +564,8 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { fn store_decrypted_tx( &mut self, d_tx: &DecryptedTransaction, - nullifiers: &[(AccountId, Nullifier)], ) -> Result { + let nullifiers = self.wallet_db.get_all_nullifiers()?; self.transactionally(|up| { let tx_ref = wallet::put_tx_data(up, d_tx.tx, None)?; @@ -599,12 +599,9 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { // If we have some transparent outputs: if !d_tx.tx.transparent_bundle().iter().any(|b| b.vout.is_empty()) { - // If there are no shielded spends, then this is t2t and we can safely ignore it - // otherwise, this is z2t and it might have originated from our wallet. - // Store received z->t transactions in the same way they would be stored by - // create_spend_to_address. If there are any of our shielded inputs, we interpret - // this as our z->t tx and store the vouts as our sent notes. - // FIXME this is a weird heuristic that is bound to trip us up somewhere. + // 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()) From 9c2d485c805b9883f1db07d3453404d4a0e8c89b Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 1 Feb 2022 11:37:43 -0700 Subject: [PATCH 71/79] Address comments from code review. Co-authored-by: str4d Co-authored-by: Daira Hopwood --- zcash_client_backend/CHANGELOG.md | 121 +++++++++++--------- zcash_client_backend/src/data_api/wallet.rs | 60 ++++++++-- zcash_client_backend/src/keys.rs | 9 +- zcash_client_sqlite/CHANGELOG.md | 65 ++++++++++- zcash_client_sqlite/src/lib.rs | 47 ++++---- zcash_client_sqlite/src/wallet.rs | 9 +- zcash_primitives/CHANGELOG.md | 38 +++--- zcash_primitives/src/legacy/keys.rs | 6 +- 8 files changed, 236 insertions(+), 119 deletions(-) diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index 1fdca7e14..cc91f92d1 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -7,34 +7,35 @@ and this library adheres to Rust's notion of ## [Unreleased] ### Added -- A new feature flag, `transparent-inputs` is now available for use in - compilation of `zcash_primitives`, `zcash_client_backend`, and - `zcash_client_sqlite`. This flag must be enabled to provide access to the - functionality which enables the receiving and spending of transparent funds. +- 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. + 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. -- 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::WalletRead::get_transaction` has been added - to provide access to decoded transaction data. -- `zcash_client_backend::data_api::WalletRead::get_all_nullifiers` has been - added. This method provides access to all Sapling nullifiers, including - for notes that have been previously marked spent. -- `zcash_client_backend::data_api::WalletRead::get_unspent_transparent_outputs` - has been added under the `transparent-inputs` feature flag to provide access - to received transparent UTXOs. -- A new `zcash_client_backend::encoding::AddressCodec` trait has been added - to facilitate address encoding. This new API should be considered unstable - and is likely to be removed or changed in an upcoming release. -- `zcash_client_backend::encoding::encode_payment_address` has been added. - This API should be considered unstable. -- `zcash_client_backend::encoding::encode_transparent_address` has been added. - This API should be considered unstable. +- `zcash_client_backend::zip321::TransactionRequest` methods: + - `TransactionRequest::new` for constructing a request from `Vec`. + - `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. @@ -45,45 +46,57 @@ 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` -- `data_api::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`. -- `data_api::WalletRead::get_spendable_notes` has been renamed to - `get_spendable_sapling_notes` -- `data_api::WalletRead::select_spendable_notes` has been renamed to - `select_spendable_sapling_notes` +- 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. -- `data_api::WalletWrite::store_received_tx` has been renamed to - `store_decrypted_tx` and it now takes an explicit list of - `(AccountId, Nullifier)` pairs that allows the library to + 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` and it now takes an explicit list of + `(AccountId, Nullifier)` pairs that allows the library to provide quick access to previously decrypted nullifiers. -- 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. -- The hardcoded `data_api::wallet::ANCHOR_OFFSET` constant has been removed. +- `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. -- Two new types, `UnifiedSpendingKey` and `UnifiedFullViewingKey` - have been added to the `zcash_client_backend::keys` module. These - types should be considered unstable as they are likely to be changed - and/or extracted into a different crate in a future release. -- A `zcash_client_backend::wallet::WalletTransparentOutput` type - has been added under the `transparent-inputs` feature flag in support - of autoshielding functionality. -- `zcash_client_backend::zip321::MemoError` has been renamed and - expanded into a more comprehensive `Zip321Error` type, and - functions in hthe `zip321` module have been updated to use - this unified error type. -- The `zcash_client_backend::wallet::AccountId` type has been moved - to the `zcash_primitives::zip32` 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 diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 2e7376782..9262118bc 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -93,6 +93,23 @@ 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 /// /// ``` @@ -174,7 +191,9 @@ where message: None, other_params: vec![], }]) - .unwrap(); + .expect( + "It should not be possible for this to violate ZIP 321 request construction invariants.", + ); spend( wallet_db, @@ -189,12 +208,34 @@ where } /// 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 the -/// identifier for the transaction. +/// 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 the +/// activation of ZIP 244 the txid would be a reasonable value for this type. /// /// 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 @@ -210,7 +251,7 @@ where /// 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. +/// spent. A value of 10 confirmations is recommended. #[allow(clippy::too_many_arguments)] pub fn spend( wallet_db: &mut D, @@ -370,6 +411,10 @@ where /// * `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 +/// 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. @@ -401,20 +446,21 @@ where .get_target_and_anchor_heights(min_confirmations) .and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?; - // derive the t-address for the extpubkey at child index 0 let account_pubkey = sk.to_account_pubkey(); let ovk = OutgoingViewingKey(account_pubkey.internal_ovk().as_bytes()); - // derive own shielded address from the provided extended spending key + // 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; - // get UTXOs from DB + // 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() diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index 51eab4198..ba9789351 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -46,6 +46,7 @@ pub mod sapling { } #[derive(Debug)] +#[doc(hidden)] pub enum DerivationError { #[cfg(feature = "transparent-inputs")] Transparent(hdwallet::error::Error), @@ -54,6 +55,7 @@ pub enum DerivationError { /// 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")] @@ -61,6 +63,7 @@ pub struct UnifiedSpendingKey { sapling: sapling::ExtendedSpendingKey, } +#[doc(hidden)] impl UnifiedSpendingKey { pub fn from_seed( params: &P, @@ -113,13 +116,17 @@ impl UnifiedSpendingKey { /// 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, + // TODO: This type is invalid for a UFVK; create a `sapling::DiversifiableFullViewingKey` + // to replace it. sapling: Option, } +#[doc(hidden)] impl UnifiedFullViewingKey { /// Construct a new unified full viewing key, if the required components are present. pub fn new( @@ -145,9 +152,9 @@ impl UnifiedFullViewingKey { self.account } - #[cfg(feature = "transparent-inputs")] /// Returns the transparent component of the unified key at the /// BIP44 path `m/44'/'/'`. + #[cfg(feature = "transparent-inputs")] pub fn transparent(&self) -> Option<&legacy::AccountPubKey> { self.transparent.as_ref() } diff --git a/zcash_client_sqlite/CHANGELOG.md b/zcash_client_sqlite/CHANGELOG.md index dc07d27e8..4465d3637 100644 --- a/zcash_client_sqlite/CHANGELOG.md +++ b/zcash_client_sqlite/CHANGELOG.md @@ -7,6 +7,16 @@ and this library adheres to Rust's notion of ## [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`. @@ -16,13 +26,56 @@ and this library adheres to Rust's notion of - `zcash_client_sqlite::WalletDB` to `WalletDb` - `zcash_client_sqlite::error::SqliteClientError::IncorrectHRPExtFVK` to `IncorrectHrpExtFvk`. -- A new error constructor `SqliteClientError::TransparentAddress` has been added - to support handling of errors in transparent address decoding. -- A new error constructor `SqliteClientError::RequestedRewindInvalid` has been added - to report when requested rewinds exceed supported bounds. -- The sqlite implemenations of `zcash_client_backend::data_api::WalletRead` - and `WalletWrite` have been updated to reflect the changes to those +- 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. + +### 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 diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index cbcd5fb47..e89ab3769 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -111,9 +111,7 @@ impl WalletDb

{ pub fn for_path>(path: F, params: P) -> Result { Connection::open(path).map(move |conn| WalletDb { conn, params }) } -} -impl WalletDb

{ /// Given a wallet database connection, obtain a handle for the write operations /// for that database. This operation may eagerly initialize and cache sqlite /// prepared statements that are used in write operations. @@ -601,7 +599,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { 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. + // 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()) @@ -787,32 +785,33 @@ mod tests { db_data: &WalletDb, ) -> (ExtendedFullViewingKey, Option) { let seed = [0u8; 32]; - - let extsk = sapling::spending_key(&seed, network().coin_type(), AccountId(0)); + 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 = Some( - legacy::keys::AccountPrivKey::from_seed(&network(), &seed, AccountId(0)) - .unwrap() - .to_account_pubkey(), - ); - let ufvk = UnifiedFullViewingKey::new(AccountId(0), tkey.clone(), Some(extfvk.clone())) - .unwrap(); - init_accounts_table(db_data, &[ufvk]).unwrap(); - ( - extfvk, - tkey.map(|k| k.derive_external_ivk().unwrap().default_address().0), - ) - } + 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 ufvk = UnifiedFullViewingKey::new(AccountId(0), Some(extfvk.clone())).unwrap(); - init_accounts_table(db_data, &[ufvk]).unwrap(); - (extfvk, None) - } + 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 diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index b27d4d096..c8824ce83 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -575,7 +575,7 @@ pub(crate) fn rewind_to_height( FROM received_notes rn LEFT OUTER JOIN transactions tx ON tx.id_tx = rn.tx - WHERE tx.block > ? + WHERE tx.block IS NOT NULL AND tx.block > ? );", &[u32::from(block_height)], )?; @@ -588,7 +588,7 @@ pub(crate) fn rewind_to_height( FROM sent_notes sn LEFT OUTER JOIN transactions tx ON tx.id_tx = sn.tx - WHERE tx.block > ? + WHERE tx.block IS NOT NULL AND tx.block > ? );", &[u32::from(block_height)], )?; @@ -601,7 +601,7 @@ pub(crate) fn rewind_to_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)], )?; @@ -950,8 +950,7 @@ pub(crate) fn put_received_transparent_utxo<'a, P: consensus::Parameters>( } /// Removes all records of UTXOs that were recorded as having been received -/// at block heights greater than the given height. Used in the case of chain -/// rollback. +/// 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." diff --git a/zcash_primitives/CHANGELOG.md b/zcash_primitives/CHANGELOG.md index 35552a2d4..09d24988b 100644 --- a/zcash_primitives/CHANGELOG.md +++ b/zcash_primitives/CHANGELOG.md @@ -82,25 +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. -- 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. + 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-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_client_backend::wallet::AccountId` type has been moved - to the `zcash_primitives::zip32` module. +- 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. @@ -109,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` @@ -150,9 +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`. -- `zcash_primitives::sapling::keys::{prf_expand, prf_expand_vec, OutgoingViewingKey}` have - all been moved to the `zcash_primitives::keys` module to reflect the fact - that they are used outside of the Sapling protocol. +- 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 diff --git a/zcash_primitives/src/legacy/keys.rs b/zcash_primitives/src/legacy/keys.rs index 60ff056c1..98166ba22 100644 --- a/zcash_primitives/src/legacy/keys.rs +++ b/zcash_primitives/src/legacy/keys.rs @@ -136,9 +136,9 @@ impl AccountPubKey { /// 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 { - let mut hash160 = ripemd::Ripemd160::new(); - hash160.update(Sha256::digest(&pubkey.serialize())); - TransparentAddress::PublicKey(*hash160.finalize().as_ref()) + TransparentAddress::PublicKey( + *ripemd::Ripemd160::digest(Sha256::digest(&pubkey.serialize())).as_ref(), + ) } pub(crate) mod private { From cf4c982483854957a19b673b05353dfcc4d308b6 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 1 Feb 2022 09:56:19 +0800 Subject: [PATCH 72/79] zcash_primitives::zip32: Include test vectors for internal key components. --- zcash_primitives/src/zip32.rs | 272 ++++++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index 33ebbca94..40408aa1c 100644 --- a/zcash_primitives/src/zip32.rs +++ b/zcash_primitives/src/zip32.rs @@ -823,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 @@ -911,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([ @@ -998,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([ @@ -1083,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, @@ -1146,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, @@ -1211,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, + ], }, ]; From 0c80399fe1772278b885adc691541b8bc826e581 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 1 Feb 2022 11:27:09 +0800 Subject: [PATCH 73/79] zcash_primitives::zip32::tests: Use internal test vectors. --- zcash_primitives/src/zip32.rs | 43 ++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index 40408aa1c..4f3f70cc8 100644 --- a/zcash_primitives/src/zip32.rs +++ b/zcash_primitives/src/zip32.rs @@ -1512,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()); @@ -1526,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); @@ -1574,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); } } } From 6b189f18fff1e959b46d6e6d407bf8451f76e3c6 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 1 Feb 2022 13:41:27 -0700 Subject: [PATCH 74/79] Correct the changelog relating to `store_decrypted_tx` --- zcash_client_backend/CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index cc91f92d1..ec81dbe47 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -71,9 +71,7 @@ and this library adheres to Rust's notion of 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` and it now takes an explicit list of - `(AccountId, Nullifier)` pairs that allows the library to - provide quick access to previously decrypted nullifiers. + `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 From b4ff3f368ee87437c48a66fbba3eee114ad27a21 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 1 Feb 2022 16:08:17 -0700 Subject: [PATCH 75/79] Add test vectors for transparent OVKs. --- zcash_primitives/src/legacy/keys.rs | 251 ++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) diff --git a/zcash_primitives/src/legacy/keys.rs b/zcash_primitives/src/legacy/keys.rs index 98166ba22..932a4bf4b 100644 --- a/zcash_primitives/src/legacy/keys.rs +++ b/zcash_primitives/src/legacy/keys.rs @@ -252,3 +252,254 @@ impl ExternalOvk { 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()); + } + } +} From 1507d1de0adcb2285654665fb613f2861dd04da1 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 1 Feb 2022 16:14:14 -0700 Subject: [PATCH 76/79] Fix incorrect construction of transparent OVKs. --- zcash_primitives/src/legacy/keys.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zcash_primitives/src/legacy/keys.rs b/zcash_primitives/src/legacy/keys.rs index 932a4bf4b..2915f10c6 100644 --- a/zcash_primitives/src/legacy/keys.rs +++ b/zcash_primitives/src/legacy/keys.rs @@ -101,8 +101,8 @@ impl AccountPubKey { &[&[0xd0], &self.0.public_key.serialize()], ); let i_ovk = i_ovk.as_bytes(); - let ovk_internal = InternalOvk(i_ovk[..32].try_into().unwrap()); - let ovk_external = ExternalOvk(i_ovk[32..].try_into().unwrap()); + let ovk_external = ExternalOvk(i_ovk[..32].try_into().unwrap()); + let ovk_internal = InternalOvk(i_ovk[32..].try_into().unwrap()); (ovk_internal, ovk_external) } From 488d13fde363306a50fc64e4767816ad30d106d9 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 2 Feb 2022 11:57:34 -0700 Subject: [PATCH 77/79] Do not delete sent note data in rewind. --- zcash_client_sqlite/CHANGELOG.md | 5 +++ zcash_client_sqlite/src/lib.rs | 6 ++-- zcash_client_sqlite/src/wallet.rs | 42 +++++++++++++++++--------- zcash_client_sqlite/src/wallet/init.rs | 3 +- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/zcash_client_sqlite/CHANGELOG.md b/zcash_client_sqlite/CHANGELOG.md index 4465d3637..59e90a30b 100644 --- a/zcash_client_sqlite/CHANGELOG.md +++ b/zcash_client_sqlite/CHANGELOG.md @@ -38,6 +38,11 @@ and this library adheres to Rust's notion of 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 distinguising 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 diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index e89ab3769..50acbac7a 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -181,11 +181,11 @@ impl WalletDb

{ 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) diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index c8824ce83..720873d7a 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -49,6 +49,23 @@ use { 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 { @@ -580,18 +597,9 @@ pub(crate) fn rewind_to_height( &[u32::from(block_height)], )?; - // Rewind sent notes - wdb.conn.execute( - "DELETE FROM sent_notes - WHERE id_note IN ( - SELECT sn.id_note - FROM sent_notes sn - LEFT OUTER JOIN transactions tx - ON tx.id_tx = sn.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( @@ -1096,7 +1104,8 @@ pub fn put_sent_note<'a, P: consensus::Parameters>( ivalue, &memo.map(|m| m.as_slice()), tx_ref, - output_index as i64 + PoolType::Sapling.typecode(), + output_index as i64, ])? == 0 { // It isn't there, so insert. @@ -1130,7 +1139,8 @@ pub fn put_sent_utxo<'a, P: consensus::Parameters>( ivalue, (None::<&[u8]>), tx_ref, - output_index as i64 + PoolType::Transparent.typecode(), + output_index as i64, ])? == 0 { // It isn't there, so insert. @@ -1164,6 +1174,7 @@ pub fn insert_sent_note<'a, P: consensus::Parameters>( 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, @@ -1192,11 +1203,12 @@ pub fn insert_sent_utxo<'a, P: consensus::Parameters>( 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]>) + (None::<&[u8]>), ])?; Ok(()) diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 1ca07ce91..28e0e3443 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -111,6 +111,7 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> 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, @@ -118,7 +119,7 @@ pub fn init_wallet_db

(wdb: &WalletDb

) -> 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, )?; From cdd899dda181cbda1b1ffa20142ad5c86f519baf Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 2 Feb 2022 12:53:59 -0700 Subject: [PATCH 78/79] Fix documentation. Co-authored-by: str4d --- zcash_client_backend/src/data_api.rs | 9 ++++----- zcash_client_backend/src/data_api/wallet.rs | 5 +++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 1ca6e2e2f..aba7d2a91 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -163,13 +163,12 @@ pub trait WalletRead { block_height: BlockHeight, ) -> Result)>, Self::Error>; - /// Returns the nullifiers for notes that the wallet is tracking, - /// along with their associated account IDs, that have not yet - /// been confirmed as a consequence of the spending transaction - /// being included in a block. + /// 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, Self::Error>; - /// Returns all nullifiers for notes that the wallet is tracing + /// 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, Self::Error>; diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 9262118bc..f2f7eeec1 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -210,8 +210,9 @@ where /// 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 the -/// activation of ZIP 244 the txid would be a reasonable value for this type. +/// 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. From 3699a6df97978b8a87099aeec4ce4e280cf5f3d9 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 2 Feb 2022 14:00:05 -0700 Subject: [PATCH 79/79] Fix typos. --- zcash_client_backend/src/data_api/wallet.rs | 2 +- zcash_client_sqlite/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index f2f7eeec1..c81c43085 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -415,7 +415,7 @@ where /// * `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 -/// those shielding transactions to the user. +/// 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. diff --git a/zcash_client_sqlite/CHANGELOG.md b/zcash_client_sqlite/CHANGELOG.md index 59e90a30b..1050aebda 100644 --- a/zcash_client_sqlite/CHANGELOG.md +++ b/zcash_client_sqlite/CHANGELOG.md @@ -39,7 +39,7 @@ and this library adheres to Rust's notion of 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 distinguising between Sapling and transparent outputs + 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.