Skip to content

Commit

Permalink
Autoshielding POC
Browse files Browse the repository at this point in the history
  • Loading branch information
nuttycom committed Feb 6, 2021
1 parent 30d77cb commit 97498f0
Show file tree
Hide file tree
Showing 16 changed files with 509 additions and 200 deletions.
4 changes: 4 additions & 0 deletions zcash_client_backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ jubjub = "0.5.1"
nom = "5.1.2"
protobuf = "2.20"
rand_core = "0.5.1"
ripemd160 = { version = "0.9.1", optional = true }
secp256k1 = { version = "0.19", optional = true }
sha2 = "0.9"
subtle = "2.2.3"
time = "0.2"
zcash_primitives = { version = "0.4", path = "../zcash_primitives" }
Expand All @@ -41,6 +44,7 @@ zcash_client_sqlite = { version = "0.2", path = "../zcash_client_sqlite" }
zcash_proofs = { version = "0.4", path = "../zcash_proofs" }

[features]
transparent-inputs = ["ripemd160", "secp256k1"]
test-dependencies = ["proptest", "zcash_primitives/test-dependencies"]

[badges]
Expand Down
11 changes: 5 additions & 6 deletions zcash_client_backend/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,11 @@ impl RecipientAddress {
RecipientAddress::Shielded(pa) => {
encode_payment_address(params.hrp_sapling_payment_address(), pa)
}
RecipientAddress::Transparent(addr) =>
encode_transparent_address(
&params.b58_pubkey_address_prefix(),
&params.b58_script_address_prefix(),
addr,
)
RecipientAddress::Transparent(addr) => encode_transparent_address(
&params.b58_pubkey_address_prefix(),
&params.b58_script_address_prefix(),
addr,
),
}
}
}
72 changes: 52 additions & 20 deletions zcash_client_backend/src/data_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use zcash_primitives::{
note_encryption::Memo,
primitives::{Note, Nullifier, PaymentAddress},
sapling::Node,
transaction::{components::Amount, Transaction, TxId},
transaction::{
components::{Amount, OutPoint},
Transaction, TxId,
},
zip32::ExtendedFullViewingKey,
};

Expand All @@ -21,7 +24,7 @@ use crate::{
data_api::wallet::ANCHOR_OFFSET,
decrypt::DecryptedOutput,
proto::compact_formats::CompactBlock,
wallet::{AccountId, SpendableNote, WalletTransparentOutput, WalletShieldedOutput, WalletTx},
wallet::{AccountId, SpendableNote, WalletShieldedOutput, WalletTransparentOutput, WalletTx},
};

