diff --git a/rusk-wallet/src/cache.rs b/rusk-wallet/src/cache.rs index 7987dffef7..bb78d5f819 100644 --- a/rusk-wallet/src/cache.rs +++ b/rusk-wallet/src/cache.rs @@ -4,8 +4,8 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use std::collections::BTreeSet; use std::path::Path; +use std::{cmp::Ordering, collections::BTreeSet}; use dusk_bytes::{DeserializableSlice, Serializable}; use rocksdb::{DBWithThreadMode, MultiThreaded, Options}; @@ -198,7 +198,7 @@ impl Cache { &self, pk: &PhoenixPublicKey, ) -> Result, Error> { - let cf_name = format!("spent_{:?}", ps); + let cf_name = format!("spent_{:?}", pk); let mut notes = vec![]; if let Some(cf) = self.db.cf_handle(&cf_name) { @@ -225,3 +225,41 @@ pub(crate) struct NoteData { pub height: u64, pub note: Note, } + +impl PartialOrd for NoteData { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for NoteData { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.note.pos().cmp(other.note.pos()) + } +} + +impl Serializable<{ u64::SIZE + Note::SIZE }> for NoteData { + type Error = dusk_bytes::Error; + /// Converts a Note into a byte representation + + fn to_bytes(&self) -> [u8; Self::SIZE] { + let mut buf = [0u8; Self::SIZE]; + + buf[0..8].copy_from_slice(&self.height.to_bytes()); + + buf[8..].copy_from_slice(&self.note.to_bytes()); + + buf + } + + /// Attempts to convert a byte representation of a note into a `Note`, + /// failing if the input is invalid + fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result { + let mut one_u64 = [0u8; 8]; + one_u64.copy_from_slice(&bytes[0..8]); + let height = u64::from_bytes(&one_u64)?; + + let note = Note::from_slice(&bytes[8..])?; + Ok(Self { height, note }) + } +} diff --git a/rusk-wallet/src/clients.rs b/rusk-wallet/src/clients.rs index 6b3a76315f..a9bb54b0db 100644 --- a/rusk-wallet/src/clients.rs +++ b/rusk-wallet/src/clients.rs @@ -6,12 +6,11 @@ mod sync; -use dusk_bytes::{DeserializableSlice, Serializable, Write}; +use dusk_bytes::Serializable; use flume::Receiver; -use flume::Sender; -use keys::derive_phoenix_pk; use tokio::time::{sleep, Duration}; -use wallet_core::prelude::*; +use wallet_core::input::{try_input_notes, Inputs}; +use wallet_core::prelude::{Error as WalletCoreError, *}; use std::path::Path; use std::sync::{Arc, Mutex}; @@ -21,10 +20,13 @@ use self::sync::sync_db; use super::block::Block; use super::cache::Cache; +use crate::cache::NoteData; use crate::rusk::{RuskHttpClient, RuskRequest}; -use crate::store::SyncStore; use crate::Error; +pub(crate) type Addresses = + Vec<(PhoenixSecretKey, PhoenixViewKey, PhoenixPublicKey)>; + const TRANSFER_CONTRACT: &str = "0100000000000000000000000000000000000000000000000000000000000000"; @@ -34,11 +36,27 @@ const STAKE_CONTRACT: &str = // Sync every 3 seconds for now const SYNC_INTERVAL_SECONDS: u64 = 3; +pub struct Prover; + +impl Prove for Prover { + fn prove(tx_circuit_vec_bytes: &[u8]) -> Result, WalletCoreError> { + let prover = RuskHttpClient::new("prover".to_string()); + let prove_req = + RuskRequest::new("prove_execute", tx_circuit_vec_bytes.to_owned()); + + prover + .call(2, "rusk", &prove_req) + .wait() + .map_err(|e| WalletCoreError::PhoenixCircuit(e.to_string())) + } +} + /// The state struct is responsible for managing the state of the wallet pub struct State { cache: Mutex>, status: fn(&str), client: RuskHttpClient, + addresses: Addresses, prover: RuskHttpClient, pub sync_rx: Option>, } @@ -50,14 +68,21 @@ impl State { status: fn(&str), client: RuskHttpClient, prover: RuskHttpClient, - addresses: Vec, + addresses: Vec<(PhoenixSecretKey, PhoenixViewKey, PhoenixPublicKey)>, ) -> Result { - let cache = - Mutex::new(Arc::new(Cache::new(data_dir, &addresses, status)?)); + let cfs = addresses + .iter() + .flat_map(|(_, _, pk)| { + [format!("{:?}", pk), format!("spent_{:?}", pk)] + }) + .collect(); + + let cache = Mutex::new(Arc::new(Cache::new(data_dir, cfs, status)?)); Ok(Self { cache, sync_rx: None, + addresses, status, prover, client, @@ -66,17 +91,19 @@ impl State { pub(crate) fn cache(&self) -> Arc { let state = self.cache.lock().unwrap(); - Arc::clone(&state.cache) + + Arc::clone(&state) } - pub async fn register_sync(&self) -> Result<(), Error> { + pub async fn register_sync(&mut self) -> Result<(), Error> { let (sync_tx, sync_rx) = flume::unbounded::(); self.sync_rx = Some(sync_rx); let cache = self.cache(); let status = self.status; - let client = self.client; + let client = &self.client; + let addresses = &self.addresses; let sender = Arc::new(sync_tx); status("Starting Sync.."); @@ -86,7 +113,8 @@ impl State { let sender = Arc::clone(&sender); let _ = sender.send("Syncing..".to_string()); - let sync_status = sync_db(&client, &store, status).await; + let sync_status = + sync_db(client, &cache, &addresses.clone(), status).await; let _ = match sync_status { Ok(_) => sender.send("Syncing Complete".to_string()), Err(e) => sender.send(format!("Error during sync:.. {e}")), @@ -100,83 +128,97 @@ impl State { } pub async fn sync(&self) -> Result<(), Error> { - sync_db(&self.client, &self.store, &self.status).await + sync_db(&self.client, &self.cache(), &self.addresses, self.status).await } /// Requests that a node prove the given transaction and later propagates it - pub fn compute_proof_and_propagate( - &self, - utx: PhoenixTransaction, - ) -> Result { - self.status("Proving tx, please wait..."); - let utx_bytes = utx.proof(); - let prove_req = RuskRequest::new("prove_execute", utx_bytes); - let proof_bytes: Vec = - self.prover.call(2, "rusk", &prove_req).wait()?; - self.status("Proof success!"); - utx.set_proof(proof_bytes); - let tx_bytes = utx.to_var_bytes(); - - self.status("Attempt to preverify tx..."); - let preverify_req = RuskRequest::new("preverify", tx_bytes.clone()); - let _ = self.client.call(2, "rusk", &preverify_req).wait()?; - self.status("Preverify success!"); - - self.status("Propagating tx..."); - let propagate_req = RuskRequest::new("propagate_tx", tx_bytes); - let _ = self.client.call(2, "Chain", &propagate_req).wait()?; - self.status("Transaction propagated!"); - - Ok(tx) + pub fn propagate(&self, utx: Transaction) -> Result { + if let Transaction::Phoenix(p) = utx { + let status = self.status; + let tx_bytes = utx.to_var_bytes(); + + status("Attempt to preverify tx..."); + let preverify_req = RuskRequest::new("preverify", tx_bytes.clone()); + let _ = self.client.call(2, "rusk", &preverify_req).wait()?; + status("Preverify success!"); + + status("Propagating tx..."); + let propagate_req = RuskRequest::new("propagate_tx", tx_bytes); + let _ = self.client.call(2, "Chain", &propagate_req).wait()?; + status("Transaction propagated!"); + + Ok(Transaction::Phoenix(p)) + } else { + Err(Error::Transaction( + "Only Phoenix transactions are supported".to_string(), + )) + } } /// Find notes for a view key, starting from the given block height. - fn fetch_notes( + pub(crate) fn inputs( + &self, + index: u8, + target: u64, + ) -> Result, Error> { + if let Some((_, vk, pk)) = self.addresses.get(index as usize) { + let inputs: Result = self + .cache() + .notes(&pk)? + .into_iter() + .map(|data| Ok((data.note, data.note.value(Some(vk)).unwrap()))) + .collect(); + + try_input_notes(inputs?, target)? + .into_iter() + .map(|(note, _)| { + let opening = self.fetch_opening(¬e)?; + + Ok((note, opening)) + }) + .collect() + } else { + Err(Error::AddressNotOwned) + } + } + + pub(crate) fn fetch_notes( &self, pk: &PhoenixPublicKey, - ) -> Result, Error> { - Ok(state - .cache - .notes(&pk)? + ) -> Result, Error> { + self.cache() + .notes(pk)? .into_iter() - .map(|data| (data.note, data.height)) - .collect()) + .map(|data| { + Ok(NoteData { + note: data.note, + height: data.height, + }) + }) + .collect() } /// Fetch the current anchor of the state. - fn fetch_anchor(&self) -> Result { - self.status("Fetching anchor..."); + pub(crate) fn fetch_anchor(&self) -> Result { + let status = self.status; + status("Fetching anchor..."); let anchor = self .client .contract_query::<(), 0>(TRANSFER_CONTRACT, "root", &()) .wait()?; - self.status("Anchor received!"); + status("Anchor received!"); let anchor = rkyv::from_bytes(&anchor).map_err(|_| Error::Rkyv)?; Ok(anchor) } - /// Queries the node to find the opening for a specific note. - fn fetch_opening(&self, note: &Note) -> Result { - self.status("Fetching opening notes..."); - - let data = self - .client - .contract_query::<_, 1024>(TRANSFER_CONTRACT, "opening", note.pos()) - .wait()?; - - self.status("Opening notes received!"); - - let branch = rkyv::from_bytes(&data).map_err(|_| Error::Rkyv)?; - Ok(branch) - } - /// Queries the node for the amount staked by a key. - fn fetch_stake( + pub(crate) fn fetch_stake( &self, pk: &PhoenixPublicKey, - ) -> Result { - self.status("Fetching stake..."); + ) -> Result, Error> { + let status = self.status; + status("Fetching stake..."); let data = self .client @@ -185,30 +227,28 @@ impl State { let res: Option = rkyv::from_bytes(&data).map_err(|_| Error::Rkyv)?; - self.status("Stake received!"); + status("Stake received!"); let staking_address = pk.to_bytes().to_vec(); let staking_address = bs58::encode(staking_address).into_string(); println!("Staking address: {}", staking_address); - // FIX_ME: proper solution should to return an Option - // changing the trait implementation. That would reflect the state of - // the stake contract. It would be up to the consumer to decide what to - // do with a None - let stake = res - .map( - |StakeData { - amount, - reward, - counter, - }| StakeInfo { - amount, - reward, - counter, - }, - ) - .unwrap_or_default(); - - Ok(stake) + Ok(res) + } + + /// Queries the node to find the opening for a specific note. + fn fetch_opening(&self, note: &Note) -> Result { + let status = self.status; + status("Fetching opening notes..."); + + let data = self + .client + .contract_query::<_, 1024>(TRANSFER_CONTRACT, "opening", note.pos()) + .wait()?; + + status("Opening notes received!"); + + let branch = rkyv::from_bytes(&data).map_err(|_| Error::Rkyv)?; + Ok(branch) } } diff --git a/rusk-wallet/src/clients/sync.rs b/rusk-wallet/src/clients/sync.rs index d948851ade..da92bd14f8 100644 --- a/rusk-wallet/src/clients/sync.rs +++ b/rusk-wallet/src/clients/sync.rs @@ -11,23 +11,18 @@ use futures::StreamExt; use wallet_core::prelude::*; use crate::block::Block; -use crate::clients::{Cache, TRANSFER_CONTRACT}; +use crate::clients::{Addresses, Cache, TRANSFER_CONTRACT}; use crate::rusk::RuskHttpClient; -use crate::store::SyncStore; -use crate::{Error, RuskRequest, MAX_ADDRESSES}; +use crate::{Error, RuskRequest}; const TREE_LEAF: usize = size_of::(); -pub(crate) async fn sync_db( +pub(crate) async fn sync_db( client: &RuskHttpClient, - store: &SyncStore, + cache: &Cache, + addresses: &Addresses, status: fn(&str), ) -> Result<(), Error> { - let cache = store.cache(); - let addresses: Vec<_> = (0..MAX_ADDRESSES) - .map(|i| (store.sk(i), store.vk(i), store.pk(i))) - .collect(); - status("Getting cached note position..."); let last_pos = cache.last_pos()?; @@ -70,7 +65,7 @@ pub(crate) async fn sync_db( last_pos = std::cmp::max(last_pos, *note.pos()); for (sk, vk, pk) in addresses.iter() { - if vk.owns(¬e) { + if vk.owns(¬e.stealth_address()) { let nullifier = note.gen_nullifier(sk); let spent = fetch_existing_nullifiers_remote(client, &[nullifier]) diff --git a/rusk-wallet/src/dat.rs b/rusk-wallet/src/dat.rs index 836f549e2a..f5d5998430 100644 --- a/rusk-wallet/src/dat.rs +++ b/rusk-wallet/src/dat.rs @@ -4,13 +4,12 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use dusk_bytes::DeserializableSlice; - use std::fs; use std::io::Read; +use wallet_core::RNG_SEED; + use crate::crypto::decrypt; -use crate::store; use crate::Error; use crate::WalletPath; @@ -26,6 +25,8 @@ pub const FILE_TYPE: u16 = 0x0200; pub const RESERVED: u16 = 0x0000; /// (Major, Minor, Patch, Pre, Pre-Higher) type Version = (u8, u8, u8, u8, bool); +/// Type alias for seed +type Seed = [u8; RNG_SEED]; /// Versions of the potential wallet DAT files we read #[derive(Copy, Clone, Debug, PartialEq)] @@ -51,7 +52,7 @@ pub(crate) fn get_seed_and_address( file: DatFileVersion, mut bytes: Vec, pwd: &[u8], -) -> Result<(store::Seed, u8), Error> { +) -> Result<(Seed, u8), Error> { match file { DatFileVersion::Legacy => { if bytes[1] == 0 && bytes[2] == 0 { @@ -61,7 +62,8 @@ pub(crate) fn get_seed_and_address( bytes = decrypt(&bytes, pwd)?; // get our seed - let seed = store::Seed::from_reader(&mut &bytes[..]) + let seed = bytes[..] + .try_into() .map_err(|_| Error::WalletFileCorrupted)?; Ok((seed, 1)) @@ -69,13 +71,13 @@ pub(crate) fn get_seed_and_address( DatFileVersion::OldWalletCli((major, minor, _, _, _)) => { bytes.drain(..5); - let result: Result<(store::Seed, u8), Error> = match (major, minor) - { + let result: Result<(Seed, u8), Error> = match (major, minor) { (1, 0) => { let content = decrypt(&bytes, pwd)?; let mut buff = &content[..]; - let seed = store::Seed::from_reader(&mut buff) + let seed = buff + .try_into() .map_err(|_| Error::WalletFileCorrupted)?; Ok((seed, 1)) @@ -85,7 +87,8 @@ pub(crate) fn get_seed_and_address( let mut buff = &content[..]; // extract seed - let seed = store::Seed::from_reader(&mut buff) + let seed = buff + .try_into() .map_err(|_| Error::WalletFileCorrupted)?; // extract addresses count @@ -102,7 +105,8 @@ pub(crate) fn get_seed_and_address( let content = decrypt(rest, pwd)?; if let Some(seed_buff) = content.get(0..65) { - let seed = store::Seed::from_reader(&mut &seed_buff[0..64]) + let seed = seed_buff[0..64] + .try_into() .map_err(|_| Error::WalletFileCorrupted)?; let count = &seed_buff[64..65]; diff --git a/rusk-wallet/src/error.rs b/rusk-wallet/src/error.rs index fdebe5400d..b49bb7fe65 100644 --- a/rusk-wallet/src/error.rs +++ b/rusk-wallet/src/error.rs @@ -116,6 +116,9 @@ pub enum Error { /// The cache database couldn't find column family required #[error("Cache database corrupted")] CacheDatabaseCorrupted, + /// Prover errors from wallet-core + #[error("Prover Error")] + ProverError, } impl From for Error { @@ -138,17 +141,30 @@ impl From for Error { InsufficientBalance => Self::NotEnoughBalance, Replay => Self::Transaction("Replay".to_string()), PhoenixOwnership => Self::AddressNotOwned, - PhoenixCircuit(_) | PhoenixProver(_) => Self::Pro, + PhoenixCircuit(_) | PhoenixProver(_) => Self::ProverError, InvalidData => Self::Bytes(dusk_bytes::Error::InvalidData), - BadLength(a, b) => Self::Bytes(dusk_bytes::Error::BadLength(a, b)), - InvalidChar(a, b) => { - Self::Bytes(dusk_bytes::Error::InvalidChar(a, b)) + BadLength(found, expected) => { + Self::Bytes(dusk_bytes::Error::BadLength { found, expected }) + } + InvalidChar(ch, index) => { + Self::Bytes(dusk_bytes::Error::InvalidChar { ch, index }) } Rkyv(s) => Self::Rkyv, } } } +impl From for Error { + fn from(e: wallet_core::input::InputNotesError) -> Self { + use wallet_core::input::InputNotesError::*; + + match e { + TargetGreaterThanSum => Self::NoteCombinationProblem, + EmptyNotes => Self::AddressNotOwned, + } + } +} + impl From for Error { fn from(e: rocksdb::Error) -> Self { Self::RocksDB(e) diff --git a/rusk-wallet/src/wallet.rs b/rusk-wallet/src/wallet.rs index 8f765be01d..64278c679a 100644 --- a/rusk-wallet/src/wallet.rs +++ b/rusk-wallet/src/wallet.rs @@ -12,20 +12,25 @@ pub use address::Address; pub use file::{SecureWalletFile, WalletPath}; use bip39::{Language, Mnemonic, Seed}; -use dusk_bytes::{DeserializableSlice, Serializable}; +use dusk_bytes::Serializable; +use keys::{ + derive_bls_sk, derive_phoenix_pk, derive_phoenix_sk, derive_phoenix_vk, +}; use rand::prelude::StdRng; use rand::SeedableRng; -use rkyv::ser::serializers::AllocSerializer; use serde::Serialize; use std::fmt::Debug; use std::fs; use std::path::{Path, PathBuf}; +use wallet_core::transaction::{ + phoenix, phoenix_stake, phoenix_stake_reward, phoenix_unstake, +}; -use wallet_core::prelude::*; +use wallet_core::{phoenix_balance, prelude::*, BalanceInfo}; use crate::cache::NoteData; -use crate::clients::State; +use crate::clients::{Prover, State}; use crate::crypto::encrypt; use crate::dat::{ self, version_bytes, DatFileVersion, FILE_TYPE, LATEST_VERSION, MAGIC, @@ -71,11 +76,11 @@ impl Wallet { return Err(Error::Unauthorized); } - let index = addr.index()? as u64; + let index = addr.index()?; // retrieve keys - let sk = self.state.sk(index)?; - let pk = self.state.pk(index); + let sk = derive_phoenix_sk(&self.seed, index); + let pk = derive_phoenix_pk(&self.seed, index); Ok((pk, sk)) } @@ -96,7 +101,7 @@ impl Wallet { // derive the mnemonic seed let seed = Seed::new(&mnemonic, ""); // Takes the mnemonic seed as bytes - let mut bytes = seed.as_bytes(); + let mut bytes = seed.as_bytes().try_into().unwrap(); // Generate the default address let address = Address::new(0, derive_phoenix_pk(bytes, 0)); @@ -105,7 +110,7 @@ impl Wallet { Ok(Wallet { addresses: vec![address], state: None, - seed: bytes, + seed: *bytes, file: None, file_version: None, }) @@ -135,7 +140,7 @@ impl Wallet { // return early if its legacy if let DatFileVersion::Legacy = file_version { - let address = Address::new(0, store.pk(0)); + let address = Address::new(0, derive_phoenix_pk(&seed, 0)); // return the store return Ok(Self { @@ -148,7 +153,7 @@ impl Wallet { } let addresses: Vec<_> = (0..address_count) - .map(|i| Address::new(i, store.pk(i as u64))) + .map(|i| Address::new(i, derive_phoenix_pk(&seed, i))) .collect(); // create and return @@ -175,7 +180,7 @@ impl Wallet { header.extend_from_slice(&version_bytes(LATEST_VERSION)); // create file payload - let seed = self.store.get_seed()?; + let seed = self.seed; let mut payload = seed.to_vec(); payload.push(self.addresses.len() as u8); @@ -246,33 +251,38 @@ impl Wallet { } }; - let cfs: Vec<_> = (0..MAX_ADDRESSES) - .flat_map(|i| { - let pk = derive_phoenix_pk(self.seed, i); - - [format!("{:?}", pk), format!("spent_{:?}", pk)] + let addresses: Vec<_> = (0..MAX_ADDRESSES) + .map(|i| { + let i = i as u8; + ( + derive_phoenix_sk(&self.seed, i), + derive_phoenix_vk(&self.seed, i), + derive_phoenix_pk(&self.seed, i), + ) }) .collect(); // create a state client - self.state = - State::new(&cache_dir, status, http_state, http_prover, cfs); - - // set our own status callback - self.status = status; + self.state = Some(State::new( + &cache_dir, + status, + http_state, + http_prover, + addresses, + )?); Ok(()) } /// Sync wallet state pub async fn sync(&self) -> Result<(), Error> { - self.connected_wallet().await?.state().sync().await + self.state()?.sync().await } /// Helper function to register for async-sync outside of connect pub async fn register_sync(&mut self) -> Result<(), Error> { - match self.state.as_ref() { - Some(w) => w.register_sync(), + match self.state.as_mut() { + Some(w) => w.register_sync().await, None => Err(Error::Offline), } } @@ -291,16 +301,16 @@ impl Wallet { return Err(Error::Unauthorized); } - let index = addr.index()? as u64; + let index = addr.index()?; let vk = derive_phoenix_vk(&self.seed, index); let pk = derive_phoenix_pk(&self.seed, index); - let live_notes = self.state()?.fetch_notes(&vk).unwrap(); + let live_notes = self.state()?.fetch_notes(&pk)?; let spent_notes = self.state()?.cache().spent_notes(&pk)?; let live_notes = live_notes .into_iter() - .map(|(note, height)| (None, note, height)); + .map(|data| (None, data.note, data.height)); let spent_notes = spent_notes.into_iter().map( |(nullifier, NoteData { note, height })| { (Some(nullifier), note, height) @@ -327,29 +337,31 @@ impl Wallet { &self, addr: &Address, ) -> Result { + let state = self.state()?; // make sure we own this address if !addr.is_owned() { return Err(Error::Unauthorized); } - // get balance - if let Some(wallet) = &self.wallet { - let index = addr.index()? as u64; - Ok(wallet.get_balance(index)?) - } else { - Err(Error::Offline) - } + let index = addr.index()?; + let notes: Vec = state + .fetch_notes(addr.pk())? + .into_iter() + .map(|data| data.note) + .collect(); + + Ok(phoenix_balance( + &derive_phoenix_vk(&self.seed, index), + notes, + )) } /// Creates a new public address. /// The addresses generated are deterministic across sessions. pub fn new_address(&mut self) -> &Address { let len = self.addresses.len(); - let ssk = self - .store - .retrieve_ssk(len as u64) - .expect("wallet seed should be available"); - let addr = Address::new(len as u8, ssk.public_spend_key()); + let pk = derive_phoenix_pk(&self.seed, len as u8); + let addr = Address::new(len as u8, pk); self.addresses.push(addr); self.addresses.last().unwrap() @@ -366,16 +378,12 @@ impl Wallet { } /// Executes a generic contract call - pub async fn execute( + pub async fn execute( &self, sender: &Address, exec: Option>, gas: Gas, - ) -> Result - where - C: rkyv::Serialize>, - { - let wallet = self.connected_wallet().await?; + ) -> Result { // make sure we own the sender address if !sender.is_owned() { return Err(Error::Unauthorized); @@ -387,21 +395,10 @@ impl Wallet { } let mut rng = StdRng::from_entropy(); - let sender_index = - sender.index().expect("owned address should have an index"); + let sender_index = sender.index()?; + let sender_sk = derive_phoenix_sk(&self.seed, sender_index); - // transfer - let tx = wallet.execute( - &mut rng, - contract_id.into(), - call_name, - call_data, - sender_index as u64, - sender.psk(), - gas.limit, - gas.price, - )?; - Ok(tx) + Err(Error::Unauthorized) } /// Transfers funds between addresses @@ -412,7 +409,6 @@ impl Wallet { amt: Dusk, gas: Gas, ) -> Result { - let wallet = self.connected_wallet().await?; // make sure we own the sender address if !sender.is_owned() { return Err(Error::Unauthorized); @@ -426,23 +422,36 @@ impl Wallet { return Err(Error::NotEnoughGas); } + let state = self.state()?; + let mut rng = StdRng::from_entropy(); - let ref_id = BlsScalar::random(&mut rng); - let sender_index = - sender.index().expect("owned address should have an index"); + let sender_index = sender.index()?; - // transfer - let tx = wallet.transfer( + let sender_sk = derive_phoenix_sk(&self.seed, sender_index); + let change_pk = derive_phoenix_pk(&self.seed, sender_index); + let reciever_pk = rcvr.pk(); + + let inputs = state.inputs(sender_index, amt + gas.limit * gas.price)?; + + let root = state.fetch_anchor()?; + + let tx = phoenix::<_, Prover>( &mut rng, - sender_index as u64, - sender.psk(), - rcvr.psk(), - *amt, + &sender_sk, + &change_pk, + &reciever_pk, + inputs, + root, + amt, + true, + 0, gas.limit, gas.price, - ref_id, + None::, )?; - Ok(tx) + + // transfer + state.propagate(tx) } /// Stakes Dusk @@ -452,7 +461,6 @@ impl Wallet { amt: Dusk, gas: Gas, ) -> Result { - let wallet = self.connected_wallet().await?; // make sure we own the staking address if !addr.is_owned() { return Err(Error::Unauthorized); @@ -466,31 +474,34 @@ impl Wallet { return Err(Error::NotEnoughGas); } + let state = self.state()?; + let mut rng = StdRng::from_entropy(); let sender_index = addr.index()?; + let sender_pk = addr.pk(); + let sender_sk = derive_phoenix_sk(&self.seed, sender_index); + let stake_sk = derive_bls_sk(&self.seed, sender_index); + let nonce = + state.fetch_stake(&sender_pk)?.map(|s| s.nonce).unwrap_or(0); - // stake - let tx = wallet.stake( - &mut rng, - sender_index as u64, - sender_index as u64, - addr.psk(), - *amt, - gas.limit, - gas.price, + let inputs = state.inputs(sender_index, amt + gas.limit * gas.price)?; + + let root = state.fetch_anchor()?; + + let stake = phoenix_stake::<_, Prover>( + &mut rng, &sender_sk, &stake_sk, inputs, root, gas.limit, + gas.price, amt, nonce, )?; - Ok(tx) + + state.propagate(stake) } /// Obtains stake information for a given address - pub async fn stake_info(&self, addr: &Address) -> Result { - let wallet = self.connected_wallet().await?; - // make sure we own the staking address - if !addr.is_owned() { - return Err(Error::Unauthorized); - } - let index = addr.index()? as u64; - wallet.get_stake(index).map_err(Error::from) + pub async fn stake_info( + &self, + addr: &Address, + ) -> Result, Error> { + self.state()?.fetch_stake(&addr.pk()) } /// Unstakes Dusk @@ -499,24 +510,50 @@ impl Wallet { addr: &Address, gas: Gas, ) -> Result { - let wallet = self.connected_wallet().await?; // make sure we own the staking address if !addr.is_owned() { return Err(Error::Unauthorized); } let mut rng = StdRng::from_entropy(); - let index = addr.index()? as u64; + let index = addr.index()?; + + let state = self.state()?; + + let sender_pk = addr.pk(); + let sender_sk = derive_phoenix_sk(&self.seed, index); + let stake_sk = derive_bls_sk(&self.seed, index); + let unstake_value = state + .fetch_stake(&sender_pk)? + .map(|s| s.amount) + .flatten() + .map(|s| s.value) + .unwrap_or(0); + + let inputs = state + .inputs(index, gas.limit * gas.price)? + .into_iter() + .map(|(note, opening)| { + let nullifier = note.gen_nullifier(&sender_sk); + + (note, opening, nullifier) + }) + .collect(); - let tx = wallet.unstake( + let root = state.fetch_anchor()?; + + let stake = phoenix_unstake::<_, Prover>( &mut rng, - index, - index, - addr.psk(), + &sender_sk, + &stake_sk, + inputs, + root, + unstake_value, gas.limit, gas.price, )?; - Ok(tx) + + state.propagate(stake) } /// Withdraw accumulated staking reward for a given address @@ -525,43 +562,60 @@ impl Wallet { addr: &Address, gas: Gas, ) -> Result { - let wallet = self.connected_wallet().await?; + let state = self.state()?; // make sure we own the staking address if !addr.is_owned() { return Err(Error::Unauthorized); } let mut rng = StdRng::from_entropy(); - let index = addr.index()? as u64; + let index = addr.index()?; + + let sender_sk = derive_phoenix_sk(&self.seed, index); + let stake_sk = derive_bls_sk(&self.seed, index); + + let inputs = state + .inputs(index, gas.limit * gas.price)? + .into_iter() + .map(|(note, opening)| { + let nullifier = note.gen_nullifier(&sender_sk); + + (note, opening, nullifier) + }) + .collect(); + + let root = state.fetch_anchor()?; - let tx = wallet.withdraw( + let reward_amount = + state.fetch_stake(addr.pk())?.map(|s| s.reward).unwrap_or(0); + + let withdraw = phoenix_stake_reward::<_, Prover>( &mut rng, - index, - index, - addr.psk(), + &sender_sk, + &stake_sk, + inputs, + root, + reward_amount, gas.limit, gas.price, )?; - Ok(tx) + + state.propagate(withdraw) } /// Returns bls key pair for provisioner nodes pub fn provisioner_keys( &self, addr: &Address, - ) -> Result<(PublicKey, SecretKey), Error> { + ) -> Result<(&PhoenixPublicKey, &PhoenixSecretKey), Error> { // make sure we own the staking address if !addr.is_owned() { return Err(Error::Unauthorized); } - let index = addr.index()? as u64; + let index = addr.index()?; - // retrieve keys - let sk = self.store.retrieve_sk(index)?; - let pk: PublicKey = From::from(&sk); - - Ok((pk, sk)) + Ok((addr.pk(), &derive_phoenix_sk(&self.seed, index))) } /// Export bls key pair for provisioners in node-compatible format @@ -609,7 +663,7 @@ impl Wallet { pub fn claim_as_address(&self, addr: Address) -> Result<&Address, Error> { self.addresses() .iter() - .find(|a| a.psk == addr.psk) + .find(|a| a.pk == addr.pk) .ok_or(Error::AddressNotOwned) } @@ -642,9 +696,9 @@ pub struct DecodedNote { #[derive(Serialize)] struct BlsKeyPair { #[serde(with = "base64")] - secret_key_bls: [u8; 32], + secret_key_bls: [u8; 64], #[serde(with = "base64")] - public_key_bls: [u8; 96], + public_key_bls: [u8; 64], } mod base64 { diff --git a/rusk-wallet/src/wallet/address.rs b/rusk-wallet/src/wallet/address.rs index 8e66473a00..e6c1c1abc8 100644 --- a/rusk-wallet/src/wallet/address.rs +++ b/rusk-wallet/src/wallet/address.rs @@ -87,26 +87,26 @@ impl TryFrom<&[u8; PhoenixPublicKey::SIZE]> for Address { impl PartialEq for Address { fn eq(&self, other: &Self) -> bool { - self.index == other.index && self.psk == other.psk + self.index == other.index && self.pk == other.pk } } impl std::hash::Hash for Address { fn hash(&self, state: &mut H) { self.index.hash(state); - self.psk.to_bytes().hash(state); + self.pk.to_bytes().hash(state); } } impl fmt::Display for Address { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", bs58::encode(self.psk.to_bytes()).into_string()) + write!(f, "{}", bs58::encode(self.pk.to_bytes()).into_string()) } } impl fmt::Debug for Address { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", bs58::encode(self.psk.to_bytes()).into_string()) + write!(f, "{}", bs58::encode(self.pk.to_bytes()).into_string()) } } diff --git a/wallet-core/src/input.rs b/wallet-core/src/input.rs index 1add8aaf81..a0159d951a 100644 --- a/wallet-core/src/input.rs +++ b/wallet-core/src/input.rs @@ -15,7 +15,7 @@ use ff::Field; use rand::{CryptoRng, RngCore}; /// Note and the .value of the note seperated by u64 -type Inputs = Vec<(Note, u64)>; +pub type Inputs = Vec<(Note, u64)>; #[derive(Debug, PartialEq)] /// Error when calculating input notes for use in transaction diff --git a/wallet-core/src/lib.rs b/wallet-core/src/lib.rs index 85299a255d..92b060a869 100644 --- a/wallet-core/src/lib.rs +++ b/wallet-core/src/lib.rs @@ -25,10 +25,11 @@ pub mod prelude { pub use execution_core::{ dusk, from_dusk, + stake::StakeData, transfer::{ contract_exec::{ContractCall, ContractExec}, phoenix::{ - Note, PublicKey as PhoenixPublicKey, + Note, Prove, PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey, Transaction as PhoenixTransaction, TreeLeaf, ViewKey as PhoenixViewKey,