diff --git a/src/cli.rs b/src/cli.rs index d2a621c..5b77b20 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,6 +1,9 @@ use std::{println, vec}; -use crate::{blockchain::Blockchain, deposit, pow::ProofOfWork, transaction, verify, wallet, wallets::Wallets}; +use crate::{ + blockchain::Blockchain, deposit, pow::ProofOfWork, transaction, verify, wallet, + wallets::Wallets, zsend, +}; use structopt::StructOpt; pub struct Cli { @@ -41,26 +44,32 @@ pub enum Command { address: String, }, #[structopt(name = "deposit", about = "deposit")] - Deposit{ + Deposit { #[structopt(help = "address")] address: String, #[structopt(help = "amount")] amount: u64, - } + }, + #[structopt(name = "zsend", about = "zsend")] + Zsend { + #[structopt(help = "from")] + from: String, + #[structopt(help = "to")] + to: String, + }, } impl Cli { pub fn run(&mut self) { match &self.cmd { - Command::CreateBlockChain { address } => self.create_blockchain(address.to_string()), + Command::CreateBlockChain { address } => self.create_blockchain(address.clone()), Command::Createwallet => self.create_wallet(), Command::PrintChain => self.print_chain(), Command::ListAddress => self.list_address(), - Command::Send { from, to, amount } => { - self.send(from.to_string(), to.to_string(), *amount) - } - Command::Getbalance { address } => self.get_balance(address.to_string()), - Command::Deposit {address, amount} => self.deposit(address.to_string(), *amount), + Command::Send { from, to, amount } => self.send(from.clone(), to.clone(), *amount), + Command::Getbalance { address } => self.get_balance(address.clone()), + Command::Deposit { address, amount } => self.deposit(address.clone(), *amount), + Command::Zsend { from, to } => self.zsend(from.clone(), to.clone()), } } @@ -79,7 +88,11 @@ impl Cli { let w = Wallets::new(); let address = w.get_addresses(); for a in &address { - println!("{:}", a); + println!("addr:{:}", a); + } + let address = w.get_z_addresses(); + for a in &address { + println!("zaddr:{:}", a); } } @@ -128,10 +141,15 @@ impl Cli { println!("Balance of '{}': {}", address, balance); } - fn deposit(&self, address:String, amount:u64){ - let bundle = deposit::deposit(address, amount); + fn deposit(&self, address: String, amount: u64) { + let bundle = deposit::deposit(&address, amount); verify::verify_bundle(&bundle); deposit::save_note(&bundle, &address); } -} + fn zsend(&self, from: String, to: String) { + let bundle = zsend::zsend(&from, &to); + verify::verify_bundle(&bundle); + zsend::save_note(&bundle, &from, &to); + } +} diff --git a/src/deposit.rs b/src/deposit.rs index e0ccfdc..8363c6a 100644 --- a/src/deposit.rs +++ b/src/deposit.rs @@ -1,23 +1,20 @@ -use crate::{wallets::Wallets, merkle, wallet}; -use rand::rngs::OsRng; +use crate::{merkle, wallet, wallets::Wallets}; +use orchard::circuit::ProvingKey; use orchard::{ builder::Builder, bundle::{Authorized, Flags}, - circuit::{ProvingKey, VerifyingKey}, - keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, - note::ExtractedNoteCommitment, + keys::{FullViewingKey, PreparedIncomingViewingKey, Scope}, note_encryption::OrchardDomain, - tree::{MerkleHashOrchard, MerklePath}, value::NoteValue, Bundle, }; +use rand::rngs::OsRng; use zcash_note_encryption::try_note_decryption; - -pub fn deposit(address: String, value: u64) -> Bundle { +pub fn deposit(address: &String, value: u64) -> Bundle { let wallets = Wallets::new(); - let wallet = wallets.get_wallet(&address).unwrap(); - + let wallet = wallets.get_wallet(address).unwrap(); + let mut rng = OsRng; let pk = ProvingKey::build(); @@ -43,7 +40,7 @@ pub fn deposit(address: String, value: u64) -> Bundle { shielding_bundle } -pub fn save_note(bundle: &Bundle, address:&String){ +pub fn save_note(bundle: &Bundle, address: &String) { let mut wallets = Wallets::new(); let wallet = wallets.get_wallet(address).unwrap(); let sk = wallet.sk(); @@ -58,13 +55,13 @@ pub fn save_note(bundle: &Bundle, address:&String){ try_note_decryption(&domain, &ivk, action) }) .unwrap(); - let n = wallet::Note{ - value:note.value().into(), + let n = wallet::Note { + value: note.value().inner(), rseed: *note.rseed().as_bytes(), - nf: *note.rho().to_bytes(), + nf: note.rho().to_bytes(), }; - let wallet = wallets.get_mut_wallet(address).unwrap(); - wallet.notes.extend(n); + let wallet = wallets.get_mut_wallet(address); + wallet.notes.push(n); _ = wallets.save_to_file(); -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index c9e677f..4d2aa34 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,18 +3,18 @@ use structopt::StructOpt; mod block; mod blockchain; mod cli; +mod deposit; mod iterator; +mod merkle; mod pow; mod transaction; mod transaction_input; mod transaction_output; +mod verify; mod wallet; mod wallets; -mod deposit; -mod merkle; -mod transfer; -mod verify; mod withdraw; +mod zsend; fn main() { let mut c = cli::Cli { diff --git a/src/merkle.rs b/src/merkle.rs index dbfb7bc..373f139 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -1,7 +1,8 @@ use bridgetree::BridgeTree; -use orchard::tree::{MerkleHashOrchard, MerklePath}; use lazy_static::lazy_static; +use orchard::tree::MerkleHashOrchard; lazy_static! { - pub static ref MERKLE:BridgeTree:: = BridgeTree::::new(100); + pub static ref MERKLE: BridgeTree:: = + BridgeTree::::new(100); } diff --git a/src/transfer.rs b/src/transfer.rs deleted file mode 100644 index 018ecf7..0000000 --- a/src/transfer.rs +++ /dev/null @@ -1,66 +0,0 @@ -use orchard::{ - builder::Builder, - bundle::{Authorized, Flags}, - circuit::{ProvingKey, VerifyingKey}, - keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, - note::ExtractedNoteCommitment, - note_encryption::OrchardDomain, - tree::{MerkleHashOrchard, MerklePath}, - value::NoteValue, - Bundle, -}; -use crate::{wallets::Wallets}; -use rand::rngs::OsRng; - -pub fn transfer(address: String, value: u64) -> Bundle { - let wallets = Wallets::new(); - let wallet = wallets.get_wallet(&address).unwrap(); - - let mut rng = OsRng; - let pk = ProvingKey::build(); - - let sk = wallet.sk(); - let fvk = FullViewingKey::from(&sk); - let recipient = fvk.address_at(0u32, Scope::External); - - let shielded_bundle: Bundle<_, i64> = { - let ivk = PreparedIncomingViewingKey::new(&fvk.to_ivk(Scope::External)); - let (note, _, _) = shielding_bundle - .actions() - .iter() - .find_map(|action| { - let domain = OrchardDomain::for_action(action); - try_note_decryption(&domain, &ivk, action) - }) - .unwrap(); - - // Use the tree with a single leaf. - let cmx: ExtractedNoteCommitment = note.commitment().into(); - let leaf = MerkleHashOrchard::from_cmx(&cmx); - let mut tree = BridgeTree::::new(100); - tree.append(leaf); - let position = tree.mark().unwrap(); - let root = tree.root(0).unwrap(); - let auth_path = tree.witness(position, 0).unwrap(); - let merkle_path = MerklePath::from_parts( - u64::from(position).try_into().unwrap(), - auth_path[..].try_into().unwrap(), - ); - let anchor = root.into(); - assert_eq!(anchor, merkle_path.root(cmx)); - - let mut builder = Builder::new(Flags::from_parts(true, true), anchor); - assert_eq!(builder.add_spend(fvk, note, merkle_path), Ok(())); - assert_eq!( - builder.add_recipient(None, recipient, NoteValue::from_raw(5000), None), - Ok(()) - ); - let unauthorized = builder.build(&mut rng).unwrap(); - let sighash = unauthorized.commitment().into(); - let proven = unauthorized.create_proof(&pk, &mut rng).unwrap(); - proven - .apply_signatures(rng, sighash, &[SpendAuthorizingKey::from(&sk)]) - .unwrap() - }; - shielded_bundle -} \ No newline at end of file diff --git a/src/verify.rs b/src/verify.rs index 08e4d15..296b213 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -1,11 +1,6 @@ -use orchard::{ - bundle::Authorized, - circuit::VerifyingKey, - Bundle, -}; -use crate::merkle; +use orchard::{bundle::Authorized, circuit::VerifyingKey, Bundle}; -pub fn verify_bundle(bundle: &Bundle){ +pub fn verify_bundle(bundle: &Bundle) { let vk = VerifyingKey::build(); assert!(matches!(bundle.verify_proof(&vk), Ok(()))); let sighash: [u8; 32] = bundle.commitment().into(); @@ -17,4 +12,4 @@ pub fn verify_bundle(bundle: &Bundle){ bvk.verify(&sighash, bundle.authorization().binding_signature()), Ok(()) ); -} \ No newline at end of file +} diff --git a/src/wallet.rs b/src/wallet.rs index df0f723..9f11088 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,11 +1,14 @@ +use orchard::keys::FullViewingKey; +use orchard::note::{Nullifier, RandomSeed}; +use orchard::value::NoteValue; +use orchard::{keys, Address}; +use rand::rngs::OsRng as randRng; +use rand::RngCore; use ripemd::{Digest as RipemdDigest, Ripemd160}; use secp256k1::rand::rngs::OsRng; use secp256k1::Secp256k1; use serde::{Deserialize, Serialize}; use sha2::Sha256; -use rand::RngCore; -use rand::rngs::OsRng as randRng; -use orchard::keys; const VERSION: u8 = 0x00; pub(crate) const CHECKSUM_LENGTH: usize = 4; @@ -14,19 +17,26 @@ pub(crate) const CHECKSUM_LENGTH: usize = 4; pub struct Wallet { pub private_key: String, pub public_key: String, - pub spend_key : String, - pub notes:Vec, + pub spend_key: String, + pub notes: Vec, } #[derive(Serialize, Deserialize, Clone)] pub struct Note { - pub value :u64, + pub value: u64, pub rseed: [u8; 32], - pub nf:[u8; 32], + pub nf: [u8; 32], } impl Note { - pub fn to_note(&self, addr : orchard::Address) -> orchard::note { - Option::from(orchard::Note::from_parts(addr, self.value.into(), self.nf.into(), self.rseed.into()))? + pub fn to_note(&self, addr: orchard::Address) -> orchard::Note { + let old_nf = Nullifier::from_bytes(&self.nf).unwrap(); + orchard::Note::from_parts( + addr, + NoteValue::from_raw(self.value), + old_nf, + RandomSeed::from_bytes(self.rseed, &old_nf).unwrap(), + ) + .unwrap() } } @@ -36,7 +46,7 @@ impl Wallet { let (private_key, public_key) = secp.generate_keypair(&mut OsRng); let mut rng = randRng::default(); - let mut random_bytes = [0u8; 32]; + let mut random_bytes = [0u8; 32]; rng.fill_bytes(&mut random_bytes); let spend_key = keys::SpendingKey::from_zip32_seed(&random_bytes, 0, 0).unwrap(); @@ -44,7 +54,7 @@ impl Wallet { private_key: hex::encode(private_key.secret_bytes()), public_key: public_key.to_string(), spend_key: hex::encode(spend_key.to_bytes()), - notes :vec![], + notes: vec![], } } @@ -56,22 +66,22 @@ impl Wallet { versioned_payload.extend_from_slice(&checksum); bs58::encode(&versioned_payload).into_string() } - + pub fn get_z_address(&self) -> String { - let spend_key = hex::decode(&self.spend_key).unwrap(); - let spend_key : Result<[u8; 32], _> = spend_key.try_into(); - let spend_key = keys::SpendingKey::from_bytes(spend_key.unwrap()).unwrap(); - let fvk: keys::FullViewingKey = (&spend_key).into(); - let addr = fvk.address_at(0u32, keys::Scope::External); + let addr = self.z_address(); hex::encode(addr.to_raw_address_bytes()) } pub fn sk(&self) -> keys::SpendingKey { let spend_key = hex::decode(&self.spend_key).unwrap(); - let spend_key : Result<[u8; 32], _> = spend_key.try_into(); + let spend_key: Result<[u8; 32], _> = spend_key.try_into(); keys::SpendingKey::from_bytes(spend_key.unwrap()).unwrap() } - + + pub fn z_address(&self) -> Address { + let fvk = FullViewingKey::from(&self.sk()); + fvk.address_at(0u32, keys::Scope::External) + } } pub fn validate_address(address: &String) -> bool { diff --git a/src/wallets.rs b/src/wallets.rs index 6e2525b..bc880bb 100644 --- a/src/wallets.rs +++ b/src/wallets.rs @@ -6,14 +6,13 @@ use std::fs::File; use std::io; use std::io::Read; use std::path::Path; -use orchard::Address; const WALLET_FILE: &str = "wallets.dat"; #[derive(Serialize, Deserialize)] pub struct Wallets { wallets: HashMap, - zwallets:HashMap, + zwallets: HashMap, } impl Wallets { @@ -26,8 +25,8 @@ impl Wallets { let address = wallet.get_address(); let zaddr = wallet.get_z_address(); - self.wallets.insert(address.clone(), wallet.clone()); - self.zwallets.insert(zaddr.clone(), wallet); + self.wallets.insert(address.clone(), wallet); + self.zwallets.insert(zaddr, address.clone()); address } @@ -36,16 +35,26 @@ impl Wallets { self.wallets.keys().cloned().collect() } + pub fn get_z_addresses(&self) -> Vec { + self.zwallets.keys().cloned().collect() + } + pub fn get_wallet(&self, address: &str) -> Option<&Wallet> { self.wallets.get(address) } pub fn get_mut_wallet(&mut self, address: &str) -> &mut Wallet { - self.wallets.get_mut(address)? + self.wallets.get_mut(address).unwrap() + } + + pub fn get_mut_z_wallet(&mut self, address: &str) -> &mut Wallet { + let addr = self.zwallets.get(address).unwrap(); + self.wallets.get_mut(addr).unwrap() } pub fn get_z_wallet(&self, address: &str) -> Option<&Wallet> { - self.zwallets.get(address) + let addr = self.zwallets.get(address).unwrap(); + self.wallets.get(addr) } fn load_from_file() -> io::Result { diff --git a/src/zsend.rs b/src/zsend.rs new file mode 100644 index 0000000..c530bec --- /dev/null +++ b/src/zsend.rs @@ -0,0 +1,95 @@ +use crate::{wallet, wallets::Wallets}; +use bridgetree::BridgeTree; +use orchard::{ + builder::Builder, + bundle::{Authorized, Flags}, + circuit::ProvingKey, + keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey}, + note::ExtractedNoteCommitment, + note_encryption::OrchardDomain, + tree::{MerkleHashOrchard, MerklePath}, + value::NoteValue, + Bundle, +}; +use rand::rngs::OsRng; +use zcash_note_encryption::try_note_decryption; + +pub fn zsend(from: &String, to: &String) -> Bundle { + let wallets = Wallets::new(); + + let mut rng = OsRng; + let pk = ProvingKey::build(); + + let from = wallets.get_z_wallet(from).unwrap(); + let from_sk = from.sk(); + let from_fvk = FullViewingKey::from(&from_sk); + let from_addr = from.z_address(); + + let to = wallets.get_z_wallet(to).unwrap(); + let recipient = to.z_address(); + + let shielded_bundle: Bundle<_, i64> = { + let old_note = from.notes.get(0).unwrap(); + let note = old_note.to_note(from_addr); + let cmx: ExtractedNoteCommitment = note.commitment().into(); + + let leaf = MerkleHashOrchard::from_cmx(&cmx); + let mut tree = BridgeTree::::new(100); + tree.append(leaf); + let position = tree.mark().unwrap(); + let root = tree.root(0).unwrap(); + let auth_path = tree.witness(position, 0).unwrap(); + let merkle_path = MerklePath::from_parts( + u64::from(position).try_into().unwrap(), + auth_path[..].try_into().unwrap(), + ); + let anchor = root.into(); + assert_eq!(anchor, merkle_path.root(cmx)); + + let mut builder = Builder::new(Flags::from_parts(true, true), anchor); + assert_eq!( + builder.add_spend(from_fvk, note, merkle_path), + Ok(()) + ); + assert_eq!( + builder.add_recipient(None, recipient, NoteValue::from_raw(old_note.value), None), + Ok(()) + ); + let unauthorized = builder.build(&mut rng).unwrap(); + let sighash = unauthorized.commitment().into(); + let proven = unauthorized.create_proof(&pk, &mut rng).unwrap(); + proven + .apply_signatures(rng, sighash, &[SpendAuthorizingKey::from(&from_sk)]) + .unwrap() + }; + shielded_bundle +} + +pub fn save_note(bundle: &Bundle, from: &String, to: &String) { + let mut wallets = Wallets::new(); + let to_wallet = wallets.get_z_wallet(to).unwrap(); + let to_sk = to_wallet.sk(); + let to_fvk = FullViewingKey::from(&to_sk); + let to_ivk = PreparedIncomingViewingKey::new(&to_fvk.to_ivk(Scope::External)); + + let (note, _, _) = bundle + .actions() + .iter() + .find_map(|action| { + let domain = OrchardDomain::for_action(action); + try_note_decryption(&domain, &to_ivk, action) + }) + .unwrap(); + let n = wallet::Note { + value: note.value().inner(), + rseed: *note.rseed().as_bytes(), + nf: note.rho().to_bytes(), + }; + + let wallet = wallets.get_mut_z_wallet(to); + wallet.notes.push(n); + + let wallet = wallets.get_mut_z_wallet(from); + wallet.notes.remove(0); + _ = wallets.save_to_file(); +}