mirror of
https://github.com/Qortal/piratewallet-light-cli.git
synced 2025-02-14 10:55:47 +00:00
Add transaction creation to WASM backend
This commit is contained in:
parent
b4252fa9d3
commit
96e6f1b5fe
@ -16,6 +16,10 @@ protobuf = "2"
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = { version = "0.3", features = ["console"] }
|
||||
|
||||
# We don't use these crates directly, but we add it as a dependency so we can
|
||||
# enable necessary features for WASM compatibility.
|
||||
rand = { version = "0.6", features = ["wasm-bindgen"] }
|
||||
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||
@ -29,6 +33,12 @@ console_error_panic_hook = { version = "0.1.1", optional = true }
|
||||
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
|
||||
wee_alloc = { version = "0.4.2", optional = true }
|
||||
|
||||
[dependencies.bellman]
|
||||
git = "https://github.com/str4d/librustzcash.git"
|
||||
branch = "demo-wasm"
|
||||
default-features = false
|
||||
features = ["groth16"]
|
||||
|
||||
[dependencies.pairing]
|
||||
git = "https://github.com/str4d/librustzcash.git"
|
||||
branch = "demo-wasm"
|
||||
@ -36,14 +46,22 @@ branch = "demo-wasm"
|
||||
[dependencies.sapling-crypto]
|
||||
git = "https://github.com/str4d/librustzcash.git"
|
||||
branch = "demo-wasm"
|
||||
default-features = false
|
||||
|
||||
[dependencies.zcash_client_backend]
|
||||
git = "https://github.com/str4d/librustzcash.git"
|
||||
branch = "demo-wasm"
|
||||
default-features = false
|
||||
|
||||
[dependencies.zcash_primitives]
|
||||
git = "https://github.com/str4d/librustzcash.git"
|
||||
branch = "demo-wasm"
|
||||
default-features = false
|
||||
|
||||
[dependencies.zcash_proofs]
|
||||
git = "https://github.com/str4d/librustzcash.git"
|
||||
branch = "demo-wasm"
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.2"
|
||||
|
56
zcash-client-backend-wasm/src/address.rs
Normal file
56
zcash-client-backend-wasm/src/address.rs
Normal file
@ -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) => {
|
||||
error!("{}", 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) => {
|
||||
error!("{}", e);
|
||||
return None;
|
||||
}
|
||||
} {
|
||||
Some(RecipientAddress::Transparent(addr))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,23 @@
|
||||
macro_rules! log {
|
||||
( $( $t:tt )* ) => {
|
||||
web_sys::console::log_1(&format!( $( $t )* ).into());
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! error {
|
||||
( $( $t:tt )* ) => {
|
||||
web_sys::console::error_1(&format!( $( $t )* ).into());
|
||||
};
|
||||
}
|
||||
|
||||
mod address;
|
||||
mod prover;
|
||||
mod utils;
|
||||
|
||||
use pairing::bls12_381::Bls12;
|
||||
use protobuf::parse_from_bytes;
|
||||
use sapling_crypto::primitives::{Note, PaymentAddress};
|
||||
use sapling_crypto::primitives::{Diversifier, Note, PaymentAddress};
|
||||
use std::cmp;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use zcash_client_backend::{
|
||||
@ -19,7 +28,11 @@ use zcash_primitives::{
|
||||
block::BlockHash,
|
||||
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||
sapling::Node,
|
||||
transaction::TxId,
|
||||
transaction::{
|
||||
builder::{Builder, DEFAULT_FEE},
|
||||
components::Amount,
|
||||
TxId,
|
||||
},
|
||||
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
||||
JUBJUB,
|
||||
};
|
||||
@ -32,6 +45,8 @@ use wasm_bindgen::prelude::*;
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
const ANCHOR_OFFSET: u32 = 10;
|
||||
|
||||
const SAPLING_ACTIVATION_HEIGHT: i32 = 280_000;
|
||||
|
||||
#[wasm_bindgen]
|
||||
@ -47,6 +62,7 @@ struct BlockData {
|
||||
|
||||
struct SaplingNoteData {
|
||||
account: usize,
|
||||
diversifier: Diversifier,
|
||||
note: Note<Bls12>,
|
||||
witnesses: Vec<IncrementalWitness<Node>>,
|
||||
nullifier: [u8; 32],
|
||||
@ -71,8 +87,9 @@ impl SaplingNoteData {
|
||||
|
||||
SaplingNoteData {
|
||||
account: output.account,
|
||||
diversifier: output.to.diversifier,
|
||||
note: output.note,
|
||||
witnesses: vec![],
|
||||
witnesses: vec![witness],
|
||||
nullifier: nf,
|
||||
spent: None,
|
||||
}
|
||||
@ -84,6 +101,32 @@ struct WalletTx {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Client {
|
||||
extsks: [ExtendedSpendingKey; 1],
|
||||
@ -151,6 +194,30 @@ impl Client {
|
||||
.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)
|
||||
}
|
||||
@ -312,4 +379,124 @@ impl Client {
|
||||
|
||||
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 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 => {
|
||||
error!("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 => {
|
||||
error!("Cannot send funds before scanning any blocks");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Select notes to cover the target value
|
||||
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 {
|
||||
error!(
|
||||
"Insufficient funds (have {}, need {})",
|
||||
selected_value, target_value
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Create the transaction
|
||||
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(),
|
||||
) {
|
||||
error!("Error adding note: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
} {
|
||||
error!("Error adding output: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
let (tx, _) = match builder.build(
|
||||
consensus_branch_id,
|
||||
prover::InMemTxProver::new(spend_params, output_params),
|
||||
) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
error!("Error creating transaction: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
log!("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())
|
||||
}
|
||||
}
|
||||
|
122
zcash-client-backend-wasm/src/prover.rs
Normal file
122
zcash-client-backend-wasm/src/prover.rs
Normal file
@ -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)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user