diff --git a/rusk-wallet/src/clients.rs b/rusk-wallet/src/clients.rs index 4c1a7ff9f9..e69d7ebd05 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; @@ -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/wallet.rs b/rusk-wallet/src/wallet.rs index 4a2c6756a7..1dbe922bd0 100644 --- a/rusk-wallet/src/wallet.rs +++ b/rusk-wallet/src/wallet.rs @@ -24,10 +24,12 @@ 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, }; @@ -395,14 +397,85 @@ impl Wallet { &self.addresses } - /// Executes a generic contract call - pub async fn execute( + /// 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 public-key for a given index + pub fn account_public_key(&self, index: u8) -> BlsPublicKey { + let seed = self.store.get_seed(); + derive_bls_pk(&seed, index) + } + + /// Returns the bls secret-key for a given index + pub fn account_secret_key(&self, index: u8) -> BlsSecretKey { + let seed = self.store.get_seed(); + derive_bls_sk(&seed, index) + } + + /// Creates a generic moonlight transaction. + #[allow(clippy::too_many_arguments)] + pub fn moonlight_transaction( &self, - sender: &Address, - reciever: &Address, + 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 seed = self.store.get_seed(); + + let state = self.state()?; + let deposit = *deposit; + + let from_index = from_addr.index()?; + let mut from_sk = derive_bls_sk(&seed, from_index); + let from_account = BlsPublicKey::from(&from_sk); + + 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(); + + Ok(tx.into()) + } + /// Executes a generic contract call, paying gas with phoenix notes + pub async fn phoenix_execute( + &self, + sender: &Address, + deposit: Dusk, gas: Gas, + exec: impl Into, ) -> Result { // make sure we own the sender address if !sender.is_owned() { @@ -422,6 +495,8 @@ impl Wallet { let mut rng = StdRng::from_entropy(); let sender_index = sender.index()?; let mut sender_sk = derive_phoenix_sk(seed, 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 +511,7 @@ impl Wallet { &mut rng, &sender_sk, sender.pk(), - reciever.pk(), + receiver_pk, inputs, root, 0, @@ -445,7 +520,7 @@ impl Wallet { gas.limit, gas.price, chain_id, - exec, + Some(exec), )?; let tx = state.prove_and_propagate(tx); @@ -455,8 +530,8 @@ impl Wallet { tx } - /// Transfers funds between addresses - pub async fn transfer( + /// Transfers funds between phoenix-addresses + pub async fn phoenix_transfer( &self, sender: &Address, rcvr: &Address, @@ -520,8 +595,8 @@ impl Wallet { tx } - /// Stakes Dusk - pub async fn stake( + /// Stakes Dusk using phoenix notes + pub async fn phoenix_stake( &self, addr: &Address, amt: Dusk, @@ -579,19 +654,18 @@ impl Wallet { /// 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()?, + seed, addr_idx, ))) } - /// Unstakes Dusk - pub async fn unstake( + /// Unstakes Dusk into phoenix notes + pub async fn phoenix_unstake( &self, addr: &Address, gas: Gas, @@ -642,25 +716,26 @@ impl Wallet { } /// 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 sender_index = sender_addr.index()?; let seed = self.store.get_seed(); - let mut sender_sk = derive_phoenix_sk(seed, index); - let mut stake_sk = derive_bls_sk(seed, index); + let mut sender_sk = derive_phoenix_sk(seed, sender_index); + let mut stake_sk = derive_bls_sk(seed, 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()?; diff --git a/rusk-wallet/tests/generate_test_wallet.rs b/rusk-wallet/tests/generate_test_wallet.rs new file mode 100644 index 0000000000..57dee4ef03 --- /dev/null +++ b/rusk-wallet/tests/generate_test_wallet.rs @@ -0,0 +1,75 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use wallet_core::keys::RNG_SEED; + +// Create a wallet for testing where the seed is an array of `0u8`. +// +// Since there is no functionality to override the seed or address-count in +// a wallet (and there also shouldn't be one), we modified the `save` method +// (called by `save_to`) of the wallet so that the seed is overridden with +// `[0u8; RNG_SEED]` and the address count with `100`. +// +// For the asserts to work the `store` field must be pub, as well as the +// `LocalStore` struct +// +// Create the wallet-file by doing the above adjustments, uncomment the +// `#[test]` line below and run +// `cargo test --release --test generate_test_wallet`. +// The generated 'test_wallet.dat' file can then be moved to where it is needed. +// +// #[test] +fn create_test_wallet() -> Result<(), rusk_wallet::Error> { + use rusk_wallet::{SecureWalletFile, Wallet, WalletPath}; + + #[derive(Debug, Clone)] + struct WalletFile { + path: WalletPath, + pwd: Vec, + } + + impl SecureWalletFile for WalletFile { + fn path(&self) -> &WalletPath { + &self.path + } + + fn pwd(&self) -> &[u8] { + &self.pwd + } + } + + // create the wallet file path + let wallet_path = WalletPath::from( + std::env::current_dir()?.as_path().join("test_wallet.dat"), + ); + let wallet_file = WalletFile { + path: wallet_path, + pwd: blake3::hash(b"mypassword").as_bytes().to_vec(), + }; + + // create a test wallet with an arbitrary passphrase (the seed will be + // overridden) + const PHRASE: &str = "park remain person kitchen mule spell knee armed position rail grid ankle"; + + let mut wallet = Wallet::new(PHRASE)?; + + // since there is no functionality to override the seed or address-count in + // a wallet (and there also shouldn't be one), we modified the `save` method + // (called by `save_to`) of the wallet so that the seed is overridden with + // `[0u8; RNG_SEED]` and the address count with `100`. + wallet.save_to(wallet_file.clone())?; + + // load wallet from file and check seed and address count + // for these assert to work the `store` field must be pub, as well as the + // `LocalStore` struct + const SEED: [u8; RNG_SEED] = [0u8; RNG_SEED]; + const ADDR_COUNT: u8 = 100; + + let loaded_wallet = Wallet::from_file(wallet_file)?; + assert_eq!(*loaded_wallet.store.get_seed(), SEED); + assert_eq!(loaded_wallet.addresses().len(), ADDR_COUNT as usize); + Ok(()) +}