mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-04-28 03:00:18 -04:00
508 lines
16 KiB
Rust
508 lines
16 KiB
Rust
use async_std::sync::{Arc, Mutex};
|
|
use bellman::groth16;
|
|
use bls12_381::Bls12;
|
|
use log::*;
|
|
use std::net::SocketAddr;
|
|
use std::path::PathBuf;
|
|
|
|
use crate::{
|
|
blockchain::{rocks::columns, Rocks, RocksColumn, Slab},
|
|
crypto::{
|
|
load_params,
|
|
merkle::{CommitmentTree, IncrementalWitness},
|
|
merkle_node::MerkleNode,
|
|
note::{EncryptedNote, Note},
|
|
nullifier::Nullifier,
|
|
save_params, setup_mint_prover, setup_spend_prover, OwnCoin,
|
|
},
|
|
serial::{Decodable, Encodable},
|
|
service::{GatewayClient, GatewaySlabsSubscriber},
|
|
state::{state_transition, ProgramState, StateUpdate},
|
|
tx,
|
|
wallet::{CashierDbPtr, Keypair, WalletPtr},
|
|
Result,
|
|
};
|
|
|
|
#[derive(Debug)]
|
|
pub enum ClientFailed {
|
|
NotEnoughValue(u64),
|
|
InvalidAddress(String),
|
|
InvalidAmount(u64),
|
|
UnableToGetDepositAddress,
|
|
UnableToGetWithdrawAddress,
|
|
DoesNotHaveCashierPublicKey,
|
|
DoesNotHaveKeypair,
|
|
EmptyPassword,
|
|
WalletInitialized,
|
|
KeyExists,
|
|
ClientError(String),
|
|
}
|
|
|
|
pub struct Client {
|
|
pub state: Arc<Mutex<State>>,
|
|
mint_params: bellman::groth16::Parameters<Bls12>,
|
|
spend_params: bellman::groth16::Parameters<Bls12>,
|
|
gateway: GatewayClient,
|
|
pub main_keypair: Keypair,
|
|
}
|
|
|
|
impl Client {
|
|
pub async fn new(
|
|
rocks: Arc<Rocks>,
|
|
gateway_addrs: (SocketAddr, SocketAddr),
|
|
params_paths: (PathBuf, PathBuf),
|
|
wallet: WalletPtr,
|
|
) -> Result<Self> {
|
|
let slabstore = RocksColumn::<columns::Slabs>::new(rocks.clone());
|
|
let merkle_roots = RocksColumn::<columns::MerkleRoots>::new(rocks.clone());
|
|
let nullifiers = RocksColumn::<columns::Nullifiers>::new(rocks);
|
|
|
|
let mint_params_path = params_paths.0.to_str().unwrap_or("mint.params");
|
|
let spend_params_path = params_paths.1.to_str().unwrap_or("spend.params");
|
|
|
|
wallet.init_db().await?;
|
|
|
|
if wallet.get_keypairs()?.is_empty() {
|
|
wallet.key_gen()?;
|
|
}
|
|
|
|
let main_keypair = wallet.get_keypairs()?[0].clone();
|
|
|
|
// Auto create trusted ceremony parameters if they don't exist
|
|
if !params_paths.0.exists() {
|
|
let params = setup_mint_prover();
|
|
save_params(mint_params_path, ¶ms)?;
|
|
}
|
|
if !params_paths.1.exists() {
|
|
let params = setup_spend_prover();
|
|
save_params(spend_params_path, ¶ms)?;
|
|
}
|
|
|
|
// Load trusted setup parameters
|
|
let (mint_params, mint_pvk) = load_params(mint_params_path)?;
|
|
let (spend_params, spend_pvk) = load_params(spend_params_path)?;
|
|
|
|
let state = Arc::new(Mutex::new(State {
|
|
tree: CommitmentTree::empty(),
|
|
merkle_roots,
|
|
nullifiers,
|
|
mint_pvk,
|
|
spend_pvk,
|
|
wallet,
|
|
}));
|
|
|
|
// create gateway client
|
|
debug!(target: "CLIENT", "Creating GatewayClient");
|
|
let gateway = GatewayClient::new(gateway_addrs.0, gateway_addrs.1, slabstore)?;
|
|
|
|
Ok(Self {
|
|
state,
|
|
mint_params,
|
|
spend_params,
|
|
gateway,
|
|
main_keypair,
|
|
})
|
|
}
|
|
|
|
pub async fn start(&mut self) -> Result<()> {
|
|
self.gateway.start().await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn transfer(
|
|
&mut self,
|
|
asset_id: jubjub::Fr,
|
|
pub_key: jubjub::SubgroupPoint,
|
|
amount: u64,
|
|
) -> Result<()> {
|
|
|
|
debug!(target: "CLIENT", "Start transfer {}", amount);
|
|
|
|
if amount == 0 {
|
|
return Err(ClientFailed::InvalidAmount(amount as u64).into());
|
|
}
|
|
|
|
self.send(pub_key, amount, asset_id, false).await?;
|
|
|
|
debug!(target: "CLIENT", "End transfer {}", amount);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn send(
|
|
&mut self,
|
|
pub_key: jubjub::SubgroupPoint,
|
|
amount: u64,
|
|
asset_id: jubjub::Fr,
|
|
clear_input: bool,
|
|
) -> Result<()> {
|
|
|
|
debug!(target: "CLIENT", "Start send {}", amount);
|
|
|
|
let slab = self
|
|
.build_slab_from_tx(pub_key, amount, asset_id, clear_input)
|
|
.await?;
|
|
|
|
self.gateway.put_slab(slab).await?;
|
|
|
|
|
|
debug!(target: "CLIENT", "End send {}", amount);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn build_slab_from_tx(
|
|
&self,
|
|
pub_key: jubjub::SubgroupPoint,
|
|
value: u64,
|
|
asset_id: jubjub::Fr,
|
|
clear_input: bool,
|
|
) -> Result<Slab> {
|
|
|
|
debug!(target: "CLIENT", "Start build slab from tx");
|
|
|
|
let mut clear_inputs: Vec<tx::TransactionBuilderClearInputInfo> = vec![];
|
|
let mut inputs: Vec<tx::TransactionBuilderInputInfo> = vec![];
|
|
let mut outputs: Vec<tx::TransactionBuilderOutputInfo> = vec![];
|
|
|
|
if clear_input {
|
|
let signature_secret = self.main_keypair.private;
|
|
let input = tx::TransactionBuilderClearInputInfo {
|
|
value,
|
|
asset_id,
|
|
signature_secret,
|
|
};
|
|
clear_inputs.push(input);
|
|
} else {
|
|
inputs = self.build_inputs(value, asset_id, &mut outputs).await?;
|
|
}
|
|
|
|
outputs.push(tx::TransactionBuilderOutputInfo {
|
|
value,
|
|
asset_id,
|
|
public: pub_key,
|
|
});
|
|
|
|
let builder = tx::TransactionBuilder {
|
|
clear_inputs,
|
|
inputs,
|
|
outputs,
|
|
};
|
|
|
|
let mut tx_data = vec![];
|
|
{
|
|
let tx = builder.build(&self.mint_params, &self.spend_params);
|
|
tx.encode(&mut tx_data).expect("encode tx");
|
|
}
|
|
|
|
let slab = Slab::new(tx_data);
|
|
|
|
debug!(target: "CLIENT", "End build slab from tx");
|
|
|
|
Ok(slab)
|
|
}
|
|
|
|
async fn build_inputs(
|
|
&self,
|
|
amount: u64,
|
|
asset_id: jubjub::Fr,
|
|
outputs: &mut Vec<tx::TransactionBuilderOutputInfo>,
|
|
) -> Result<Vec<tx::TransactionBuilderInputInfo>> {
|
|
|
|
debug!(target: "CLIENT", "Start build inputs");
|
|
|
|
let mut inputs: Vec<tx::TransactionBuilderInputInfo> = vec![];
|
|
let mut inputs_value: u64 = 0;
|
|
|
|
let own_coins = self.state.lock().await.wallet.get_own_coins()?;
|
|
|
|
for own_coin in own_coins.iter() {
|
|
if inputs_value >= amount {
|
|
break;
|
|
}
|
|
let witness = &own_coin.witness;
|
|
let merkle_path = witness.path().unwrap();
|
|
inputs_value += own_coin.note.value;
|
|
let input = tx::TransactionBuilderInputInfo {
|
|
merkle_path,
|
|
secret: own_coin.secret,
|
|
note: own_coin.note.clone(),
|
|
};
|
|
|
|
inputs.push(input);
|
|
}
|
|
|
|
if inputs_value < amount {
|
|
return Err(ClientFailed::NotEnoughValue(inputs_value).into());
|
|
}
|
|
|
|
if inputs_value > amount {
|
|
let inputs_len = inputs.len();
|
|
let input = &inputs[inputs_len - 1];
|
|
|
|
let return_value: u64 = inputs_value - amount;
|
|
|
|
let own_pub_key = zcash_primitives::constants::SPENDING_KEY_GENERATOR * input.secret;
|
|
|
|
outputs.push(tx::TransactionBuilderOutputInfo {
|
|
value: return_value,
|
|
asset_id,
|
|
public: own_pub_key,
|
|
});
|
|
}
|
|
|
|
debug!(target: "CLIENT", "End build inputs");
|
|
|
|
Ok(inputs)
|
|
}
|
|
|
|
pub async fn connect_to_subscriber_from_cashier(
|
|
&self,
|
|
cashier_wallet: CashierDbPtr,
|
|
notify: async_channel::Sender<(jubjub::SubgroupPoint, u64)>,
|
|
) -> Result<()> {
|
|
// start subscribing
|
|
debug!(target: "CLIENT", "Start subscriber for cashier");
|
|
let gateway_slabs_sub: GatewaySlabsSubscriber =
|
|
self.gateway.start_subscriber().await?;
|
|
|
|
let secret_key = self.main_keypair.private;
|
|
let state = self.state.clone();
|
|
|
|
let task: smol::Task<Result<()>> = smol::spawn(async move {
|
|
loop {
|
|
let slab = gateway_slabs_sub.recv().await?;
|
|
let tx = tx::Transaction::decode(&slab.get_payload()[..])?;
|
|
|
|
let mut state = state.lock().await;
|
|
|
|
let update = state_transition(&state, tx)?;
|
|
|
|
let mut secret_keys: Vec<jubjub::Fr> = vec![secret_key];
|
|
let mut withdraw_keys = cashier_wallet.get_withdraw_private_keys()?;
|
|
secret_keys.append(&mut withdraw_keys);
|
|
|
|
state
|
|
.apply(update, secret_keys.clone(), notify.clone())
|
|
.await?;
|
|
}
|
|
});
|
|
|
|
task.detach();
|
|
|
|
debug!(target: "CLIENT", "End subscriber for cashier");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn connect_to_subscriber(&self) -> Result<()> {
|
|
// start subscribing
|
|
debug!(target: "CLIENT", "Start subscriber");
|
|
let gateway_slabs_sub: GatewaySlabsSubscriber =
|
|
self.gateway.start_subscriber().await?;
|
|
|
|
let (notify, _) = async_channel::unbounded::<(jubjub::SubgroupPoint, u64)>();
|
|
|
|
let secret_key = self.main_keypair.private;
|
|
let state = self.state.clone();
|
|
|
|
let task: smol::Task<Result<()>> = smol::spawn(async move {
|
|
loop {
|
|
let slab = gateway_slabs_sub.recv().await?;
|
|
let tx = tx::Transaction::decode(&slab.get_payload()[..])?;
|
|
|
|
let mut state = state.lock().await;
|
|
|
|
let update = state_transition(&state, tx)?;
|
|
|
|
let secret_keys: Vec<jubjub::Fr> = vec![secret_key];
|
|
|
|
state
|
|
.apply(update, secret_keys.clone(), notify.clone())
|
|
.await?;
|
|
}
|
|
});
|
|
|
|
task.detach();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn init_db(&self) -> Result<()> {
|
|
self.state.lock().await.wallet.init_db().await
|
|
}
|
|
|
|
pub async fn key_gen(&self) -> Result<()> {
|
|
self.state.lock().await.wallet.key_gen()
|
|
}
|
|
|
|
//pub async fn token_and_balances(&self) -> Result<()> {
|
|
// self.state.lock().await.wallet.get_token_ids()
|
|
//}
|
|
|
|
pub async fn get_token_id(&self) -> Result<Vec<jubjub::Fr>> {
|
|
self.state.lock().await.wallet.get_token_id()
|
|
}
|
|
}
|
|
|
|
pub struct State {
|
|
// The entire merkle tree state
|
|
pub tree: CommitmentTree<MerkleNode>,
|
|
// List of all previous and the current merkle roots
|
|
// This is the hashed value of all the children.
|
|
pub merkle_roots: RocksColumn<columns::MerkleRoots>,
|
|
// Nullifiers prevent double spending
|
|
pub nullifiers: RocksColumn<columns::Nullifiers>,
|
|
// Mint verifying key used by ZK
|
|
pub mint_pvk: groth16::PreparedVerifyingKey<Bls12>,
|
|
// Spend verifying key used by ZK
|
|
pub spend_pvk: groth16::PreparedVerifyingKey<Bls12>,
|
|
pub wallet: WalletPtr,
|
|
}
|
|
|
|
impl ProgramState for State {
|
|
fn is_valid_cashier_public_key(&self, public: &jubjub::SubgroupPoint) -> bool {
|
|
self.wallet
|
|
.get_cashier_public_keys()
|
|
.expect("Get cashier public keys")
|
|
.contains(public)
|
|
}
|
|
|
|
fn is_valid_merkle(&self, merkle_root: &MerkleNode) -> bool {
|
|
self.merkle_roots
|
|
.key_exist(*merkle_root)
|
|
.expect("Check if the merkle_root valid")
|
|
}
|
|
|
|
fn nullifier_exists(&self, nullifier: &Nullifier) -> bool {
|
|
self.nullifiers
|
|
.key_exist(nullifier.repr)
|
|
.expect("Check if nullifier exists")
|
|
}
|
|
|
|
// load from disk
|
|
fn mint_pvk(&self) -> &groth16::PreparedVerifyingKey<Bls12> {
|
|
&self.mint_pvk
|
|
}
|
|
|
|
fn spend_pvk(&self) -> &groth16::PreparedVerifyingKey<Bls12> {
|
|
&self.spend_pvk
|
|
}
|
|
}
|
|
|
|
impl State {
|
|
pub async fn apply(
|
|
&mut self,
|
|
update: StateUpdate,
|
|
secret_keys: Vec<jubjub::Fr>,
|
|
notify: async_channel::Sender<(jubjub::SubgroupPoint, u64)>,
|
|
) -> Result<()> {
|
|
// Extend our list of nullifiers with the ones from the update
|
|
|
|
debug!(target: "CLIENT STATE", "Extend nullifiers");
|
|
for nullifier in update.nullifiers {
|
|
self.nullifiers.put(nullifier, vec![] as Vec<u8>)?;
|
|
}
|
|
|
|
debug!(target: "CLIENT STATE", "Update merkle tree and witness ");
|
|
// Update merkle tree and witnesses
|
|
for (coin, enc_note) in update.coins.into_iter().zip(update.enc_notes.iter()) {
|
|
// Add the new coins to the merkle tree
|
|
let node = MerkleNode::from_coin(&coin);
|
|
self.tree.append(node).expect("Append to merkle tree");
|
|
|
|
// Keep track of all merkle roots that have existed
|
|
self.merkle_roots.put(self.tree.root(), vec![] as Vec<u8>)?;
|
|
|
|
// Also update all the coin witnesses
|
|
for (coin_id, witness) in self.wallet.get_witnesses()?.iter_mut() {
|
|
witness.append(node).expect("Append to witness");
|
|
self.wallet.update_witness(*coin_id, witness.clone())?;
|
|
}
|
|
|
|
for secret in secret_keys.iter() {
|
|
if let Some(note) = Self::try_decrypt_note(enc_note, *secret) {
|
|
// We need to keep track of the witness for this coin.
|
|
// This allows us to prove inclusion of the coin in the merkle tree with ZK.
|
|
// Just as we update the merkle tree with every new coin, so we do the same with
|
|
// the witness.
|
|
|
|
// Derive the current witness from the current tree.
|
|
// This is done right after we add our coin to the tree (but before any other
|
|
// coins are added)
|
|
|
|
// Make a new witness for this coin
|
|
let witness = IncrementalWitness::from_tree(&self.tree);
|
|
|
|
let own_coin = OwnCoin {
|
|
coin: coin.clone(),
|
|
note: note.clone(),
|
|
secret: *secret,
|
|
witness: witness.clone(),
|
|
};
|
|
|
|
|
|
self.wallet.put_own_coins(own_coin)?;
|
|
let pub_key = zcash_primitives::constants::SPENDING_KEY_GENERATOR * secret;
|
|
|
|
debug!(target: "CLIENT STATE", "Received a coin: amount {} from {}", note.value, pub_key);
|
|
|
|
debug!(target: "CLIENT STATE", "Send a notification");
|
|
|
|
notify.send((pub_key, note.value)).await?;
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn try_decrypt_note(ciphertext: &EncryptedNote, secret: jubjub::Fr) -> Option<Note> {
|
|
match ciphertext.decrypt(&secret) {
|
|
// ... and return the decrypted note for this coin.
|
|
Ok(note) => Some(note),
|
|
// We weren't able to decrypt the note with our key.
|
|
Err(_) => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for ClientFailed {}
|
|
|
|
impl std::fmt::Display for ClientFailed {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
match self {
|
|
ClientFailed::NotEnoughValue(i) => {
|
|
write!(f, "There is no enough value {}", i)
|
|
}
|
|
ClientFailed::InvalidAddress(i) => {
|
|
write!(f, "Invalid Address {}", i)
|
|
}
|
|
ClientFailed::InvalidAmount(i) => {
|
|
write!(f, "Invalid Amount {}", i)
|
|
}
|
|
ClientFailed::UnableToGetDepositAddress => f.write_str("Unable to get deposit address"),
|
|
ClientFailed::UnableToGetWithdrawAddress => {
|
|
f.write_str("Unable to get withdraw address")
|
|
}
|
|
ClientFailed::DoesNotHaveCashierPublicKey => {
|
|
f.write_str("Does not have cashier public key")
|
|
}
|
|
ClientFailed::DoesNotHaveKeypair => f.write_str("Does not have keypair"),
|
|
ClientFailed::EmptyPassword => f.write_str("Password is empty. Cannot create database"),
|
|
ClientFailed::WalletInitialized => f.write_str("Wallet already initalized"),
|
|
ClientFailed::KeyExists => f.write_str("Keypair already exists"),
|
|
ClientFailed::ClientError(i) => {
|
|
write!(f, "ClientError: {}", i)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<super::error::Error> for ClientFailed {
|
|
fn from(err: super::error::Error) -> ClientFailed {
|
|
ClientFailed::ClientError(err.to_string())
|
|
}
|
|
}
|
|
|
|
pub type ClientResult<T> = std::result::Result<T, ClientFailed>;
|