pub mod chain;
Expand Down Expand Up @@ -142,10 +145,7 @@ pub trait WalletRead {
///
/// This will return `Ok(None)` if the note identifier does not appear in the
/// database as a known note ID.
fn get_memo_as_utf8(
&self,
id_note: Self::NoteRef,
) -> Result<Option<String>, Self::Error>;
fn get_memo_as_utf8(&self, id_note: Self::NoteRef) -> Result<Option<String>, Self::Error>;

/// Returns the note commitment tree at the specified block height.
fn get_commitment_tree(
Expand All @@ -164,25 +164,25 @@ pub trait WalletRead {
fn get_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error>;

/// Return all spendable notes.
fn get_spendable_notes(
fn get_spendable_sapling_notes(
&self,
account: AccountId,
anchor_height: BlockHeight,
) -> Result<Vec<SpendableNote>, Self::Error>;

/// Returns a list of spendable notes sufficient to cover the specified
/// target value, if possible.
fn select_spendable_notes(
fn select_spendable_sapling_notes(
&self,
account: AccountId,
target_value: Amount,
anchor_height: BlockHeight,
) -> Result<Vec<SpendableNote>, Self::Error>;

fn get_confirmed_utxos_for_address(
fn get_spendable_transparent_utxos(
&self,
anchor_height: BlockHeight,
address: &TransparentAddress
address: &TransparentAddress,
) -> Result<Vec<WalletTransparentOutput>, Self::Error>;
}

Expand Down Expand Up @@ -238,7 +238,18 @@ pub trait WalletWrite: WalletRead {
) -> Result<Self::TxRef, Self::Error>;

/// Mark the specified transaction as spent and record the nullifier.
fn mark_spent(&mut self, tx_ref: Self::TxRef, nf: &Nullifier) -> Result<(), Self::Error>;
fn mark_transparent_utxo_spent(
&mut self,
tx_ref: Self::TxRef,
outpoint: &OutPoint,
) -> Result<(), Self::Error>;

/// Mark the specified transaction as spent and record the nullifier.
fn mark_sapling_note_spent(
&mut self,
tx_ref: Self::TxRef,
nf: &Nullifier,
) -> Result<(), Self::Error>;

/// Record a note as having been received, along with its nullifier and the transaction
/// within which the note was created.
Expand Down Expand Up @@ -377,19 +388,23 @@ pub mod testing {
use zcash_primitives::{
block::BlockHash,
consensus::BlockHeight,
legacy::TransparentAddress,
merkle_tree::{CommitmentTree, IncrementalWitness},
note_encryption::Memo,
primitives::{Nullifier, PaymentAddress},
sapling::Node,
transaction::{components::Amount, Transaction, TxId},
transaction::{
components::{Amount, OutPoint},
Transaction, TxId,
},
zip32::ExtendedFullViewingKey,
};

use crate::{
address::RecipientAddress,
decrypt::DecryptedOutput,
proto::compact_formats::CompactBlock,
wallet::{AccountId, SpendableNote, WalletTx},
wallet::{AccountId, SpendableNote, WalletTransparentOutput, WalletTx},
};

use super::{error::Error, BlockSource, ShieldedOutput, WalletRead, WalletWrite};
Expand Down Expand Up @@ -460,10 +475,7 @@ pub mod testing {
Ok(Amount::zero())
}

fn get_memo_as_utf8(
&self,
_id_note: Self::NoteRef,
) -> Result<Option<String>, Self::Error> {
fn get_memo_as_utf8(&self, _id_note: Self::NoteRef) -> Result<Option<String>, Self::Error> {
Ok(None)
}

Expand All @@ -485,22 +497,30 @@ pub mod testing {
Ok(Vec::new())
}

fn get_spendable_notes(
fn get_spendable_sapling_notes(
&self,
_account: AccountId,
_anchor_height: BlockHeight,
) -> Result<Vec<SpendableNote>, Self::Error> {
Ok(Vec::new())
}

fn select_spendable_notes(
fn select_spendable_sapling_notes(
&self,
_account: AccountId,
_target_value: Amount,
_anchor_height: BlockHeight,
) -> Result<Vec<SpendableNote>, Self::Error> {
Ok(Vec::new())
}

fn get_spendable_transparent_utxos(
&self,
_anchor_height: BlockHeight,
_address: &TransparentAddress,
) -> Result<Vec<WalletTransparentOutput>, Self::Error> {
Ok(Vec::new())
}
}

impl WalletWrite for MockWalletDB {
Expand Down Expand Up @@ -541,7 +561,19 @@ pub mod testing {
Ok(TxId([0u8; 32]))
}

fn mark_spent(&mut self, _tx_ref: Self::TxRef, _nf: &Nullifier) -> Result<(), Self::Error> {
fn mark_transparent_utxo_spent(
&mut self,
_tx_ref: Self::TxRef,
_out: &OutPoint,
) -> Result<(), Self::Error> {
Ok(())
}

fn mark_sapling_note_spent(
&mut self,
_tx_ref: Self::TxRef,
_nf: &Nullifier,
) -> Result<(), Self::Error> {
Ok(())
}

Expand Down
2 changes: 1 addition & 1 deletion zcash_client_backend/src/data_api/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ where

// Mark notes as spent and remove them from the scanning cache
for spend in &tx.shielded_spends {
up.mark_spent(tx_row, &spend.nf)?;
up.mark_sapling_note_spent(tx_row, &spend.nf)?;
}

// remove spent nullifiers from the nullifier set
Expand Down
2 changes: 2 additions & 0 deletions zcash_client_backend/src/data_api/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub enum ChainInvalid {
#[derive(Debug)]
pub enum Error<NoteId> {
/// 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.
Expand Down
125 changes: 123 additions & 2 deletions zcash_client_backend/src/data_api/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -190,7 +196,8 @@ where
.and_then(|x| x.ok_or(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();
Expand Down Expand Up @@ -256,7 +263,7 @@ where
// 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 &tx.shielded_spends {
up.mark_spent(tx_ref, &spend.nullifier)?;
up.mark_sapling_note_spent(tx_ref, &spend.nullifier)?;
}

up.insert_sent_note(tx_ref, output_index as usize, account, to, value, memo)?;
Expand All @@ -266,3 +273,117 @@ where
})
.map_err(|e| e.into())
}

#[cfg(feature = "transparent-inputs")]
pub fn shield_funds<E, N, P, D, R>(
wallet_db: &mut D,
params: &P,
prover: impl TxProver,
account: AccountId,
sk: &secp256k1::key::SecretKey,
extsk: &ExtendedSpendingKey,
memo: &Memo,
) -> Result<D::TxRef, E>
where
E: From<Error<N>>,
P: consensus::Parameters,
R: Copy + Debug,
D: WalletWrite<Error = E, TxRef = R>,
{
let (latest_scanned_height, latest_anchor) = wallet_db
.get_target_and_anchor_heights()
.and_then(|x| x.ok_or(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(latest_anchor, &taddr)?;
let total_amount = utxos.iter().map(|utxo| utxo.value).sum::<Amount>();

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.clone(),
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.",
);

// Update the database atomically, to ensure the result is internally consistent.
wallet_db.transactionally(|up| {
let created = time::OffsetDateTime::now_utc();
let tx_ref = up.put_tx_data(&tx, Some(created))?;

for utxo in utxos {
up.mark_transparent_utxo_spent(tx_ref, &utxo.outpoint)?;
}

// Mark notes as spent.
//
// This locks the notes so they aren't selected again by a subsequent call to
// create_spend_to_address() before this transaction has been mined (at which point the notes
// get re-marked as spent).
//
// 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 &tx.shielded_spends {
up.mark_sapling_note_spent(tx_ref, &spend.nullifier)?;
}

// We only called add_sapling_output() once.
up.insert_sent_note(
tx_ref,
output_index as usize,
account,
&RecipientAddress::from(z_address),
amount_to_shield,
Some(memo.clone()),
)?;

// Return the row number of the transaction, so the caller can fetch it for sending.
Ok(tx_ref)
})
}
Loading

0 comments on commit 97498f0

Please sign in to comment.