![adityapk@gmail.com](/assets/img/avatar_default.png)
7 changed files with 803 additions and 6 deletions
@ -1,2 +1,3 @@ |
|||||
target/ |
target/ |
||||
Cargo.lock |
Cargo.lock |
||||
|
.vscode/ |
@ -0,0 +1,56 @@ |
|||||
|
//! Structs for handling supported address types.
|
||||
|
|
||||
|
use pairing::bls12_381::Bls12; |
||||
|
use sapling_crypto::primitives::PaymentAddress; |
||||
|
use zcash_client_backend::encoding::{decode_payment_address, decode_transparent_address}; |
||||
|
use zcash_primitives::legacy::TransparentAddress; |
||||
|
|
||||
|
use zcash_client_backend::constants::testnet::{ |
||||
|
B58_PUBKEY_ADDRESS_PREFIX, B58_SCRIPT_ADDRESS_PREFIX, HRP_SAPLING_PAYMENT_ADDRESS, |
||||
|
}; |
||||
|
|
||||
|
/// An address that funds can be sent to.
|
||||
|
pub enum RecipientAddress { |
||||
|
Shielded(PaymentAddress<Bls12>), |
||||
|
Transparent(TransparentAddress), |
||||
|
} |
||||
|
|
||||
|
impl From<PaymentAddress<Bls12>> for RecipientAddress { |
||||
|
fn from(addr: PaymentAddress<Bls12>) -> Self { |
||||
|
RecipientAddress::Shielded(addr) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
impl From<TransparentAddress> for RecipientAddress { |
||||
|
fn from(addr: TransparentAddress) -> Self { |
||||
|
RecipientAddress::Transparent(addr) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
impl RecipientAddress { |
||||
|
pub fn from_str(s: &str) -> Option<Self> { |
||||
|
if let Some(pa) = match decode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, s) { |
||||
|
Ok(ret) => ret, |
||||
|
Err(e) => { |
||||
|
eprintln!("{}", e); |
||||
|
return None; |
||||
|
} |
||||
|
} { |
||||
|
Some(RecipientAddress::Shielded(pa)) |
||||
|
} else if let Some(addr) = match decode_transparent_address( |
||||
|
&B58_PUBKEY_ADDRESS_PREFIX, |
||||
|
&B58_SCRIPT_ADDRESS_PREFIX, |
||||
|
s, |
||||
|
) { |
||||
|
Ok(ret) => ret, |
||||
|
Err(e) => { |
||||
|
eprintln!("{}", e); |
||||
|
return None; |
||||
|
} |
||||
|
} { |
||||
|
Some(RecipientAddress::Transparent(addr)) |
||||
|
} else { |
||||
|
None |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,530 @@ |
|||||
|
use std::time::SystemTime; |
||||
|
|
||||
|
use pairing::bls12_381::Bls12; |
||||
|
use sapling_crypto::primitives::{Diversifier, Note, PaymentAddress}; |
||||
|
use std::cmp; |
||||
|
use std::collections::HashMap; |
||||
|
use std::sync::{Arc, RwLock}; |
||||
|
use protobuf::*; |
||||
|
use zcash_client_backend::{ |
||||
|
constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS, encoding::encode_payment_address, |
||||
|
proto::compact_formats::CompactBlock, welding_rig::scan_block, |
||||
|
}; |
||||
|
use zcash_primitives::{ |
||||
|
block::BlockHash, |
||||
|
merkle_tree::{CommitmentTree, IncrementalWitness}, |
||||
|
sapling::Node, |
||||
|
transaction::{ |
||||
|
builder::{Builder, DEFAULT_FEE}, |
||||
|
components::Amount, |
||||
|
TxId, |
||||
|
}, |
||||
|
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, |
||||
|
JUBJUB, |
||||
|
}; |
||||
|
|
||||
|
use crate::address; |
||||
|
use crate::prover; |
||||
|
|
||||
|
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||
|
// allocator.
|
||||
|
#[cfg(feature = "wee_alloc")] |
||||
|
#[global_allocator] |
||||
|
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; |
||||
|
|
||||
|
const ANCHOR_OFFSET: u32 = 10; |
||||
|
|
||||
|
const SAPLING_ACTIVATION_HEIGHT: i32 = 280_000; |
||||
|
|
||||
|
|
||||
|
fn now() -> f64 { |
||||
|
// web_sys::window()
|
||||
|
// .expect("should have a Window")
|
||||
|
// .performance()
|
||||
|
// .expect("should have a Performance")
|
||||
|
// .now()
|
||||
|
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() as f64
|
||||
|
} |
||||
|
|
||||
|
struct BlockData { |
||||
|
height: i32, |
||||
|
hash: BlockHash, |
||||
|
tree: CommitmentTree<Node>, |
||||
|
} |
||||
|
|
||||
|
struct SaplingNoteData { |
||||
|
account: usize, |
||||
|
diversifier: Diversifier, |
||||
|
note: Note<Bls12>, |
||||
|
witnesses: Vec<IncrementalWitness<Node>>, |
||||
|
nullifier: [u8; 32], |
||||
|
spent: Option<TxId>, |
||||
|
} |
||||
|
|
||||
|
impl SaplingNoteData { |
||||
|
fn new( |
||||
|
extfvk: &ExtendedFullViewingKey, |
||||
|
output: zcash_client_backend::wallet::WalletShieldedOutput, |
||||
|
witness: IncrementalWitness<Node>, |
||||
|
) -> Self { |
||||
|
let nf = { |
||||
|
let mut nf = [0; 32]; |
||||
|
nf.copy_from_slice( |
||||
|
&output |
||||
|
.note |
||||
|
.nf(&extfvk.fvk.vk, witness.position() as u64, &JUBJUB), |
||||
|
); |
||||
|
nf |
||||
|
}; |
||||
|
|
||||
|
SaplingNoteData { |
||||
|
account: output.account, |
||||
|
diversifier: output.to.diversifier, |
||||
|
note: output.note, |
||||
|
witnesses: vec![witness], |
||||
|
nullifier: nf, |
||||
|
spent: None, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
struct WalletTx { |
||||
|
block: i32, |
||||
|
notes: Vec<SaplingNoteData>, |
||||
|
} |
||||
|
|
||||
|
struct SpendableNote { |
||||
|
txid: TxId, |
||||
|
nullifier: [u8; 32], |
||||
|
diversifier: Diversifier, |
||||
|
note: Note<Bls12>, |
||||
|
witness: IncrementalWitness<Node>, |
||||
|
} |
||||
|
|
||||
|
impl SpendableNote { |
||||
|
fn from(txid: TxId, nd: &SaplingNoteData, anchor_offset: usize) -> Option<Self> { |
||||
|
if nd.spent.is_none() { |
||||
|
let witness = nd.witnesses.get(nd.witnesses.len() - anchor_offset - 1); |
||||
|
|
||||
|
witness.map(|w| SpendableNote { |
||||
|
txid, |
||||
|
nullifier: nd.nullifier, |
||||
|
diversifier: nd.diversifier, |
||||
|
note: nd.note.clone(), |
||||
|
witness: w.clone(), |
||||
|
}) |
||||
|
} else { |
||||
|
None |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
pub struct Client { |
||||
|
extsks: [ExtendedSpendingKey; 1], |
||||
|
extfvks: [ExtendedFullViewingKey; 1], |
||||
|
address: PaymentAddress<Bls12>, |
||||
|
blocks: Arc<RwLock<Vec<BlockData>>>, |
||||
|
txs: Arc<RwLock<HashMap<TxId, WalletTx>>>, |
||||
|
} |
||||
|
|
||||
|
/// Public methods, exported to JavaScript.
|
||||
|
impl Client { |
||||
|
pub fn new() -> Self { |
||||
|
|
||||
|
let extsk = ExtendedSpendingKey::master(&[0; 32]); |
||||
|
let extfvk = ExtendedFullViewingKey::from(&extsk); |
||||
|
let address = extfvk.default_address().unwrap().1; |
||||
|
|
||||
|
Client { |
||||
|
extsks: [extsk], |
||||
|
extfvks: [extfvk], |
||||
|
address, |
||||
|
blocks: Arc::new(RwLock::new(vec![])), |
||||
|
txs: Arc::new(RwLock::new(HashMap::new())), |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
pub fn set_initial_block(&self, height: i32, hash: &str, sapling_tree: &str) -> bool { |
||||
|
let mut blocks = self.blocks.write().unwrap(); |
||||
|
if !blocks.is_empty() { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
let hash = match hex::decode(hash) { |
||||
|
Ok(hash) => BlockHash::from_slice(&hash), |
||||
|
Err(e) => { |
||||
|
eprintln!("{}", e); |
||||
|
return false; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
let sapling_tree = match hex::decode(sapling_tree) { |
||||
|
Ok(tree) => tree, |
||||
|
Err(e) => { |
||||
|
eprintln!("{}", e); |
||||
|
return false; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
if let Ok(tree) = CommitmentTree::read(&sapling_tree[..]) { |
||||
|
blocks.push(BlockData { height, hash, tree }); |
||||
|
true |
||||
|
} else { |
||||
|
false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
pub fn last_scanned_height(&self) -> i32 { |
||||
|
self.blocks |
||||
|
.read() |
||||
|
.unwrap() |
||||
|
.last() |
||||
|
.map(|block| block.height) |
||||
|
.unwrap_or(SAPLING_ACTIVATION_HEIGHT - 1) |
||||
|
} |
||||
|
|
||||
|
/// Determines the target height for a transaction, and the offset from which to
|
||||
|
/// select anchors, based on the current synchronised block chain.
|
||||
|
fn get_target_height_and_anchor_offset(&self) -> Option<(u32, usize)> { |
||||
|
match { |
||||
|
let blocks = self.blocks.read().unwrap(); |
||||
|
( |
||||
|
blocks.first().map(|block| block.height as u32), |
||||
|
blocks.last().map(|block| block.height as u32), |
||||
|
) |
||||
|
} { |
||||
|
(Some(min_height), Some(max_height)) => { |
||||
|
let target_height = max_height + 1; |
||||
|
|
||||
|
// Select an anchor ANCHOR_OFFSET back from the target block,
|
||||
|
// unless that would be before the earliest block we have.
|
||||
|
let anchor_height = |
||||
|
cmp::max(target_height.saturating_sub(ANCHOR_OFFSET), min_height); |
||||
|
|
||||
|
Some((target_height, (target_height - anchor_height) as usize)) |
||||
|
} |
||||
|
_ => None, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
pub fn address(&self) -> String { |
||||
|
encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &self.address) |
||||
|
} |
||||
|
|
||||
|
// TODO: This will be inaccurate if the balance exceeds a u32, but u64 -> JavaScript
|
||||
|
// requires BigUint64Array which has limited support across browsers, and is not
|
||||
|
// implemented in the LTS version of Node.js. For now, let's assume that no one is
|
||||
|
// going to use a web wallet with more than ~21 TAZ.
|
||||
|
pub fn balance(&self) -> u32 { |
||||
|
self.txs |
||||
|
.read() |
||||
|
.unwrap() |
||||
|
.values() |
||||
|
.map(|tx| { |
||||
|
tx.notes |
||||
|
.iter() |
||||
|
.map(|nd| if nd.spent.is_none() { nd.note.value } else { 0 }) |
||||
|
.sum::<u64>() |
||||
|
}) |
||||
|
.sum::<u64>() as u32 |
||||
|
} |
||||
|
|
||||
|
// TODO: This will be inaccurate if the balance exceeds a u32, but u64 -> JavaScript
|
||||
|
// requires BigUint64Array which has limited support across browsers, and is not
|
||||
|
// implemented in the LTS version of Node.js. For now, let's assume that no one is
|
||||
|
// going to use a web wallet with more than ~21 TAZ.
|
||||
|
pub fn verified_balance(&self) -> u32 { |
||||
|
let anchor_height = match self.get_target_height_and_anchor_offset() { |
||||
|
Some((height, anchor_offset)) => height - anchor_offset as u32, |
||||
|
None => return 0, |
||||
|
}; |
||||
|
|
||||
|
self.txs |
||||
|
.read() |
||||
|
.unwrap() |
||||
|
.values() |
||||
|
.map(|tx| { |
||||
|
if tx.block as u32 <= anchor_height { |
||||
|
tx.notes |
||||
|
.iter() |
||||
|
.map(|nd| if nd.spent.is_none() { nd.note.value } else { 0 }) |
||||
|
.sum::<u64>() |
||||
|
} else { |
||||
|
0 |
||||
|
} |
||||
|
}) |
||||
|
.sum::<u64>() as u32 |
||||
|
} |
||||
|
|
||||
|
pub fn scan_block(&self, block: &[u8]) -> bool { |
||||
|
let block: CompactBlock = match parse_from_bytes(block) { |
||||
|
Ok(block) => block, |
||||
|
Err(e) => { |
||||
|
eprintln!("Could not parse CompactBlock from bytes: {}", e); |
||||
|
return false; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// Scanned blocks MUST be height-sequential.
|
||||
|
let height = block.get_height() as i32; |
||||
|
if height == self.last_scanned_height() { |
||||
|
// If the last scanned block is rescanned, check it still matches.
|
||||
|
if let Some(hash) = self.blocks.read().unwrap().last().map(|block| block.hash) { |
||||
|
if block.hash() != hash { |
||||
|
eprintln!("Block hash does not match"); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
return true; |
||||
|
} else if height != (self.last_scanned_height() + 1) { |
||||
|
eprintln!( |
||||
|
"Block is not height-sequential (expected {}, found {})", |
||||
|
self.last_scanned_height() + 1, |
||||
|
height |
||||
|
); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// Get the most recent scanned data.
|
||||
|
let mut block_data = BlockData { |
||||
|
height, |
||||
|
hash: block.hash(), |
||||
|
tree: self |
||||
|
.blocks |
||||
|
.read() |
||||
|
.unwrap() |
||||
|
.last() |
||||
|
.map(|block| block.tree.clone()) |
||||
|
.unwrap_or(CommitmentTree::new()), |
||||
|
}; |
||||
|
let mut txs = self.txs.write().unwrap(); |
||||
|
|
||||
|
// Create a Vec containing all unspent nullifiers.
|
||||
|
let nfs: Vec<_> = txs |
||||
|
.iter() |
||||
|
.map(|(txid, tx)| { |
||||
|
let txid = *txid; |
||||
|
tx.notes.iter().filter_map(move |nd| { |
||||
|
if nd.spent.is_none() { |
||||
|
Some((nd.nullifier, nd.account, txid)) |
||||
|
} else { |
||||
|
None |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
.flatten() |
||||
|
.collect(); |
||||
|
|
||||
|
// Prepare the note witnesses for updating
|
||||
|
for tx in txs.values_mut() { |
||||
|
for nd in tx.notes.iter_mut() { |
||||
|
// Duplicate the most recent witness
|
||||
|
if let Some(witness) = nd.witnesses.last() { |
||||
|
nd.witnesses.push(witness.clone()); |
||||
|
} |
||||
|
// Trim the oldest witnesses
|
||||
|
nd.witnesses = nd |
||||
|
.witnesses |
||||
|
.split_off(nd.witnesses.len().saturating_sub(100)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
let new_txs = { |
||||
|
let nf_refs: Vec<_> = nfs.iter().map(|(nf, acc, _)| (&nf[..], *acc)).collect(); |
||||
|
|
||||
|
// Create a single mutable slice of all the newly-added witnesses.
|
||||
|
let mut witness_refs: Vec<_> = txs |
||||
|
.values_mut() |
||||
|
.map(|tx| tx.notes.iter_mut().filter_map(|nd| nd.witnesses.last_mut())) |
||||
|
.flatten() |
||||
|
.collect(); |
||||
|
|
||||
|
scan_block( |
||||
|
block, |
||||
|
&self.extfvks, |
||||
|
&nf_refs[..], |
||||
|
&mut block_data.tree, |
||||
|
&mut witness_refs[..], |
||||
|
) |
||||
|
}; |
||||
|
|
||||
|
for (tx, new_witnesses) in new_txs { |
||||
|
// Mark notes as spent.
|
||||
|
for spend in &tx.shielded_spends { |
||||
|
let txid = nfs |
||||
|
.iter() |
||||
|
.find(|(nf, _, _)| &nf[..] == &spend.nf[..]) |
||||
|
.unwrap() |
||||
|
.2; |
||||
|
let mut spent_note = txs |
||||
|
.get_mut(&txid) |
||||
|
.unwrap() |
||||
|
.notes |
||||
|
.iter_mut() |
||||
|
.find(|nd| &nd.nullifier[..] == &spend.nf[..]) |
||||
|
.unwrap(); |
||||
|
spent_note.spent = Some(tx.txid); |
||||
|
} |
||||
|
|
||||
|
// Find the existing transaction entry, or create a new one.
|
||||
|
if !txs.contains_key(&tx.txid) { |
||||
|
let tx_entry = WalletTx { |
||||
|
block: block_data.height, |
||||
|
notes: vec![], |
||||
|
}; |
||||
|
txs.insert(tx.txid, tx_entry); |
||||
|
} |
||||
|
let tx_entry = txs.get_mut(&tx.txid).unwrap(); |
||||
|
|
||||
|
// Save notes.
|
||||
|
for (output, witness) in tx |
||||
|
.shielded_outputs |
||||
|
.into_iter() |
||||
|
.zip(new_witnesses.into_iter()) |
||||
|
{ |
||||
|
tx_entry.notes.push(SaplingNoteData::new( |
||||
|
&self.extfvks[output.account], |
||||
|
output, |
||||
|
witness, |
||||
|
)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Store scanned data for this block.
|
||||
|
self.blocks.write().unwrap().push(block_data); |
||||
|
|
||||
|
true |
||||
|
} |
||||
|
|
||||
|
pub fn send_to_address( |
||||
|
&self, |
||||
|
consensus_branch_id: u32, |
||||
|
spend_params: &[u8], |
||||
|
output_params: &[u8], |
||||
|
to: &str, |
||||
|
value: u32, |
||||
|
) -> Option<Box<[u8]>> { |
||||
|
let start_time = now(); |
||||
|
println!( |
||||
|
"0: Creating transaction sending {} tazoshis to {}", |
||||
|
value, |
||||
|
to |
||||
|
); |
||||
|
|
||||
|
let extsk = &self.extsks[0]; |
||||
|
let extfvk = &self.extfvks[0]; |
||||
|
let ovk = extfvk.fvk.ovk; |
||||
|
|
||||
|
let to = match address::RecipientAddress::from_str(to) { |
||||
|
Some(to) => to, |
||||
|
None => { |
||||
|
eprintln!("Invalid recipient address"); |
||||
|
return None; |
||||
|
} |
||||
|
}; |
||||
|
let value = Amount(value as i64); |
||||
|
|
||||
|
// Target the next block, assuming we are up-to-date.
|
||||
|
let (height, anchor_offset) = match self.get_target_height_and_anchor_offset() { |
||||
|
Some(res) => res, |
||||
|
None => { |
||||
|
eprintln!("Cannot send funds before scanning any blocks"); |
||||
|
return None; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// Select notes to cover the target value
|
||||
|
println!("{}: Selecting notes", now() - start_time); |
||||
|
let target_value = value.0 + DEFAULT_FEE.0; |
||||
|
let notes: Vec<_> = self |
||||
|
.txs |
||||
|
.read() |
||||
|
.unwrap() |
||||
|
.iter() |
||||
|
.map(|(txid, tx)| tx.notes.iter().map(move |note| (*txid, note))) |
||||
|
.flatten() |
||||
|
.filter_map(|(txid, note)| SpendableNote::from(txid, note, anchor_offset)) |
||||
|
.scan(0, |running_total, spendable| { |
||||
|
let value = spendable.note.value; |
||||
|
let ret = if *running_total < target_value as u64 { |
||||
|
Some(spendable) |
||||
|
} else { |
||||
|
None |
||||
|
}; |
||||
|
*running_total = *running_total + value; |
||||
|
ret |
||||
|
}) |
||||
|
.collect(); |
||||
|
|
||||
|
// Confirm we were able to select sufficient value
|
||||
|
let selected_value = notes |
||||
|
.iter() |
||||
|
.map(|selected| selected.note.value) |
||||
|
.sum::<u64>(); |
||||
|
if selected_value < target_value as u64 { |
||||
|
eprintln!( |
||||
|
"Insufficient funds (have {}, need {})", |
||||
|
selected_value, target_value |
||||
|
); |
||||
|
return None; |
||||
|
} |
||||
|
|
||||
|
// Create the transaction
|
||||
|
println!("{}: Adding {} inputs", now() - start_time, notes.len()); |
||||
|
let mut builder = Builder::new(height); |
||||
|
for selected in notes.iter() { |
||||
|
if let Err(e) = builder.add_sapling_spend( |
||||
|
extsk.clone(), |
||||
|
selected.diversifier, |
||||
|
selected.note.clone(), |
||||
|
selected.witness.clone(), |
||||
|
) { |
||||
|
eprintln!("Error adding note: {:?}", e); |
||||
|
return None; |
||||
|
} |
||||
|
} |
||||
|
println!("{}: Adding output", now() - start_time); |
||||
|
if let Err(e) = match to { |
||||
|
address::RecipientAddress::Shielded(to) => { |
||||
|
builder.add_sapling_output(ovk, to.clone(), value, None) |
||||
|
} |
||||
|
address::RecipientAddress::Transparent(to) => { |
||||
|
builder.add_transparent_output(&to, value) |
||||
|
} |
||||
|
} { |
||||
|
eprintln!("Error adding output: {:?}", e); |
||||
|
return None; |
||||
|
} |
||||
|
println!("{}: Building transaction", now() - start_time); |
||||
|
let (tx, _) = match builder.build( |
||||
|
consensus_branch_id, |
||||
|
prover::InMemTxProver::new(spend_params, output_params), |
||||
|
) { |
||||
|
Ok(res) => res, |
||||
|
Err(e) => { |
||||
|
eprintln!("Error creating transaction: {:?}", e); |
||||
|
return None; |
||||
|
} |
||||
|
}; |
||||
|
println!("{}: Transaction created", now() - start_time); |
||||
|
println!("Transaction ID: {}", tx.txid()); |
||||
|
|
||||
|
// Mark notes as spent.
|
||||
|
let mut txs = self.txs.write().unwrap(); |
||||
|
for selected in notes { |
||||
|
let mut spent_note = txs |
||||
|
.get_mut(&selected.txid) |
||||
|
.unwrap() |
||||
|
.notes |
||||
|
.iter_mut() |
||||
|
.find(|nd| &nd.nullifier[..] == &selected.nullifier[..]) |
||||
|
.unwrap(); |
||||
|
spent_note.spent = Some(tx.txid()); |
||||
|
} |
||||
|
|
||||
|
// Return the encoded transaction, so the caller can send it.
|
||||
|
let mut raw_tx = vec![]; |
||||
|
tx.write(&mut raw_tx).unwrap(); |
||||
|
Some(raw_tx.into_boxed_slice()) |
||||
|
} |
||||
|
} |
@ -0,0 +1,122 @@ |
|||||
|
//! Abstractions over the proving system and parameters for ease of use.
|
||||
|
|
||||
|
use bellman::groth16::{prepare_verifying_key, Parameters, PreparedVerifyingKey}; |
||||
|
use pairing::bls12_381::{Bls12, Fr}; |
||||
|
use sapling_crypto::{ |
||||
|
jubjub::{edwards, fs::Fs, Unknown}, |
||||
|
primitives::{Diversifier, PaymentAddress, ProofGenerationKey}, |
||||
|
redjubjub::{PublicKey, Signature}, |
||||
|
}; |
||||
|
use zcash_primitives::{ |
||||
|
merkle_tree::CommitmentTreeWitness, prover::TxProver, sapling::Node, |
||||
|
transaction::components::GROTH_PROOF_SIZE, JUBJUB, |
||||
|
}; |
||||
|
use zcash_proofs::sapling::SaplingProvingContext; |
||||
|
|
||||
|
/// An implementation of [`TxProver`] using Sapling Spend and Output parameters provided
|
||||
|
/// in-memory.
|
||||
|
pub struct InMemTxProver { |
||||
|
spend_params: Parameters<Bls12>, |
||||
|
spend_vk: PreparedVerifyingKey<Bls12>, |
||||
|
output_params: Parameters<Bls12>, |
||||
|
} |
||||
|
|
||||
|
impl InMemTxProver { |
||||
|
pub fn new(spend_params: &[u8], output_params: &[u8]) -> Self { |
||||
|
// Deserialize params
|
||||
|
let spend_params = Parameters::<Bls12>::read(spend_params, false) |
||||
|
.expect("couldn't deserialize Sapling spend parameters file"); |
||||
|
let output_params = Parameters::<Bls12>::read(output_params, false) |
||||
|
.expect("couldn't deserialize Sapling spend parameters file"); |
||||
|
|
||||
|
// Prepare verifying keys
|
||||
|
let spend_vk = prepare_verifying_key(&spend_params.vk); |
||||
|
|
||||
|
InMemTxProver { |
||||
|
spend_params, |
||||
|
spend_vk, |
||||
|
output_params, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
impl TxProver for InMemTxProver { |
||||
|
type SaplingProvingContext = SaplingProvingContext; |
||||
|
|
||||
|
fn new_sapling_proving_context(&self) -> Self::SaplingProvingContext { |
||||
|
SaplingProvingContext::new() |
||||
|
} |
||||
|
|
||||
|
fn spend_proof( |
||||
|
&self, |
||||
|
ctx: &mut Self::SaplingProvingContext, |
||||
|
proof_generation_key: ProofGenerationKey<Bls12>, |
||||
|
diversifier: Diversifier, |
||||
|
rcm: Fs, |
||||
|
ar: Fs, |
||||
|
value: u64, |
||||
|
anchor: Fr, |
||||
|
witness: CommitmentTreeWitness<Node>, |
||||
|
) -> Result< |
||||
|
( |
||||
|
[u8; GROTH_PROOF_SIZE], |
||||
|
edwards::Point<Bls12, Unknown>, |
||||
|
PublicKey<Bls12>, |
||||
|
), |
||||
|
(), |
||||
|
> { |
||||
|
let (proof, cv, rk) = ctx.spend_proof( |
||||
|
proof_generation_key, |
||||
|
diversifier, |
||||
|
rcm, |
||||
|
ar, |
||||
|
value, |
||||
|
anchor, |
||||
|
witness, |
||||
|
&self.spend_params, |
||||
|
&self.spend_vk, |
||||
|
&JUBJUB, |
||||
|
)?; |
||||
|
|
||||
|
let mut zkproof = [0u8; GROTH_PROOF_SIZE]; |
||||
|
proof |
||||
|
.write(&mut zkproof[..]) |
||||
|
.expect("should be able to serialize a proof"); |
||||
|
|
||||
|
Ok((zkproof, cv, rk)) |
||||
|
} |
||||
|
|
||||
|
fn output_proof( |
||||
|
&self, |
||||
|
ctx: &mut Self::SaplingProvingContext, |
||||
|
esk: Fs, |
||||
|
payment_address: PaymentAddress<Bls12>, |
||||
|
rcm: Fs, |
||||
|
value: u64, |
||||
|
) -> ([u8; GROTH_PROOF_SIZE], edwards::Point<Bls12, Unknown>) { |
||||
|
let (proof, cv) = ctx.output_proof( |
||||
|
esk, |
||||
|
payment_address, |
||||
|
rcm, |
||||
|
value, |
||||
|
&self.output_params, |
||||
|
&JUBJUB, |
||||
|
); |
||||
|
|
||||
|
let mut zkproof = [0u8; GROTH_PROOF_SIZE]; |
||||
|
proof |
||||
|
.write(&mut zkproof[..]) |
||||
|
.expect("should be able to serialize a proof"); |
||||
|
|
||||
|
(zkproof, cv) |
||||
|
} |
||||
|
|
||||
|
fn binding_sig( |
||||
|
&self, |
||||
|
ctx: &mut Self::SaplingProvingContext, |
||||
|
value_balance: i64, |
||||
|
sighash: &[u8; 32], |
||||
|
) -> Result<Signature, ()> { |
||||
|
ctx.binding_sig(value_balance, sighash, &JUBJUB) |
||||
|
} |
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
pub fn set_panic_hook() { |
||||
|
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
|
// `set_panic_hook` function at least once during initialization, and then
|
||||
|
// we will get better error messages if our code ever panics.
|
||||
|
//
|
||||
|
// For more details see
|
||||
|
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
|
#[cfg(feature = "console_error_panic_hook")] |
||||
|
console_error_panic_hook::set_once(); |
||||
|
} |
Loading…
Reference in new issue