From da74081ced6ba411229585ea6b8d34dda3feb86a Mon Sep 17 00:00:00 2001 From: moana Date: Fri, 6 Sep 2024 08:29:18 +0200 Subject: [PATCH] rusk-wallet: Add moonlight support Also included in the PR is the following is the addition of functions that return a key for a given index --- rusk-wallet/src/bin/command.rs | 17 +-- rusk-wallet/src/cache.rs | 6 + rusk-wallet/src/clients.rs | 38 ++++-- rusk-wallet/src/clients/sync.rs | 26 ++--- rusk-wallet/src/wallet.rs | 198 ++++++++++++++++++++------------ 5 files changed, 182 insertions(+), 103 deletions(-) diff --git a/rusk-wallet/src/bin/command.rs b/rusk-wallet/src/bin/command.rs index e75126097b..5c26127ec3 100644 --- a/rusk-wallet/src/bin/command.rs +++ b/rusk-wallet/src/bin/command.rs @@ -231,7 +231,8 @@ impl Command { }; let gas = Gas::new(gas_limit).with_price(gas_price); - let tx = wallet.transfer(sender, &rcvr, amt, gas).await?; + let tx = + wallet.phoenix_transfer(sender, &rcvr, amt, gas).await?; Ok(RunResult::Tx(tx.hash())) } Command::Stake { @@ -247,7 +248,7 @@ impl Command { }; let gas = Gas::new(gas_limit).with_price(gas_price); - let tx = wallet.stake(addr, amt, gas).await?; + let tx = wallet.phoenix_stake(addr, amt, gas).await?; Ok(RunResult::Tx(tx.hash())) } Command::StakeInfo { addr, reward } => { @@ -255,8 +256,10 @@ impl Command { Some(addr) => wallet.claim_as_address(addr)?, None => wallet.default_address(), }; - let si = - wallet.stake_info(addr).await?.ok_or(Error::NotStaked)?; + let si = wallet + .stake_info(addr.index) + .await? + .ok_or(Error::NotStaked)?; Ok(RunResult::StakeInfo(si, reward)) } @@ -273,7 +276,7 @@ impl Command { let gas = Gas::new(gas_limit).with_price(gas_price); - let tx = wallet.unstake(addr, gas).await?; + let tx = wallet.phoenix_unstake(addr, gas).await?; Ok(RunResult::Tx(tx.hash())) } Command::Withdraw { @@ -289,7 +292,7 @@ impl Command { let gas = Gas::new(gas_limit).with_price(gas_price); - let tx = wallet.withdraw_reward(addr, gas).await?; + let tx = wallet.phoenix_stake_withdraw(addr, gas).await?; Ok(RunResult::Tx(tx.hash())) } Command::Export { addr, dir, name } => { @@ -305,7 +308,7 @@ impl Command { )?; let (pub_key, key_pair) = - wallet.export_keys(addr, &dir, name, &pwd)?; + wallet.export_provisioner_keys(addr, &dir, name, &pwd)?; Ok(RunResult::ExportedKeys(pub_key, key_pair)) } diff --git a/rusk-wallet/src/cache.rs b/rusk-wallet/src/cache.rs index 2dd4936d57..8f400386f7 100644 --- a/rusk-wallet/src/cache.rs +++ b/rusk-wallet/src/cache.rs @@ -239,6 +239,12 @@ impl Ord for NoteData { } } +impl AsRef for NoteData { + fn as_ref(&self) -> &Note { + &self.note + } +} + impl Serializable<{ u64::SIZE + Note::SIZE }> for NoteData { type Error = dusk_bytes::Error; /// Converts a Note into a byte representation diff --git a/rusk-wallet/src/clients.rs b/rusk-wallet/src/clients.rs index 4c1a7ff9f9..53a456ea77 100644 --- a/rusk-wallet/src/clients.rs +++ b/rusk-wallet/src/clients.rs @@ -8,7 +8,8 @@ mod sync; use dusk_bytes::Serializable; use execution_core::{ - transfer::{phoenix::Prove, Transaction}, + signatures::bls::PublicKey as AccountPublicKey, + transfer::{moonlight::AccountData, phoenix::Prove, Transaction}, Error as ExecutionCoreError, }; use flume::Receiver; @@ -139,15 +140,15 @@ impl State { /// Skips writing the proof for non phoenix transactions pub fn prove_and_propagate( &self, - utx: Transaction, + tx: Transaction, ) -> Result { let status = self.status; let prover = &self.prover; - let mut utx = utx; + let mut tx = tx; - if let Transaction::Phoenix(tx) = &mut utx { + if let Transaction::Phoenix(utx) = &mut tx { let status = self.status; - let proof = tx.proof(); + let proof = utx.proof(); status("Attempt to prove tx..."); @@ -158,12 +159,12 @@ impl State { ExecutionCoreError::PhoenixCircuit(e.to_string()) })?; - tx.set_proof(proof); + utx.set_proof(proof); status("Proving sucesss!"); } - let tx_bytes = utx.to_var_bytes(); + let tx_bytes = tx.to_var_bytes(); status("Attempt to preverify tx..."); let preverify_req = RuskRequest::new("preverify", tx_bytes.clone()); @@ -175,7 +176,7 @@ impl State { let _ = self.client.call(2, "Chain", &propagate_req).wait()?; status("Transaction propagated!"); - Ok(utx) + Ok(tx) } /// Find notes for a view key, starting from the given block height. @@ -215,6 +216,27 @@ impl State { inputs } + pub(crate) fn fetch_account( + &self, + pk: &AccountPublicKey, + ) -> Result { + let status = self.status; + status("Fetching account-data..."); + + let account = self + .client + .contract_query::<_, 1024>(TRANSFER_CONTRACT, "account", pk) + .wait()?; + let account = rkyv::from_bytes(&account).map_err(|_| Error::Rkyv)?; + status("account-data received!"); + + let account_address = pk.to_bytes().to_vec(); + let account_address = bs58::encode(account_address).into_string(); + println!("Account address: {}", account_address); + + Ok(account) + } + pub(crate) fn fetch_notes( &self, pk: &PhoenixPublicKey, diff --git a/rusk-wallet/src/clients/sync.rs b/rusk-wallet/src/clients/sync.rs index c884c91b63..32eb5a3e65 100644 --- a/rusk-wallet/src/clients/sync.rs +++ b/rusk-wallet/src/clients/sync.rs @@ -25,17 +25,17 @@ pub(crate) async fn sync_db( ) -> Result<(), Error> { let seed = store.get_seed(); - let addresses: Vec<(PhoenixSecretKey, PhoenixViewKey, PhoenixPublicKey)> = - (0..MAX_ADDRESSES) - .map(|i| { - let i = i as u8; - ( - derive_phoenix_sk(seed, i), - derive_phoenix_vk(seed, i), - derive_phoenix_pk(seed, i), - ) - }) - .collect(); + let keys: Vec<(PhoenixSecretKey, PhoenixViewKey, PhoenixPublicKey)> = (0 + ..MAX_ADDRESSES) + .map(|i| { + let i = i as u8; + ( + derive_phoenix_sk(seed, i), + derive_phoenix_vk(seed, i), + derive_phoenix_pk(seed, i), + ) + }) + .collect(); status("Getting cached note position..."); @@ -87,7 +87,7 @@ pub(crate) async fn sync_db( buffer = leaf_chunk.remainder().to_vec(); } - for (sk, vk, pk) in addresses.iter() { + for (sk, vk, pk) in keys.iter() { for (block_height, note) in note_data.iter() { if vk.owns(note.stealth_address()) { let nullifier = note.gen_nullifier(sk); @@ -108,7 +108,7 @@ pub(crate) async fn sync_db( // Remove spent nullifiers from live notes // zerorize all the secret keys - for (mut sk, _, pk) in addresses { + for (mut sk, _, pk) in keys { let nullifiers: Vec = cache.unspent_notes_id(&pk)?; if !nullifiers.is_empty() { diff --git a/rusk-wallet/src/wallet.rs b/rusk-wallet/src/wallet.rs index f68f4edcbc..c8cacb0d8e 100644 --- a/rusk-wallet/src/wallet.rs +++ b/rusk-wallet/src/wallet.rs @@ -24,20 +24,19 @@ use std::path::{Path, PathBuf}; use wallet_core::{ phoenix_balance, prelude::keys::{ - derive_bls_sk, derive_phoenix_pk, derive_phoenix_sk, derive_phoenix_vk, + derive_bls_pk, derive_bls_sk, derive_phoenix_pk, derive_phoenix_sk, + derive_phoenix_vk, }, transaction::{ - phoenix, phoenix_stake, phoenix_stake_reward, phoenix_unstake, + moonlight, phoenix, phoenix_stake, phoenix_stake_reward, + phoenix_unstake, }, BalanceInfo, }; use execution_core::{ signatures::bls::{PublicKey as BlsPublicKey, SecretKey as BlsSecretKey}, - transfer::{ - data::{ContractCall, TransactionData}, - Transaction, - }, + transfer::{data::TransactionData, Transaction}, }; use zeroize::Zeroize; @@ -99,7 +98,7 @@ impl Wallet { let index = addr.index()?; // retrieve keys - let sk = derive_phoenix_sk(self.store.get_seed(), index); + let sk = self.phoenix_secret_key(index); let pk = addr.pk(); Ok((*pk, sk)) @@ -356,20 +355,13 @@ impl Wallet { } let index = addr.index()?; - let notes: Vec = state - .fetch_notes(addr.pk())? - .into_iter() - .map(|data| NoteLeaf { - note: data.note, - block_height: data.height, - }) - .collect(); + let notes = state.fetch_notes(addr.pk())?; let seed = self.store.get_seed(); Ok(phoenix_balance( &derive_phoenix_vk(seed, index), - notes.into_iter(), + notes.iter(), )) } @@ -395,14 +387,90 @@ impl Wallet { &self.addresses } - /// Executes a generic contract call - pub async fn execute( + /// Returns the phoenix secret-key for a given index + pub(crate) fn phoenix_secret_key(&self, index: u8) -> PhoenixSecretKey { + let seed = self.store.get_seed(); + derive_phoenix_sk(&seed, index) + } + + /// Returns the phoenix public-key for a given index + pub fn phoenix_public_key(&self, index: u8) -> PhoenixPublicKey { + let seed = self.store.get_seed(); + derive_phoenix_pk(&seed, index) + } + + /// Returns the bls secret-key for a given index + pub(crate) fn bls_secret_key(&self, index: u8) -> BlsSecretKey { + let seed = self.store.get_seed(); + derive_bls_sk(&seed, index) + } + + /// Returns the bls public-key for a given index + pub fn bls_public_key(&self, index: u8) -> BlsPublicKey { + let seed = self.store.get_seed(); + derive_bls_pk(&seed, index) + } + + /// Creates a generic moonlight transaction. + #[allow(clippy::too_many_arguments)] + pub fn moonlight_transaction( + &self, + from_addr: &Address, + to_account: Option, + transfer_value: Dusk, + deposit: Dusk, + gas: Gas, + exec: Option>, + ) -> Result { + // make sure we own the sender address + if !from_addr.is_owned() { + return Err(Error::Unauthorized); + } + + // check gas limits + if !gas.is_enough() { + return Err(Error::NotEnoughGas); + } + + let state = self.state()?; + let deposit = *deposit; + + let from_index = from_addr.index()?; + let mut from_sk = self.bls_secret_key(from_index); + let from_account = self.bls_public_key(from_index); + + let account = state.fetch_account(&from_account)?; + + // technically this check is not necessary, but it's nice to not spam + // the network with transactions that are unspendable. + let nonce = account.nonce + 1; + + let chain_id = state.fetch_chain_id()?; + + let tx = moonlight( + &from_sk, + to_account, + *transfer_value, + deposit, + gas.limit, + gas.price, + nonce, + chain_id, + exec, + )?; + + from_sk.zeroize(); + + state.prove_and_propagate(tx) + } + + /// Executes a generic contract call, paying gas with phoenix notes + pub async fn phoenix_execute( &self, sender: &Address, - reciever: &Address, deposit: Dusk, - data: Option>, gas: Gas, + data: TransactionData, ) -> Result { // make sure we own the sender address if !sender.is_owned() { @@ -414,14 +482,14 @@ impl Wallet { return Err(Error::NotEnoughGas); } - let seed = self.store.get_seed(); - let state = self.state()?; let deposit = *deposit; let mut rng = StdRng::from_entropy(); let sender_index = sender.index()?; - let mut sender_sk = derive_phoenix_sk(seed, sender_index); + let mut sender_sk = self.phoenix_secret_key(sender_index); + // in a contract execution, the sender and receiver are the same + let receiver_pk = sender.pk(); let inputs = state .inputs(sender_index, deposit + gas.limit * gas.price)? @@ -436,7 +504,7 @@ impl Wallet { &mut rng, &sender_sk, sender.pk(), - reciever.pk(), + receiver_pk, inputs, root, 0, @@ -445,18 +513,16 @@ impl Wallet { gas.limit, gas.price, chain_id, - data, + Some(data), )?; - let tx = state.prove_and_propagate(tx); - sender_sk.zeroize(); - tx + state.prove_and_propagate(tx) } - /// Transfers funds between addresses - pub async fn transfer( + /// Transfers funds between phoenix-addresses + pub async fn phoenix_transfer( &self, sender: &Address, rcvr: &Address, @@ -482,9 +548,7 @@ impl Wallet { let sender_index = sender.index()?; let amt = *amt; - let seed = self.store.get_seed(); - - let mut sender_sk = derive_phoenix_sk(seed, sender_index); + let mut sender_sk = self.phoenix_secret_key(sender_index); let change_pk = sender.pk(); let reciever_pk = rcvr.pk(); @@ -510,18 +574,16 @@ impl Wallet { gas.limit, gas.price, chain_id, - None::, + None::, )?; - let tx = state.prove_and_propagate(tx); - sender_sk.zeroize(); - tx + state.prove_and_propagate(tx) } - /// Stakes Dusk - pub async fn stake( + /// Stakes Dusk using phoenix notes + pub async fn phoenix_stake( &self, addr: &Address, amt: Dusk, @@ -541,13 +603,12 @@ impl Wallet { } let state = self.state()?; - let seed = self.store.get_seed(); let mut rng = StdRng::from_entropy(); let amt = *amt; let sender_index = addr.index()?; - let mut sender_sk = derive_phoenix_sk(seed, sender_index); - let mut stake_sk = derive_bls_sk(seed, sender_index); + let mut sender_sk = self.phoenix_secret_key(sender_index); + let mut stake_sk = self.bls_secret_key(sender_index); let nonce = state .fetch_stake(&AccountPublicKey::from(&stake_sk))? @@ -568,30 +629,22 @@ impl Wallet { gas.price, chain_id, amt, nonce, )?; - let tx = state.prove_and_propagate(stake); - sender_sk.zeroize(); stake_sk.zeroize(); - tx + state.prove_and_propagate(stake) } /// Obtains stake information for a given address pub async fn stake_info( &self, - addr: &Address, + addr_idx: u8, ) -> Result, Error> { - let seed = self.store.get_seed(); - - self.state()? - .fetch_stake(&AccountPublicKey::from(&derive_bls_sk( - seed, - addr.index()?, - ))) + self.state()?.fetch_stake(&self.bls_public_key(addr_idx)) } - /// Unstakes Dusk - pub async fn unstake( + /// Unstakes Dusk into phoenix notes + pub async fn phoenix_unstake( &self, addr: &Address, gas: Gas, @@ -603,12 +656,11 @@ impl Wallet { let mut rng = StdRng::from_entropy(); let index = addr.index()?; - let seed = self.store.get_seed(); let state = self.state()?; - let mut sender_sk = derive_phoenix_sk(seed, index); - let mut stake_sk = derive_bls_sk(seed, index); + let mut sender_sk = self.phoenix_secret_key(index); + let mut stake_sk = self.bls_secret_key(index); let unstake_value = state .fetch_stake(&AccountPublicKey::from(&stake_sk))? @@ -633,34 +685,32 @@ impl Wallet { chain_id, )?; - let tx = state.prove_and_propagate(unstake); - sender_sk.zeroize(); stake_sk.zeroize(); - tx + state.prove_and_propagate(unstake) } /// Withdraw accumulated staking reward for a given address - pub async fn withdraw_reward( + pub async fn phoenix_stake_withdraw( &self, - addr: &Address, + sender_addr: &Address, + staker_index: u8, gas: Gas, ) -> Result { let state = self.state()?; // make sure we own the staking address - if !addr.is_owned() { + if !sender_addr.is_owned() { return Err(Error::Unauthorized); } let mut rng = StdRng::from_entropy(); - let index = addr.index()?; - let seed = self.store.get_seed(); + let sender_index = sender_addr.index()?; - let mut sender_sk = derive_phoenix_sk(seed, index); - let mut stake_sk = derive_bls_sk(seed, index); + let mut sender_sk = self.phoenix_secret_key(sender_index); + let mut stake_sk = self.bls_secret_key(staker_index); - let inputs = state.inputs(index, gas.limit * gas.price)?; + let inputs = state.inputs(sender_index, gas.limit * gas.price)?; let root = state.fetch_root()?; let chain_id = state.fetch_chain_id()?; @@ -682,12 +732,10 @@ impl Wallet { chain_id, )?; - let tx = state.prove_and_propagate(withdraw); - sender_sk.zeroize(); stake_sk.zeroize(); - tx + state.prove_and_propagate(withdraw) } /// Returns bls key pair for provisioner nodes @@ -701,14 +749,14 @@ impl Wallet { } let index = addr.index()?; - let sk = derive_bls_sk(self.store.get_seed(), index); - let pk = BlsPublicKey::from(&sk); + let sk = self.bls_secret_key(index); + let pk = self.bls_public_key(index); Ok((pk, sk)) } /// Export bls key pair for provisioners in node-compatible format - pub fn export_keys( + pub fn export_provisioner_keys( &self, addr: &Address, dir: &Path,