Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: verify spend in cli and auditing all the way to genesis #1014

Merged
merged 3 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sn_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ walkdir = "~2.4.0"
xor_name = "5.0.0"

[dev-dependencies]
eyre = "0.6.8"
criterion = "0.5.1"
tempfile = "3.6.0"
rand = { version = "~0.8.5", features = ["small_rng"] }
Expand Down
78 changes: 72 additions & 6 deletions sn_cli/src/subcommands/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use clap::Parser;
use color_eyre::{eyre::eyre, Result};
use sn_client::{Client, ClientEvent, Error as ClientError};
use sn_transfers::{
parse_main_pubkey, CashNoteRedemption, Error as TransferError, LocalWallet, MainSecretKey,
NanoTokens, Transfer, WalletError,
CashNoteRedemption, Error as TransferError, LocalWallet, MainPubkey, MainSecretKey, NanoTokens,
SpendAddress, Transfer, UniquePubkey, WalletError,
};
use std::{
io::Read,
Expand Down Expand Up @@ -108,6 +108,18 @@ pub enum WalletCmds {
#[clap(name = "path")]
path: Option<PathBuf>,
},
/// Verify a spend on the Network.
Verify {
/// The Network address or hex encoded UniquePubkey of the Spend to verify
#[clap(name = "spend")]
spend_address: String,
/// Verify all the way to Genesis
///
/// Used for auditing, note that this might take a very long time
/// Analogous to verifying the entire blockchain in Bitcoin
#[clap(long, default_value = "false")]
genesis: bool,
},
}

pub(crate) async fn wallet_cmds_without_client(cmds: &WalletCmds, root_dir: &Path) -> Result<()> {
Expand Down Expand Up @@ -162,16 +174,48 @@ pub(crate) async fn wallet_cmds(
let wallet_dir = path.unwrap_or(root_dir.join(DEFAULT_RECEIVE_ONLINE_WALLET_DIR));
listen_notifs_and_deposit(&wallet_dir, client, sk).await
}
WalletCmds::Verify {
spend_address,
genesis,
} => verify(spend_address, genesis, client).await,
cmd => Err(eyre!(
"{cmd:?} has to be processed before connecting to the network"
)),
}
}

fn parse_pubkey_address(str_addr: &str) -> Result<SpendAddress> {
let pk_res = UniquePubkey::from_hex(str_addr);
let addr_res = SpendAddress::from_hex(str_addr);

match (pk_res, addr_res) {
(Ok(pk), _) => Ok(SpendAddress::from_unique_pubkey(&pk)),
(_, Ok(addr)) => Ok(addr),
_ => Err(eyre!("Failed to parse address: {str_addr}")),
}
}

/// Verify a spend on the Network.
/// if genesis is true, verify all the way to Genesis, note that this might take A VERY LONG TIME
async fn verify(spend_address: String, genesis: bool, client: &Client) -> Result<()> {
if genesis {
println!("Verifying spend all the way to Genesis, note that this might take a while...");
} else {
println!("Verifying spend...");
}

let addr = parse_pubkey_address(&spend_address)?;
match client.verify_spend(addr, genesis).await {
Ok(()) => println!("Spend verified to be stored and unique at {addr:?}"),
Err(e) => println!("Failed to verify spend at {addr:?}: {e}"),
}

Ok(())
}

fn address(root_dir: &Path) -> Result<()> {
let wallet = LocalWallet::load_from(root_dir)?;
let address_hex = hex::encode(wallet.address().to_bytes());
println!("{address_hex}");
println!("{:?}", wallet.address());
Ok(())
}

