diff --git a/rusk-wallet/src/bin/command.rs b/rusk-wallet/src/bin/command.rs index fa50c3756c..ef1aec0ded 100644 --- a/rusk-wallet/src/bin/command.rs +++ b/rusk-wallet/src/bin/command.rs @@ -51,9 +51,9 @@ pub(crate) enum Command { /// Check your current Phoenix balance PhoenixBalance { - /// Address + /// Address index #[clap(short, long)] - addr: Option
, + addr_idx: Option, /// Check maximum spendable balance #[clap(long)] @@ -62,9 +62,9 @@ pub(crate) enum Command { /// Check your current Moonlight balance MoonlightBalance { - /// Address + /// Address index #[clap(short, long)] - addr: Option
, + addr_idx: Option, }, /// List your existing addresses and generate new ones @@ -77,16 +77,16 @@ pub(crate) enum Command { // Phoenix transaction commands /// Show address transaction history PhoenixHistory { - /// Address for which you want to see the history + /// Address index for which you want to see the history #[clap(short, long)] - addr: Option
, + addr_idx: Option, }, /// Send DUSK privately through the network using Phoenix PhoenixTransfer { /// Phoenix address from which to send DUSK [default: first address] #[clap(short, long)] - sndr: Option
, + sndr_idx: Option, /// Phoenix receiver address #[clap(short, long)] @@ -109,7 +109,7 @@ pub(crate) enum Command { PhoenixMemo { /// Phoenix address from which to send DUSK [default: first address] #[clap(short, long)] - sndr: Option
, + sndr_idx: Option, /// Optional memo to attach to the transaction #[clap(short, long)] @@ -136,7 +136,7 @@ pub(crate) enum Command { PhoenixStake { /// Phoenix address from which to stake DUSK [default: first address] #[clap(short = 's', long)] - addr: Option
, + addr_idx: Option, /// Amount of DUSK to stake #[clap(short, long)] @@ -156,7 +156,7 @@ pub(crate) enum Command { /// Phoenix address from which to make the unstake request [default: /// first address] #[clap(short, long)] - addr: Option
, + addr_idx: Option, /// Max amount of gas for this transaction #[clap(short = 'l', long, default_value_t = DEFAULT_STAKE_GAS_LIMIT)] @@ -172,7 +172,7 @@ pub(crate) enum Command { /// Phoenix address from which to make the withdraw request [default: /// first address] #[clap(short, long)] - addr: Option
, + addr_idx: Option, /// Max amount of gas for this transaction #[clap(short = 'l', long, default_value_t = DEFAULT_STAKE_GAS_LIMIT)] @@ -187,7 +187,7 @@ pub(crate) enum Command { PhoenixContractDeploy { /// Phoenix address from which to deploy the contract [default: first] #[clap(short, long)] - addr: Option
, + addr_idx: Option, /// Path to the WASM contract code #[clap(short, long)] @@ -210,7 +210,7 @@ pub(crate) enum Command { PhoenixContractCall { /// Phoenix address from which to call the contract [default: first] #[clap(short, long)] - addr: Option
, + addr_idx: Option, /// Contract id of the contract to call #[clap(short, long)] @@ -238,7 +238,7 @@ pub(crate) enum Command { StakeInfo { /// Address used to stake [default: first address] #[clap(short, long)] - addr: Option
, + addr_idx: Option, /// Check accumulated reward #[clap(long, action)] @@ -250,7 +250,7 @@ pub(crate) enum Command { MoonlightTransfer { /// Moonlight Address from which to send DUSK [default: first address] #[clap(short, long)] - sndr: Option
, + sndr_idx: Option, /// Moonlight receiver address #[clap(short, long)] @@ -273,7 +273,7 @@ pub(crate) enum Command { MoonlightMemo { /// Moonlight Address from which to send DUSK [default: first address] #[clap(short, long)] - sndr: Option
, + sndr_idx: Option, /// Optional memo to attach to the transaction #[clap(short, long)] @@ -300,7 +300,7 @@ pub(crate) enum Command { MoonlightStake { /// Moonlight address from which to stake DUSK [default: first address] #[clap(short = 's', long)] - addr: Option
, + addr_idx: Option, /// Amount of DUSK to stake #[clap(short, long)] @@ -320,7 +320,7 @@ pub(crate) enum Command { /// Moonlight address from which to make the unstake request [default: /// first address] #[clap(short, long)] - addr: Option
, + addr_idx: Option, /// Max amount of gas for this transaction #[clap(short = 'l', long, default_value_t = DEFAULT_STAKE_GAS_LIMIT)] @@ -336,7 +336,7 @@ pub(crate) enum Command { /// Moonlight address from which to make the withdraw request [default: /// first address] #[clap(short, long)] - addr: Option
, + addr_idx: Option, /// Max amount of gas for this transaction #[clap(short = 'l', long, default_value_t = DEFAULT_STAKE_GAS_LIMIT)] @@ -352,7 +352,7 @@ pub(crate) enum Command { /// Moonlight address from which to deploy the contract [default: /// first] #[clap(short, long)] - addr: Option
, + addr_idx: Option, /// Path to the WASM contract code #[clap(short, long)] @@ -373,9 +373,9 @@ pub(crate) enum Command { /// Call a contract using Moonlight MoonlightContractCall { - /// ContractId to call + /// address index of the moonlight account that will pay for the gas #[clap(short, long)] - addr: Option
, + addr_idx: Option, /// contract id of the contract to call #[clap(short, long)] @@ -403,7 +403,7 @@ pub(crate) enum Command { PhoenixToMoonlight { /// Moonlight or Phoenix address from which to convert DUSK to #[clap(short = 's', long)] - addr: Option
, + addr_idx: Option, /// Amount of DUSK to transfer to your Moonlight account #[clap(short, long)] @@ -422,7 +422,7 @@ pub(crate) enum Command { MoonlightToPhoenix { /// Moonlight or Phoenix Address from which to convert DUSK to #[clap(short = 's', long)] - addr: Option
, + addr_idx: Option, /// Amount of DUSK to transfer to your phoenix account #[clap(short, long)] @@ -442,7 +442,7 @@ pub(crate) enum Command { /// Address for which you want the exported keys [default: first /// address] #[clap(short, long)] - addr: Option
, + addr_idx: Option, /// Output directory for the exported keys #[clap(short, long)] @@ -465,7 +465,10 @@ impl Command { settings: &Settings, ) -> anyhow::Result { match self { - Command::PhoenixBalance { addr, spendable } => { + Command::PhoenixBalance { + addr_idx, + spendable, + } => { let sync_result = wallet.sync().await; if let Err(e) = sync_result { // Sync error should be reported only if wallet is online @@ -473,23 +476,15 @@ impl Command { tracing::error!("Unable to update the balance {e:?}") } } + let addr_idx = addr_idx.unwrap_or_default(); - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; - - let balance = wallet.get_phoenix_balance(addr).await?; + let balance = wallet.get_phoenix_balance(addr_idx).await?; Ok(RunResult::PhoenixBalance(balance, spendable)) } - Command::MoonlightBalance { addr } => { - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; - + Command::MoonlightBalance { addr_idx } => { + let addr_idx = addr_idx.unwrap_or_default(); Ok(RunResult::MoonlightBalance( - wallet.get_moonlight_balance(addr).await?, + wallet.get_moonlight_balance(addr_idx).await?, )) } Command::Addresses { new } => { @@ -501,34 +496,47 @@ impl Command { std::process::exit(0); } - let addr = wallet.new_address().clone(); + let new_addr_idx = wallet.add_address(); wallet.save()?; - Ok(RunResult::Address(Box::new(addr))) + + // leave this hack here until `RunResult` gets an overhaul + let phoenix_addr = Address::Phoenix { + pk: *wallet.phoenix_pk(new_addr_idx)?, + }; + Ok(RunResult::Address(Box::new(phoenix_addr))) } else { - Ok(RunResult::Addresses(wallet.addresses().clone())) + let phoenix_addresses = wallet + .addresses() + .iter() + .enumerate() + .map(|(_index, (phoenix_pk, _bls_pk))| { + Address::Phoenix { pk: *phoenix_pk } + }) + .collect(); + + Ok(RunResult::Addresses(phoenix_addresses)) } } Command::PhoenixTransfer { - sndr, + sndr_idx, rcvr, amt, gas_limit, gas_price, } => { wallet.sync().await?; - let sender = match sndr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; let gas = Gas::new(gas_limit).with_price(gas_price); + let sender_idx = sndr_idx.unwrap_or_default(); + + let receiver = rcvr.try_phoenix_pk()?; let tx = wallet - .phoenix_transfer(sender, &rcvr, None, amt, gas) + .phoenix_transfer(sender_idx, receiver, None, amt, gas) .await?; Ok(RunResult::Tx(tx.hash())) } Command::PhoenixMemo { - sndr, + sndr_idx, memo, rcvr, amt, @@ -536,38 +544,42 @@ impl Command { gas_price, } => { wallet.sync().await?; - let sender = match sndr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; let gas = Gas::new(gas_limit).with_price(gas_price); + let sender_idx = sndr_idx.unwrap_or_default(); + + let receiver = rcvr.try_phoenix_pk()?; let tx = wallet - .phoenix_transfer(sender, &rcvr, Some(memo), amt, gas) + .phoenix_transfer( + sender_idx, + receiver, + Some(memo), + amt, + gas, + ) .await?; Ok(RunResult::Tx(tx.hash())) } Command::MoonlightTransfer { - sndr, + sndr_idx, rcvr, amt, gas_limit, gas_price, } => { let gas = Gas::new(gas_limit).with_price(gas_price); - let sender = match sndr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; + let sender_idx = sndr_idx.unwrap_or_default(); + + let receiver = rcvr.try_bls_pk()?; let tx = wallet - .moonlight_transfer(sender, &rcvr, None, amt, gas) + .moonlight_transfer(sender_idx, receiver, None, amt, gas) .await?; Ok(RunResult::Tx(tx.hash())) } Command::MoonlightMemo { - sndr, + sndr_idx, memo, rcvr, amt, @@ -575,101 +587,91 @@ impl Command { gas_price, } => { let gas = Gas::new(gas_limit).with_price(gas_price); - let sender = match sndr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; + let sender_idx = sndr_idx.unwrap_or_default(); + + let receiver = rcvr.try_bls_pk()?; let tx = wallet - .moonlight_transfer(sender, &rcvr, Some(memo), amt, gas) + .moonlight_transfer( + sender_idx, + receiver, + Some(memo), + amt, + gas, + ) .await?; Ok(RunResult::Tx(tx.hash())) } Command::PhoenixStake { - addr, + addr_idx, amt, gas_limit, gas_price, } => { wallet.sync().await?; let gas = Gas::new(gas_limit).with_price(gas_price); - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; + let addr_idx = addr_idx.unwrap_or_default(); - let tx = wallet.phoenix_stake(addr, amt, gas).await?; + let tx = wallet.phoenix_stake(addr_idx, amt, gas).await?; Ok(RunResult::Tx(tx.hash())) } - Command::StakeInfo { addr, reward } => { - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; - let si = wallet - .stake_info(addr.index()?) + Command::StakeInfo { addr_idx, reward } => { + let addr_idx = addr_idx.unwrap_or_default(); + let stake_info = wallet + .stake_info(addr_idx) .await? .ok_or(Error::NotStaked)?; - Ok(RunResult::StakeInfo(si, reward)) + Ok(RunResult::StakeInfo(stake_info, reward)) } Command::PhoenixUnstake { - addr, + addr_idx, gas_limit, gas_price, } => { wallet.sync().await?; - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; let gas = Gas::new(gas_limit).with_price(gas_price); + let addr_idx = addr_idx.unwrap_or_default(); - let tx = wallet.phoenix_unstake(addr, gas).await?; + let tx = wallet.phoenix_unstake(addr_idx, gas).await?; Ok(RunResult::Tx(tx.hash())) } Command::PhoenixWithdraw { - addr, + addr_idx, gas_limit, gas_price, } => { wallet.sync().await?; - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; let gas = Gas::new(gas_limit).with_price(gas_price); + let addr_idx = addr_idx.unwrap_or_default(); - let tx = wallet.phoenix_stake_withdraw(addr, gas).await?; + let tx = wallet.phoenix_stake_withdraw(addr_idx, gas).await?; Ok(RunResult::Tx(tx.hash())) } - Command::Export { addr, dir, name } => { - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; - + Command::Export { + addr_idx, + dir, + name, + } => { let pwd = prompt::request_auth( "Provide a password for your provisioner keys", &settings.password, wallet.get_file_version()?, )?; + let addr_idx = addr_idx.unwrap_or_default(); - let (pub_key, key_pair) = - wallet.export_provisioner_keys(addr, &dir, name, &pwd)?; + let (pub_key, key_pair) = wallet + .export_provisioner_keys(addr_idx, &dir, name, &pwd)?; Ok(RunResult::ExportedKeys(pub_key, key_pair)) } - Command::PhoenixHistory { addr } => { + Command::PhoenixHistory { addr_idx } => { wallet.sync().await?; - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; - let notes = wallet.get_all_notes(addr).await?; + let addr_idx = addr_idx.unwrap_or_default(); + let notes = wallet.get_all_notes(addr_idx).await?; let transactions = history::transaction_from_notes(settings, notes).await?; @@ -677,100 +679,80 @@ impl Command { Ok(RunResult::PhoenixHistory(transactions)) } Command::PhoenixToMoonlight { - addr, + addr_idx, gas_limit, gas_price, amt, } => { wallet.sync().await?; - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; let gas = Gas::new(gas_limit).with_price(gas_price); + let addr_idx = addr_idx.unwrap_or_default(); - let tx = wallet.phoenix_to_moonlight(addr, amt, gas).await?; + let tx = + wallet.phoenix_to_moonlight(addr_idx, amt, gas).await?; Ok(RunResult::Tx(tx.hash())) } Command::MoonlightToPhoenix { - addr, + addr_idx, amt, gas_limit, gas_price, } => { wallet.sync().await?; - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; let gas = Gas::new(gas_limit).with_price(gas_price); + let addr_idx = addr_idx.unwrap_or_default(); - let tx = wallet.moonlight_to_phoenix(addr, amt, gas).await?; + let tx = + wallet.moonlight_to_phoenix(addr_idx, amt, gas).await?; Ok(RunResult::Tx(tx.hash())) } Command::MoonlightStake { - addr, + addr_idx, amt, gas_limit, gas_price, } => { - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; - let gas = Gas::new(gas_limit).with_price(gas_price); + let addr_idx = addr_idx.unwrap_or_default(); - let tx = wallet.moonlight_stake(addr, amt, gas).await?; + let tx = wallet.moonlight_stake(addr_idx, amt, gas).await?; Ok(RunResult::Tx(tx.hash())) } Command::MoonlightUnstake { - addr, + addr_idx, gas_limit, gas_price, } => { - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; - let gas = Gas::new(gas_limit).with_price(gas_price); + let addr_idx = addr_idx.unwrap_or_default(); - let tx = wallet.moonlight_unstake(addr, gas).await?; + let tx = wallet.moonlight_unstake(addr_idx, gas).await?; Ok(RunResult::Tx(tx.hash())) } Command::MoonlightWithdraw { - addr, + addr_idx, gas_limit, gas_price, } => { - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; - let gas = Gas::new(gas_limit).with_price(gas_price); + let addr_idx = addr_idx.unwrap_or_default(); - let tx = wallet.moonlight_stake_withdraw(addr, gas).await?; + let tx = wallet.moonlight_stake_withdraw(addr_idx, gas).await?; Ok(RunResult::Tx(tx.hash())) } Command::PhoenixContractCall { - addr, + addr_idx, contract_id, fn_name, fn_args, gas_limit, gas_price, } => { - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; - let gas = Gas::new(gas_limit).with_price(gas_price); + let addr_idx = addr_idx.unwrap_or_default(); let contract_id: [u8; 32] = contract_id .try_into() @@ -780,25 +762,21 @@ impl Command { .map_err(|_| Error::Rkyv)?; let tx = wallet - .phoenix_execute(addr, Dusk::from(0), gas, call.into()) + .phoenix_execute(addr_idx, Dusk::from(0), gas, call.into()) .await?; Ok(RunResult::Tx(tx.hash())) } Command::MoonlightContractCall { - addr, + addr_idx, contract_id, fn_name, fn_args, gas_limit, gas_price, } => { - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; - let gas = Gas::new(gas_limit).with_price(gas_price); + let addr_idx = addr_idx.unwrap_or_default(); let contract_id: [u8; 32] = contract_id .try_into() @@ -809,8 +787,7 @@ impl Command { let tx = wallet .moonlight_execute( - addr, - None, + addr_idx, Dusk::from(0), Dusk::from(0), gas, @@ -821,18 +798,14 @@ impl Command { Ok(RunResult::Tx(tx.hash())) } Self::PhoenixContractDeploy { - addr, + addr_idx, code, init_args, gas_limit, gas_price, } => { - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; - let gas = Gas::new(gas_limit).with_price(gas_price); + let addr_idx = addr_idx.unwrap_or_default(); if code.extension().unwrap_or_default() != "wasm" { return Err(Error::InvalidWasmContractPath.into()); @@ -841,24 +814,21 @@ impl Command { let code = std::fs::read(code) .map_err(|_| Error::InvalidWasmContractPath)?; - let tx = - wallet.phoenix_deploy(addr, code, init_args, gas).await?; + let tx = wallet + .phoenix_deploy(addr_idx, code, init_args, gas) + .await?; Ok(RunResult::Tx(tx.hash())) } Self::MoonlightContractDeploy { - addr, + addr_idx, code, init_args, gas_limit, gas_price, } => { - let addr = match addr { - Some(addr) => wallet.claim_as_address(addr)?, - None => wallet.default_address(), - }; - let gas = Gas::new(gas_limit).with_price(gas_price); + let addr_idx = addr_idx.unwrap_or_default(); if code.extension().unwrap_or_default() != "wasm" { return Err(Error::InvalidWasmContractPath.into()); @@ -867,8 +837,9 @@ impl Command { let code = std::fs::read(code) .map_err(|_| Error::InvalidWasmContractPath)?; - let tx = - wallet.moonlight_deploy(addr, code, init_args, gas).await?; + let tx = wallet + .moonlight_deploy(addr_idx, code, init_args, gas) + .await?; Ok(RunResult::Tx(tx.hash())) } diff --git a/rusk-wallet/src/bin/interactive.rs b/rusk-wallet/src/bin/interactive.rs index c3d0e8e529..db284371f2 100644 --- a/rusk-wallet/src/bin/interactive.rs +++ b/rusk-wallet/src/bin/interactive.rs @@ -29,8 +29,8 @@ pub(crate) async fn run_loop( ) -> anyhow::Result<()> { loop { // let the user choose (or create) an address - let addr = match menu_addr(wallet)? { - AddrSelect::Address(addr) => *addr, + let addr_idx = match menu_addr(wallet)? { + AddrSelect::AddressIndex(addr_idx) => addr_idx, AddrSelect::NewAddress => { if wallet.addresses().len() >= MAX_ADDRESSES { println!( @@ -39,7 +39,7 @@ pub(crate) async fn run_loop( std::process::exit(0); } - let addr = wallet.new_address().clone(); + let addr_idx = wallet.add_address(); let file_version = wallet.get_file_version()?; let password = &settings.password; @@ -61,26 +61,18 @@ pub(crate) async fn run_loop( wallet.save()?; } - addr + addr_idx } AddrSelect::Exit => std::process::exit(0), }; - let index = addr.index()?; - - let moonlight = Address::Bls { - index: Some(index), - addr: wallet.bls_public_key(addr.index()?), - }; - let is_synced = wallet.is_synced().await?; loop { // get balance for this address prompt::hide_cursor()?; - let moonlight_bal = - wallet.get_moonlight_balance(&moonlight).await?; - let phoenix_bal = wallet.get_phoenix_balance(&addr).await?; + let moonlight_bal = wallet.get_moonlight_balance(addr_idx).await?; + let phoenix_bal = wallet.get_phoenix_balance(addr_idx).await?; let phoenix_spendable = phoenix_bal.spendable.into(); let phoenix_total: Dusk = phoenix_bal.value.into(); @@ -89,35 +81,41 @@ pub(crate) async fn run_loop( // display address information println!(); println!(); + // display phoenix balance and keys information if is_synced { println!( - "{0: <20} - Total: {moonlight_bal}", - "Moonlight Balance", + "{0: <23} - Spendable: {phoenix_spendable}", + "Phoenix Balance", ); + println!("{0: <23} - Total: {phoenix_total}", "",); } - println!("{0: <20} {moonlight}", "Moonlight Address"); + let phoenix_addr = Address::Phoenix { + pk: *wallet.phoenix_pk(addr_idx)?, + }; + println!("{phoenix_addr}\n"); - println!(); + // display moonlight balance and keys information if is_synced { println!( - "{0: <20} - Spendable: {phoenix_spendable}", - "Phoenix Balance", + "{0: <23} - Total: {moonlight_bal}", + "Moonlight Balance", ); - println!("{0: <20} - Total: {phoenix_total}", "",); } - println!("{0: <20} {addr}", "Phoenix Address"); - println!(); + let moonlight_addr = Address::Bls { + pk: *wallet.bls_pk(addr_idx)?, + }; + println!("{moonlight_addr}\n"); // request operation to perform let op = match wallet.is_online().await { true => menu_op( - addr.clone(), + addr_idx, phoenix_spendable, moonlight_bal, settings, is_synced, ), - false => menu_op_offline(addr.clone(), settings), + false => menu_op_offline(addr_idx, settings), }; // perform operations with this address @@ -164,35 +162,42 @@ pub(crate) async fn run_loop( #[derive(PartialEq, Eq, Hash, Debug, Clone)] enum AddrSelect { - Address(Box
), + AddressIndex(u8), NewAddress, Exit, } +fn address_idx_string(addr_idx: u8) -> String { + if addr_idx == 0 { + "Default Address".to_string() + } else { + format!("Address no {:4}", addr_idx) + } +} + /// Allows the user to choose an address from the selected wallet /// to start performing operations. fn menu_addr(wallet: &Wallet) -> anyhow::Result { let mut address_menu = Menu::title("Addresses"); - for addr in wallet.addresses() { - let preview = addr.preview(); - address_menu = address_menu - .add(AddrSelect::Address(Box::new(addr.clone())), preview); + let total_addresses = wallet.addresses().len() as u8; + for addr_idx in 0..total_addresses { + address_menu = address_menu.add( + AddrSelect::AddressIndex(addr_idx), + address_idx_string(addr_idx), + ); } let remaining_addresses = - MAX_ADDRESSES.saturating_sub(wallet.addresses().len()); - let mut action_menu = Menu::new() - .separator() - .add(AddrSelect::NewAddress, "New address"); - - // show warning if less than - if remaining_addresses < 5 { - action_menu = action_menu.separator().separator_msg(format!( - "\x1b[93m{}\x1b[0m This wallet only supports up to {MAX_ADDRESSES} addresses, you have {} addresses ", - "Warning:", - wallet.addresses().len() - )); - } + MAX_ADDRESSES.saturating_sub(total_addresses as usize); + + let mut action_menu = Menu::new(); + // only show the option to create a new address if we don't already have + // `MAX_ADDRESSES` + if remaining_addresses > 0 { + action_menu = action_menu + .separator() + .add(AddrSelect::NewAddress, "New address") + }; if let Some(rx) = &wallet.state()?.sync_rx { if let Ok(status) = rx.try_recv() { @@ -221,7 +226,7 @@ fn menu_addr(wallet: &Wallet) -> anyhow::Result { /// Allows the user to choose an operation to perform with the selected /// transaction type fn transaction_op_menu_moonlight( - addr: Address, + addr_idx: u8, moonlight_bal: Dusk, ) -> anyhow::Result { use TransactionOp::*; @@ -248,14 +253,14 @@ fn transaction_op_menu_moonlight( let x = match val { Transfer => AddrOp::Run(Box::new(Command::MoonlightTransfer { - sndr: Some(addr), + sndr_idx: Some(addr_idx), rcvr: prompt::request_rcvr_addr("recipient")?, amt: prompt::request_token_amt("transfer", moonlight_bal)?, gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT)?, gas_price: prompt::request_gas_price()?, })), Memo => AddrOp::Run(Box::new(Command::MoonlightMemo { - sndr: Some(addr), + sndr_idx: Some(addr_idx), memo: prompt::request_str("memo")?, rcvr: prompt::request_rcvr_addr("recipient")?, amt: prompt::request_optional_token_amt("transfer", moonlight_bal)?, @@ -263,24 +268,24 @@ fn transaction_op_menu_moonlight( gas_price: prompt::request_gas_price()?, })), Stake => AddrOp::Run(Box::new(Command::MoonlightStake { - addr: Some(addr), + addr_idx: Some(addr_idx), amt: prompt::request_stake_token_amt(moonlight_bal)?, gas_limit: prompt::request_gas_limit(DEFAULT_STAKE_GAS_LIMIT)?, gas_price: prompt::request_gas_price()?, })), Unstake => AddrOp::Run(Box::new(Command::MoonlightUnstake { - addr: Some(addr), + addr_idx: Some(addr_idx), gas_limit: prompt::request_gas_limit(DEFAULT_STAKE_GAS_LIMIT)?, gas_price: prompt::request_gas_price()?, })), Withdraw => AddrOp::Run(Box::new(Command::MoonlightWithdraw { - addr: Some(addr), + addr_idx: Some(addr_idx), gas_limit: prompt::request_gas_limit(DEFAULT_STAKE_GAS_LIMIT)?, gas_price: prompt::request_gas_price()?, })), ContractDeploy => { AddrOp::Run(Box::new(Command::MoonlightContractDeploy { - addr: Some(addr), + addr_idx: Some(addr_idx), code: prompt::request_contract_code()?, init_args: prompt::request_bytes("init arguments")?, gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT)?, @@ -288,7 +293,7 @@ fn transaction_op_menu_moonlight( })) } ContractCall => AddrOp::Run(Box::new(Command::MoonlightContractCall { - addr: Some(addr), + addr_idx: Some(addr_idx), contract_id: prompt::request_bytes("contract id")?, fn_name: prompt::request_str("function name to call")?, fn_args: prompt::request_bytes("arguments of calling function")?, @@ -305,7 +310,7 @@ fn transaction_op_menu_moonlight( /// Allows the user to choose an operation to perform with the selected /// transaction type fn transaction_op_menu_phoenix( - addr: Address, + addr_idx: u8, phoenix_balance: Dusk, ) -> anyhow::Result { use TransactionOp::*; @@ -332,14 +337,14 @@ fn transaction_op_menu_phoenix( let x = match val { Transfer => AddrOp::Run(Box::new(Command::PhoenixTransfer { - sndr: Some(addr), + sndr_idx: Some(addr_idx), rcvr: prompt::request_rcvr_addr("recipient")?, amt: prompt::request_token_amt("transfer", phoenix_balance)?, gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT)?, gas_price: prompt::request_gas_price()?, })), Memo => AddrOp::Run(Box::new(Command::PhoenixMemo { - sndr: Some(addr), + sndr_idx: Some(addr_idx), memo: prompt::request_str("memo")?, rcvr: prompt::request_rcvr_addr("recipient")?, amt: prompt::request_optional_token_amt( @@ -350,24 +355,24 @@ fn transaction_op_menu_phoenix( gas_price: prompt::request_gas_price()?, })), Stake => AddrOp::Run(Box::new(Command::PhoenixStake { - addr: Some(addr), + addr_idx: Some(addr_idx), amt: prompt::request_stake_token_amt(phoenix_balance)?, gas_limit: prompt::request_gas_limit(DEFAULT_STAKE_GAS_LIMIT)?, gas_price: prompt::request_gas_price()?, })), Unstake => AddrOp::Run(Box::new(Command::PhoenixUnstake { - addr: Some(addr), + addr_idx: Some(addr_idx), gas_limit: prompt::request_gas_limit(DEFAULT_STAKE_GAS_LIMIT)?, gas_price: prompt::request_gas_price()?, })), Withdraw => AddrOp::Run(Box::new(Command::PhoenixWithdraw { - addr: Some(addr), + addr_idx: Some(addr_idx), gas_limit: prompt::request_gas_limit(DEFAULT_STAKE_GAS_LIMIT)?, gas_price: prompt::request_gas_price()?, })), ContractDeploy => { AddrOp::Run(Box::new(Command::PhoenixContractDeploy { - addr: Some(addr), + addr_idx: Some(addr_idx), code: prompt::request_contract_code()?, init_args: prompt::request_bytes("init arguments")?, gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT)?, @@ -375,16 +380,16 @@ fn transaction_op_menu_phoenix( })) } ContractCall => AddrOp::Run(Box::new(Command::PhoenixContractCall { - addr: Some(addr), + addr_idx: Some(addr_idx), contract_id: prompt::request_bytes("contract id")?, fn_name: prompt::request_str("function name to call")?, fn_args: prompt::request_bytes("arguments of calling function")?, gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT)?, gas_price: prompt::request_gas_price()?, })), - History => { - AddrOp::Run(Box::new(Command::PhoenixHistory { addr: Some(addr) })) - } + History => AddrOp::Run(Box::new(Command::PhoenixHistory { + addr_idx: Some(addr_idx), + })), Back => AddrOp::Back, }; @@ -429,7 +434,7 @@ enum TransactionOp { /// Allows the user to choose the operation to perform for the /// selected address fn menu_op( - addr: Address, + addr_idx: u8, phoenix_balance: Dusk, moonlight_balance: Dusk, settings: &Settings, @@ -471,18 +476,18 @@ fn menu_op( let res = match cmd { CMI::PhoenixTransactions => { - transaction_op_menu_phoenix(addr, phoenix_balance)? + transaction_op_menu_phoenix(addr_idx, phoenix_balance)? } CMI::MoonlightTransactions => { - transaction_op_menu_moonlight(addr, moonlight_balance)? + transaction_op_menu_moonlight(addr_idx, moonlight_balance)? } CMI::StakeInfo => AddrOp::Run(Box::new(Command::StakeInfo { - addr: Some(addr), + addr_idx: Some(addr_idx), reward: false, })), CMI::MoonlightToPhoenix => { AddrOp::Run(Box::new(Command::MoonlightToPhoenix { - addr: Some(addr), + addr_idx: Some(addr_idx), amt: prompt::request_token_amt("convert", moonlight_balance)?, gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT)?, gas_price: prompt::request_gas_price()?, @@ -490,14 +495,14 @@ fn menu_op( } CMI::PhoenixToMoonlight => { AddrOp::Run(Box::new(Command::PhoenixToMoonlight { - addr: Some(addr), + addr_idx: Some(addr_idx), amt: prompt::request_token_amt("convert", phoenix_balance)?, gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT)?, gas_price: prompt::request_gas_price()?, })) } CMI::Export => AddrOp::Run(Box::new(Command::Export { - addr: Some(addr), + addr_idx: Some(addr_idx), name: None, dir: prompt::request_dir("export keys", settings.profile.clone())?, })), @@ -509,7 +514,7 @@ fn menu_op( /// Allows the user to choose the operation to perform for the /// selected address while in offline mode fn menu_op_offline( - addr: Address, + addr_idx: u8, settings: &Settings, ) -> anyhow::Result { use CommandMenuItem as CMI; @@ -530,7 +535,7 @@ fn menu_op_offline( let res = match cmd { CMI::Export => AddrOp::Run(Box::new(Command::Export { - addr: Some(addr), + addr_idx: Some(addr_idx), name: None, dir: prompt::request_dir("export keys", settings.profile.clone())?, })), @@ -664,30 +669,34 @@ fn menu_wallet(wallet_found: Option) -> anyhow::Result { fn confirm(cmd: &Command) -> anyhow::Result { match cmd { Command::PhoenixTransfer { - sndr, + sndr_idx, rcvr, amt, gas_limit, gas_price, } => { - let sndr = sndr.as_ref().expect("sender to be a valid address"); let max_fee = gas_limit * gas_price; - println!(" > Send from = {}", sndr.preview()); + println!( + " > Send from {}", + address_idx_string(sndr_idx.unwrap_or_default()) + ); println!(" > Recipient = {}", rcvr.preview()); println!(" > Amount to transfer = {} DUSK", amt); println!(" > Max fee = {} DUSK", Dusk::from(max_fee)); prompt::ask_confirm() } Command::MoonlightTransfer { - sndr, + sndr_idx, rcvr, amt, gas_limit, gas_price, } => { - let sndr = sndr.as_ref().expect("sender to be a valid address"); let max_fee = gas_limit * gas_price; - println!(" > Send from = {}", sndr.preview()); + println!( + " > Send from {}", + address_idx_string(sndr_idx.unwrap_or_default()) + ); println!(" > Recipient = {}", rcvr.preview()); println!(" > Amount to transfer = {} DUSK", amt); println!(" > Max fee = {} DUSK", Dusk::from(max_fee)); @@ -695,37 +704,43 @@ fn confirm(cmd: &Command) -> anyhow::Result { prompt::ask_confirm() } Command::PhoenixStake { - addr, + addr_idx, amt, gas_limit, gas_price, } => { - let addr = addr.as_ref().expect("address to be valid"); let max_fee = gas_limit * gas_price; - println!(" > Stake from {}", addr.preview()); + println!( + " > Send from {}", + address_idx_string(addr_idx.unwrap_or_default()) + ); println!(" > Amount to stake = {} DUSK", amt); println!(" > Max fee = {} DUSK", Dusk::from(max_fee)); prompt::ask_confirm() } Command::PhoenixUnstake { - addr, + addr_idx, gas_limit, gas_price, } => { - let addr = addr.as_ref().expect("address to be valid"); let max_fee = gas_limit * gas_price; - println!(" > Unstake from {}", addr.preview()); + println!( + " > Send from {}", + address_idx_string(addr_idx.unwrap_or_default()) + ); println!(" > Max fee = {} DUSK", Dusk::from(max_fee)); prompt::ask_confirm() } Command::PhoenixWithdraw { - addr, + addr_idx, gas_limit, gas_price, } => { - let addr = addr.as_ref().expect("address to be valid"); let max_fee = gas_limit * gas_price; - println!(" > Reward from {}", addr.preview()); + println!( + " > Send from {}", + address_idx_string(addr_idx.unwrap_or_default()) + ); println!(" > Max fee = {} DUSK", Dusk::from(max_fee)); prompt::ask_confirm() } diff --git a/rusk-wallet/src/clients.rs b/rusk-wallet/src/clients.rs index ab3faa2319..df9b560529 100644 --- a/rusk-wallet/src/clients.rs +++ b/rusk-wallet/src/clients.rs @@ -8,7 +8,7 @@ mod sync; use dusk_bytes::Serializable; use execution_core::{ - signatures::bls::PublicKey as AccountPublicKey, + signatures::bls::PublicKey as BlsPublicKey, transfer::{ moonlight::AccountData, phoenix::{Note, NoteLeaf, Prove}, @@ -246,7 +246,7 @@ impl State { pub(crate) async fn fetch_account( &self, - pk: &AccountPublicKey, + pk: &BlsPublicKey, ) -> Result { let status = self.status; status("Fetching account-data..."); @@ -285,7 +285,7 @@ impl State { /// Queries the node for the amount staked by a key. pub(crate) async fn fetch_stake( &self, - pk: &AccountPublicKey, + pk: &BlsPublicKey, ) -> Result, Error> { let status = self.status; status("Fetching stake..."); diff --git a/rusk-wallet/src/lib.rs b/rusk-wallet/src/lib.rs index d33eeb3ef1..d245afcfc2 100644 --- a/rusk-wallet/src/lib.rs +++ b/rusk-wallet/src/lib.rs @@ -38,7 +38,6 @@ pub use wallet::{Address, DecodedNote, SecureWalletFile, Wallet, WalletPath}; use execution_core::{ dusk, from_dusk, - signatures::bls::PublicKey as AccountPublicKey, stake::StakeData, transfer::phoenix::{ ArchivedNoteLeaf, Note, NoteOpening, PublicKey as PhoenixPublicKey, diff --git a/rusk-wallet/src/wallet.rs b/rusk-wallet/src/wallet.rs index ce74ebff18..d97681076f 100644 --- a/rusk-wallet/src/wallet.rs +++ b/rusk-wallet/src/wallet.rs @@ -70,7 +70,7 @@ use gas::Gas; /// able to perform common operations such as checking balance, transfernig /// funds, or staking Dusk. pub struct Wallet { - addresses: Vec
, + addresses: Vec<(PhoenixPublicKey, BlsPublicKey)>, state: Option, store: LocalStore, file: Option, @@ -82,30 +82,6 @@ impl Wallet { pub fn file(&self) -> &Option { &self.file } - - /// Returns Phoenix key pair for a given address - /// - /// # Errors - /// - /// - If the Address provided is not a Phoenix address - /// - If the address is not owned - pub fn phoenix_keys( - &self, - addr: &Address, - ) -> Result<(PhoenixPublicKey, PhoenixSecretKey), Error> { - // make sure we own the address - if !addr.is_owned() { - return Err(Error::Unauthorized); - } - - let index = addr.index()?; - - // retrieve keys - let sk = self.phoenix_secret_key(index); - let pk = addr.pk()?; - - Ok((*pk, sk)) - } } impl Wallet { @@ -123,19 +99,19 @@ impl Wallet { // derive the mnemonic seed let seed = Seed::new(&mnemonic, ""); // Takes the mnemonic seed as bytes - let bytes = seed.as_bytes().try_into().unwrap(); + let seed_bytes = seed.as_bytes().try_into().unwrap(); - // Generate the default address - let address = Address::Phoenix { - index: Some(0), - addr: derive_phoenix_pk(&bytes, 0), - }; + // Generate the default address at index 0 + let addresses = vec![( + derive_phoenix_pk(&seed_bytes, 0), + derive_bls_pk(&seed_bytes, 0), + )]; // return new wallet instance Ok(Wallet { - addresses: vec![address], + addresses, state: None, - store: LocalStore::from(bytes), + store: LocalStore::from(seed_bytes), file: None, file_version: None, }) @@ -165,14 +141,13 @@ impl Wallet { // return early if its legacy if let DatFileVersion::Legacy = file_version { - let address = Address::Phoenix { - index: Some(0), - addr: derive_phoenix_pk(&seed, 0), - }; + // Generate the default address at index 0 + let addresses = + vec![(derive_phoenix_pk(&seed, 0), derive_bls_pk(&seed, 0))]; // return the store return Ok(Self { - addresses: vec![address], + addresses, store: LocalStore::from(seed), state: None, file: Some(file), @@ -181,10 +156,7 @@ impl Wallet { } let addresses: Vec<_> = (0..address_count) - .map(|i| Address::Phoenix { - index: Some(i), - addr: derive_phoenix_pk(&seed, i), - }) + .map(|i| (derive_phoenix_pk(&seed, i), derive_bls_pk(&seed, i))) .collect(); // create and return @@ -316,17 +288,10 @@ impl Wallet { /// Fetches the notes from the state. pub async fn get_all_notes( &self, - addr: &Address, + addr_idx: u8, ) -> Result, Error> { - if !addr.is_owned() { - return Err(Error::Unauthorized); - } - - let seed = self.store.get_seed(); - - let index = addr.index()?; - let vk = derive_phoenix_vk(seed, index); - let pk = addr.pk()?; + let vk = self.derive_phoenix_vk(addr_idx); + let pk = self.phoenix_pk(addr_idx)?; let live_notes = self.state()?.fetch_notes(pk)?; let spent_notes = self.state()?.cache().spent_notes(pk)?; @@ -358,21 +323,14 @@ impl Wallet { /// Obtain balance information for a given address pub async fn get_phoenix_balance( &self, - addr: &Address, + addr_idx: u8, ) -> Result { let state = self.state()?; - // make sure we own this address - if !addr.is_owned() { - return Err(Error::Unauthorized); - } - let index = addr.index()?; - let notes = state.fetch_notes(addr.pk()?)?; - - let seed = self.store.get_seed(); + let notes = state.fetch_notes(self.phoenix_pk(addr_idx)?)?; Ok(phoenix_balance( - &derive_phoenix_vk(seed, index), + &self.derive_phoenix_vk(addr_idx), notes.iter(), )) } @@ -380,80 +338,97 @@ impl Wallet { /// Get Moonlight account balance pub async fn get_moonlight_balance( &self, - addr: &Address, + addr_idx: u8, ) -> Result { - let pk = addr.apk()?; + let pk = self.bls_pk(addr_idx)?; let state = self.state()?; let account = state.fetch_account(pk).await?; Ok(Dusk::from(account.balance)) } - /// Creates a new public address. - /// The addresses generated are deterministic across sessions. - pub fn new_address(&mut self) -> &Address { + /// Pushes a new entry to the internal address vector and returns its index. + pub fn add_address(&mut self) -> u8 { let seed = self.store.get_seed(); - let len = self.addresses.len(); - let pk = derive_phoenix_pk(seed, len as u8); - let addr = Address::Phoenix { - index: Some(len as u8), - addr: pk, - }; + let index = self.addresses.len() as u8; + let addr = (derive_phoenix_pk(seed, index), derive_bls_pk(seed, index)); self.addresses.push(addr); - self.addresses.last().unwrap() + + index } - /// Default public address for this wallet - pub fn default_address(&self) -> &Address { - &self.addresses[0] + /// Default phoenix public key for this wallet + pub fn default_phoenix_pk(&self) -> &PhoenixPublicKey { + &self.addresses[0].0 + } + + /// Default bls public key for this wallet + pub fn default_bls_pk(&self) -> &BlsPublicKey { + &self.addresses[0].1 } /// Addresses that have been generated by the user - pub fn addresses(&self) -> &Vec
{ + pub fn addresses(&self) -> &Vec<(PhoenixPublicKey, BlsPublicKey)> { &self.addresses } /// Returns the Phoenix secret-key for a given index - pub(crate) fn phoenix_secret_key(&self, index: u8) -> PhoenixSecretKey { + pub(crate) fn derive_phoenix_sk(&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 { + /// Returns the Phoenix secret-key for a given index + pub(crate) fn derive_phoenix_vk(&self, index: u8) -> PhoenixViewKey { let seed = self.store.get_seed(); - derive_phoenix_pk(seed, index) + derive_phoenix_vk(seed, index) + } + + /// Returns the Phoenix public-key for a given index. + /// + /// # Errors + /// This will error if the wallet doesn't have addresses stored for the + /// given index. + pub fn phoenix_pk(&self, index: u8) -> Result<&PhoenixPublicKey, Error> { + let index = usize::from(index); + if index >= self.addresses.len() { + return Err(Error::AddressNotOwned); + } + + Ok(&self.addresses()[index].0) } /// Returns the BLS secret-key for a given index - pub(crate) fn bls_secret_key(&self, index: u8) -> BlsSecretKey { + pub(crate) fn derive_bls_sk(&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) + /// + /// # Errors + /// This will error if the wallet doesn't have addresses stored for the + /// given index. + pub fn bls_pk(&self, index: u8) -> Result<&BlsPublicKey, Error> { + let index = usize::from(index); + if index >= self.addresses.len() { + return Err(Error::AddressNotOwned); + } + + Ok(&self.addresses()[index].1) } - /// Creates a generic Moonlight transaction. + /// Creates a generic moonlight execution, paying gas with Moonlight tokens. #[allow(clippy::too_many_arguments)] pub async fn moonlight_execute( &self, - sender_addr: &Address, - receiver: Option, + sender_idx: u8, transfer_value: Dusk, deposit: Dusk, gas: Gas, exec: Option>, ) -> Result { - // make sure we own the sender address - if !sender_addr.is_owned() { - return Err(Error::Unauthorized); - } - // check gas limits if !gas.is_enough() { return Err(Error::NotEnoughGas); @@ -462,11 +437,10 @@ impl Wallet { let state = self.state()?; let deposit = *deposit; - let sender_index = sender_addr.index()?; - let mut sender_sk = self.bls_secret_key(sender_index); - let sender = self.bls_public_key(sender_index); + let mut sender_sk = self.derive_bls_sk(sender_idx); + let sender = self.bls_pk(sender_idx)?; - let account = state.fetch_account(&sender).await?; + let account = state.fetch_account(sender).await?; // technically this check is not necessary, but it's nice to not spam // the network with transactions that are unspendable. @@ -476,7 +450,7 @@ impl Wallet { let tx = moonlight( &sender_sk, - receiver, + None, *transfer_value, deposit, gas.limit, @@ -494,16 +468,11 @@ impl Wallet { /// Executes a generic contract call, paying gas with phoenix notes pub async fn phoenix_execute( &self, - sender: &Address, + sender_idx: u8, deposit: Dusk, gas: Gas, data: TransactionData, ) -> Result { - // make sure we own the sender address - if !sender.is_owned() { - return Err(Error::Unauthorized); - } - // check gas limits if !gas.is_enough() { return Err(Error::NotEnoughGas); @@ -513,13 +482,13 @@ impl Wallet { let deposit = *deposit; let mut rng = StdRng::from_entropy(); - let sender_index = 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 mut sender_sk = self.derive_phoenix_sk(sender_idx); + // in a contract execution or deployment, the sender and receiver are + // the same + let receiver_pk = self.phoenix_pk(sender_idx)?; let inputs = state - .inputs(sender_index, deposit + gas.limit * gas.price) + .inputs(sender_idx, deposit + gas.limit * gas.price) .await? .into_iter() .map(|(a, b, _)| (a, b)) @@ -531,7 +500,7 @@ impl Wallet { let tx = phoenix( &mut rng, &sender_sk, - sender.pk()?, + self.phoenix_pk(sender_idx)?, receiver_pk, inputs, root, @@ -553,16 +522,12 @@ impl Wallet { /// Transfers funds between Phoenix addresses pub async fn phoenix_transfer( &self, - sender: &Address, - rcvr: &Address, + sender_idx: u8, + receiver_pk: &PhoenixPublicKey, memo: Option, amt: Dusk, gas: Gas, ) -> Result { - // make sure we own the sender address - if !sender.is_owned() { - return Err(Error::Unauthorized); - } // make sure amount is positive if amt == 0 && memo.is_none() { return Err(Error::AmountIsZero); @@ -575,15 +540,13 @@ impl Wallet { let state = self.state()?; let mut rng = StdRng::from_entropy(); - let sender_index = sender.index()?; let amt = *amt; - let mut sender_sk = self.phoenix_secret_key(sender_index); - let change_pk = sender.pk()?; - let reciever_pk = rcvr.pk()?; + let mut sender_sk = self.derive_phoenix_sk(sender_idx); + let change_pk = self.phoenix_pk(sender_idx)?; let inputs = state - .inputs(sender_index, amt + gas.limit * gas.price) + .inputs(sender_idx, amt + gas.limit * gas.price) .await? .into_iter() .map(|(a, b, _)| (a, b)) @@ -596,7 +559,7 @@ impl Wallet { &mut rng, &sender_sk, change_pk, - reciever_pk, + receiver_pk, inputs, root, amt, @@ -617,16 +580,12 @@ impl Wallet { /// Transfer through Moonlight pub async fn moonlight_transfer( &self, - sender: &Address, - rcvr: &Address, + sender_idx: u8, + rcvr: &BlsPublicKey, memo: Option, amt: Dusk, gas: Gas, ) -> Result { - // make sure we own the sender address - if !sender.is_owned() { - return Err(Error::Unauthorized); - } // make sure amount is positive if amt == 0 && memo.is_none() { return Err(Error::AmountIsZero); @@ -636,20 +595,17 @@ impl Wallet { return Err(Error::NotEnoughGas); } - let sender = sender.index()?; - - let mut sender_sk = self.bls_secret_key(sender); - let apk = rcvr.apk()?; - let sender_pk = self.bls_public_key(sender); + let mut sender_sk = self.derive_bls_sk(sender_idx); + let sender_pk = self.bls_pk(sender_idx)?; let amt = *amt; let state = self.state()?; - let nonce = state.fetch_account(&sender_pk).await?.nonce + 1; + let nonce = state.fetch_account(sender_pk).await?.nonce + 1; let chain_id = state.fetch_chain_id().await?; let tx = moonlight( &sender_sk, - Some(*apk), + Some(*rcvr), amt, 0, gas.limit, @@ -667,14 +623,10 @@ impl Wallet { /// Stakes Dusk using Phoenix notes pub async fn phoenix_stake( &self, - addr: &Address, + addr_idx: u8, amt: Dusk, gas: Gas, ) -> Result { - // make sure we own the staking address - if !addr.is_owned() { - return Err(Error::Unauthorized); - } // make sure amount is positive if amt == 0 { return Err(Error::AmountIsZero); @@ -688,19 +640,18 @@ impl Wallet { let mut rng = StdRng::from_entropy(); let amt = *amt; - let sender_index = addr.index()?; - let mut sender_sk = self.phoenix_secret_key(sender_index); - let mut stake_sk = self.bls_secret_key(sender_index); + let mut sender_sk = self.derive_phoenix_sk(addr_idx); + let mut stake_sk = self.derive_bls_sk(addr_idx); let nonce = state - .fetch_stake(&AccountPublicKey::from(&stake_sk)) + .fetch_stake(&BlsPublicKey::from(&stake_sk)) .await? .map(|s| s.nonce) .unwrap_or(0) + 1; let inputs = state - .inputs(sender_index, amt + gas.limit * gas.price) + .inputs(addr_idx, amt + gas.limit * gas.price) .await? .into_iter() .map(|(a, b, _)| (a, b)) @@ -720,17 +671,13 @@ impl Wallet { state.prove_and_propagate(stake).await } - /// Stake via Moonlight + /// Stake via Moonlight, using the moonlight public key to stake. pub async fn moonlight_stake( &self, - addr: &Address, + addr_idx: u8, amt: Dusk, gas: Gas, ) -> Result { - // make sure we own the staking address - if !addr.is_owned() { - return Err(Error::Unauthorized); - } // make sure amount is positive if amt == 0 { return Err(Error::AmountIsZero); @@ -742,14 +689,18 @@ impl Wallet { let state = self.state()?; let amt = *amt; - let sender_index = addr.index()?; - let mut stake_sk = self.bls_secret_key(sender_index); - let pk = self.bls_public_key(sender_index); + let mut stake_sk = self.derive_bls_sk(addr_idx); + let stake_pk = self.bls_pk(addr_idx)?; let chain_id = state.fetch_chain_id().await?; - let moonlight_current_nonce = state.fetch_account(&pk).await?.nonce + 1; + let moonlight_current_nonce = + state.fetch_account(stake_pk).await?.nonce + 1; - let nonce = - state.fetch_stake(&pk).await?.map(|s| s.nonce).unwrap_or(0) + 1; + let nonce = state + .fetch_stake(stake_pk) + .await? + .map(|s| s.nonce) + .unwrap_or(0) + + 1; let stake = moonlight_stake( &stake_sk, @@ -772,32 +723,24 @@ impl Wallet { &self, addr_idx: u8, ) -> Result, Error> { - self.state()? - .fetch_stake(&self.bls_public_key(addr_idx)) - .await + self.state()?.fetch_stake(self.bls_pk(addr_idx)?).await } /// Unstakes Dusk into Phoenix notes pub async fn phoenix_unstake( &self, - addr: &Address, + addr_idx: u8, gas: Gas, ) -> Result { - // 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()?; let state = self.state()?; - let mut sender_sk = self.phoenix_secret_key(index); - let mut stake_sk = self.bls_secret_key(index); + let mut sender_sk = self.derive_phoenix_sk(addr_idx); + let mut stake_sk = self.derive_bls_sk(addr_idx); let unstake_value = state - .fetch_stake(&AccountPublicKey::from(&stake_sk)) + .fetch_stake(&BlsPublicKey::from(&stake_sk)) .await? .and_then(|s| s.amount) .map(|s| s.total_funds()) @@ -807,7 +750,7 @@ impl Wallet { return Err(Error::NotStaked); } - let inputs = state.inputs(index, gas.limit * gas.price).await?; + let inputs = state.inputs(addr_idx, gas.limit * gas.price).await?; let root = state.fetch_root().await?; let chain_id = state.fetch_chain_id().await?; @@ -831,23 +774,18 @@ impl Wallet { state.prove_and_propagate(unstake).await } - /// Unstakes Dusk through Moonlight + /// Unstakes Dusk through Moonlight, using the same key for the + /// Moonlight-account and Staking-account. pub async fn moonlight_unstake( &self, - addr: &Address, + addr_idx: u8, gas: Gas, ) -> Result { - // 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()?; let state = self.state()?; - let mut stake_sk = self.bls_secret_key(index); + let mut stake_sk = self.derive_bls_sk(addr_idx); - let pk = addr.apk()?; + let pk = self.bls_pk(addr_idx)?; let chain_id = state.fetch_chain_id().await?; let account_nonce = state.fetch_account(pk).await?.nonce + 1; @@ -882,28 +820,22 @@ impl Wallet { /// Withdraw accumulated staking reward for a given address to Phoenix pub async fn phoenix_stake_withdraw( &self, - sender_addr: &Address, + sender_idx: u8, gas: Gas, ) -> Result { let state = self.state()?; - // make sure we own the staking address - if !sender_addr.is_owned() { - return Err(Error::Unauthorized); - } - let mut rng = StdRng::from_entropy(); - let sender_index = sender_addr.index()?; - let mut sender_sk = self.phoenix_secret_key(sender_index); - let mut stake_sk = self.bls_secret_key(sender_index); + let mut sender_sk = self.derive_phoenix_sk(sender_idx); + let mut stake_sk = self.derive_bls_sk(sender_idx); - let inputs = state.inputs(sender_index, gas.limit * gas.price).await?; + let inputs = state.inputs(sender_idx, gas.limit * gas.price).await?; let root = state.fetch_root().await?; let chain_id = state.fetch_chain_id().await?; let reward_amount = state - .fetch_stake(&AccountPublicKey::from(&stake_sk)) + .fetch_stake(&BlsPublicKey::from(&stake_sk)) .await? .map(|s| s.reward) .unwrap_or(0); @@ -930,31 +862,37 @@ impl Wallet { /// Convert balance from Phoenix to Moonlight pub async fn phoenix_to_moonlight( &self, - sender_addr: &Address, + addr_idx: u8, amt: Dusk, gas: Gas, ) -> Result { let mut rng = StdRng::from_entropy(); let state = self.state()?; - let sender_index = sender_addr.index()?; let amt = *amt; - let inputs = state - .inputs(sender_index, amt + gas.limit * gas.price) - .await?; + let inputs = + state.inputs(addr_idx, amt + gas.limit * gas.price).await?; let root = state.fetch_root().await?; let chain_id = state.fetch_chain_id().await?; - let mut sender_sk = self.phoenix_secret_key(sender_index); - let mut stake_sk = self.bls_secret_key(sender_index); + let mut phoenix_sk = self.derive_phoenix_sk(addr_idx); + let mut moonlight_sk = self.derive_bls_sk(addr_idx); let convert = phoenix_to_moonlight( - &mut rng, &sender_sk, &stake_sk, inputs, root, amt, gas.limit, - gas.price, chain_id, &Prover, + &mut rng, + &phoenix_sk, + &moonlight_sk, + inputs, + root, + amt, + gas.limit, + gas.price, + chain_id, + &Prover, )?; - sender_sk.zeroize(); - stake_sk.zeroize(); + phoenix_sk.zeroize(); + moonlight_sk.zeroize(); state.prove_and_propagate(convert).await } @@ -962,29 +900,34 @@ impl Wallet { /// Convert balance from Moonlight to Phoenix pub async fn moonlight_to_phoenix( &self, - sender_addr: &Address, + addr_idx: u8, amt: Dusk, gas: Gas, ) -> Result { let mut rng = StdRng::from_entropy(); let state = self.state()?; - let sender_index = sender_addr.index()?; - let pk = self.bls_public_key(sender_index); + let moonlight_pk = self.bls_pk(addr_idx)?; - let nonce = state.fetch_account(&pk).await?.nonce + 1; + let nonce = state.fetch_account(moonlight_pk).await?.nonce + 1; let chain_id = state.fetch_chain_id().await?; - let mut sender_sk = self.phoenix_secret_key(sender_index); - let mut stake_sk = self.bls_secret_key(sender_index); + let mut phoenix_sk = self.derive_phoenix_sk(addr_idx); + let mut moonlight_sk = self.derive_bls_sk(addr_idx); let convert = moonlight_to_phoenix( - &mut rng, &stake_sk, &sender_sk, *amt, gas.limit, gas.price, nonce, + &mut rng, + &moonlight_sk, + &phoenix_sk, + *amt, + gas.limit, + gas.price, + nonce, chain_id, )?; - sender_sk.zeroize(); - stake_sk.zeroize(); + phoenix_sk.zeroize(); + moonlight_sk.zeroize(); state.prove_and_propagate(convert).await } @@ -992,20 +935,20 @@ impl Wallet { /// Withdraw accumulated staking reward for a given address to Moonlight pub async fn moonlight_stake_withdraw( &self, - sender: &Address, + sender_idx: u8, gas: Gas, ) -> Result { let mut rng = StdRng::from_entropy(); let state = self.state()?; - let sender_index = sender.index()?; - let pk = self.bls_public_key(sender_index); - let nonce = state.fetch_account(&pk).await?.nonce + 1; + + let pk = self.bls_pk(sender_idx)?; + let nonce = state.fetch_account(pk).await?.nonce + 1; let chain_id = state.fetch_chain_id().await?; - let stake_info = state.fetch_stake(&pk).await?; + let stake_info = state.fetch_stake(pk).await?; let reward = stake_info.map(|s| s.reward).ok_or(Error::NoReward)?; let reward = Dusk::from(reward); - let mut sender_sk = self.bls_secret_key(sender_index); + let mut sender_sk = self.derive_bls_sk(sender_idx); let withdraw = moonlight_stake_reward( &mut rng, &sender_sk, &sender_sk, *reward, gas.limit, gas.price, @@ -1020,18 +963,18 @@ impl Wallet { /// Deploy a contract using Moonlight pub async fn moonlight_deploy( &self, - sender: &Address, + sender_idx: u8, bytes_code: Vec, init_args: Vec, gas: Gas, ) -> Result { let state = self.state()?; - let sender_index = sender.index()?; - let pk = sender.apk()?; + + let pk = self.bls_pk(sender_idx)?; let nonce = state.fetch_account(pk).await?.nonce + 1; let chain_id = state.fetch_chain_id().await?; - let mut sender_sk = self.bls_secret_key(sender_index); + let mut sender_sk = self.derive_bls_sk(sender_idx); let deploy = moonlight_deployment( &sender_sk, bytes_code, pk, init_args, gas.limit, gas.price, nonce, @@ -1046,26 +989,25 @@ impl Wallet { /// Deploy a contract using Phoenix pub async fn phoenix_deploy( &self, - sender: &Address, + sender_idx: u8, bytes_code: Vec, init_args: Vec, gas: Gas, ) -> Result { let mut rng = StdRng::from_entropy(); let state = self.state()?; - let sender_index = sender.index()?; let chain_id = state.fetch_chain_id().await?; let root = state.fetch_root().await?; - let inputs = state.inputs(sender_index, gas.limit * gas.price).await?; + let inputs = state.inputs(sender_idx, gas.limit * gas.price).await?; - let mut sender_sk = self.phoenix_secret_key(sender_index); - let apk = self.bls_public_key(sender_index); + let mut sender_sk = self.derive_phoenix_sk(sender_idx); + let owner_pk = self.bls_pk(sender_idx)?; let deploy = phoenix_deployment( - &mut rng, &sender_sk, inputs, root, bytes_code, &apk, init_args, 0, - gas.limit, gas.price, chain_id, &Prover, + &mut rng, &sender_sk, inputs, root, bytes_code, owner_pk, + init_args, 0, gas.limit, gas.price, chain_id, &Prover, )?; sender_sk.zeroize(); @@ -1076,16 +1018,15 @@ impl Wallet { /// Returns BLS key-pair for provisioner nodes pub fn provisioner_keys( &self, - addr: &Address, + index: u8, ) -> Result<(BlsPublicKey, BlsSecretKey), Error> { - // make sure we own the staking address - if !addr.is_owned() { - return Err(Error::Unauthorized); - } + let pk = *self.bls_pk(index)?; + let sk = self.derive_bls_sk(index); - let index = addr.index()?; - let sk = self.bls_secret_key(index); - let pk = self.bls_public_key(index); + // make sure our internal addresses are not corrupted + if pk != BlsPublicKey::from(&sk) { + return Err(Error::AddressNotOwned); + } Ok((pk, sk)) } @@ -1093,7 +1034,7 @@ impl Wallet { /// Export BLS key-pair for provisioners in node-compatible format pub fn export_provisioner_keys( &self, - addr: &Address, + addr_idx: u8, dir: &Path, filename: Option, pwd: &[u8], @@ -1104,11 +1045,11 @@ impl Wallet { } // get our keys for this address - let keys = self.provisioner_keys(addr)?; + let keys = self.provisioner_keys(addr_idx)?; // set up the path let mut path = PathBuf::from(dir); - path.push(filename.unwrap_or(addr.to_string())); + path.push(filename.unwrap_or(addr_idx.to_string())); // export public key to disk let bytes = keys.0.to_bytes(); @@ -1131,12 +1072,23 @@ impl Wallet { Ok((path.with_extension("keys"), path.with_extension("cpk"))) } - /// Obtain the owned `Address` for a given address - pub fn claim_as_address(&self, addr: Address) -> Result<&Address, Error> { - self.addresses() - .iter() - .find(|&a| a == &addr) - .ok_or(Error::AddressNotOwned) + /// Return the index of the address passed, returns an error if the address + /// is not in the wallet addresses. + pub fn find_index(&self, addr: &Address) -> Result { + // check if the key is stored in our addresses, return its index if + // found + for (index, (phoenix_pk, bls_pk)) in self.addresses().iter().enumerate() + { + if match addr { + Address::Phoenix { pk } => pk == phoenix_pk, + Address::Bls { pk } => pk == bls_pk, + } { + return Ok(index as u8); + } + } + + // return an error otherwise + Err(Error::AddressNotOwned) } /// Return the dat file version from memory or by reading the file @@ -1208,7 +1160,7 @@ mod tests { use super::*; use tempfile::tempdir; - const TEST_ADDR: &str = "2w7fRQW23Jn9Bgm1GQW9eC2bD9U883dAwqP7HAr2F8g1syzPQaPYrxSyyVZ81yDS5C1rv9L8KjdPBsvYawSx3QCW"; + const TEST_ADDR: &str = "Phoenix Address - 2w7fRQW23Jn9Bgm1GQW9eC2bD9U883dAwqP7HAr2F8g1syzPQaPYrxSyyVZ81yDS5C1rv9L8KjdPBsvYawSx3QCW"; #[derive(Debug, Clone)] struct WalletFile { @@ -1226,24 +1178,35 @@ mod tests { } } + fn default_phoenix_address(wallet: &Wallet) -> Address { + Address::Phoenix { + pk: *wallet + .phoenix_pk(0) + .expect("There to be a key at the index"), + } + } + #[test] fn wallet_basics() -> Result<(), Box> { // create a wallet from a mnemonic phrase let mut wallet: Wallet = Wallet::new("uphold stove tennis fire menu three quick apple close guilt poem garlic volcano giggle comic")?; // check address generation - let default_addr = wallet.default_address().clone(); - let other_addr = wallet.new_address(); + let default_addr = default_phoenix_address(&wallet); + let other_addr_idx = wallet.add_address(); + let other_addr = Address::Phoenix { + pk: *wallet.phoenix_pk(other_addr_idx)?, + }; - assert!(format!("{}", default_addr).eq(TEST_ADDR)); - assert_ne!(&default_addr, other_addr); + assert!(format!("{default_addr}").eq(TEST_ADDR)); + assert_ne!(default_addr, other_addr); assert_eq!(wallet.addresses.len(), 2); // create another wallet with different mnemonic let wallet: Wallet = Wallet::new("demise monitor elegant cradle squeeze cheap parrot venture stereo humor scout denial action receive flat")?; // check addresses are different - let addr = wallet.default_address(); + let addr = default_phoenix_address(&wallet); assert!(format!("{}", addr).ne(TEST_ADDR)); // attempt to create a wallet from an invalid mnemonic @@ -1272,9 +1235,9 @@ mod tests { // load from file and check let loaded_wallet = Wallet::from_file(file)?; - let original_addr = wallet.default_address(); - let loaded_addr = loaded_wallet.default_address(); - assert!(original_addr.eq(loaded_addr)); + let original_addr = default_phoenix_address(&wallet); + let loaded_addr = default_phoenix_address(&loaded_wallet); + assert!(original_addr.eq(&loaded_addr)); Ok(()) } diff --git a/rusk-wallet/src/wallet/address.rs b/rusk-wallet/src/wallet/address.rs index 755f95bea9..c942811972 100644 --- a/rusk-wallet/src/wallet/address.rs +++ b/rusk-wallet/src/wallet/address.rs @@ -10,124 +10,71 @@ use std::{fmt, str::FromStr}; use super::*; use crate::Error; -use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable}; +use dusk_bytes::{DeserializableSlice, Serializable}; /// Address for which to perform transactions with -/// it may be owned by the user or not, if the address is a reciever +/// it may be owned by the user or not, if the address is a receiver /// then the index field will be none #[derive(Clone, Eq)] #[allow(missing_docs)] pub enum Address { - /// A Phoenix address - Phoenix { - index: Option, - addr: PhoenixPublicKey, - }, - /// A BLS address for Moonlight account - Bls { - index: Option, - addr: AccountPublicKey, - }, + /// A Phoenix address used for Phoenix transaction + Phoenix { pk: PhoenixPublicKey }, + /// A BLS address used for Moonlight transactions and staking operations + Bls { pk: BlsPublicKey }, } /// A public address within Dusk impl Address { - /// Returns true if the current user owns this address - pub fn is_owned(&self) -> bool { - self.index().is_ok() - } - - pub(crate) fn pk(&self) -> Result<&PhoenixPublicKey, Error> { - if let Self::Phoenix { addr, .. } = self { - Ok(addr) + /// Returns the phoenix-key of the Address if there is any. + /// + /// # Errors + /// If the address carries a bls-key. + pub fn try_phoenix_pk(&self) -> Result<&PhoenixPublicKey, Error> { + if let Self::Phoenix { pk } = self { + Ok(pk) } else { Err(Error::ExpectedPhoenixPublicKey) } } - pub(crate) fn apk(&self) -> Result<&AccountPublicKey, Error> { - if let Self::Bls { addr, .. } = self { - Ok(addr) + /// Returns the bls-key of the Address if there is any. + /// + /// # Errors + /// If the address carries a phoenix-key. + pub fn try_bls_pk(&self) -> Result<&BlsPublicKey, Error> { + if let Self::Bls { pk } = self { + Ok(pk) } else { Err(Error::ExpectedBlsPublicKey) } } - /// find index of the address - pub fn index(&self) -> Result { + pub(crate) fn to_bytes(&self) -> Vec { match self { - Self::Phoenix { index, .. } => index, - Self::Bls { index, .. } => index, + Self::Phoenix { pk } => pk.to_bytes().to_vec(), + Self::Bls { pk } => pk.to_bytes().to_vec(), } - .ok_or(Error::AddressNotOwned) } - pub(crate) fn to_bytes(&self) -> Vec { + // Returns a string of 23 character specifying the address kind (Phoenix or + // Moonlight/Stake for Bls) + fn addr_kind_str(&self) -> String { match self { - Self::Phoenix { addr, .. } => addr.to_bytes().to_vec(), - Self::Bls { addr, .. } => addr.to_bytes().to_vec(), + Address::Phoenix { pk: _ } => "Phoenix Address".to_string(), + Address::Bls { pk: _ } => "Moonlight/Stake Address".to_string(), } } /// A trimmed version of the address to display as preview pub fn preview(&self) -> String { - let bytes = self.to_bytes(); - let addr = bs58::encode(bytes).into_string(); - format!("{}...{}", &addr[..7], &addr[addr.len() - 7..]) - } - - /// try to create Phoenix address from string - pub fn try_from_str_phoenix(s: &str) -> Result { - let bytes = bs58::decode(s).into_vec()?; - - let pk = PhoenixPublicKey::from_reader(&mut &bytes[..]) - .map_err(|_| Error::BadAddress)?; - - let addr = Self::Phoenix { - index: None, - addr: pk, - }; - - Ok(addr) - } - - /// try to create Moonlight address from string - pub fn try_from_str_bls(s: &str) -> Result { - let bytes = bs58::decode(s).into_vec()?; - - let apk = AccountPublicKey::from_reader(&mut &bytes[..]) - .map_err(|_| Error::BadAddress)?; - - let addr = Self::Bls { - index: None, - addr: apk, - }; - - Ok(addr) - } - - /// try to create Phoenix public key from bytes - pub fn try_from_bytes_phoenix( - bytes: &[u8; PhoenixPublicKey::SIZE], - ) -> Result { - let addr = Self::Phoenix { - index: None, - addr: PhoenixPublicKey::from_bytes(bytes)?, - }; - - Ok(addr) - } - - /// Create an address instance from `BlsPublicKey` bytes. - pub fn try_from_bytes_bls( - bytes: &[u8; AccountPublicKey::SIZE], - ) -> Result { - let addr = Self::Bls { - index: None, - addr: AccountPublicKey::from_bytes(bytes)?, - }; - - Ok(addr) + let addr_key_str = String::from(self); + format!( + "{:<23} - {}...{}", + self.addr_kind_str(), + &addr_key_str[..7], + &addr_key_str[addr_key_str.len() - 7..] + ) } } @@ -135,78 +82,63 @@ impl FromStr for Address { type Err = Error; fn from_str(s: &str) -> Result { - let try_phoenix = Self::try_from_str_phoenix(s); - let try_bls = Self::try_from_str_bls(s); + let address_bytes = bs58::decode(s).into_vec()?; + let mut address_reader = &address_bytes[..]; + + match address_bytes.len() { + PhoenixPublicKey::SIZE => Ok(Self::Phoenix { + pk: PhoenixPublicKey::from_reader(&mut address_reader) + .map_err(Error::Bytes)?, + }), + BlsPublicKey::SIZE => Ok(Self::Bls { + pk: BlsPublicKey::from_reader(&mut address_reader) + .map_err(Error::Bytes)?, + }), + _ => Err(Error::Bytes(dusk_bytes::Error::InvalidData)), + } + } +} - if let Ok(addr) = try_phoenix { - Ok(addr) - } else { - try_bls +impl From<&Address> for String { + fn from(address: &Address) -> Self { + match address { + Address::Phoenix { pk } => { + bs58::encode(pk.to_bytes()).into_string() + } + Address::Bls { pk } => bs58::encode(pk.to_bytes()).into_string(), } } } impl PartialEq for Address { fn eq(&self, other: &Self) -> bool { - match (self.index(), other.index()) { - (Ok(x), Ok(y)) => x == y && self.preview() == other.preview(), - (_, _) => self.preview() == other.preview(), + match (self, other) { + ( + Address::Phoenix { pk: self_pk }, + Address::Phoenix { pk: other_pk }, + ) => self_pk == other_pk, + (Address::Bls { pk: self_pk }, Address::Bls { pk: other_pk }) => { + self_pk == other_pk + } + _ => false, } } } impl std::hash::Hash for Address { fn hash(&self, state: &mut H) { - // we dont care about addresses we dont own - let _ = self.index().map(|f| f.hash(state)); - self.preview().hash(state); + self.to_bytes().hash(state); } } impl fmt::Display for Address { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", bs58::encode(self.to_bytes()).into_string()) + write!(f, "{:<23} - {}", self.addr_kind_str(), String::from(self)) } } impl fmt::Debug for Address { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", bs58::encode(self.to_bytes()).into_string()) - } -} - -/// Addresses holds address-related metadata that needs to be -/// persisted in the wallet file. -pub(crate) struct Addresses { - pub(crate) count: u8, -} - -impl Default for Addresses { - fn default() -> Self { - Self { count: 1 } - } -} - -impl Serializable<1> for Addresses { - type Error = BytesError; - - fn from_bytes(buf: &[u8; Addresses::SIZE]) -> Result - where - Self: Sized, - { - Ok(Self { count: buf[0] }) + write!(f, "{:<23} - {}", self.addr_kind_str(), String::from(self)) } - - fn to_bytes(&self) -> [u8; Addresses::SIZE] { - [self.count] - } -} - -#[test] -fn addresses_serde() -> Result<(), Box> { - let addrs = Addresses { count: 6 }; - let read = Addresses::from_bytes(&addrs.to_bytes()) - .map_err(|_| Error::WalletFileCorrupted)?; - assert!(read.count == addrs.count); - Ok(()) }