Expand All @@ -183,7 +227,7 @@ fn balance(root_dir: &Path) -> Result<NanoTokens> {

async fn get_faucet(root_dir: &Path, client: &Client, url: String) -> Result<()> {
let wallet = LocalWallet::load_from(root_dir)?;
let address_hex = hex::encode(wallet.address().to_bytes());
let address_hex = wallet.address().to_hex();
let url = if !url.contains("://") {
format!("{}://{}", "http", url)
} else {
Expand Down Expand Up @@ -269,7 +313,7 @@ async fn send(
return Err(err.into());
}
};
let to = match parse_main_pubkey(to) {
let to = match MainPubkey::from_hex(to) {
Ok(to) => to,
Err(err) => {
println!("Error while parsing the recipient's 'to' key: {err:?}");
Expand Down Expand Up @@ -445,3 +489,25 @@ fn try_decode_transfer_notif(msg: &[u8]) -> Result<(PublicKey, Vec<CashNoteRedem
let cashnote_redemptions: Vec<CashNoteRedemption> = rmp_serde::from_slice(&msg[PK_SIZE..])?;
Ok((key, cashnote_redemptions))
}

#[cfg(test)]
mod tests {
use super::*;
use sn_transfers::SpendAddress;

#[test]
fn test_parse_pubkey_address() -> eyre::Result<()> {
let public_key = SecretKey::random().public_key();
let unique_pk = UniquePubkey::new(public_key);
let spend_address = SpendAddress::from_unique_pubkey(&unique_pk);
let addr_hex = spend_address.to_hex();
let unique_pk_hex = unique_pk.to_hex();

let addr = parse_pubkey_address(&addr_hex)?;
assert_eq!(addr, spend_address);

let addr2 = parse_pubkey_address(&unique_pk_hex)?;
assert_eq!(addr2, spend_address);
Ok(())
}
}
44 changes: 18 additions & 26 deletions sn_client/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ use sn_protocol::{
NetworkAddress, PrettyPrintRecordKey,
};
use sn_registers::SignedRegister;
use sn_transfers::{
CashNote, CashNoteRedemption, MainPubkey, NanoTokens, Payment, SignedSpend, UniquePubkey,
};
use sn_transfers::{CashNote, CashNoteRedemption, MainPubkey, NanoTokens, Payment, SignedSpend};
use std::{
collections::{HashMap, HashSet},
num::NonZeroUsize,
Expand Down Expand Up @@ -500,7 +498,7 @@ impl Client {
) -> Result<()> {
let unique_pubkey = *spend.unique_pubkey();
let cash_note_addr = SpendAddress::from_unique_pubkey(&unique_pubkey);
let network_address = NetworkAddress::from_cash_note_address(cash_note_addr);
let network_address = NetworkAddress::from_spend_address(cash_note_addr);

trace!("Sending spend {unique_pubkey:?} to the network via put_record, with addr of {cash_note_addr:?}");
let key = network_address.to_record_key();
Expand Down Expand Up @@ -530,16 +528,12 @@ impl Client {
.await?)
}

/// Get a cash_note spend from network
pub async fn get_spend_from_network(
&self,
unique_pubkey: &UniquePubkey,
) -> Result<SignedSpend> {
let address = SpendAddress::from_unique_pubkey(unique_pubkey);
let key = NetworkAddress::from_cash_note_address(address).to_record_key();
/// Get a spend from network
pub async fn get_spend_from_network(&self, address: SpendAddress) -> Result<SignedSpend> {
let key = NetworkAddress::from_spend_address(address).to_record_key();

trace!(
"Getting spend {unique_pubkey:?} with record_key {:?}",
"Getting spend at {address:?} with record_key {:?}",
PrettyPrintRecordKey::from(&key)
);
let record = self
Expand All @@ -548,56 +542,54 @@ impl Client {
.await
.map_err(|err| {
Error::CouldNotVerifyTransfer(format!(
"unique_pubkey {unique_pubkey:?} errored: {err:?}"
"failed to get spend at {address:?}: {err:?}"
))
})?;
debug!(
"For spend {unique_pubkey:?} got record from the network, {:?}",
"For spend at {address:?} got record from the network, {:?}",
PrettyPrintRecordKey::from(&record.key)
);

let header = RecordHeader::from_record(&record).map_err(|err| {
Error::CouldNotVerifyTransfer(format!(
"Can't parse RecordHeader for the unique_pubkey {unique_pubkey:?} with error {err:?}"
"Can't parse RecordHeader for the spend at {address:?} with error {err:?}"
))
})?;

if let RecordKind::Spend = header.kind {
let mut deserialized_record = try_deserialize_record::<Vec<SignedSpend>>(&record)
.map_err(|err| {
Error::CouldNotVerifyTransfer(format!(
"Can't deserialize record for the unique_pubkey {unique_pubkey:?} with error {err:?}"
"Can't deserialize record for the spend at {address:?} with error {err:?}"
))
})?;

match deserialized_record.len() {
0 => {
trace!("Found no spend for {address:?}");
Err(Error::CouldNotVerifyTransfer(format!(
"Fetched record shows no spend for cash_note {unique_pubkey:?}."
"Fetched record shows no spend for cash_note {address:?}."
)))
}
1 => {
let signed_spend = deserialized_record.remove(0);
trace!("Spend get for address: {address:?} successful");
if unique_pubkey == signed_spend.unique_pubkey() {
if address == SpendAddress::from_unique_pubkey(signed_spend.unique_pubkey()) {
match signed_spend.verify(signed_spend.spent_tx_hash()) {
Ok(_) => {
trace!(
"Verified signed spend got from networkfor {unique_pubkey:?}"
);
trace!("Verified signed spend got from network for {address:?}");
Ok(signed_spend)
}
Err(err) => {
warn!("Invalid signed spend got from network for {unique_pubkey:?}: {err:?}.");
warn!("Invalid signed spend got from network for {address:?}: {err:?}.");
Err(Error::CouldNotVerifyTransfer(format!(
"Spend failed verifiation for the unique_pubkey {unique_pubkey:?} with error {err:?}")))
"Spend failed verifiation for the unique_pubkey {address:?} with error {err:?}")))
}
}
} else {
warn!("Signed spend ({:?}) got from network mismatched the expected one {unique_pubkey:?}.", signed_spend.unique_pubkey());
warn!("Signed spend ({:?}) got from network mismatched the expected one {address:?}.", signed_spend.unique_pubkey());
Err(Error::CouldNotVerifyTransfer(format!(
"Signed spend ({:?}) got from network mismatched the expected one {unique_pubkey:?}.", signed_spend.unique_pubkey())))
"Signed spend ({:?}) got from network mismatched the expected one {address:?}.", signed_spend.unique_pubkey())))
}
}
_ => {
Expand All @@ -606,7 +598,7 @@ impl Client {
let two = deserialized_record.remove(0);
error!("Found double spend for {address:?}");
Err(Error::CouldNotVerifyTransfer(format!(
"Found double spend for the unique_pubkey {unique_pubkey:?} - {:?}: spend_one {:?} and spend_two {:?}",
"Found double spend for the unique_pubkey {address:?} - {:?}: spend_one {:?} and spend_two {:?}",
PrettyPrintRecordKey::from(&key), one.derived_key_sig, two.derived_key_sig
)))
}
Expand Down
6 changes: 3 additions & 3 deletions sn_client/src/faucet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ pub async fn load_faucet_wallet_from_genesis_wallet(client: &Client) -> Result<L

println!("Verifying the transfer from genesis...");
debug!("Verifying the transfer from genesis...");
if let Err(error) = client.verify(&cash_note).await {
error!("Could not verify the transfer from genesis: {error:?}. Panicking.");
panic!("Could not verify the transfer from genesis: {error:?}");
if let Err(error) = client.verify_cashnote(&cash_note).await {
error!("Could not verify the transfer from genesis: {error}. Panicking.");
panic!("Could not verify the transfer from genesis: {error}");
} else {
println!("Successfully verified the transfer from genesis on the second try.");
info!("Successfully verified the transfer from genesis on the second try.");
Expand Down