Merge branch 'main' into schouhy/add-readme-explainers

This commit is contained in:
Sergio Chouhy 2025-12-02 16:40:26 -03:00
commit c360db9698
45 changed files with 3068 additions and 2067 deletions

View File

@ -36,9 +36,9 @@ path = "../wallet"
[dependencies.common]
path = "../common"
[dependencies.key_protocol]
path = "../key_protocol"
[dependencies.nssa]
path = "../nssa"
features = ["no_docker"]
[dependencies.key_protocol]
path = "../key_protocol"

View File

@ -56,6 +56,8 @@ fn make_private_account_input_from_str(account_id: &str) -> String {
pub async fn pre_test(
home_dir: PathBuf,
) -> Result<(ServerHandle, JoinHandle<Result<()>>, TempDir)> {
wallet::cli::execute_setup("test_pass".to_owned()).await?;
let home_dir_sequencer = home_dir.join("sequencer");
let mut sequencer_config =

View File

@ -8,6 +8,7 @@ use std::{
use actix_web::dev::ServerHandle;
use anyhow::Result;
use common::{PINATA_BASE58, sequencer_client::SequencerClient};
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use log::info;
use nssa::{AccountId, ProgramDeploymentTransaction, program::Program};
use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point};
@ -15,15 +16,17 @@ use sequencer_runner::startup_sequencer;
use tempfile::TempDir;
use tokio::task::JoinHandle;
use wallet::{
Command, SubcommandReturnValue, WalletCore,
WalletCore,
cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
config::ConfigSubcommand,
native_token_transfer_program::AuthTransferSubcommand,
pinata_program::PinataProgramAgnosticSubcommand,
token_program::TokenProgramAgnosticSubcommand,
programs::{
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
token::TokenProgramAgnosticSubcommand,
},
},
config::{PersistentAccountData, PersistentStorage},
config::PersistentStorage,
helperfunctions::{fetch_config, fetch_persistent_storage},
};
@ -56,7 +59,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
@ -83,13 +86,15 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
#[nssa_integration_test]
pub async fn test_success_move_to_another_account() {
info!("########## test_success_move_to_another_account ##########");
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public {}));
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: ChainIndex::root(),
}));
let wallet_config = fetch_config().await.unwrap();
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
let PersistentStorage {
accounts: persistent_accounts,
@ -120,7 +125,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
amount: 100,
});
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
@ -159,7 +164,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
let failed_send = wallet::execute_subcommand(command).await;
let failed_send = wallet::cli::execute_subcommand(command).await;
assert!(failed_send.is_err());
@ -200,7 +205,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
@ -231,7 +236,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
amount: 100,
});
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
@ -284,51 +289,44 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
let wallet_config = fetch_config().await.unwrap();
// Create new account for the token definition
wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {},
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap();
.unwrap()
else {
panic!("invalid subcommand return value");
};
// Create new account for the token supply holder
wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {},
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap();
.unwrap()
else {
panic!("invalid subcommand return value");
};
// Create new account for receiving a token transaction
wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {},
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id,
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap();
let PersistentStorage {
accounts: persistent_accounts,
last_synced_block: _,
} = fetch_persistent_storage().await.unwrap();
let mut new_persistent_accounts_account_id = Vec::new();
for per_acc in persistent_accounts {
match per_acc {
PersistentAccountData::Public(per_acc) => {
if (per_acc.account_id.to_string() != ACC_RECEIVER)
&& (per_acc.account_id.to_string() != ACC_SENDER)
{
new_persistent_accounts_account_id.push(per_acc.account_id);
}
}
_ => continue,
}
}
let [
definition_account_id,
supply_account_id,
recipient_account_id,
] = new_persistent_accounts_account_id
.try_into()
.expect("Failed to produce new account, not present in persistent accounts");
.unwrap()
else {
panic!("invalid subcommand return value");
};
// Create new token
let subcommand = TokenProgramAgnosticSubcommand::New {
@ -339,7 +337,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
name: "A NAME".to_string(),
total_supply: 37,
};
wallet::execute_subcommand(Command::Token(subcommand))
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
info!("Waiting for next block creation");
@ -398,7 +396,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
amount: 7,
};
wallet::execute_subcommand(Command::Token(subcommand))
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
info!("Waiting for next block creation");
@ -453,8 +451,10 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// Create new account for the token definition (public)
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -464,8 +464,10 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// Create new account for the token supply holder (private)
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -475,8 +477,10 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// Create new account for receiving a token transaction
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id,
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -494,7 +498,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
total_supply: 37,
};
wallet::execute_subcommand(Command::Token(subcommand))
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
@ -541,7 +545,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
amount: 7,
};
wallet::execute_subcommand(Command::Token(subcommand))
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
@ -575,7 +579,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
amount: 7,
};
wallet::execute_subcommand(Command::Token(subcommand))
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
@ -608,8 +612,10 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// Create new account for the token definition (public)
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -619,8 +625,10 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// Create new account for the token supply holder (private)
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -630,8 +638,10 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// Create new account for receiving a token transaction
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id,
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -649,7 +659,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
total_supply: 37,
};
wallet::execute_subcommand(Command::Token(subcommand))
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
@ -703,7 +713,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
};
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } =
wallet::execute_subcommand(Command::Token(subcommand))
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap()
else {
@ -715,7 +725,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
let command = Command::Account(AccountSubcommand::SyncPrivate {});
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
let wallet_config = fetch_config().await.unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config)
@ -744,8 +754,10 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// Create new account for the token definition (public)
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -755,8 +767,10 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// Create new account for the token supply holder (public)
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -766,8 +780,10 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// Create new account for receiving a token transaction
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id,
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -785,7 +801,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
total_supply: 37,
};
wallet::execute_subcommand(Command::Token(subcommand))
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
@ -822,7 +838,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
amount: 7,
};
wallet::execute_subcommand(Command::Token(subcommand))
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
@ -851,7 +867,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
amount: 7,
};
wallet::execute_subcommand(Command::Token(subcommand))
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
@ -880,8 +896,10 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// Create new account for the token definition (public)
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -891,8 +909,10 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// Create new account for the token supply holder (private)
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -902,8 +922,10 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// Create new account for receiving a token transaction
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id,
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -921,7 +943,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
total_supply: 37,
};
wallet::execute_subcommand(Command::Token(subcommand))
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
@ -968,7 +990,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
amount: 7,
};
wallet::execute_subcommand(Command::Token(subcommand))
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
@ -997,7 +1019,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
amount: 7,
};
wallet::execute_subcommand(Command::Token(subcommand))
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
@ -1029,7 +1051,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
amount: 100,
});
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
@ -1068,7 +1090,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
});
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } =
wallet::execute_subcommand(command).await.unwrap()
wallet::cli::execute_subcommand(command).await.unwrap()
else {
panic!("invalid subcommand return value");
};
@ -1104,9 +1126,11 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
);
let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap();
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {}));
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
cci: ChainIndex::root(),
}));
let sub_ret = wallet::execute_subcommand(command).await.unwrap();
let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap();
let SubcommandReturnValue::RegisterAccount {
account_id: to_account_id,
} = sub_ret
@ -1123,8 +1147,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
let (to_keys, _) = wallet_storage
.storage
.user_data
.user_private_accounts
.get(&to_account_id)
.get_private_account(&to_account_id)
.cloned()
.unwrap();
@ -1136,7 +1159,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
amount: 100,
});
let sub_ret = wallet::execute_subcommand(command).await.unwrap();
let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap();
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else {
panic!("FAILED TO SEND TX");
};
@ -1144,7 +1167,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await;
let command = Command::Account(AccountSubcommand::SyncPrivate {});
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
@ -1171,13 +1194,13 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// info!(
// "########## test_success_private_transfer_to_another_owned_account_cont_run_path
// ##########" );
// let continious_run_handle = tokio::spawn(wallet::execute_continious_run());
// let continious_run_handle = tokio::spawn(wallet::cli::execute_continious_run());
// let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap();
// let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {}));
// let sub_ret = wallet::execute_subcommand(command).await.unwrap();
// let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap();
// let SubcommandReturnValue::RegisterAccount {
// account_id: to_account_id,
// } = sub_ret
@ -1207,7 +1230,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// amount: 100,
// });
// let sub_ret = wallet::execute_subcommand(command).await.unwrap();
// let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap();
// let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else {
// panic!("FAILED TO SEND TX");
// };
@ -1258,7 +1281,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
let from_acc = wallet_storage.get_account_private(&from).unwrap();
assert_eq!(from_acc.balance, 10000);
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
@ -1301,7 +1324,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
let wallet_config = fetch_config().await.unwrap();
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
@ -1347,7 +1370,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } =
wallet::execute_subcommand(command).await.unwrap()
wallet::cli::execute_subcommand(command).await.unwrap()
else {
panic!("invalid subcommand return value");
};
@ -1377,10 +1400,8 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
let pinata_account_id = PINATA_BASE58;
let pinata_prize = 150;
let solution = 989106;
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to_account_id: make_public_account_input_from_str(ACC_SENDER),
solution,
to: make_public_account_input_from_str(ACC_SENDER),
});
let wallet_config = fetch_config().await.unwrap();
@ -1393,7 +1414,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
.unwrap()
.balance;
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
@ -1468,9 +1489,11 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
#[nssa_integration_test]
pub async fn test_authenticated_transfer_initialize_function() {
info!("########## test initialize account for authenticated transfer ##########");
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public {}));
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: ChainIndex::root(),
}));
let SubcommandReturnValue::RegisterAccount { account_id } =
wallet::execute_subcommand(command).await.unwrap()
wallet::cli::execute_subcommand(command).await.unwrap()
else {
panic!("Error creating account");
};
@ -1478,7 +1501,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
let command = Command::AuthTransfer(AuthTransferSubcommand::Init {
account_id: make_public_account_input_from_str(&account_id.to_string()),
});
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
info!("Checking correct execution");
let wallet_config = fetch_config().await.unwrap();
@ -1506,11 +1529,9 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
info!("########## test_pinata_private_receiver ##########");
let pinata_account_id = PINATA_BASE58;
let pinata_prize = 150;
let solution = 989106;
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to_account_id: make_private_account_input_from_str(ACC_SENDER_PRIVATE),
solution,
to: make_private_account_input_from_str(ACC_SENDER_PRIVATE),
});
let wallet_config = fetch_config().await.unwrap();
@ -1524,7 +1545,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
.balance;
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } =
wallet::execute_subcommand(command).await.unwrap()
wallet::cli::execute_subcommand(command).await.unwrap()
else {
panic!("invalid subcommand return value");
};
@ -1540,7 +1561,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
.balance;
let command = Command::Account(AccountSubcommand::SyncPrivate {});
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
let wallet_config = fetch_config().await.unwrap();
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
@ -1560,16 +1581,17 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
#[nssa_integration_test]
pub async fn test_pinata_private_receiver_new_account() {
info!("########## test_pinata_private_receiver ##########");
info!("########## test_pinata_private_receiver_new_account ##########");
let pinata_account_id = PINATA_BASE58;
let pinata_prize = 150;
let solution = 989106;
// Create new account for the token supply holder (private)
let SubcommandReturnValue::RegisterAccount {
account_id: winner_account_id,
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -1578,8 +1600,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
};
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to_account_id: make_private_account_input_from_str(&winner_account_id.to_string()),
solution,
to: make_private_account_input_from_str(&winner_account_id.to_string()),
});
let wallet_config = fetch_config().await.unwrap();
@ -1592,7 +1613,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
.unwrap()
.balance;
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
@ -1632,7 +1653,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
key: "seq_poll_retry_delay_millis".to_string(),
value: "1000".to_string(),
});
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
let wallet_config = fetch_config().await.unwrap();
@ -1643,7 +1664,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
key: "seq_poll_retry_delay_millis".to_string(),
value: old_seq_poll_retry_delay_millis.to_string(),
});
wallet::execute_subcommand(command).await.unwrap();
wallet::cli::execute_subcommand(command).await.unwrap();
info!("Success!");
}

View File

@ -14,6 +14,7 @@ hex = "0.4.3"
aes-gcm.workspace = true
bip39.workspace = true
hmac-sha512.workspace = true
thiserror.workspace = true
nssa-core = { path = "../nssa/core", features = ["host"] }
[dependencies.common]

View File

@ -0,0 +1,148 @@
use std::{fmt::Display, str::FromStr};
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
pub struct ChainIndex(Vec<u32>);
#[derive(thiserror::Error, Debug)]
pub enum ChainIndexError {
#[error("No root found")]
NoRootFound,
#[error("Failed to parse segment into a number")]
ParseIntError(#[from] std::num::ParseIntError),
}
impl FromStr for ChainIndex {
type Err = ChainIndexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !s.starts_with('/') {
return Err(ChainIndexError::NoRootFound);
}
if s == "/" {
return Ok(ChainIndex(vec![]));
}
let uprooted_substring = s.strip_prefix("/").unwrap();
let splitted_chain: Vec<&str> = uprooted_substring.split("/").collect();
let mut res = vec![];
for split_ch in splitted_chain {
let cci = split_ch.parse()?;
res.push(cci);
}
Ok(Self(res))
}
}
impl Display for ChainIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "/")?;
for cci in &self.0[..(self.0.len().saturating_sub(1))] {
write!(f, "{cci}/")?;
}
if let Some(last) = self.0.last() {
write!(f, "{}", last)?;
}
Ok(())
}
}
impl Default for ChainIndex {
fn default() -> Self {
ChainIndex::from_str("/").expect("Root parsing failure")
}
}
impl ChainIndex {
pub fn root() -> Self {
ChainIndex::default()
}
pub fn chain(&self) -> &[u32] {
&self.0
}
pub fn next_in_line(&self) -> ChainIndex {
let mut chain = self.0.clone();
// ToDo: Add overflow check
if let Some(last_p) = chain.last_mut() {
*last_p += 1
}
ChainIndex(chain)
}
pub fn nth_child(&self, child_id: u32) -> ChainIndex {
let mut chain = self.0.clone();
chain.push(child_id);
ChainIndex(chain)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chain_id_root_correct() {
let chain_id = ChainIndex::root();
let chain_id_2 = ChainIndex::from_str("/").unwrap();
assert_eq!(chain_id, chain_id_2);
}
#[test]
fn test_chain_id_deser_correct() {
let chain_id = ChainIndex::from_str("/257").unwrap();
assert_eq!(chain_id.chain(), &[257]);
}
#[test]
fn test_chain_id_deser_failure_no_root() {
let chain_index_error = ChainIndex::from_str("257").err().unwrap();
assert!(matches!(chain_index_error, ChainIndexError::NoRootFound));
}
#[test]
fn test_chain_id_deser_failure_int_parsing_failure() {
let chain_index_error = ChainIndex::from_str("/hello").err().unwrap();
assert!(matches!(
chain_index_error,
ChainIndexError::ParseIntError(_)
));
}
#[test]
fn test_chain_id_next_in_line_correct() {
let chain_id = ChainIndex::from_str("/257").unwrap();
let next_in_line = chain_id.next_in_line();
assert_eq!(next_in_line, ChainIndex::from_str("/258").unwrap());
}
#[test]
fn test_chain_id_child_correct() {
let chain_id = ChainIndex::from_str("/257").unwrap();
let child = chain_id.nth_child(3);
assert_eq!(child, ChainIndex::from_str("/257/3").unwrap());
}
#[test]
fn test_correct_display() {
let chainid = ChainIndex(vec![5, 7, 8]);
let string_index = format!("{chainid}");
assert_eq!(string_index, "/5/7/8".to_string());
}
}

View File

@ -0,0 +1,263 @@
use k256::{Scalar, elliptic_curve::PrimeField};
use nssa_core::encryption::IncomingViewingPublicKey;
use serde::{Deserialize, Serialize};
use crate::key_management::{
KeyChain,
key_tree::traits::KeyNode,
secret_holders::{PrivateKeyHolder, SecretSpendingKey},
};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ChildKeysPrivate {
pub value: (KeyChain, nssa::Account),
pub ccc: [u8; 32],
/// Can be [`None`] if root
pub cci: Option<u32>,
}
impl KeyNode for ChildKeysPrivate {
fn root(seed: [u8; 64]) -> Self {
let hash_value = hmac_sha512::HMAC::mac(seed, "NSSA_master_priv");
let ssk = SecretSpendingKey(
*hash_value
.first_chunk::<32>()
.expect("hash_value is 64 bytes, must be safe to get first 32"),
);
let ccc = *hash_value
.last_chunk::<32>()
.expect("hash_value is 64 bytes, must be safe to get last 32");
let nsk = ssk.generate_nullifier_secret_key();
let isk = ssk.generate_incoming_viewing_secret_key();
let ovk = ssk.generate_outgoing_viewing_secret_key();
let npk = (&nsk).into();
let ipk = IncomingViewingPublicKey::from_scalar(isk);
Self {
value: (
KeyChain {
secret_spending_key: ssk,
nullifer_public_key: npk,
incoming_viewing_public_key: ipk,
private_key_holder: PrivateKeyHolder {
nullifier_secret_key: nsk,
incoming_viewing_secret_key: isk,
outgoing_viewing_secret_key: ovk,
},
},
nssa::Account::default(),
),
ccc,
cci: None,
}
}
fn nth_child(&self, cci: u32) -> Self {
let parent_pt = Scalar::from_repr(
self.value
.0
.private_key_holder
.outgoing_viewing_secret_key
.into(),
)
.expect("Key generated as scalar, must be valid representation")
+ Scalar::from_repr(self.value.0.private_key_holder.nullifier_secret_key.into())
.expect("Key generated as scalar, must be valid representation")
* Scalar::from_repr(
self.value
.0
.private_key_holder
.incoming_viewing_secret_key
.into(),
)
.expect("Key generated as scalar, must be valid representation");
let mut input = vec![];
input.extend_from_slice(b"NSSA_seed_priv");
input.extend_from_slice(&parent_pt.to_bytes());
input.extend_from_slice(&cci.to_le_bytes());
let hash_value = hmac_sha512::HMAC::mac(input, self.ccc);
let ssk = SecretSpendingKey(
*hash_value
.first_chunk::<32>()
.expect("hash_value is 64 bytes, must be safe to get first 32"),
);
let ccc = *hash_value
.last_chunk::<32>()
.expect("hash_value is 64 bytes, must be safe to get last 32");
let nsk = ssk.generate_nullifier_secret_key();
let isk = ssk.generate_incoming_viewing_secret_key();
let ovk = ssk.generate_outgoing_viewing_secret_key();
let npk = (&nsk).into();
let ipk = IncomingViewingPublicKey::from_scalar(isk);
Self {
value: (
KeyChain {
secret_spending_key: ssk,
nullifer_public_key: npk,
incoming_viewing_public_key: ipk,
private_key_holder: PrivateKeyHolder {
nullifier_secret_key: nsk,
incoming_viewing_secret_key: isk,
outgoing_viewing_secret_key: ovk,
},
},
nssa::Account::default(),
),
ccc,
cci: Some(cci),
}
}
fn chain_code(&self) -> &[u8; 32] {
&self.ccc
}
fn child_index(&self) -> Option<u32> {
self.cci
}
fn account_id(&self) -> nssa::AccountId {
nssa::AccountId::from(&self.value.0.nullifer_public_key)
}
}
impl<'a> From<&'a ChildKeysPrivate> for &'a (KeyChain, nssa::Account) {
fn from(value: &'a ChildKeysPrivate) -> Self {
&value.value
}
}
impl<'a> From<&'a mut ChildKeysPrivate> for &'a mut (KeyChain, nssa::Account) {
fn from(value: &'a mut ChildKeysPrivate) -> Self {
&mut value.value
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keys_deterministic_generation() {
let root_keys = ChildKeysPrivate::root([42; 64]);
let child_keys = root_keys.nth_child(5);
assert_eq!(root_keys.cci, None);
assert_eq!(child_keys.cci, Some(5));
assert_eq!(
root_keys.value.0.secret_spending_key.0,
[
249, 83, 253, 32, 174, 204, 185, 44, 253, 167, 61, 92, 128, 5, 152, 4, 220, 21, 88,
84, 167, 180, 154, 249, 44, 77, 33, 136, 59, 131, 203, 152
]
);
assert_eq!(
child_keys.value.0.secret_spending_key.0,
[
16, 242, 229, 242, 252, 158, 153, 210, 234, 120, 70, 85, 83, 196, 5, 53, 28, 26,
187, 230, 22, 193, 146, 232, 237, 3, 166, 184, 122, 1, 233, 93
]
);
assert_eq!(
root_keys.value.0.private_key_holder.nullifier_secret_key,
[
38, 195, 52, 182, 16, 66, 167, 156, 9, 14, 65, 100, 17, 93, 166, 71, 27, 148, 93,
85, 116, 109, 130, 8, 195, 222, 159, 214, 141, 41, 124, 57
]
);
assert_eq!(
child_keys.value.0.private_key_holder.nullifier_secret_key,
[
215, 46, 2, 151, 174, 60, 86, 154, 5, 3, 175, 245, 12, 176, 220, 58, 250, 118, 236,
49, 254, 221, 229, 58, 40, 1, 170, 145, 175, 108, 23, 170
]
);
assert_eq!(
root_keys
.value
.0
.private_key_holder
.incoming_viewing_secret_key,
[
153, 161, 15, 34, 96, 184, 165, 165, 27, 244, 155, 40, 70, 5, 241, 133, 78, 40, 61,
118, 48, 148, 226, 5, 97, 18, 201, 128, 82, 248, 163, 72
]
);
assert_eq!(
child_keys
.value
.0
.private_key_holder
.incoming_viewing_secret_key,
[
192, 155, 55, 43, 164, 115, 71, 145, 227, 225, 21, 57, 55, 12, 226, 44, 10, 103,
39, 73, 230, 173, 60, 69, 69, 122, 110, 241, 164, 3, 192, 57
]
);
assert_eq!(
root_keys
.value
.0
.private_key_holder
.outgoing_viewing_secret_key,
[
205, 87, 71, 129, 90, 242, 217, 200, 140, 252, 124, 46, 207, 7, 33, 156, 83, 166,
150, 81, 98, 131, 182, 156, 110, 92, 78, 140, 125, 218, 152, 154
]
);
assert_eq!(
child_keys
.value
.0
.private_key_holder
.outgoing_viewing_secret_key,
[
131, 202, 219, 172, 219, 29, 48, 120, 226, 209, 209, 10, 216, 173, 48, 167, 233,
17, 35, 155, 30, 217, 176, 120, 72, 146, 250, 226, 165, 178, 255, 90
]
);
assert_eq!(
root_keys.value.0.nullifer_public_key.0,
[
65, 176, 149, 243, 192, 45, 216, 177, 169, 56, 229, 7, 28, 66, 204, 87, 109, 83,
152, 64, 14, 188, 179, 210, 147, 60, 22, 251, 203, 70, 89, 215
]
);
assert_eq!(
child_keys.value.0.nullifer_public_key.0,
[
69, 104, 130, 115, 48, 134, 19, 188, 67, 148, 163, 54, 155, 237, 57, 27, 136, 228,
111, 233, 205, 158, 149, 31, 84, 11, 241, 176, 243, 12, 138, 249
]
);
assert_eq!(
root_keys.value.0.incoming_viewing_public_key.0,
&[
3, 174, 56, 136, 244, 179, 18, 122, 38, 220, 36, 50, 200, 41, 104, 167, 70, 18, 60,
202, 93, 193, 29, 16, 125, 252, 96, 51, 199, 152, 47, 233, 178
]
);
assert_eq!(
child_keys.value.0.incoming_viewing_public_key.0,
&[
3, 18, 202, 246, 79, 141, 169, 51, 55, 202, 120, 169, 244, 201, 156, 162, 216, 115,
126, 53, 46, 94, 235, 125, 114, 178, 215, 81, 171, 93, 93, 88, 117
]
);
}
}

View File

@ -0,0 +1,132 @@
use serde::{Deserialize, Serialize};
use crate::key_management::key_tree::traits::KeyNode;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ChildKeysPublic {
pub csk: nssa::PrivateKey,
pub cpk: nssa::PublicKey,
pub ccc: [u8; 32],
/// Can be [`None`] if root
pub cci: Option<u32>,
}
impl KeyNode for ChildKeysPublic {
fn root(seed: [u8; 64]) -> Self {
let hash_value = hmac_sha512::HMAC::mac(seed, "NSSA_master_pub");
let csk = nssa::PrivateKey::try_new(*hash_value.first_chunk::<32>().unwrap()).unwrap();
let ccc = *hash_value.last_chunk::<32>().unwrap();
let cpk = nssa::PublicKey::new_from_private_key(&csk);
Self {
csk,
cpk,
ccc,
cci: None,
}
}
fn nth_child(&self, cci: u32) -> Self {
let mut hash_input = vec![];
hash_input.extend_from_slice(self.csk.value());
hash_input.extend_from_slice(&cci.to_le_bytes());
let hash_value = hmac_sha512::HMAC::mac(&hash_input, self.ccc);
let csk = nssa::PrivateKey::try_new(
*hash_value
.first_chunk::<32>()
.expect("hash_value is 64 bytes, must be safe to get first 32"),
)
.unwrap();
let ccc = *hash_value
.last_chunk::<32>()
.expect("hash_value is 64 bytes, must be safe to get last 32");
let cpk = nssa::PublicKey::new_from_private_key(&csk);
Self {
csk,
cpk,
ccc,
cci: Some(cci),
}
}
fn chain_code(&self) -> &[u8; 32] {
&self.ccc
}
fn child_index(&self) -> Option<u32> {
self.cci
}
fn account_id(&self) -> nssa::AccountId {
nssa::AccountId::from(&self.cpk)
}
}
impl<'a> From<&'a ChildKeysPublic> for &'a nssa::PrivateKey {
fn from(value: &'a ChildKeysPublic) -> Self {
&value.csk
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keys_deterministic_generation() {
let root_keys = ChildKeysPublic::root([42; 64]);
let child_keys = root_keys.nth_child(5);
assert_eq!(root_keys.cci, None);
assert_eq!(child_keys.cci, Some(5));
assert_eq!(
root_keys.ccc,
[
61, 30, 91, 26, 133, 91, 236, 192, 231, 53, 186, 139, 11, 221, 202, 11, 178, 215,
254, 103, 191, 60, 117, 112, 1, 226, 31, 156, 83, 104, 150, 224
]
);
assert_eq!(
child_keys.ccc,
[
67, 26, 102, 68, 189, 155, 102, 80, 199, 188, 112, 142, 207, 157, 36, 210, 48, 224,
35, 6, 112, 180, 11, 190, 135, 218, 9, 14, 84, 231, 58, 98
]
);
assert_eq!(
root_keys.csk.value(),
&[
241, 82, 246, 237, 62, 130, 116, 47, 189, 112, 99, 67, 178, 40, 115, 245, 141, 193,
77, 164, 243, 76, 222, 64, 50, 146, 23, 145, 91, 164, 92, 116
]
);
assert_eq!(
child_keys.csk.value(),
&[
11, 151, 27, 212, 167, 26, 77, 234, 103, 145, 53, 191, 184, 25, 240, 191, 156, 25,
60, 144, 65, 22, 193, 163, 246, 227, 212, 81, 49, 170, 33, 158
]
);
assert_eq!(
root_keys.cpk.value(),
&[
220, 170, 95, 177, 121, 37, 86, 166, 56, 238, 232, 72, 21, 106, 107, 217, 158, 74,
133, 91, 143, 244, 155, 15, 2, 230, 223, 169, 13, 20, 163, 138
]
);
assert_eq!(
child_keys.cpk.value(),
&[
152, 249, 236, 111, 132, 96, 184, 122, 21, 179, 240, 15, 234, 155, 164, 144, 108,
110, 120, 74, 176, 147, 196, 168, 243, 186, 203, 79, 97, 17, 194, 52
]
);
}
}

View File

@ -0,0 +1,324 @@
use std::collections::{BTreeMap, HashMap};
use serde::{Deserialize, Serialize};
use crate::key_management::{
key_tree::{
chain_index::ChainIndex, keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic,
traits::KeyNode,
},
secret_holders::SeedHolder,
};
pub mod chain_index;
pub mod keys_private;
pub mod keys_public;
pub mod traits;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct KeyTree<N: KeyNode> {
pub key_map: BTreeMap<ChainIndex, N>,
pub account_id_map: HashMap<nssa::AccountId, ChainIndex>,
}
pub type KeyTreePublic = KeyTree<ChildKeysPublic>;
pub type KeyTreePrivate = KeyTree<ChildKeysPrivate>;
impl<N: KeyNode> KeyTree<N> {
pub fn new(seed: &SeedHolder) -> Self {
let seed_fit: [u8; 64] = seed
.seed
.clone()
.try_into()
.expect("SeedHolder seed is 64 bytes long");
let root_keys = N::root(seed_fit);
let account_id = root_keys.account_id();
let key_map = BTreeMap::from_iter([(ChainIndex::root(), root_keys)]);
let account_id_map = HashMap::from_iter([(account_id, ChainIndex::root())]);
Self {
key_map,
account_id_map,
}
}
pub fn new_from_root(root: N) -> Self {
let account_id_map = HashMap::from_iter([(root.account_id(), ChainIndex::root())]);
let key_map = BTreeMap::from_iter([(ChainIndex::root(), root)]);
Self {
key_map,
account_id_map,
}
}
// ToDo: Add function to create a tree from list of nodes with consistency check.
pub fn find_next_last_child_of_id(&self, parent_id: &ChainIndex) -> Option<u32> {
if !self.key_map.contains_key(parent_id) {
return None;
}
let leftmost_child = parent_id.nth_child(u32::MIN);
if !self.key_map.contains_key(&leftmost_child) {
return Some(0);
}
let mut right = u32::MAX - 1;
let mut left_border = u32::MIN;
let mut right_border = u32::MAX;
loop {
let rightmost_child = parent_id.nth_child(right);
let rightmost_ref = self.key_map.get(&rightmost_child);
let rightmost_ref_next = self.key_map.get(&rightmost_child.next_in_line());
match (&rightmost_ref, &rightmost_ref_next) {
(Some(_), Some(_)) => {
left_border = right;
right = (right + right_border) / 2;
}
(Some(_), None) => {
break Some(right + 1);
}
(None, None) => {
right_border = right;
right = (left_border + right) / 2;
}
(None, Some(_)) => {
unreachable!();
}
}
}
}
pub fn generate_new_node(&mut self, parent_cci: ChainIndex) -> Option<nssa::AccountId> {
let father_keys = self.key_map.get(&parent_cci)?;
let next_child_id = self
.find_next_last_child_of_id(&parent_cci)
.expect("Can be None only if parent is not present");
let next_cci = parent_cci.nth_child(next_child_id);
let child_keys = father_keys.nth_child(next_child_id);
let account_id = child_keys.account_id();
self.key_map.insert(next_cci.clone(), child_keys);
self.account_id_map.insert(account_id, next_cci);
Some(account_id)
}
pub fn get_node(&self, account_id: nssa::AccountId) -> Option<&N> {
self.account_id_map
.get(&account_id)
.and_then(|chain_id| self.key_map.get(chain_id))
}
pub fn get_node_mut(&mut self, account_id: nssa::AccountId) -> Option<&mut N> {
self.account_id_map
.get(&account_id)
.and_then(|chain_id| self.key_map.get_mut(chain_id))
}
pub fn insert(&mut self, account_id: nssa::AccountId, chain_index: ChainIndex, node: N) {
self.account_id_map.insert(account_id, chain_index.clone());
self.key_map.insert(chain_index, node);
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use nssa::AccountId;
use super::*;
fn seed_holder_for_tests() -> SeedHolder {
SeedHolder {
seed: [42; 64].to_vec(),
}
}
#[test]
fn test_simple_key_tree() {
let seed_holder = seed_holder_for_tests();
let tree = KeyTreePublic::new(&seed_holder);
assert!(tree.key_map.contains_key(&ChainIndex::root()));
assert!(tree.account_id_map.contains_key(&AccountId::new([
46, 223, 229, 177, 59, 18, 189, 219, 153, 31, 249, 90, 112, 230, 180, 164, 80, 25, 106,
159, 14, 238, 1, 192, 91, 8, 210, 165, 199, 41, 60, 104,
])));
}
#[test]
fn test_small_key_tree() {
let seed_holder = seed_holder_for_tests();
let mut tree = KeyTreePublic::new(&seed_holder);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 0);
tree.generate_new_node(ChainIndex::root()).unwrap();
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/0").unwrap())
);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 1);
tree.generate_new_node(ChainIndex::root()).unwrap();
tree.generate_new_node(ChainIndex::root()).unwrap();
tree.generate_new_node(ChainIndex::root()).unwrap();
tree.generate_new_node(ChainIndex::root()).unwrap();
tree.generate_new_node(ChainIndex::root()).unwrap();
tree.generate_new_node(ChainIndex::root()).unwrap();
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 7);
}
#[test]
fn test_key_tree_can_not_make_child_keys() {
let seed_holder = seed_holder_for_tests();
let mut tree = KeyTreePublic::new(&seed_holder);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 0);
tree.generate_new_node(ChainIndex::root()).unwrap();
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/0").unwrap())
);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 1);
let key_opt = tree.generate_new_node(ChainIndex::from_str("/3").unwrap());
assert_eq!(key_opt, None);
}
#[test]
fn test_key_tree_complex_structure() {
let seed_holder = seed_holder_for_tests();
let mut tree = KeyTreePublic::new(&seed_holder);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 0);
tree.generate_new_node(ChainIndex::root()).unwrap();
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/0").unwrap())
);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 1);
tree.generate_new_node(ChainIndex::root()).unwrap();
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/1").unwrap())
);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 2);
tree.generate_new_node(ChainIndex::from_str("/0").unwrap())
.unwrap();
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::from_str("/0").unwrap())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 1);
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/0/0").unwrap())
);
tree.generate_new_node(ChainIndex::from_str("/0").unwrap())
.unwrap();
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::from_str("/0").unwrap())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 2);
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/0/1").unwrap())
);
tree.generate_new_node(ChainIndex::from_str("/0").unwrap())
.unwrap();
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::from_str("/0").unwrap())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 3);
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/0/2").unwrap())
);
tree.generate_new_node(ChainIndex::from_str("/0/1").unwrap())
.unwrap();
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/0/1/0").unwrap())
);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::from_str("/0/1").unwrap())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 1);
}
}

View File

@ -0,0 +1,14 @@
/// Trait, that reperesents a Node in hierarchical key tree
pub trait KeyNode {
/// Tree root node
fn root(seed: [u8; 64]) -> Self;
/// `cci`'s child of node
fn nth_child(&self, cci: u32) -> Self;
fn chain_code(&self) -> &[u8; 32];
fn child_index(&self) -> Option<u32>;
fn account_id(&self) -> nssa::AccountId;
}

View File

@ -8,12 +8,13 @@ use serde::{Deserialize, Serialize};
pub type PublicAccountSigningKey = [u8; 32];
pub mod ephemeral_key_holder;
pub mod key_tree;
pub mod secret_holders;
#[derive(Serialize, Deserialize, Clone, Debug)]
/// Entrypoint to key management
pub struct KeyChain {
secret_spending_key: SecretSpendingKey,
pub secret_spending_key: SecretSpendingKey,
pub private_key_holder: PrivateKeyHolder,
pub nullifer_public_key: NullifierPublicKey,
pub incoming_viewing_public_key: IncomingViewingPublicKey,
@ -39,6 +40,25 @@ impl KeyChain {
}
}
pub fn new_mnemonic(passphrase: String) -> Self {
// Currently dropping SeedHolder at the end of initialization.
// Not entirely sure if we need it in the future.
let seed_holder = SeedHolder::new_mnemonic(passphrase);
let secret_spending_key = seed_holder.produce_top_secret_key_holder();
let private_key_holder = secret_spending_key.produce_private_key_holder();
let nullifer_public_key = private_key_holder.generate_nullifier_public_key();
let incoming_viewing_public_key = private_key_holder.generate_incoming_viewing_public_key();
Self {
secret_spending_key,
private_key_holder,
nullifer_public_key,
incoming_viewing_public_key,
}
}
pub fn calculate_shared_secret_receiver(
&self,
ephemeral_public_key_sender: EphemeralPublicKey,

View File

@ -8,6 +8,8 @@ use rand::{RngCore, rngs::OsRng};
use serde::{Deserialize, Serialize};
use sha2::{Digest, digest::FixedOutput};
const NSSA_ENTROPY_BYTES: [u8; 32] = [0; 32];
#[derive(Debug)]
/// Seed holder. Non-clonable to ensure that different holders use different seeds.
/// Produces `TopSecretKeyHolder` objects.
@ -37,7 +39,8 @@ impl SeedHolder {
let mut enthopy_bytes: [u8; 32] = [0; 32];
OsRng.fill_bytes(&mut enthopy_bytes);
let mnemonic = Mnemonic::from_entropy(&enthopy_bytes).unwrap();
let mnemonic = Mnemonic::from_entropy(&enthopy_bytes)
.expect("Enthropy must be a multiple of 32 bytes");
let seed_wide = mnemonic.to_seed("mnemonic");
Self {
@ -45,6 +48,16 @@ impl SeedHolder {
}
}
pub fn new_mnemonic(passphrase: String) -> Self {
let mnemonic = Mnemonic::from_entropy(&NSSA_ENTROPY_BYTES)
.expect("Enthropy must be a multiple of 32 bytes");
let seed_wide = mnemonic.to_seed(passphrase);
Self {
seed: seed_wide.to_vec(),
}
}
pub fn generate_secret_spending_key_hash(&self) -> HashType {
let mut hash = hmac_sha512::HMAC::mac(&self.seed, "NSSA_seed");
@ -155,4 +168,14 @@ mod tests {
let _ = top_secret_key_holder.generate_outgoing_viewing_secret_key();
}
#[test]
fn two_seeds_generated_same_from_same_mnemonic() {
let mnemonic = "test_pass";
let seed_holder1 = SeedHolder::new_mnemonic(mnemonic.to_string());
let seed_holder2 = SeedHolder::new_mnemonic(mnemonic.to_string());
assert_eq!(seed_holder1.seed, seed_holder2.seed);
}
}

View File

@ -4,16 +4,25 @@ use anyhow::Result;
use k256::AffinePoint;
use serde::{Deserialize, Serialize};
use crate::key_management::KeyChain;
use crate::key_management::{
KeyChain,
key_tree::{KeyTreePrivate, KeyTreePublic, chain_index::ChainIndex},
secret_holders::SeedHolder,
};
pub type PublicKey = AffinePoint;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NSSAUserData {
/// Map for all user public accounts
pub pub_account_signing_keys: HashMap<nssa::AccountId, nssa::PrivateKey>,
/// Map for all user private accounts
pub user_private_accounts: HashMap<nssa::AccountId, (KeyChain, nssa_core::account::Account)>,
/// Default public accounts
pub default_pub_account_signing_keys: HashMap<nssa::AccountId, nssa::PrivateKey>,
/// Default private accounts
pub default_user_private_accounts:
HashMap<nssa::AccountId, (KeyChain, nssa_core::account::Account)>,
/// Tree of public keys
pub public_key_tree: KeyTreePublic,
/// Tree of private keys
pub private_key_tree: KeyTreePrivate,
}
impl NSSAUserData {
@ -47,39 +56,42 @@ impl NSSAUserData {
}
pub fn new_with_accounts(
accounts_keys: HashMap<nssa::AccountId, nssa::PrivateKey>,
accounts_key_chains: HashMap<nssa::AccountId, (KeyChain, nssa_core::account::Account)>,
default_accounts_keys: HashMap<nssa::AccountId, nssa::PrivateKey>,
default_accounts_key_chains: HashMap<
nssa::AccountId,
(KeyChain, nssa_core::account::Account),
>,
public_key_tree: KeyTreePublic,
private_key_tree: KeyTreePrivate,
) -> Result<Self> {
if !Self::valid_public_key_transaction_pairing_check(&accounts_keys) {
if !Self::valid_public_key_transaction_pairing_check(&default_accounts_keys) {
anyhow::bail!(
"Key transaction pairing check not satisfied, there is account_ids, which is not derived from keys"
);
}
if !Self::valid_private_key_transaction_pairing_check(&accounts_key_chains) {
if !Self::valid_private_key_transaction_pairing_check(&default_accounts_key_chains) {
anyhow::bail!(
"Key transaction pairing check not satisfied, there is account_ids, which is not derived from keys"
);
}
Ok(Self {
pub_account_signing_keys: accounts_keys,
user_private_accounts: accounts_key_chains,
default_pub_account_signing_keys: default_accounts_keys,
default_user_private_accounts: default_accounts_key_chains,
public_key_tree,
private_key_tree,
})
}
/// Generated new private key for public transaction signatures
///
/// Returns the account_id of new account
pub fn generate_new_public_transaction_private_key(&mut self) -> nssa::AccountId {
let private_key = nssa::PrivateKey::new_os_random();
let account_id =
nssa::AccountId::from(&nssa::PublicKey::new_from_private_key(&private_key));
self.pub_account_signing_keys
.insert(account_id, private_key);
account_id
pub fn generate_new_public_transaction_private_key(
&mut self,
parent_cci: ChainIndex,
) -> nssa::AccountId {
self.public_key_tree.generate_new_node(parent_cci).unwrap()
}
/// Returns the signing key for public transaction signatures
@ -87,22 +99,23 @@ impl NSSAUserData {
&self,
account_id: &nssa::AccountId,
) -> Option<&nssa::PrivateKey> {
self.pub_account_signing_keys.get(account_id)
// First seek in defaults
if let Some(key) = self.default_pub_account_signing_keys.get(account_id) {
Some(key)
// Then seek in tree
} else {
self.public_key_tree.get_node(*account_id).map(Into::into)
}
}
/// Generated new private key for privacy preserving transactions
///
/// Returns the account_id of new account
pub fn generate_new_privacy_preserving_transaction_key_chain(&mut self) -> nssa::AccountId {
let key_chain = KeyChain::new_os_random();
let account_id = nssa::AccountId::from(&key_chain.nullifer_public_key);
self.user_private_accounts.insert(
account_id,
(key_chain, nssa_core::account::Account::default()),
);
account_id
pub fn generate_new_privacy_preserving_transaction_key_chain(
&mut self,
parent_cci: ChainIndex,
) -> nssa::AccountId {
self.private_key_tree.generate_new_node(parent_cci).unwrap()
}
/// Returns the signing key for public transaction signatures
@ -110,7 +123,13 @@ impl NSSAUserData {
&self,
account_id: &nssa::AccountId,
) -> Option<&(KeyChain, nssa_core::account::Account)> {
self.user_private_accounts.get(account_id)
// First seek in defaults
if let Some(key) = self.default_user_private_accounts.get(account_id) {
Some(key)
// Then seek in tree
} else {
self.private_key_tree.get_node(*account_id).map(Into::into)
}
}
/// Returns the signing key for public transaction signatures
@ -118,14 +137,27 @@ impl NSSAUserData {
&mut self,
account_id: &nssa::AccountId,
) -> Option<&mut (KeyChain, nssa_core::account::Account)> {
self.user_private_accounts.get_mut(account_id)
// First seek in defaults
if let Some(key) = self.default_user_private_accounts.get_mut(account_id) {
Some(key)
// Then seek in tree
} else {
self.private_key_tree
.get_node_mut(*account_id)
.map(Into::into)
}
}
}
impl Default for NSSAUserData {
fn default() -> Self {
// Safe unwrap as maps are empty
Self::new_with_accounts(HashMap::default(), HashMap::default()).unwrap()
Self::new_with_accounts(
HashMap::new(),
HashMap::new(),
KeyTreePublic::new(&SeedHolder::new_mnemonic("default".to_string())),
KeyTreePrivate::new(&SeedHolder::new_mnemonic("default".to_string())),
)
.unwrap()
}
}
@ -137,20 +169,27 @@ mod tests {
fn test_new_account() {
let mut user_data = NSSAUserData::default();
let addr_pub = user_data.generate_new_public_transaction_private_key();
let addr_private = user_data.generate_new_privacy_preserving_transaction_key_chain();
let account_id_pub =
user_data.generate_new_public_transaction_private_key(ChainIndex::root());
let account_id_private =
user_data.generate_new_privacy_preserving_transaction_key_chain(ChainIndex::root());
let is_private_key_generated = user_data.get_pub_account_signing_key(&addr_pub).is_some();
let is_private_key_generated = user_data
.get_pub_account_signing_key(&account_id_pub)
.is_some();
assert!(is_private_key_generated);
let is_key_chain_generated = user_data.get_private_account(&addr_private).is_some();
let is_key_chain_generated = user_data.get_private_account(&account_id_private).is_some();
assert!(is_key_chain_generated);
let addr_private_str = addr_private.to_string();
println!("{addr_private_str:#?}");
let key_chain = &user_data.get_private_account(&addr_private).unwrap().0;
let account_id_private_str = account_id_private.to_string();
println!("{account_id_private_str:#?}");
let key_chain = &user_data
.get_private_account(&account_id_private)
.unwrap()
.0;
println!("{key_chain:#?}");
}
}

View File

@ -3,7 +3,7 @@ use nssa_core::{
program::{ProgramInput, read_nssa_inputs, write_nssa_outputs},
};
// The token program has two functions:
// The token program has three functions:
// 1. New token definition.
// Arguments to this function are:
// * Two **default** accounts: [definition_account, holding_account].
@ -18,6 +18,11 @@ use nssa_core::{
// * Two accounts: [sender_account, recipient_account].
// * An instruction data byte string of length 23, indicating the total supply with the following layout
// [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00].
// 3. Initialize account with zero balance
// Arguments to this function are:
// * Two accounts: [definition_account, account_to_initialize].
// * An dummy byte string of length 23, with the following layout
// [0x02 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00].
const TOKEN_DEFINITION_TYPE: u8 = 0;
const TOKEN_DEFINITION_DATA_SIZE: usize = 23;
@ -45,6 +50,25 @@ impl TokenDefinition {
bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes());
bytes.into()
}
fn parse(data: &[u8]) -> Option<Self> {
if data.len() != TOKEN_DEFINITION_DATA_SIZE || data[0] != TOKEN_DEFINITION_TYPE {
None
} else {
let account_type = data[0];
let name = data[1..7].try_into().unwrap();
let total_supply = u128::from_le_bytes(
data[7..]
.try_into()
.expect("Total supply must be 16 bytes little-endian"),
);
Some(Self {
account_type,
name,
total_supply,
})
}
}
}
impl TokenHolding {
@ -61,8 +85,16 @@ impl TokenHolding {
None
} else {
let account_type = data[0];
let definition_id = AccountId::new(data[1..33].try_into().unwrap());
let balance = u128::from_le_bytes(data[33..].try_into().unwrap());
let definition_id = AccountId::new(
data[1..33]
.try_into()
.expect("Defintion ID must be 32 bytes long"),
);
let balance = u128::from_le_bytes(
data[33..]
.try_into()
.expect("balance must be 16 bytes little-endian"),
);
Some(Self {
definition_id,
balance,
@ -167,6 +199,33 @@ fn new_definition(
vec![definition_target_account_post, holding_target_account_post]
}
fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec<Account> {
if pre_states.len() != 2 {
panic!("Invalid number of accounts");
}
let definition = &pre_states[0];
let account_to_initialize = &pre_states[1];
if account_to_initialize.account != Account::default() {
panic!("Only uninitialized accounts can be initialized");
}
// TODO: We should check that this is an account owned by the token program.
// This check can't be done here since the ID of the program is known only after compiling it
//
// Check definition account is valid
let _definition_values =
TokenDefinition::parse(&definition.account.data).expect("Definition account must be valid");
let holding_values = TokenHolding::new(&definition.account_id);
let definition_post = definition.account.clone();
let mut account_to_initialize_post = account_to_initialize.account.clone();
account_to_initialize_post.data = holding_values.into_data();
vec![definition_post, account_to_initialize_post]
}
type Instruction = [u8; 23];
fn main() {
@ -175,36 +234,59 @@ fn main() {
instruction,
} = read_nssa_inputs::<Instruction>();
match instruction[0] {
let (pre_states, post_states) = match instruction[0] {
0 => {
// Parse instruction
let total_supply = u128::from_le_bytes(instruction[1..17].try_into().unwrap());
let name: [u8; 6] = instruction[17..].try_into().unwrap();
let total_supply = u128::from_le_bytes(
instruction[1..17]
.try_into()
.expect("Total supply must be 16 bytes little-endian"),
);
let name: [u8; 6] = instruction[17..]
.try_into()
.expect("Name must be 6 bytes long");
assert_ne!(name, [0; 6]);
// Execute
let post_states = new_definition(&pre_states, name, total_supply);
write_nssa_outputs(pre_states, post_states);
(pre_states, post_states)
}
1 => {
// Parse instruction
let balance_to_move = u128::from_le_bytes(instruction[1..17].try_into().unwrap());
let name: [u8; 6] = instruction[17..].try_into().unwrap();
let balance_to_move = u128::from_le_bytes(
instruction[1..17]
.try_into()
.expect("Balance to move must be 16 bytes little-endian"),
);
let name: [u8; 6] = instruction[17..]
.try_into()
.expect("Name must be 6 bytes long");
assert_eq!(name, [0; 6]);
// Execute
let post_states = transfer(&pre_states, balance_to_move);
write_nssa_outputs(pre_states, post_states);
(pre_states, post_states)
}
2 => {
// Initialize account
assert_eq!(instruction[1..], [0; 22]);
let post_states = initialize_account(&pre_states);
(pre_states, post_states)
}
_ => panic!("Invalid instruction"),
};
write_nssa_outputs(pre_states, post_states);
}
#[cfg(test)]
mod tests {
use nssa_core::account::{Account, AccountId, AccountWithMetadata};
use crate::{new_definition, transfer, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE};
use crate::{
TOKEN_DEFINITION_DATA_SIZE, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE,
initialize_account, new_definition, transfer,
};
#[should_panic(expected = "Invalid number of input accounts")]
#[test]
@ -551,4 +633,37 @@ mod tests {
]
);
}
#[test]
fn test_token_initialize_account_succeeds() {
let pre_states = vec![
AccountWithMetadata {
account: Account {
// Definition ID with
data: vec![0; TOKEN_DEFINITION_DATA_SIZE - 16]
.into_iter()
.chain(u128::to_le_bytes(1000))
.collect(),
..Account::default()
},
is_authorized: false,
account_id: AccountId::new([1; 32]),
},
AccountWithMetadata {
account: Account::default(),
is_authorized: false,
account_id: AccountId::new([2; 32]),
},
];
let post_states = initialize_account(&pre_states);
let [definition, holding] = post_states.try_into().ok().unwrap();
assert_eq!(definition.data, pre_states[0].account.data);
assert_eq!(
holding.data,
vec![
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
);
}
}

View File

@ -1,10 +1,11 @@
use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::account::AccountId;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::{PrivateKey, error::NssaError};
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, Serialize, Deserialize)]
pub struct PublicKey([u8; 32]);
impl BorshDeserialize for PublicKey {

View File

@ -20,6 +20,7 @@ base58.workspace = true
hex = "0.4.3"
rand.workspace = true
itertools = "0.14.0"
sha2.workspace = true
[dependencies.key_protocol]
path = "../key_protocol"

294
wallet/src/chain_storage.rs Normal file
View File

@ -0,0 +1,294 @@
use std::collections::{HashMap, hash_map::Entry};
use anyhow::Result;
use key_protocol::{
key_management::{
key_tree::{KeyTreePrivate, KeyTreePublic, chain_index::ChainIndex},
secret_holders::SeedHolder,
},
key_protocol_core::NSSAUserData,
};
use nssa::program::Program;
use crate::config::{InitialAccountData, PersistentAccountData, WalletConfig};
pub struct WalletChainStore {
pub user_data: NSSAUserData,
pub wallet_config: WalletConfig,
}
impl WalletChainStore {
pub fn new(
config: WalletConfig,
persistent_accounts: Vec<PersistentAccountData>,
) -> Result<Self> {
if persistent_accounts.is_empty() {
anyhow::bail!("Roots not found; please run setup beforehand");
}
let mut public_init_acc_map = HashMap::new();
let mut private_init_acc_map = HashMap::new();
let public_root = persistent_accounts
.iter()
.find(|data| match data {
&PersistentAccountData::Public(data) => data.chain_index == ChainIndex::root(),
_ => false,
})
.cloned()
.expect("Malformed persistent account data, must have public root");
let private_root = persistent_accounts
.iter()
.find(|data| match data {
&PersistentAccountData::Private(data) => data.chain_index == ChainIndex::root(),
_ => false,
})
.cloned()
.expect("Malformed persistent account data, must have private root");
let mut public_tree = KeyTreePublic::new_from_root(match public_root {
PersistentAccountData::Public(data) => data.data,
_ => unreachable!(),
});
let mut private_tree = KeyTreePrivate::new_from_root(match private_root {
PersistentAccountData::Private(data) => data.data,
_ => unreachable!(),
});
for pers_acc_data in persistent_accounts {
match pers_acc_data {
PersistentAccountData::Public(data) => {
public_tree.insert(data.account_id, data.chain_index, data.data);
}
PersistentAccountData::Private(data) => {
private_tree.insert(data.account_id, data.chain_index, data.data);
}
PersistentAccountData::Preconfigured(acc_data) => match acc_data {
InitialAccountData::Public(data) => {
public_init_acc_map.insert(data.account_id.parse()?, data.pub_sign_key);
}
InitialAccountData::Private(data) => {
private_init_acc_map
.insert(data.account_id.parse()?, (data.key_chain, data.account));
}
},
}
}
Ok(Self {
user_data: NSSAUserData::new_with_accounts(
public_init_acc_map,
private_init_acc_map,
public_tree,
private_tree,
)?,
wallet_config: config,
})
}
pub fn new_storage(config: WalletConfig, password: String) -> Result<Self> {
let mut public_init_acc_map = HashMap::new();
let mut private_init_acc_map = HashMap::new();
for init_acc_data in config.initial_accounts.clone() {
match init_acc_data {
InitialAccountData::Public(data) => {
public_init_acc_map.insert(data.account_id.parse()?, data.pub_sign_key);
}
InitialAccountData::Private(data) => {
let mut account = data.account;
// TODO: Program owner is only known after code is compiled and can't be set in
// the config. Therefore we overwrite it here on startup. Fix this when program
// id can be fetched from the node and queried from the wallet.
account.program_owner = Program::authenticated_transfer_program().id();
private_init_acc_map
.insert(data.account_id.parse()?, (data.key_chain, account));
}
}
}
let public_tree = KeyTreePublic::new(&SeedHolder::new_mnemonic(password.clone()));
let private_tree = KeyTreePrivate::new(&SeedHolder::new_mnemonic(password));
Ok(Self {
user_data: NSSAUserData::new_with_accounts(
public_init_acc_map,
private_init_acc_map,
public_tree,
private_tree,
)?,
wallet_config: config,
})
}
pub fn insert_private_account_data(
&mut self,
account_id: nssa::AccountId,
account: nssa_core::account::Account,
) {
println!("inserting at address {account_id}, this account {account:?}");
let entry = self
.user_data
.default_user_private_accounts
.entry(account_id)
.and_modify(|data| data.1 = account.clone());
if matches!(entry, Entry::Vacant(_)) {
self.user_data
.private_key_tree
.account_id_map
.get(&account_id)
.map(|chain_index| {
self.user_data
.private_key_tree
.key_map
.entry(chain_index.clone())
.and_modify(|data| data.value.1 = account)
});
}
}
}
#[cfg(test)]
mod tests {
use key_protocol::key_management::key_tree::{
keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic, traits::KeyNode,
};
use super::*;
use crate::config::{
InitialAccountData, PersistentAccountDataPrivate, PersistentAccountDataPublic,
};
fn create_initial_accounts() -> Vec<InitialAccountData> {
let initial_acc1 = serde_json::from_str(
r#"{
"Public": {
"account_id": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy",
"pub_sign_key": [
16,
162,
106,
154,
236,
125,
52,
184,
35,
100,
238,
174,
69,
197,
41,
77,
187,
10,
118,
75,
0,
11,
148,
238,
185,
181,
133,
17,
220,
72,
124,
77
]
}
}"#,
)
.unwrap();
let initial_acc2 = serde_json::from_str(
r#"{
"Public": {
"account_id": "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw",
"pub_sign_key": [
113,
121,
64,
177,
204,
85,
229,
214,
178,
6,
109,
191,
29,
154,
63,
38,
242,
18,
244,
219,
8,
208,
35,
136,
23,
127,
207,
237,
216,
169,
190,
27
]
}
}"#,
)
.unwrap();
let initial_accounts = vec![initial_acc1, initial_acc2];
initial_accounts
}
fn create_sample_wallet_config() -> WalletConfig {
WalletConfig {
override_rust_log: None,
sequencer_addr: "http://127.0.0.1".to_string(),
seq_poll_timeout_millis: 12000,
seq_poll_max_blocks: 5,
seq_poll_max_retries: 10,
seq_poll_retry_delay_millis: 500,
initial_accounts: create_initial_accounts(),
}
}
fn create_sample_persistent_accounts() -> Vec<PersistentAccountData> {
let public_data = ChildKeysPublic::root([42; 64]);
let private_data = ChildKeysPrivate::root([47; 64]);
vec![
PersistentAccountData::Public(PersistentAccountDataPublic {
account_id: public_data.account_id(),
chain_index: ChainIndex::root(),
data: public_data,
}),
PersistentAccountData::Private(PersistentAccountDataPrivate {
account_id: private_data.account_id(),
chain_index: ChainIndex::root(),
data: private_data,
}),
]
}
#[test]
fn test_new_initializes_correctly() {
let config = create_sample_wallet_config();
let accs = create_sample_persistent_accounts();
let _ = WalletChainStore::new(config.clone(), accs).unwrap();
}
}

View File

@ -1,188 +0,0 @@
use std::collections::HashMap;
use anyhow::Result;
use key_protocol::key_protocol_core::NSSAUserData;
use nssa::program::Program;
use crate::config::{InitialAccountData, PersistentAccountData, WalletConfig};
pub struct WalletChainStore {
pub user_data: NSSAUserData,
pub wallet_config: WalletConfig,
}
impl WalletChainStore {
pub fn new(config: WalletConfig) -> Result<Self> {
let mut public_init_acc_map = HashMap::new();
let mut private_init_acc_map = HashMap::new();
for init_acc_data in config.initial_accounts.clone() {
match init_acc_data {
InitialAccountData::Public(data) => {
public_init_acc_map.insert(data.account_id.parse()?, data.pub_sign_key);
}
InitialAccountData::Private(data) => {
let mut account = data.account;
// TODO: Program owner is only known after code is compiled and can't be set in
// the config. Therefore we overwrite it here on startup. Fix this when program
// id can be fetched from the node and queried from the wallet.
account.program_owner = Program::authenticated_transfer_program().id();
private_init_acc_map
.insert(data.account_id.parse()?, (data.key_chain, account));
}
}
}
Ok(Self {
user_data: NSSAUserData::new_with_accounts(public_init_acc_map, private_init_acc_map)?,
wallet_config: config,
})
}
pub fn insert_private_account_data(
&mut self,
account_id: nssa::AccountId,
account: nssa_core::account::Account,
) {
println!(
"inserting at addres {}, this account {:?}",
account_id, account
);
self.user_data
.user_private_accounts
.entry(account_id)
.and_modify(|(_, acc)| *acc = account);
}
pub(crate) fn insert_account_data(&mut self, acc_data: PersistentAccountData) {
match acc_data {
PersistentAccountData::Public(acc_data) => {
self.user_data
.pub_account_signing_keys
.insert(acc_data.account_id, acc_data.pub_sign_key);
}
PersistentAccountData::Private(acc_data) => {
self.user_data
.user_private_accounts
.insert(acc_data.account_id, (acc_data.key_chain, acc_data.account));
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::InitialAccountData;
fn create_initial_accounts() -> Vec<InitialAccountData> {
let initial_acc1 = serde_json::from_str(
r#"{
"Public": {
"account_id": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy",
"pub_sign_key": [
16,
162,
106,
154,
236,
125,
52,
184,
35,
100,
238,
174,
69,
197,
41,
77,
187,
10,
118,
75,
0,
11,
148,
238,
185,
181,
133,
17,
220,
72,
124,
77
]
}
}"#,
)
.unwrap();
let initial_acc2 = serde_json::from_str(
r#"{
"Public": {
"account_id": "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw",
"pub_sign_key": [
113,
121,
64,
177,
204,
85,
229,
214,
178,
6,
109,
191,
29,
154,
63,
38,
242,
18,
244,
219,
8,
208,
35,
136,
23,
127,
207,
237,
216,
169,
190,
27
]
}
}"#,
)
.unwrap();
let initial_accounts = vec![initial_acc1, initial_acc2];
initial_accounts
}
fn create_sample_wallet_config() -> WalletConfig {
WalletConfig {
override_rust_log: None,
sequencer_addr: "http://127.0.0.1".to_string(),
seq_poll_timeout_millis: 12000,
seq_poll_max_blocks: 5,
seq_poll_max_retries: 10,
seq_poll_retry_delay_millis: 500,
initial_accounts: create_initial_accounts(),
}
}
#[test]
fn test_new_initializes_correctly() {
let config = create_sample_wallet_config();
let _ = WalletChainStore::new(config.clone()).unwrap();
}
}

View File

@ -2,14 +2,16 @@ use anyhow::Result;
use base58::ToBase58;
use clap::Subcommand;
use itertools::Itertools as _;
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use nssa::{Account, AccountId, program::Program};
use serde::Serialize;
use crate::{
SubcommandReturnValue, WalletCore,
cli::WalletSubcommand,
helperfunctions::{AccountPrivacyKind, HumanReadableAccount, parse_addr_with_privacy_prefix},
parse_block_range,
WalletCore,
cli::{SubcommandReturnValue, WalletSubcommand},
helperfunctions::{
AccountPrivacyKind, HumanReadableAccount, parse_addr_with_privacy_prefix, parse_block_range,
},
};
const TOKEN_DEFINITION_TYPE: u8 = 0;
@ -93,9 +95,17 @@ pub enum AccountSubcommand {
#[derive(Subcommand, Debug, Clone)]
pub enum NewSubcommand {
/// Register new public account
Public {},
Public {
#[arg(long)]
/// Chain index of a parent node
cci: ChainIndex,
},
/// Register new private account
Private {},
Private {
#[arg(long)]
/// Chain index of a parent node
cci: ChainIndex,
},
}
impl WalletSubcommand for NewSubcommand {
@ -104,8 +114,8 @@ impl WalletSubcommand for NewSubcommand {
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
NewSubcommand::Public {} => {
let account_id = wallet_core.create_new_account_public();
NewSubcommand::Public { cci } => {
let account_id = wallet_core.create_new_account_public(cci);
println!("Generated new account with account_id Public/{account_id}");
@ -115,8 +125,8 @@ impl WalletSubcommand for NewSubcommand {
Ok(SubcommandReturnValue::RegisterAccount { account_id })
}
NewSubcommand::Private {} => {
let account_id = wallet_core.create_new_account_private();
NewSubcommand::Private { cci } => {
let account_id = wallet_core.create_new_account_private(cci);
let (key, _) = wallet_core
.storage
@ -275,12 +285,19 @@ impl WalletSubcommand for AccountSubcommand {
.await?
.last_block;
if !wallet_core
if wallet_core
.storage
.user_data
.user_private_accounts
.private_key_tree
.account_id_map
.is_empty()
{
wallet_core.last_synced_block = curr_last_block;
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent data at {path:#?}");
} else {
parse_block_range(
last_synced_block + 1,
curr_last_block,
@ -288,12 +305,6 @@ impl WalletSubcommand for AccountSubcommand {
wallet_core,
)
.await?;
} else {
wallet_core.last_synced_block = curr_last_block;
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent data at {path:#?}");
}
Ok(SubcommandReturnValue::SyncedToBlock(curr_last_block))
@ -301,14 +312,28 @@ impl WalletSubcommand for AccountSubcommand {
AccountSubcommand::List {} => {
let user_data = &wallet_core.storage.user_data;
let accounts = user_data
.pub_account_signing_keys
.default_pub_account_signing_keys
.keys()
.map(|id| format!("Public/{id}"))
.map(|id| format!("Preconfigured Public/{id}"))
.chain(
user_data
.user_private_accounts
.default_user_private_accounts
.keys()
.map(|id| format!("Private/{id}")),
.map(|id| format!("Preconfigured Private/{id}")),
)
.chain(
user_data
.public_key_tree
.account_id_map
.iter()
.map(|(id, chain_index)| format!("{chain_index} Public/{id}")),
)
.chain(
user_data
.private_key_tree
.account_id_map
.iter()
.map(|(id, chain_index)| format!("{chain_index} Private/{id}")),
)
.format(",\n");

View File

@ -1,7 +1,10 @@
use anyhow::Result;
use clap::Subcommand;
use crate::{SubcommandReturnValue, WalletCore, cli::WalletSubcommand};
use crate::{
WalletCore,
cli::{SubcommandReturnValue, WalletSubcommand},
};
/// Represents generic chain CLI subcommand
#[derive(Subcommand, Debug, Clone)]

View File

@ -1,7 +1,10 @@
use anyhow::Result;
use clap::Subcommand;
use crate::{SubcommandReturnValue, WalletCore, cli::WalletSubcommand};
use crate::{
WalletCore,
cli::{SubcommandReturnValue, WalletSubcommand},
};
/// Represents generic config CLI subcommand
#[derive(Subcommand, Debug, Clone)]

View File

@ -1,15 +1,200 @@
use anyhow::Result;
use std::sync::Arc;
use crate::{SubcommandReturnValue, WalletCore};
use anyhow::Result;
use clap::{Parser, Subcommand};
use common::sequencer_client::SequencerClient;
use nssa::program::Program;
use crate::{
WalletCore,
cli::{
account::AccountSubcommand,
chain::ChainSubcommand,
config::ConfigSubcommand,
programs::{
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
token::TokenProgramAgnosticSubcommand,
},
},
helperfunctions::{fetch_config, parse_block_range},
};
pub mod account;
pub mod chain;
pub mod config;
pub mod native_token_transfer_program;
pub mod pinata_program;
pub mod token_program;
pub mod programs;
pub(crate) trait WalletSubcommand {
async fn handle_subcommand(self, wallet_core: &mut WalletCore)
-> Result<SubcommandReturnValue>;
}
/// Represents CLI command for a wallet
#[derive(Subcommand, Debug, Clone)]
#[clap(about)]
pub enum Command {
/// Authenticated transfer subcommand
#[command(subcommand)]
AuthTransfer(AuthTransferSubcommand),
/// Generic chain info subcommand
#[command(subcommand)]
ChainInfo(ChainSubcommand),
/// Account view and sync subcommand
#[command(subcommand)]
Account(AccountSubcommand),
/// Pinata program interaction subcommand
#[command(subcommand)]
Pinata(PinataProgramAgnosticSubcommand),
/// Token program interaction subcommand
#[command(subcommand)]
Token(TokenProgramAgnosticSubcommand),
/// Check the wallet can connect to the node and builtin local programs
/// match the remote versions
CheckHealth {},
/// Command to setup config, get and set config fields
#[command(subcommand)]
Config(ConfigSubcommand),
}
/// Represents overarching CLI command for a wallet with setup included
#[derive(Debug, Subcommand, Clone)]
#[clap(about)]
pub enum OverCommand {
/// Represents CLI command for a wallet
#[command(subcommand)]
Command(Command),
/// Setup of a storage. Initializes rots for public and private trees from `password`.
Setup {
#[arg(short, long)]
password: String,
},
}
/// To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config
///
/// All account adresses must be valid 32 byte base58 strings.
///
/// All account account_ids must be provided as {privacy_prefix}/{account_id},
/// where valid options for `privacy_prefix` is `Public` and `Private`
#[derive(Parser, Debug)]
#[clap(version, about)]
pub struct Args {
/// Continious run flag
#[arg(short, long)]
pub continuous_run: bool,
/// Wallet command
#[command(subcommand)]
pub command: Option<OverCommand>,
}
#[derive(Debug, Clone)]
pub enum SubcommandReturnValue {
PrivacyPreservingTransfer { tx_hash: String },
RegisterAccount { account_id: nssa::AccountId },
Account(nssa::Account),
Empty,
SyncedToBlock(u64),
}
pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValue> {
let wallet_config = fetch_config().await?;
let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?;
let subcommand_ret = match command {
Command::AuthTransfer(transfer_subcommand) => {
transfer_subcommand
.handle_subcommand(&mut wallet_core)
.await?
}
Command::ChainInfo(chain_subcommand) => {
chain_subcommand.handle_subcommand(&mut wallet_core).await?
}
Command::Account(account_subcommand) => {
account_subcommand
.handle_subcommand(&mut wallet_core)
.await?
}
Command::Pinata(pinata_subcommand) => {
pinata_subcommand
.handle_subcommand(&mut wallet_core)
.await?
}
Command::CheckHealth {} => {
let remote_program_ids = wallet_core
.sequencer_client
.get_program_ids()
.await
.expect("Error fetching program ids");
let Some(authenticated_transfer_id) = remote_program_ids.get("authenticated_transfer")
else {
panic!("Missing authenticated transfer ID from remote");
};
if authenticated_transfer_id != &Program::authenticated_transfer_program().id() {
panic!("Local ID for authenticated transfer program is different from remote");
}
let Some(token_id) = remote_program_ids.get("token") else {
panic!("Missing token program ID from remote");
};
if token_id != &Program::token().id() {
panic!("Local ID for token program is different from remote");
}
let Some(circuit_id) = remote_program_ids.get("privacy_preserving_circuit") else {
panic!("Missing privacy preserving circuit ID from remote");
};
if circuit_id != &nssa::PRIVACY_PRESERVING_CIRCUIT_ID {
panic!("Local ID for privacy preserving circuit is different from remote");
}
println!("✅All looks good!");
SubcommandReturnValue::Empty
}
Command::Token(token_subcommand) => {
token_subcommand.handle_subcommand(&mut wallet_core).await?
}
Command::Config(config_subcommand) => {
config_subcommand
.handle_subcommand(&mut wallet_core)
.await?
}
};
Ok(subcommand_ret)
}
pub async fn execute_continuous_run() -> Result<()> {
let config = fetch_config().await?;
let seq_client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?);
let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?;
let mut latest_block_num = seq_client.get_last_block().await?.last_block;
let mut curr_last_block = latest_block_num;
loop {
parse_block_range(
curr_last_block,
latest_block_num,
seq_client.clone(),
&mut wallet_core,
)
.await?;
curr_last_block = latest_block_num + 1;
tokio::time::sleep(std::time::Duration::from_millis(
config.seq_poll_timeout_millis,
))
.await;
latest_block_num = seq_client.get_last_block().await?.last_block;
}
}
pub async fn execute_setup(password: String) -> Result<()> {
let config = fetch_config().await?;
let wallet_core = WalletCore::start_from_config_new_storage(config.clone(), password).await?;
wallet_core.store_persistent_data().await?;
Ok(())
}

View File

@ -0,0 +1,3 @@
pub mod native_token_transfer;
pub mod pinata;
pub mod token;

View File

@ -4,9 +4,10 @@ use common::transaction::NSSATransaction;
use nssa::AccountId;
use crate::{
SubcommandReturnValue, WalletCore,
cli::WalletSubcommand,
WalletCore,
cli::{SubcommandReturnValue, WalletSubcommand},
helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix},
program_facades::native_token_transfer::NativeTokenTransfer,
};
/// Represents generic CLI subcommand for a wallet working with native token transfer program
@ -56,11 +57,11 @@ impl WalletSubcommand for AuthTransferSubcommand {
AccountPrivacyKind::Public => {
let account_id = account_id.parse()?;
let res = wallet_core
.register_account_under_authenticated_transfers_programs(account_id)
let res = NativeTokenTransfer(wallet_core)
.register_account(account_id)
.await?;
println!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let transfer_tx =
wallet_core.poll_native_token_transfer(res.tx_hash).await?;
@ -74,13 +75,11 @@ impl WalletSubcommand for AuthTransferSubcommand {
AccountPrivacyKind::Private => {
let account_id = account_id.parse()?;
let (res, [secret]) = wallet_core
.register_account_under_authenticated_transfers_programs_private(
account_id,
)
let (res, secret) = NativeTokenTransfer(wallet_core)
.register_account_private(account_id)
.await?;
println!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
@ -317,23 +316,11 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
let from: AccountId = from.parse().unwrap();
let to: AccountId = to.parse().unwrap();
let to_initialization = wallet_core.check_private_account_initialized(&to).await?;
let (res, [secret_from, secret_to]) = NativeTokenTransfer(wallet_core)
.send_private_transfer_to_owned_account(from, to, amount)
.await?;
let (res, [secret_from, secret_to]) = if let Some(to_proof) = to_initialization {
wallet_core
.send_private_native_token_transfer_owned_account_already_initialized(
from, to, amount, to_proof,
)
.await?
} else {
wallet_core
.send_private_native_token_transfer_owned_account_not_initialized(
from, to, amount,
)
.await?
};
println!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
@ -373,11 +360,11 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
let to_ipk =
nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_ipk.to_vec());
let (res, [secret_from, _]) = wallet_core
.send_private_native_token_transfer_outer_account(from, to_npk, to_ipk, amount)
let (res, [secret_from, _]) = NativeTokenTransfer(wallet_core)
.send_private_transfer_to_outer_account(from, to_npk, to_ipk, amount)
.await?;
println!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
@ -413,21 +400,11 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
let from: AccountId = from.parse().unwrap();
let to: AccountId = to.parse().unwrap();
let to_initialization = wallet_core.check_private_account_initialized(&to).await?;
let (res, secret) = NativeTokenTransfer(wallet_core)
.send_shielded_transfer(from, to, amount)
.await?;
let (res, [secret]) = if let Some(to_proof) = to_initialization {
wallet_core
.send_shielded_native_token_transfer_already_initialized(
from, to, amount, to_proof,
)
.await?
} else {
wallet_core
.send_shielded_native_token_transfer_not_initialized(from, to, amount)
.await?
};
println!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
@ -468,11 +445,11 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
let to_ipk =
nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_ipk.to_vec());
let res = wallet_core
.send_shielded_native_token_transfer_outer_account(from, to_npk, to_ipk, amount)
let (res, _) = NativeTokenTransfer(wallet_core)
.send_shielded_transfer_to_outer_account(from, to_npk, to_ipk, amount)
.await?;
println!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
@ -502,11 +479,11 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand {
let from: AccountId = from.parse().unwrap();
let to: AccountId = to.parse().unwrap();
let (res, [secret]) = wallet_core
.send_deshielded_native_token_transfer(from, to, amount)
let (res, secret) = NativeTokenTransfer(wallet_core)
.send_deshielded_transfer(from, to, amount)
.await?;
println!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
@ -532,11 +509,11 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand {
let from: AccountId = from.parse().unwrap();
let to: AccountId = to.parse().unwrap();
let res = wallet_core
.send_public_native_token_transfer(from, to, amount)
let res = NativeTokenTransfer(wallet_core)
.send_public_transfer(from, to, amount)
.await?;
println!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let transfer_tx = wallet_core.poll_native_token_transfer(res.tx_hash).await?;

View File

@ -1,12 +1,12 @@
use anyhow::Result;
use anyhow::{Context, Result};
use clap::Subcommand;
use common::{PINATA_BASE58, transaction::NSSATransaction};
use log::info;
use crate::{
SubcommandReturnValue, WalletCore,
cli::WalletSubcommand,
WalletCore,
cli::{SubcommandReturnValue, WalletSubcommand},
helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix},
program_facades::pinata::Pinata,
};
/// Represents generic CLI subcommand for a wallet working with pinata program
@ -14,12 +14,9 @@ use crate::{
pub enum PinataProgramAgnosticSubcommand {
/// Claim pinata
Claim {
/// to_account_id - valid 32 byte base58 string with privacy prefix
/// to - valid 32 byte base58 string with privacy prefix
#[arg(long)]
to_account_id: String,
/// solution - solution to pinata challenge
#[arg(long)]
solution: u128,
to: String,
},
}
@ -29,26 +26,20 @@ impl WalletSubcommand for PinataProgramAgnosticSubcommand {
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
let underlying_subcommand = match self {
PinataProgramAgnosticSubcommand::Claim {
to_account_id,
solution,
} => {
let (to_account_id, to_addr_privacy) =
parse_addr_with_privacy_prefix(&to_account_id)?;
PinataProgramAgnosticSubcommand::Claim { to } => {
let (to, to_addr_privacy) = parse_addr_with_privacy_prefix(&to)?;
match to_addr_privacy {
AccountPrivacyKind::Public => {
PinataProgramSubcommand::Public(PinataProgramSubcommandPublic::Claim {
pinata_account_id: PINATA_BASE58.to_string(),
winner_account_id: to_account_id,
solution,
winner_account_id: to,
})
}
AccountPrivacyKind::Private => PinataProgramSubcommand::Private(
PinataProgramSubcommandPrivate::ClaimPrivateOwned {
pinata_account_id: PINATA_BASE58.to_string(),
winner_account_id: to_account_id,
solution,
winner_account_id: to,
},
),
}
@ -82,9 +73,6 @@ pub enum PinataProgramSubcommandPublic {
/// winner_account_id - valid 32 byte hex string
#[arg(long)]
winner_account_id: String,
/// solution - solution to pinata challenge
#[arg(long)]
solution: u128,
},
}
@ -100,9 +88,6 @@ pub enum PinataProgramSubcommandPrivate {
/// winner_account_id - valid 32 byte hex string
#[arg(long)]
winner_account_id: String,
/// solution - solution to pinata challenge
#[arg(long)]
solution: u128,
},
}
@ -115,16 +100,28 @@ impl WalletSubcommand for PinataProgramSubcommandPublic {
PinataProgramSubcommandPublic::Claim {
pinata_account_id,
winner_account_id,
solution,
} => {
let res = wallet_core
.claim_pinata(
pinata_account_id.parse().unwrap(),
let pinata_account_id = pinata_account_id.parse().unwrap();
let solution = find_solution(wallet_core, pinata_account_id)
.await
.context("failed to compute solution")?;
let res = Pinata(wallet_core)
.claim(
pinata_account_id,
winner_account_id.parse().unwrap(),
solution,
)
.await?;
info!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
println!("Transaction data is {transfer_tx:?}");
Ok(SubcommandReturnValue::Empty)
}
@ -141,41 +138,26 @@ impl WalletSubcommand for PinataProgramSubcommandPrivate {
PinataProgramSubcommandPrivate::ClaimPrivateOwned {
pinata_account_id,
winner_account_id,
solution,
} => {
let pinata_account_id = pinata_account_id.parse().unwrap();
let winner_account_id = winner_account_id.parse().unwrap();
let solution = find_solution(wallet_core, pinata_account_id)
.await
.context("failed to compute solution")?;
let winner_initialization = wallet_core
.check_private_account_initialized(&winner_account_id)
let (res, secret_winner) = Pinata(wallet_core)
.claim_private_owned_account(pinata_account_id, winner_account_id, solution)
.await?;
let (res, [secret_winner]) = if let Some(winner_proof) = winner_initialization {
wallet_core
.claim_pinata_private_owned_account_already_initialized(
pinata_account_id,
winner_account_id,
solution,
winner_proof,
)
.await?
} else {
wallet_core
.claim_pinata_private_owned_account_not_initialized(
pinata_account_id,
winner_account_id,
solution,
)
.await?
};
info!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
println!("Transaction data is {transfer_tx:?}");
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let acc_decode_data = vec![(secret_winner, winner_account_id)];
@ -210,3 +192,46 @@ impl WalletSubcommand for PinataProgramSubcommand {
}
}
}
async fn find_solution(wallet: &WalletCore, pinata_account_id: nssa::AccountId) -> Result<u128> {
let account = wallet.get_account_public(pinata_account_id).await?;
let data: [u8; 33] = account
.data
.try_into()
.map_err(|_| anyhow::Error::msg("invalid pinata account data"))?;
println!("Computing solution for pinata...");
let now = std::time::Instant::now();
let solution = compute_solution(data);
println!("Found solution {solution} in {:?}", now.elapsed());
Ok(solution)
}
fn compute_solution(data: [u8; 33]) -> u128 {
let difficulty = data[0];
let seed = &data[1..];
let mut solution = 0u128;
while !validate_solution(difficulty, seed, solution) {
solution = solution.checked_add(1).expect("solution overflowed u128");
}
solution
}
fn validate_solution(difficulty: u8, seed: &[u8], solution: u128) -> bool {
use sha2::{Digest as _, digest::FixedOutput as _};
let mut bytes = [0; 32 + 16];
bytes[..32].copy_from_slice(seed);
bytes[32..].copy_from_slice(&solution.to_le_bytes());
let mut hasher = sha2::Sha256::new();
hasher.update(bytes);
let digest: [u8; 32] = hasher.finalize_fixed().into();
let difficulty = difficulty as usize;
digest[..difficulty].iter().all(|&b| b == 0)
}

View File

@ -4,9 +4,10 @@ use common::transaction::NSSATransaction;
use nssa::AccountId;
use crate::{
SubcommandReturnValue, WalletCore,
cli::WalletSubcommand,
WalletCore,
cli::{SubcommandReturnValue, WalletSubcommand},
helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix},
program_facades::token::Token,
};
/// Represents generic CLI subcommand for a wallet working with token program
@ -338,8 +339,8 @@ impl WalletSubcommand for TokenProgramSubcommandPublic {
}
let mut name_bytes = [0; 6];
name_bytes[..name.len()].copy_from_slice(name);
wallet_core
.send_new_token_definition(
Token(wallet_core)
.send_new_definition(
definition_account_id.parse().unwrap(),
supply_account_id.parse().unwrap(),
name_bytes,
@ -353,8 +354,8 @@ impl WalletSubcommand for TokenProgramSubcommandPublic {
recipient_account_id,
balance_to_move,
} => {
wallet_core
.send_transfer_token_transaction(
Token(wallet_core)
.send_transfer_transaction(
sender_account_id.parse().unwrap(),
recipient_account_id.parse().unwrap(),
balance_to_move,
@ -389,8 +390,8 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
let supply_account_id: AccountId = supply_account_id.parse().unwrap();
let (res, [secret_supply]) = wallet_core
.send_new_token_definition_private_owned(
let (res, secret_supply) = Token(wallet_core)
.send_new_definition_private_owned(
definition_account_id,
supply_account_id,
name_bytes,
@ -398,7 +399,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
)
.await?;
println!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
@ -428,31 +429,15 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
let sender_account_id: AccountId = sender_account_id.parse().unwrap();
let recipient_account_id: AccountId = recipient_account_id.parse().unwrap();
let recipient_initialization = wallet_core
.check_private_account_initialized(&recipient_account_id)
let (res, [secret_sender, secret_recipient]) = Token(wallet_core)
.send_transfer_transaction_private_owned_account(
sender_account_id,
recipient_account_id,
balance_to_move,
)
.await?;
let (res, [secret_sender, secret_recipient]) =
if let Some(recipient_proof) = recipient_initialization {
wallet_core
.send_transfer_token_transaction_private_owned_account_already_initialized(
sender_account_id,
recipient_account_id,
balance_to_move,
recipient_proof,
)
.await?
} else {
wallet_core
.send_transfer_token_transaction_private_owned_account_not_initialized(
sender_account_id,
recipient_account_id,
balance_to_move,
)
.await?
};
println!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
@ -496,8 +481,8 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
recipient_ipk.to_vec(),
);
let (res, [secret_sender, _]) = wallet_core
.send_transfer_token_transaction_private_foreign_account(
let (res, [secret_sender, _]) = Token(wallet_core)
.send_transfer_transaction_private_foreign_account(
sender_account_id,
recipient_npk,
recipient_ipk,
@ -505,7 +490,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
)
.await?;
println!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
@ -545,15 +530,15 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded {
let sender_account_id: AccountId = sender_account_id.parse().unwrap();
let recipient_account_id: AccountId = recipient_account_id.parse().unwrap();
let (res, [secret_sender]) = wallet_core
.send_transfer_token_transaction_deshielded(
let (res, secret_sender) = Token(wallet_core)
.send_transfer_transaction_deshielded(
sender_account_id,
recipient_account_id,
balance_to_move,
)
.await?;
println!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
@ -604,8 +589,8 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
recipient_ipk.to_vec(),
);
let res = wallet_core
.send_transfer_token_transaction_shielded_foreign_account(
let (res, _) = Token(wallet_core)
.send_transfer_transaction_shielded_foreign_account(
sender_account_id,
recipient_npk,
recipient_ipk,
@ -613,7 +598,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
)
.await?;
println!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
@ -638,31 +623,15 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
let sender_account_id: AccountId = sender_account_id.parse().unwrap();
let recipient_account_id: AccountId = recipient_account_id.parse().unwrap();
let recipient_initialization = wallet_core
.check_private_account_initialized(&recipient_account_id)
let (res, secret_recipient) = Token(wallet_core)
.send_transfer_transaction_shielded_owned_account(
sender_account_id,
recipient_account_id,
balance_to_move,
)
.await?;
let (res, [secret_recipient]) =
if let Some(recipient_proof) = recipient_initialization {
wallet_core
.send_transfer_token_transaction_shielded_owned_account_already_initialized(
sender_account_id,
recipient_account_id,
balance_to_move,
recipient_proof,
)
.await?
} else {
wallet_core
.send_transfer_token_transaction_shielded_owned_account_not_initialized(
sender_account_id,
recipient_account_id,
balance_to_move,
)
.await?
};
println!("Results of tx send is {res:#?}");
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core

View File

@ -1,4 +1,9 @@
use key_protocol::key_management::KeyChain;
use key_protocol::key_management::{
KeyChain,
key_tree::{
chain_index::ChainIndex, keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic,
},
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -10,7 +15,8 @@ pub struct InitialAccountDataPublic {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistentAccountDataPublic {
pub account_id: nssa::AccountId,
pub pub_sign_key: nssa::PrivateKey,
pub chain_index: ChainIndex,
pub data: ChildKeysPublic,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -23,8 +29,8 @@ pub struct InitialAccountDataPrivate {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistentAccountDataPrivate {
pub account_id: nssa::AccountId,
pub account: nssa_core::account::Account,
pub key_chain: KeyChain,
pub chain_index: ChainIndex,
pub data: ChildKeysPrivate,
}
// Big difference in enum variants sizes
@ -45,6 +51,7 @@ pub enum InitialAccountData {
pub enum PersistentAccountData {
Public(PersistentAccountDataPublic),
Private(PersistentAccountDataPrivate),
Preconfigured(InitialAccountData),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -67,6 +74,7 @@ impl PersistentAccountData {
match &self {
Self::Public(acc) => acc.account_id,
Self::Private(acc) => acc.account_id,
Self::Preconfigured(acc) => acc.account_id(),
}
}
}
@ -95,6 +103,12 @@ impl From<PersistentAccountDataPrivate> for PersistentAccountData {
}
}
impl From<InitialAccountData> for PersistentAccountData {
fn from(value: InitialAccountData) -> Self {
Self::Preconfigured(value)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GasConfig {
/// Gas spent per deploying one byte of data

View File

@ -1,17 +1,23 @@
use std::{path::PathBuf, str::FromStr};
use std::{path::PathBuf, str::FromStr, sync::Arc};
use anyhow::Result;
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
use key_protocol::key_protocol_core::NSSAUserData;
use nssa::Account;
use common::{
block::HashableBlockData, sequencer_client::SequencerClient, transaction::NSSATransaction,
};
use key_protocol::{
key_management::key_tree::traits::KeyNode as _, key_protocol_core::NSSAUserData,
};
use nssa::{Account, privacy_preserving_transaction::message::EncryptedAccountData};
use nssa_core::account::Nonce;
use rand::{RngCore, rngs::OsRng};
use serde::Serialize;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::{
HOME_DIR_ENV_VAR,
HOME_DIR_ENV_VAR, WalletCore,
config::{
InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic,
PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, WalletConfig,
},
};
@ -90,7 +96,7 @@ pub async fn fetch_config() -> Result<WalletConfig> {
/// Fetch data stored at home
///
/// If file not present, it is considered as empty list of persistent accounts
/// File must be created through setup beforehand.
pub async fn fetch_persistent_storage() -> Result<PersistentStorage> {
let home = get_home()?;
let accs_path = home.join("storage.json");
@ -102,10 +108,9 @@ pub async fn fetch_persistent_storage() -> Result<PersistentStorage> {
Ok(serde_json::from_slice(&storage_content)?)
}
Err(err) => match err.kind() {
std::io::ErrorKind::NotFound => Ok(PersistentStorage {
accounts: vec![],
last_synced_block: 0,
}),
std::io::ErrorKind::NotFound => {
anyhow::bail!("Not found, please setup roots from config command beforehand");
}
_ => {
anyhow::bail!("IO error {err:#?}");
}
@ -120,25 +125,51 @@ pub fn produce_data_for_storage(
) -> PersistentStorage {
let mut vec_for_storage = vec![];
for (account_id, key) in &user_data.pub_account_signing_keys {
vec_for_storage.push(
PersistentAccountDataPublic {
account_id: *account_id,
pub_sign_key: key.clone(),
}
.into(),
);
for (account_id, key) in &user_data.public_key_tree.account_id_map {
if let Some(data) = user_data.public_key_tree.key_map.get(key) {
vec_for_storage.push(
PersistentAccountDataPublic {
account_id: *account_id,
chain_index: key.clone(),
data: data.clone(),
}
.into(),
);
}
}
for (account_id, (key, acc)) in &user_data.user_private_accounts {
for (account_id, key) in &user_data.private_key_tree.account_id_map {
if let Some(data) = user_data.private_key_tree.key_map.get(key) {
vec_for_storage.push(
PersistentAccountDataPrivate {
account_id: *account_id,
chain_index: key.clone(),
data: data.clone(),
}
.into(),
);
}
}
for (account_id, key) in &user_data.default_pub_account_signing_keys {
vec_for_storage.push(
PersistentAccountDataPrivate {
account_id: *account_id,
account: acc.clone(),
key_chain: key.clone(),
}
InitialAccountData::Public(InitialAccountDataPublic {
account_id: account_id.to_string(),
pub_sign_key: key.clone(),
})
.into(),
);
)
}
for (account_id, (key_chain, account)) in &user_data.default_user_private_accounts {
vec_for_storage.push(
InitialAccountData::Private(InitialAccountDataPrivate {
account_id: account_id.to_string(),
account: account.clone(),
key_chain: key_chain.clone(),
})
.into(),
)
}
PersistentStorage {
@ -199,6 +230,125 @@ impl From<Account> for HumanReadableAccount {
}
}
pub async fn parse_block_range(
start: u64,
stop: u64,
seq_client: Arc<SequencerClient>,
wallet_core: &mut WalletCore,
) -> Result<()> {
for block_id in start..(stop + 1) {
let block =
borsh::from_slice::<HashableBlockData>(&seq_client.get_block(block_id).await?.block)?;
for tx in block.transactions {
let nssa_tx = NSSATransaction::try_from(&tx)?;
if let NSSATransaction::PrivacyPreserving(tx) = nssa_tx {
let mut affected_accounts = vec![];
for (acc_account_id, (key_chain, _)) in
&wallet_core.storage.user_data.default_user_private_accounts
{
let view_tag = EncryptedAccountData::compute_view_tag(
key_chain.nullifer_public_key.clone(),
key_chain.incoming_viewing_public_key.clone(),
);
for (ciph_id, encrypted_data) in tx
.message()
.encrypted_private_post_states
.iter()
.enumerate()
{
if encrypted_data.view_tag == view_tag {
let ciphertext = &encrypted_data.ciphertext;
let commitment = &tx.message.new_commitments[ciph_id];
let shared_secret = key_chain
.calculate_shared_secret_receiver(encrypted_data.epk.clone());
let res_acc = nssa_core::EncryptionScheme::decrypt(
ciphertext,
&shared_secret,
commitment,
ciph_id as u32,
);
if let Some(res_acc) = res_acc {
println!(
"Received new account for account_id {acc_account_id:#?} with account object {res_acc:#?}"
);
affected_accounts.push((*acc_account_id, res_acc));
}
}
}
}
for keys_node in wallet_core
.storage
.user_data
.private_key_tree
.key_map
.values()
{
let acc_account_id = keys_node.account_id();
let key_chain = &keys_node.value.0;
let view_tag = EncryptedAccountData::compute_view_tag(
key_chain.nullifer_public_key.clone(),
key_chain.incoming_viewing_public_key.clone(),
);
for (ciph_id, encrypted_data) in tx
.message()
.encrypted_private_post_states
.iter()
.enumerate()
{
if encrypted_data.view_tag == view_tag {
let ciphertext = &encrypted_data.ciphertext;
let commitment = &tx.message.new_commitments[ciph_id];
let shared_secret = key_chain
.calculate_shared_secret_receiver(encrypted_data.epk.clone());
let res_acc = nssa_core::EncryptionScheme::decrypt(
ciphertext,
&shared_secret,
commitment,
ciph_id as u32,
);
if let Some(res_acc) = res_acc {
println!(
"Received new account for account_id {acc_account_id:#?} with account object {res_acc:#?}"
);
affected_accounts.push((acc_account_id, res_acc));
}
}
}
}
for (affected_account_id, new_acc) in affected_accounts {
wallet_core
.storage
.insert_private_account_data(affected_account_id, new_acc);
}
}
}
wallet_core.last_synced_block = block_id;
wallet_core.store_persistent_data().await?;
println!(
"Block at id {block_id} with timestamp {} parsed",
block.timestamp
);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -3,30 +3,24 @@ use std::{path::PathBuf, sync::Arc};
use anyhow::Result;
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
use chain_storage::WalletChainStore;
use clap::{Parser, Subcommand};
use common::{
block::HashableBlockData,
sequencer_client::SequencerClient,
error::ExecutionFailureKind,
sequencer_client::{SequencerClient, json::SendTxResponse},
transaction::{EncodedTransaction, NSSATransaction},
};
use config::WalletConfig;
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use log::info;
use nssa::{
Account, AccountId, privacy_preserving_transaction::message::EncryptedAccountData,
program::Program,
};
use nssa_core::{Commitment, MembershipProof};
use nssa::{Account, AccountId, PrivacyPreservingTransaction, program::Program};
use nssa_core::{Commitment, MembershipProof, SharedSecretKey, program::InstructionData};
pub use privacy_preserving_tx::PrivacyPreservingAccount;
use tokio::io::AsyncWriteExt;
use crate::{
cli::{
WalletSubcommand, account::AccountSubcommand, chain::ChainSubcommand,
config::ConfigSubcommand, native_token_transfer_program::AuthTransferSubcommand,
pinata_program::PinataProgramAgnosticSubcommand,
token_program::TokenProgramAgnosticSubcommand,
},
config::PersistentStorage,
helperfunctions::{fetch_config, fetch_persistent_storage, get_home, produce_data_for_storage},
helperfunctions::{
fetch_persistent_storage, get_home, produce_data_for_storage, produce_random_nonces,
},
poller::TxPoller,
};
@ -36,11 +30,9 @@ pub mod chain_storage;
pub mod cli;
pub mod config;
pub mod helperfunctions;
pub mod pinata_interactions;
pub mod poller;
pub mod token_program_interactions;
pub mod token_transfers;
pub mod transaction_utils;
mod privacy_preserving_tx;
pub mod program_facades;
pub struct WalletCore {
pub storage: WalletChainStore,
@ -54,15 +46,12 @@ impl WalletCore {
let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?);
let tx_poller = TxPoller::new(config.clone(), client.clone());
let mut storage = WalletChainStore::new(config)?;
let PersistentStorage {
accounts: persistent_accounts,
last_synced_block,
} = fetch_persistent_storage().await?;
for pers_acc_data in persistent_accounts {
storage.insert_account_data(pers_acc_data);
}
let storage = WalletChainStore::new(config, persistent_accounts)?;
Ok(Self {
storage,
@ -72,6 +61,23 @@ impl WalletCore {
})
}
pub async fn start_from_config_new_storage(
config: WalletConfig,
password: String,
) -> Result<Self> {
let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?);
let tx_poller = TxPoller::new(config.clone(), client.clone());
let storage = WalletChainStore::new_storage(config, password)?;
Ok(Self {
storage,
poller: tx_poller,
sequencer_client: client.clone(),
last_synced_block: 0,
})
}
/// Store persistent data at home
pub async fn store_persistent_data(&self) -> Result<PathBuf> {
let home = get_home()?;
@ -102,16 +108,16 @@ impl WalletCore {
Ok(config_path)
}
pub fn create_new_account_public(&mut self) -> AccountId {
pub fn create_new_account_public(&mut self, chain_index: ChainIndex) -> AccountId {
self.storage
.user_data
.generate_new_public_transaction_private_key()
.generate_new_public_transaction_private_key(chain_index)
}
pub fn create_new_account_private(&mut self) -> AccountId {
pub fn create_new_account_private(&mut self, chain_index: ChainIndex) -> AccountId {
self.storage
.user_data
.generate_new_privacy_preserving_transaction_key_chain()
.generate_new_privacy_preserving_transaction_key_chain(chain_index)
}
/// Get account balance
@ -141,20 +147,24 @@ impl WalletCore {
Ok(response.account)
}
pub fn get_account_public_signing_key(
&self,
account_id: &AccountId,
) -> Option<&nssa::PrivateKey> {
self.storage
.user_data
.get_pub_account_signing_key(account_id)
}
pub fn get_account_private(&self, account_id: &AccountId) -> Option<Account> {
self.storage
.user_data
.user_private_accounts
.get(account_id)
.get_private_account(account_id)
.map(|value| value.1.clone())
}
pub fn get_private_account_commitment(&self, account_id: &AccountId) -> Option<Commitment> {
let (keys, account) = self
.storage
.user_data
.user_private_accounts
.get(account_id)?;
let (keys, account) = self.storage.user_data.get_private_account(account_id)?;
Some(Commitment::new(&keys.nullifer_public_key, account))
}
@ -208,225 +218,79 @@ impl WalletCore {
Ok(())
}
}
/// Represents CLI command for a wallet
#[derive(Subcommand, Debug, Clone)]
#[clap(about)]
pub enum Command {
/// Authenticated transfer subcommand
#[command(subcommand)]
AuthTransfer(AuthTransferSubcommand),
/// Generic chain info subcommand
#[command(subcommand)]
ChainInfo(ChainSubcommand),
/// Account view and sync subcommand
#[command(subcommand)]
Account(AccountSubcommand),
/// Pinata program interaction subcommand
#[command(subcommand)]
Pinata(PinataProgramAgnosticSubcommand),
/// Token program interaction subcommand
#[command(subcommand)]
Token(TokenProgramAgnosticSubcommand),
/// Check the wallet can connect to the node and builtin local programs
/// match the remote versions
CheckHealth {},
/// Command to setup config, get and set config fields
#[command(subcommand)]
Config(ConfigSubcommand),
}
/// To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config
///
/// All account adresses must be valid 32 byte base58 strings.
///
/// All account account_ids must be provided as {privacy_prefix}/{account_id},
/// where valid options for `privacy_prefix` is `Public` and `Private`
#[derive(Parser, Debug)]
#[clap(version, about)]
pub struct Args {
/// Continious run flag
#[arg(short, long)]
pub continious_run: bool,
/// Wallet command
#[command(subcommand)]
pub command: Option<Command>,
}
#[derive(Debug, Clone)]
pub enum SubcommandReturnValue {
PrivacyPreservingTransfer { tx_hash: String },
RegisterAccount { account_id: nssa::AccountId },
Account(nssa::Account),
Empty,
SyncedToBlock(u64),
}
pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValue> {
let wallet_config = fetch_config().await?;
let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?;
let subcommand_ret = match command {
Command::AuthTransfer(transfer_subcommand) => {
transfer_subcommand
.handle_subcommand(&mut wallet_core)
.await?
}
Command::ChainInfo(chain_subcommand) => {
chain_subcommand.handle_subcommand(&mut wallet_core).await?
}
Command::Account(account_subcommand) => {
account_subcommand
.handle_subcommand(&mut wallet_core)
.await?
}
Command::Pinata(pinata_subcommand) => {
pinata_subcommand
.handle_subcommand(&mut wallet_core)
.await?
}
Command::CheckHealth {} => {
let remote_program_ids = wallet_core
.sequencer_client
.get_program_ids()
.await
.expect("Error fetching program ids");
let Some(authenticated_transfer_id) = remote_program_ids.get("authenticated_transfer")
else {
panic!("Missing authenticated transfer ID from remote");
};
if authenticated_transfer_id != &Program::authenticated_transfer_program().id() {
panic!("Local ID for authenticated transfer program is different from remote");
}
let Some(token_id) = remote_program_ids.get("token") else {
panic!("Missing token program ID from remote");
};
if token_id != &Program::token().id() {
panic!("Local ID for token program is different from remote");
}
let Some(circuit_id) = remote_program_ids.get("privacy_preserving_circuit") else {
panic!("Missing privacy preserving circuit ID from remote");
};
if circuit_id != &nssa::PRIVACY_PRESERVING_CIRCUIT_ID {
panic!("Local ID for privacy preserving circuit is different from remote");
}
println!("✅All looks good!");
SubcommandReturnValue::Empty
}
Command::Token(token_subcommand) => {
token_subcommand.handle_subcommand(&mut wallet_core).await?
}
Command::Config(config_subcommand) => {
config_subcommand
.handle_subcommand(&mut wallet_core)
.await?
}
};
Ok(subcommand_ret)
}
pub async fn parse_block_range(
start: u64,
stop: u64,
seq_client: Arc<SequencerClient>,
wallet_core: &mut WalletCore,
) -> Result<()> {
for block_id in start..(stop + 1) {
let block =
borsh::from_slice::<HashableBlockData>(&seq_client.get_block(block_id).await?.block)?;
for tx in block.transactions {
let nssa_tx = NSSATransaction::try_from(&tx)?;
if let NSSATransaction::PrivacyPreserving(tx) = nssa_tx {
let mut affected_accounts = vec![];
for (acc_account_id, (key_chain, _)) in
&wallet_core.storage.user_data.user_private_accounts
{
let view_tag = EncryptedAccountData::compute_view_tag(
key_chain.nullifer_public_key.clone(),
key_chain.incoming_viewing_public_key.clone(),
);
for (ciph_id, encrypted_data) in tx
.message()
.encrypted_private_post_states
.iter()
.enumerate()
{
if encrypted_data.view_tag == view_tag {
let ciphertext = &encrypted_data.ciphertext;
let commitment = &tx.message.new_commitments[ciph_id];
let shared_secret = key_chain
.calculate_shared_secret_receiver(encrypted_data.epk.clone());
let res_acc = nssa_core::EncryptionScheme::decrypt(
ciphertext,
&shared_secret,
commitment,
ciph_id as u32,
);
if let Some(res_acc) = res_acc {
println!(
"Received new account for account_id {acc_account_id:#?} with account object {res_acc:#?}"
);
affected_accounts.push((*acc_account_id, res_acc));
}
}
}
}
for (affected_account_id, new_acc) in affected_accounts {
wallet_core
.storage
.insert_private_account_data(affected_account_id, new_acc);
}
}
}
wallet_core.last_synced_block = block_id;
wallet_core.store_persistent_data().await?;
println!(
"Block at id {block_id} with timestamp {} parsed",
block.timestamp
);
pub async fn send_privacy_preserving_tx(
&self,
accounts: Vec<PrivacyPreservingAccount>,
instruction_data: &InstructionData,
program: &Program,
) -> Result<(SendTxResponse, Vec<SharedSecretKey>), ExecutionFailureKind> {
self.send_privacy_preserving_tx_with_pre_check(accounts, instruction_data, program, |_| {
Ok(())
})
.await
}
Ok(())
}
pub async fn send_privacy_preserving_tx_with_pre_check(
&self,
accounts: Vec<PrivacyPreservingAccount>,
instruction_data: &InstructionData,
program: &Program,
tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>,
) -> Result<(SendTxResponse, Vec<SharedSecretKey>), ExecutionFailureKind> {
let acc_manager = privacy_preserving_tx::AccountManager::new(self, accounts).await?;
pub async fn execute_continious_run() -> Result<()> {
let config = fetch_config().await?;
let seq_client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?);
let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?;
let pre_states = acc_manager.pre_states();
tx_pre_check(
&pre_states
.iter()
.map(|pre| &pre.account)
.collect::<Vec<_>>(),
)?;
let mut latest_block_num = seq_client.get_last_block().await?.last_block;
let mut curr_last_block = latest_block_num;
loop {
parse_block_range(
curr_last_block,
latest_block_num,
seq_client.clone(),
&mut wallet_core,
let private_account_keys = acc_manager.private_account_keys();
let (output, proof) = nssa::privacy_preserving_transaction::circuit::execute_and_prove(
&pre_states,
instruction_data,
acc_manager.visibility_mask(),
&produce_random_nonces(private_account_keys.len()),
&private_account_keys
.iter()
.map(|keys| (keys.npk.clone(), keys.ssk.clone()))
.collect::<Vec<_>>(),
&acc_manager.private_account_auth(),
program,
)
.await?;
.unwrap();
curr_last_block = latest_block_num + 1;
let message =
nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output(
acc_manager.public_account_ids(),
Vec::from_iter(acc_manager.public_account_nonces()),
private_account_keys
.iter()
.map(|keys| (keys.npk.clone(), keys.ipk.clone(), keys.epk.clone()))
.collect(),
output,
)
.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(
config.seq_poll_timeout_millis,
let witness_set =
nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message(
&message,
proof,
&acc_manager.witness_signing_keys(),
);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
let shared_secrets = private_account_keys
.into_iter()
.map(|keys| keys.ssk)
.collect();
Ok((
self.sequencer_client.send_tx_private(tx).await?,
shared_secrets,
))
.await;
latest_block_num = seq_client.get_last_block().await?.last_block;
}
}

View File

@ -1,14 +1,16 @@
use anyhow::Result;
use clap::{CommandFactory, Parser};
use clap::{CommandFactory as _, Parser as _};
use tokio::runtime::Builder;
use wallet::{Args, execute_continious_run, execute_subcommand};
use wallet::cli::{Args, OverCommand, execute_continuous_run, execute_setup, execute_subcommand};
pub const NUM_THREADS: usize = 2;
// TODO #169: We have sample configs for sequencer, but not for wallet
// TODO #168: Why it requires config as a directory? Maybe better to deduce directory from config
// file path? TODO #172: Why it requires config as env var while sequencer_runner accepts as
// argument? TODO #171: Running pinata doesn't give output about transaction hash and etc.
// file path?
// TODO #172: Why it requires config as env var while sequencer_runner accepts as
// argument?
// TODO #171: Running pinata doesn't give output about transaction hash and etc.
fn main() -> Result<()> {
let runtime = Builder::new_multi_thread()
.worker_threads(NUM_THREADS)
@ -21,16 +23,20 @@ fn main() -> Result<()> {
env_logger::init();
runtime.block_on(async move {
if let Some(command) = args.command {
// TODO: It should return error, not panic
execute_subcommand(command).await.unwrap();
} else if args.continious_run {
execute_continious_run().await.unwrap();
if let Some(over_command) = args.command {
match over_command {
OverCommand::Command(command) => {
let _output = execute_subcommand(command).await?;
Ok(())
}
OverCommand::Setup { password } => execute_setup(password).await,
}
} else if args.continuous_run {
execute_continuous_run().await
} else {
let help = Args::command().render_long_help();
println!("{help}");
Ok(())
}
});
Ok(())
})
}

View File

@ -1,161 +0,0 @@
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
use nssa::{AccountId, privacy_preserving_transaction::circuit};
use nssa_core::{MembershipProof, SharedSecretKey, account::AccountWithMetadata};
use crate::{
WalletCore, helperfunctions::produce_random_nonces, transaction_utils::AccountPreparedData,
};
impl WalletCore {
pub async fn claim_pinata(
&self,
pinata_account_id: AccountId,
winner_account_id: AccountId,
solution: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let account_ids = vec![pinata_account_id, winner_account_id];
let program_id = nssa::program::Program::pinata().id();
let message =
nssa::public_transaction::Message::try_new(program_id, account_ids, vec![], solution)
.unwrap();
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
let tx = nssa::PublicTransaction::new(message, witness_set);
Ok(self.sequencer_client.send_tx_public(tx).await?)
}
pub async fn claim_pinata_private_owned_account_already_initialized(
&self,
pinata_account_id: AccountId,
winner_account_id: AccountId,
solution: u128,
winner_proof: MembershipProof,
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
let AccountPreparedData {
nsk: winner_nsk,
npk: winner_npk,
ipk: winner_ipk,
auth_acc: winner_pre,
proof: _,
} = self
.private_acc_preparation(winner_account_id, true, false)
.await?;
let pinata_acc = self.get_account_public(pinata_account_id).await.unwrap();
let program = nssa::program::Program::pinata();
let pinata_pre = AccountWithMetadata::new(pinata_acc.clone(), false, pinata_account_id);
let eph_holder_winner = EphemeralKeyHolder::new(&winner_npk);
let shared_secret_winner = eph_holder_winner.calculate_shared_secret_sender(&winner_ipk);
let (output, proof) = circuit::execute_and_prove(
&[pinata_pre, winner_pre],
&nssa::program::Program::serialize_instruction(solution).unwrap(),
&[0, 1],
&produce_random_nonces(1),
&[(winner_npk.clone(), shared_secret_winner.clone())],
&[(winner_nsk.unwrap(), winner_proof)],
&program,
)
.unwrap();
let message =
nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output(
vec![pinata_account_id],
vec![],
vec![(
winner_npk.clone(),
winner_ipk.clone(),
eph_holder_winner.generate_ephemeral_public_key(),
)],
output,
)
.unwrap();
let witness_set =
nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message(
&message,
proof,
&[],
);
let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new(
message,
witness_set,
);
Ok((
self.sequencer_client.send_tx_private(tx).await?,
[shared_secret_winner],
))
}
pub async fn claim_pinata_private_owned_account_not_initialized(
&self,
pinata_account_id: AccountId,
winner_account_id: AccountId,
solution: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
let AccountPreparedData {
nsk: _,
npk: winner_npk,
ipk: winner_ipk,
auth_acc: winner_pre,
proof: _,
} = self
.private_acc_preparation(winner_account_id, false, false)
.await?;
let pinata_acc = self.get_account_public(pinata_account_id).await.unwrap();
let program = nssa::program::Program::pinata();
let pinata_pre = AccountWithMetadata::new(pinata_acc.clone(), false, pinata_account_id);
let eph_holder_winner = EphemeralKeyHolder::new(&winner_npk);
let shared_secret_winner = eph_holder_winner.calculate_shared_secret_sender(&winner_ipk);
let (output, proof) = circuit::execute_and_prove(
&[pinata_pre, winner_pre],
&nssa::program::Program::serialize_instruction(solution).unwrap(),
&[0, 2],
&produce_random_nonces(1),
&[(winner_npk.clone(), shared_secret_winner.clone())],
&[],
&program,
)
.unwrap();
let message =
nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output(
vec![pinata_account_id],
vec![],
vec![(
winner_npk.clone(),
winner_ipk.clone(),
eph_holder_winner.generate_ephemeral_public_key(),
)],
output,
)
.unwrap();
let witness_set =
nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message(
&message,
proof,
&[],
);
let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new(
message,
witness_set,
);
Ok((
self.sequencer_client.send_tx_private(tx).await?,
[shared_secret_winner],
))
}
}

View File

@ -0,0 +1,212 @@
use common::error::ExecutionFailureKind;
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
use nssa::{AccountId, PrivateKey};
use nssa_core::{
MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
account::{AccountWithMetadata, Nonce},
encryption::{EphemeralPublicKey, IncomingViewingPublicKey},
};
use crate::WalletCore;
pub enum PrivacyPreservingAccount {
Public(AccountId),
PrivateOwned(AccountId),
PrivateForeign {
npk: NullifierPublicKey,
ipk: IncomingViewingPublicKey,
},
}
pub struct PrivateAccountKeys {
pub npk: NullifierPublicKey,
pub ssk: SharedSecretKey,
pub ipk: IncomingViewingPublicKey,
pub epk: EphemeralPublicKey,
}
enum State {
Public {
account: AccountWithMetadata,
sk: Option<PrivateKey>,
},
Private(AccountPreparedData),
}
pub struct AccountManager {
states: Vec<State>,
visibility_mask: Vec<u8>,
}
impl AccountManager {
pub async fn new(
wallet: &WalletCore,
accounts: Vec<PrivacyPreservingAccount>,
) -> Result<Self, ExecutionFailureKind> {
let mut pre_states = Vec::with_capacity(accounts.len());
let mut visibility_mask = Vec::with_capacity(accounts.len());
for account in accounts {
let (state, mask) = match account {
PrivacyPreservingAccount::Public(account_id) => {
let acc = wallet
.get_account_public(account_id)
.await
.map_err(|_| ExecutionFailureKind::KeyNotFoundError)?;
let sk = wallet.get_account_public_signing_key(&account_id).cloned();
let account = AccountWithMetadata::new(acc.clone(), sk.is_some(), account_id);
(State::Public { account, sk }, 0)
}
PrivacyPreservingAccount::PrivateOwned(account_id) => {
let pre = private_acc_preparation(wallet, account_id).await?;
let mask = if pre.auth_acc.is_authorized { 1 } else { 2 };
(State::Private(pre), mask)
}
PrivacyPreservingAccount::PrivateForeign { npk, ipk } => {
let acc = nssa_core::account::Account::default();
let auth_acc = AccountWithMetadata::new(acc, false, &npk);
let pre = AccountPreparedData {
nsk: None,
npk,
ipk,
auth_acc,
proof: None,
};
(State::Private(pre), 2)
}
};
pre_states.push(state);
visibility_mask.push(mask);
}
Ok(Self {
states: pre_states,
visibility_mask,
})
}
pub fn pre_states(&self) -> Vec<AccountWithMetadata> {
self.states
.iter()
.map(|state| match state {
State::Public { account, .. } => account.clone(),
State::Private(pre) => pre.auth_acc.clone(),
})
.collect()
}
pub fn visibility_mask(&self) -> &[u8] {
&self.visibility_mask
}
pub fn public_account_nonces(&self) -> Vec<Nonce> {
self.states
.iter()
.filter_map(|state| match state {
State::Public { account, sk } => sk.as_ref().map(|_| account.account.nonce),
_ => None,
})
.collect()
}
pub fn private_account_keys(&self) -> Vec<PrivateAccountKeys> {
self.states
.iter()
.filter_map(|state| match state {
State::Private(pre) => {
let eph_holder = EphemeralKeyHolder::new(&pre.npk);
Some(PrivateAccountKeys {
npk: pre.npk.clone(),
ssk: eph_holder.calculate_shared_secret_sender(&pre.ipk),
ipk: pre.ipk.clone(),
epk: eph_holder.generate_ephemeral_public_key(),
})
}
_ => None,
})
.collect()
}
pub fn private_account_auth(&self) -> Vec<(NullifierSecretKey, MembershipProof)> {
self.states
.iter()
.filter_map(|state| match state {
State::Private(pre) => Some((pre.nsk?, pre.proof.clone()?)),
_ => None,
})
.collect()
}
pub fn public_account_ids(&self) -> Vec<AccountId> {
self.states
.iter()
.filter_map(|state| match state {
State::Public { account, .. } => Some(account.account_id),
_ => None,
})
.collect()
}
pub fn witness_signing_keys(&self) -> Vec<&PrivateKey> {
self.states
.iter()
.filter_map(|state| match state {
State::Public { sk, .. } => sk.as_ref(),
_ => None,
})
.collect()
}
}
struct AccountPreparedData {
nsk: Option<NullifierSecretKey>,
npk: NullifierPublicKey,
ipk: IncomingViewingPublicKey,
auth_acc: AccountWithMetadata,
proof: Option<MembershipProof>,
}
async fn private_acc_preparation(
wallet: &WalletCore,
account_id: AccountId,
) -> Result<AccountPreparedData, ExecutionFailureKind> {
let Some((from_keys, from_acc)) = wallet
.storage
.user_data
.get_private_account(&account_id)
.cloned()
else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let mut nsk = Some(from_keys.private_key_holder.nullifier_secret_key);
let from_npk = from_keys.nullifer_public_key;
let from_ipk = from_keys.incoming_viewing_public_key;
// TODO: Remove this unwrap, error types must be compatible
let proof = wallet
.check_private_account_initialized(&account_id)
.await
.unwrap();
if proof.is_none() {
nsk = None;
}
let sender_pre = AccountWithMetadata::new(from_acc.clone(), proof.is_some(), &from_npk);
Ok(AccountPreparedData {
nsk,
npk: from_npk,
ipk: from_ipk,
auth_acc: sender_pre,
proof,
})
}

View File

@ -0,0 +1,6 @@
//! This module contains [`WalletCore`](crate::WalletCore) facades for interacting with various
//! on-chain programs.
pub mod native_token_transfer;
pub mod pinata;
pub mod token;

View File

@ -0,0 +1,35 @@
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
use nssa::AccountId;
use super::{NativeTokenTransfer, auth_transfer_preparation};
use crate::PrivacyPreservingAccount;
impl NativeTokenTransfer<'_> {
pub async fn send_deshielded_transfer(
&self,
from: AccountId,
to: AccountId,
balance_to_move: u128,
) -> Result<(SendTxResponse, nssa_core::SharedSecretKey), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
self.0
.send_privacy_preserving_tx_with_pre_check(
vec![
PrivacyPreservingAccount::PrivateOwned(from),
PrivacyPreservingAccount::Public(to),
],
&instruction_data,
&program,
tx_pre_check,
)
.await
.map(|(resp, secrets)| {
let first = secrets
.into_iter()
.next()
.expect("expected sender's secret");
(resp, first)
})
}
}

View File

@ -0,0 +1,33 @@
use common::error::ExecutionFailureKind;
use nssa::{Account, program::Program};
use nssa_core::program::InstructionData;
use crate::WalletCore;
pub mod deshielded;
pub mod private;
pub mod public;
pub mod shielded;
pub struct NativeTokenTransfer<'w>(pub &'w WalletCore);
fn auth_transfer_preparation(
balance_to_move: u128,
) -> (
InstructionData,
Program,
impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>,
) {
let instruction_data = Program::serialize_instruction(balance_to_move).unwrap();
let program = Program::authenticated_transfer_program();
let tx_pre_check = move |accounts: &[&Account]| {
let from = accounts[0];
if from.balance >= balance_to_move {
Ok(())
} else {
Err(ExecutionFailureKind::InsufficientFundsError)
}
};
(instruction_data, program, tx_pre_check)
}

View File

@ -0,0 +1,89 @@
use std::vec;
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
use nssa::{AccountId, program::Program};
use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey};
use super::{NativeTokenTransfer, auth_transfer_preparation};
use crate::PrivacyPreservingAccount;
impl NativeTokenTransfer<'_> {
pub async fn register_account_private(
&self,
from: AccountId,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let instruction: u128 = 0;
self.0
.send_privacy_preserving_tx_with_pre_check(
vec![PrivacyPreservingAccount::PrivateOwned(from)],
&Program::serialize_instruction(instruction).unwrap(),
&Program::authenticated_transfer_program(),
|_| Ok(()),
)
.await
.map(|(resp, secrets)| {
let mut secrets_iter = secrets.into_iter();
let first = secrets_iter.next().expect("expected sender's secret");
(resp, first)
})
}
pub async fn send_private_transfer_to_outer_account(
&self,
from: AccountId,
to_npk: NullifierPublicKey,
to_ipk: IncomingViewingPublicKey,
balance_to_move: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
self.0
.send_privacy_preserving_tx_with_pre_check(
vec![
PrivacyPreservingAccount::PrivateOwned(from),
PrivacyPreservingAccount::PrivateForeign {
npk: to_npk,
ipk: to_ipk,
},
],
&instruction_data,
&program,
tx_pre_check,
)
.await
.map(|(resp, secrets)| {
let mut secrets_iter = secrets.into_iter();
let first = secrets_iter.next().expect("expected sender's secret");
let second = secrets_iter.next().expect("expected receiver's secret");
(resp, [first, second])
})
}
pub async fn send_private_transfer_to_owned_account(
&self,
from: AccountId,
to: AccountId,
balance_to_move: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
self.0
.send_privacy_preserving_tx_with_pre_check(
vec![
PrivacyPreservingAccount::PrivateOwned(from),
PrivacyPreservingAccount::PrivateOwned(to),
],
&instruction_data,
&program,
tx_pre_check,
)
.await
.map(|(resp, secrets)| {
let mut secrets_iter = secrets.into_iter();
let first = secrets_iter.next().expect("expected sender's secret");
let second = secrets_iter.next().expect("expected receiver's secret");
(resp, [first, second])
})
}
}

View File

@ -5,21 +5,21 @@ use nssa::{
public_transaction::{Message, WitnessSet},
};
use crate::WalletCore;
use super::NativeTokenTransfer;
impl WalletCore {
pub async fn send_public_native_token_transfer(
impl NativeTokenTransfer<'_> {
pub async fn send_public_transfer(
&self,
from: AccountId,
to: AccountId,
balance_to_move: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let Ok(balance) = self.get_account_balance(from).await else {
let Ok(balance) = self.0.get_account_balance(from).await else {
return Err(ExecutionFailureKind::SequencerError);
};
if balance >= balance_to_move {
let Ok(nonces) = self.get_accounts_nonces(vec![from]).await else {
let Ok(nonces) = self.0.get_accounts_nonces(vec![from]).await else {
return Err(ExecutionFailureKind::SequencerError);
};
@ -28,7 +28,7 @@ impl WalletCore {
let message =
Message::try_new(program_id, account_ids, nonces, balance_to_move).unwrap();
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
let signing_key = self.0.storage.user_data.get_pub_account_signing_key(&from);
let Some(signing_key) = signing_key else {
return Err(ExecutionFailureKind::KeyNotFoundError);
@ -38,17 +38,17 @@ impl WalletCore {
let tx = PublicTransaction::new(message, witness_set);
Ok(self.sequencer_client.send_tx_public(tx).await?)
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
} else {
Err(ExecutionFailureKind::InsufficientFundsError)
}
}
pub async fn register_account_under_authenticated_transfers_programs(
pub async fn register_account(
&self,
from: AccountId,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let Ok(nonces) = self.get_accounts_nonces(vec![from]).await else {
let Ok(nonces) = self.0.get_accounts_nonces(vec![from]).await else {
return Err(ExecutionFailureKind::SequencerError);
};
@ -57,7 +57,7 @@ impl WalletCore {
let program_id = Program::authenticated_transfer_program().id();
let message = Message::try_new(program_id, account_ids, nonces, instruction).unwrap();
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
let signing_key = self.0.storage.user_data.get_pub_account_signing_key(&from);
let Some(signing_key) = signing_key else {
return Err(ExecutionFailureKind::KeyNotFoundError);
@ -67,6 +67,6 @@ impl WalletCore {
let tx = PublicTransaction::new(message, witness_set);
Ok(self.sequencer_client.send_tx_public(tx).await?)
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
}
}

View File

@ -0,0 +1,68 @@
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
use nssa::AccountId;
use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey};
use super::{NativeTokenTransfer, auth_transfer_preparation};
use crate::PrivacyPreservingAccount;
impl NativeTokenTransfer<'_> {
pub async fn send_shielded_transfer(
&self,
from: AccountId,
to: AccountId,
balance_to_move: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
self.0
.send_privacy_preserving_tx_with_pre_check(
vec![
PrivacyPreservingAccount::Public(from),
PrivacyPreservingAccount::PrivateOwned(to),
],
&instruction_data,
&program,
tx_pre_check,
)
.await
.map(|(resp, secrets)| {
let first = secrets
.into_iter()
.next()
.expect("expected sender's secret");
(resp, first)
})
}
pub async fn send_shielded_transfer_to_outer_account(
&self,
from: AccountId,
to_npk: NullifierPublicKey,
to_ipk: IncomingViewingPublicKey,
balance_to_move: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
self.0
.send_privacy_preserving_tx_with_pre_check(
vec![
PrivacyPreservingAccount::Public(from),
PrivacyPreservingAccount::PrivateForeign {
npk: to_npk,
ipk: to_ipk,
},
],
&instruction_data,
&program,
tx_pre_check,
)
.await
.map(|(resp, secrets)| {
let first = secrets
.into_iter()
.next()
.expect("expected sender's secret");
(resp, first)
})
}
}

View File

@ -0,0 +1,52 @@
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
use nssa::AccountId;
use nssa_core::SharedSecretKey;
use crate::{PrivacyPreservingAccount, WalletCore};
pub struct Pinata<'w>(pub &'w WalletCore);
impl Pinata<'_> {
pub async fn claim(
&self,
pinata_account_id: AccountId,
winner_account_id: AccountId,
solution: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let account_ids = vec![pinata_account_id, winner_account_id];
let program_id = nssa::program::Program::pinata().id();
let message =
nssa::public_transaction::Message::try_new(program_id, account_ids, vec![], solution)
.unwrap();
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
let tx = nssa::PublicTransaction::new(message, witness_set);
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
}
pub async fn claim_private_owned_account(
&self,
pinata_account_id: AccountId,
winner_account_id: AccountId,
solution: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::Public(pinata_account_id),
PrivacyPreservingAccount::PrivateOwned(winner_account_id),
],
&nssa::program::Program::serialize_instruction(solution).unwrap(),
&nssa::program::Program::pinata(),
)
.await
.map(|(resp, secrets)| {
let first = secrets
.into_iter()
.next()
.expect("expected recipient's secret");
(resp, first)
})
}
}

View File

@ -0,0 +1,275 @@
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
use nssa::{AccountId, program::Program};
use nssa_core::{
NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey,
program::InstructionData,
};
use crate::{PrivacyPreservingAccount, WalletCore};
pub struct Token<'w>(pub &'w WalletCore);
impl Token<'_> {
pub async fn send_new_definition(
&self,
definition_account_id: AccountId,
supply_account_id: AccountId,
name: [u8; 6],
total_supply: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let account_ids = vec![definition_account_id, supply_account_id];
let program_id = nssa::program::Program::token().id();
// Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
let mut instruction = [0; 23];
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
instruction[17..].copy_from_slice(&name);
let message = nssa::public_transaction::Message::try_new(
program_id,
account_ids,
vec![],
instruction,
)
.unwrap();
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
let tx = nssa::PublicTransaction::new(message, witness_set);
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
}
pub async fn send_new_definition_private_owned(
&self,
definition_account_id: AccountId,
supply_account_id: AccountId,
name: [u8; 6],
total_supply: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_definition(name, total_supply);
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::Public(definition_account_id),
PrivacyPreservingAccount::PrivateOwned(supply_account_id),
],
&instruction_data,
&program,
)
.await
.map(|(resp, secrets)| {
let first = secrets
.into_iter()
.next()
.expect("expected recipient's secret");
(resp, first)
})
}
pub async fn send_transfer_transaction(
&self,
sender_account_id: AccountId,
recipient_account_id: AccountId,
amount: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let account_ids = vec![sender_account_id, recipient_account_id];
let program_id = nssa::program::Program::token().id();
// Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
// 0x00 || 0x00 || 0x00].
let mut instruction = [0; 23];
instruction[0] = 0x01;
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
let Ok(nonces) = self.0.get_accounts_nonces(vec![sender_account_id]).await else {
return Err(ExecutionFailureKind::SequencerError);
};
let message = nssa::public_transaction::Message::try_new(
program_id,
account_ids,
nonces,
instruction,
)
.unwrap();
let Some(signing_key) = self
.0
.storage
.user_data
.get_pub_account_signing_key(&sender_account_id)
else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let witness_set =
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
let tx = nssa::PublicTransaction::new(message, witness_set);
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
}
pub async fn send_transfer_transaction_private_owned_account(
&self,
sender_account_id: AccountId,
recipient_account_id: AccountId,
amount: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_transfer(amount);
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(sender_account_id),
PrivacyPreservingAccount::PrivateOwned(recipient_account_id),
],
&instruction_data,
&program,
)
.await
.map(|(resp, secrets)| {
let mut iter = secrets.into_iter();
let first = iter.next().expect("expected sender's secret");
let second = iter.next().expect("expected recipient's secret");
(resp, [first, second])
})
}
pub async fn send_transfer_transaction_private_foreign_account(
&self,
sender_account_id: AccountId,
recipient_npk: NullifierPublicKey,
recipient_ipk: IncomingViewingPublicKey,
amount: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_transfer(amount);
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(sender_account_id),
PrivacyPreservingAccount::PrivateForeign {
npk: recipient_npk,
ipk: recipient_ipk,
},
],
&instruction_data,
&program,
)
.await
.map(|(resp, secrets)| {
let mut iter = secrets.into_iter();
let first = iter.next().expect("expected sender's secret");
let second = iter.next().expect("expected recipient's secret");
(resp, [first, second])
})
}
pub async fn send_transfer_transaction_deshielded(
&self,
sender_account_id: AccountId,
recipient_account_id: AccountId,
amount: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_transfer(amount);
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(sender_account_id),
PrivacyPreservingAccount::Public(recipient_account_id),
],
&instruction_data,
&program,
)
.await
.map(|(resp, secrets)| {
let first = secrets
.into_iter()
.next()
.expect("expected sender's secret");
(resp, first)
})
}
pub async fn send_transfer_transaction_shielded_owned_account(
&self,
sender_account_id: AccountId,
recipient_account_id: AccountId,
amount: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_transfer(amount);
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::Public(sender_account_id),
PrivacyPreservingAccount::PrivateOwned(recipient_account_id),
],
&instruction_data,
&program,
)
.await
.map(|(resp, secrets)| {
let first = secrets
.into_iter()
.next()
.expect("expected recipient's secret");
(resp, first)
})
}
pub async fn send_transfer_transaction_shielded_foreign_account(
&self,
sender_account_id: AccountId,
recipient_npk: NullifierPublicKey,
recipient_ipk: IncomingViewingPublicKey,
amount: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_transfer(amount);
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::Public(sender_account_id),
PrivacyPreservingAccount::PrivateForeign {
npk: recipient_npk,
ipk: recipient_ipk,
},
],
&instruction_data,
&program,
)
.await
.map(|(resp, secrets)| {
let first = secrets
.into_iter()
.next()
.expect("expected recipient's secret");
(resp, first)
})
}
}
fn token_program_preparation_transfer(amount: u128) -> (InstructionData, Program) {
// Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
// 0x00 || 0x00 || 0x00].
let mut instruction = [0; 23];
instruction[0] = 0x01;
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
let instruction_data = Program::serialize_instruction(instruction).unwrap();
let program = Program::token();
(instruction_data, program)
}
fn token_program_preparation_definition(
name: [u8; 6],
total_supply: u128,
) -> (InstructionData, Program) {
// Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
let mut instruction = [0; 23];
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
instruction[17..].copy_from_slice(&name);
let instruction_data = Program::serialize_instruction(instruction).unwrap();
let program = Program::token();
(instruction_data, program)
}

View File

@ -1,278 +0,0 @@
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
use nssa::{Account, AccountId, program::Program};
use nssa_core::{
MembershipProof, NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey,
program::InstructionData,
};
use crate::WalletCore;
impl WalletCore {
pub fn token_program_preparation_transfer(
amount: u128,
) -> (
InstructionData,
Program,
impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
) {
// Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
// 0x00 || 0x00 || 0x00].
let mut instruction = [0; 23];
instruction[0] = 0x01;
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
let instruction_data = Program::serialize_instruction(instruction).unwrap();
let program = Program::token();
let tx_pre_check = |_: &Account, _: &Account| Ok(());
(instruction_data, program, tx_pre_check)
}
pub fn token_program_preparation_definition(
name: [u8; 6],
total_supply: u128,
) -> (
InstructionData,
Program,
impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
) {
// Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
let mut instruction = [0; 23];
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
instruction[17..].copy_from_slice(&name);
let instruction_data = Program::serialize_instruction(instruction).unwrap();
let program = Program::token();
let tx_pre_check = |_: &Account, _: &Account| Ok(());
(instruction_data, program, tx_pre_check)
}
pub async fn send_new_token_definition(
&self,
definition_account_id: AccountId,
supply_account_id: AccountId,
name: [u8; 6],
total_supply: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let account_ids = vec![definition_account_id, supply_account_id];
let program_id = nssa::program::Program::token().id();
// Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
let mut instruction = [0; 23];
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
instruction[17..].copy_from_slice(&name);
let message = nssa::public_transaction::Message::try_new(
program_id,
account_ids,
vec![],
instruction,
)
.unwrap();
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
let tx = nssa::PublicTransaction::new(message, witness_set);
Ok(self.sequencer_client.send_tx_public(tx).await?)
}
pub async fn send_new_token_definition_private_owned(
&self,
definition_account_id: AccountId,
supply_account_id: AccountId,
name: [u8; 6],
total_supply: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) =
WalletCore::token_program_preparation_definition(name, total_supply);
// Kind of non-obvious naming
// Basically this funtion is called because authentication mask is [0, 2]
self.shielded_two_accs_receiver_uninit(
definition_account_id,
supply_account_id,
instruction_data,
tx_pre_check,
program,
)
.await
}
pub async fn send_transfer_token_transaction(
&self,
sender_account_id: AccountId,
recipient_account_id: AccountId,
amount: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let account_ids = vec![sender_account_id, recipient_account_id];
let program_id = nssa::program::Program::token().id();
// Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
// 0x00 || 0x00 || 0x00].
let mut instruction = [0; 23];
instruction[0] = 0x01;
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
let Ok(nonces) = self.get_accounts_nonces(vec![sender_account_id]).await else {
return Err(ExecutionFailureKind::SequencerError);
};
let message = nssa::public_transaction::Message::try_new(
program_id,
account_ids,
nonces,
instruction,
)
.unwrap();
let Some(signing_key) = self
.storage
.user_data
.get_pub_account_signing_key(&sender_account_id)
else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let witness_set =
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
let tx = nssa::PublicTransaction::new(message, witness_set);
Ok(self.sequencer_client.send_tx_public(tx).await?)
}
pub async fn send_transfer_token_transaction_private_owned_account_already_initialized(
&self,
sender_account_id: AccountId,
recipient_account_id: AccountId,
amount: u128,
recipient_proof: MembershipProof,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) =
WalletCore::token_program_preparation_transfer(amount);
self.private_tx_two_accs_all_init(
sender_account_id,
recipient_account_id,
instruction_data,
tx_pre_check,
program,
recipient_proof,
)
.await
}
pub async fn send_transfer_token_transaction_private_owned_account_not_initialized(
&self,
sender_account_id: AccountId,
recipient_account_id: AccountId,
amount: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) =
WalletCore::token_program_preparation_transfer(amount);
self.private_tx_two_accs_receiver_uninit(
sender_account_id,
recipient_account_id,
instruction_data,
tx_pre_check,
program,
)
.await
}
pub async fn send_transfer_token_transaction_private_foreign_account(
&self,
sender_account_id: AccountId,
recipient_npk: NullifierPublicKey,
recipient_ipk: IncomingViewingPublicKey,
amount: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) =
WalletCore::token_program_preparation_transfer(amount);
self.private_tx_two_accs_receiver_outer(
sender_account_id,
recipient_npk,
recipient_ipk,
instruction_data,
tx_pre_check,
program,
)
.await
}
pub async fn send_transfer_token_transaction_deshielded(
&self,
sender_account_id: AccountId,
recipient_account_id: AccountId,
amount: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) =
WalletCore::token_program_preparation_transfer(amount);
self.deshielded_tx_two_accs(
sender_account_id,
recipient_account_id,
instruction_data,
tx_pre_check,
program,
)
.await
}
pub async fn send_transfer_token_transaction_shielded_owned_account_already_initialized(
&self,
sender_account_id: AccountId,
recipient_account_id: AccountId,
amount: u128,
recipient_proof: MembershipProof,
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) =
WalletCore::token_program_preparation_transfer(amount);
self.shielded_two_accs_all_init(
sender_account_id,
recipient_account_id,
instruction_data,
tx_pre_check,
program,
recipient_proof,
)
.await
}
pub async fn send_transfer_token_transaction_shielded_owned_account_not_initialized(
&self,
sender_account_id: AccountId,
recipient_account_id: AccountId,
amount: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) =
WalletCore::token_program_preparation_transfer(amount);
self.shielded_two_accs_receiver_uninit(
sender_account_id,
recipient_account_id,
instruction_data,
tx_pre_check,
program,
)
.await
}
pub async fn send_transfer_token_transaction_shielded_foreign_account(
&self,
sender_account_id: AccountId,
recipient_npk: NullifierPublicKey,
recipient_ipk: IncomingViewingPublicKey,
amount: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) =
WalletCore::token_program_preparation_transfer(amount);
self.shielded_two_accs_receiver_outer(
sender_account_id,
recipient_npk,
recipient_ipk,
instruction_data,
tx_pre_check,
program,
)
.await
}
}

View File

@ -1,19 +0,0 @@
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
use nssa::AccountId;
use crate::WalletCore;
impl WalletCore {
pub async fn send_deshielded_native_token_transfer(
&self,
from: AccountId,
to: AccountId,
balance_to_move: u128,
) -> Result<(SendTxResponse, [nssa_core::SharedSecretKey; 1]), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) =
WalletCore::auth_transfer_preparation(balance_to_move);
self.deshielded_tx_two_accs(from, to, instruction_data, tx_pre_check, program)
.await
}
}

View File

@ -1,32 +0,0 @@
use common::error::ExecutionFailureKind;
use nssa::{Account, program::Program};
use nssa_core::program::InstructionData;
use crate::WalletCore;
pub mod deshielded;
pub mod private;
pub mod public;
pub mod shielded;
impl WalletCore {
pub fn auth_transfer_preparation(
balance_to_move: u128,
) -> (
InstructionData,
Program,
impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
) {
let instruction_data = Program::serialize_instruction(balance_to_move).unwrap();
let program = Program::authenticated_transfer_program();
let tx_pre_check = move |from: &Account, _: &Account| {
if from.balance >= balance_to_move {
Ok(())
} else {
Err(ExecutionFailureKind::InsufficientFundsError)
}
};
(instruction_data, program, tx_pre_check)
}
}

View File

@ -1,64 +0,0 @@
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
use nssa::AccountId;
use nssa_core::{
MembershipProof, NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey,
};
use crate::WalletCore;
impl WalletCore {
pub async fn send_private_native_token_transfer_outer_account(
&self,
from: AccountId,
to_npk: NullifierPublicKey,
to_ipk: IncomingViewingPublicKey,
balance_to_move: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) =
WalletCore::auth_transfer_preparation(balance_to_move);
self.private_tx_two_accs_receiver_outer(
from,
to_npk,
to_ipk,
instruction_data,
tx_pre_check,
program,
)
.await
}
pub async fn send_private_native_token_transfer_owned_account_not_initialized(
&self,
from: AccountId,
to: AccountId,
balance_to_move: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) =
WalletCore::auth_transfer_preparation(balance_to_move);
self.private_tx_two_accs_receiver_uninit(from, to, instruction_data, tx_pre_check, program)
.await
}
pub async fn send_private_native_token_transfer_owned_account_already_initialized(
&self,
from: AccountId,
to: AccountId,
balance_to_move: u128,
to_proof: MembershipProof,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) =
WalletCore::auth_transfer_preparation(balance_to_move);
self.private_tx_two_accs_all_init(
from,
to,
instruction_data,
tx_pre_check,
program,
to_proof,
)
.await
}
}

View File

@ -1,57 +0,0 @@
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
use nssa::AccountId;
use nssa_core::{
MembershipProof, NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey,
};
use crate::WalletCore;
impl WalletCore {
pub async fn send_shielded_native_token_transfer_already_initialized(
&self,
from: AccountId,
to: AccountId,
balance_to_move: u128,
to_proof: MembershipProof,
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) =
WalletCore::auth_transfer_preparation(balance_to_move);
self.shielded_two_accs_all_init(from, to, instruction_data, tx_pre_check, program, to_proof)
.await
}
pub async fn send_shielded_native_token_transfer_not_initialized(
&self,
from: AccountId,
to: AccountId,
balance_to_move: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) =
WalletCore::auth_transfer_preparation(balance_to_move);
self.shielded_two_accs_receiver_uninit(from, to, instruction_data, tx_pre_check, program)
.await
}
pub async fn send_shielded_native_token_transfer_outer_account(
&self,
from: AccountId,
to_npk: NullifierPublicKey,
to_ipk: IncomingViewingPublicKey,
balance_to_move: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) =
WalletCore::auth_transfer_preparation(balance_to_move);
self.shielded_two_accs_receiver_outer(
from,
to_npk,
to_ipk,
instruction_data,
tx_pre_check,
program,
)
.await
}
}

View File

@ -1,592 +0,0 @@
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
use nssa::{
Account, AccountId, PrivacyPreservingTransaction,
privacy_preserving_transaction::{circuit, message::Message, witness_set::WitnessSet},
program::Program,
};
use nssa_core::{
Commitment, MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
account::AccountWithMetadata, encryption::IncomingViewingPublicKey, program::InstructionData,
};
use crate::{WalletCore, helperfunctions::produce_random_nonces};
pub(crate) struct AccountPreparedData {
pub nsk: Option<NullifierSecretKey>,
pub npk: NullifierPublicKey,
pub ipk: IncomingViewingPublicKey,
pub auth_acc: AccountWithMetadata,
pub proof: Option<MembershipProof>,
}
impl WalletCore {
pub(crate) async fn private_acc_preparation(
&self,
account_id: AccountId,
is_authorized: bool,
needs_proof: bool,
) -> Result<AccountPreparedData, ExecutionFailureKind> {
let Some((from_keys, from_acc)) = self
.storage
.user_data
.get_private_account(&account_id)
.cloned()
else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let mut nsk = None;
let mut proof = None;
let from_npk = from_keys.nullifer_public_key;
let from_ipk = from_keys.incoming_viewing_public_key;
let sender_commitment = Commitment::new(&from_npk, &from_acc);
let sender_pre = AccountWithMetadata::new(from_acc.clone(), is_authorized, &from_npk);
if is_authorized {
nsk = Some(from_keys.private_key_holder.nullifier_secret_key);
}
if needs_proof {
proof = self
.sequencer_client
.get_proof_for_commitment(sender_commitment)
.await
.unwrap();
}
Ok(AccountPreparedData {
nsk,
npk: from_npk,
ipk: from_ipk,
auth_acc: sender_pre,
proof,
})
}
pub(crate) async fn private_tx_two_accs_all_init(
&self,
from: AccountId,
to: AccountId,
instruction_data: InstructionData,
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
program: Program,
to_proof: MembershipProof,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let AccountPreparedData {
nsk: from_nsk,
npk: from_npk,
ipk: from_ipk,
auth_acc: sender_pre,
proof: from_proof,
} = self.private_acc_preparation(from, true, true).await?;
let AccountPreparedData {
nsk: to_nsk,
npk: to_npk,
ipk: to_ipk,
auth_acc: recipient_pre,
proof: _,
} = self.private_acc_preparation(to, true, false).await?;
tx_pre_check(&sender_pre.account, &recipient_pre.account)?;
let eph_holder_from = EphemeralKeyHolder::new(&from_npk);
let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk);
let eph_holder_to = EphemeralKeyHolder::new(&to_npk);
let shared_secret_to = eph_holder_to.calculate_shared_secret_sender(&to_ipk);
let (output, proof) = circuit::execute_and_prove(
&[sender_pre, recipient_pre],
&instruction_data,
&[1, 1],
&produce_random_nonces(2),
&[
(from_npk.clone(), shared_secret_from.clone()),
(to_npk.clone(), shared_secret_to.clone()),
],
&[
(from_nsk.unwrap(), from_proof.unwrap()),
(to_nsk.unwrap(), to_proof),
],
&program,
)
.unwrap();
let message = Message::try_from_circuit_output(
vec![],
vec![],
vec![
(
from_npk.clone(),
from_ipk.clone(),
eph_holder_from.generate_ephemeral_public_key(),
),
(
to_npk.clone(),
to_ipk.clone(),
eph_holder_to.generate_ephemeral_public_key(),
),
],
output,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, proof, &[]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok((
self.sequencer_client.send_tx_private(tx).await?,
[shared_secret_from, shared_secret_to],
))
}
pub(crate) async fn private_tx_two_accs_receiver_uninit(
&self,
from: AccountId,
to: AccountId,
instruction_data: InstructionData,
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
program: Program,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let AccountPreparedData {
nsk: from_nsk,
npk: from_npk,
ipk: from_ipk,
auth_acc: sender_pre,
proof: from_proof,
} = self.private_acc_preparation(from, true, true).await?;
let AccountPreparedData {
nsk: _,
npk: to_npk,
ipk: to_ipk,
auth_acc: recipient_pre,
proof: _,
} = self.private_acc_preparation(to, false, false).await?;
tx_pre_check(&sender_pre.account, &recipient_pre.account)?;
let eph_holder_from = EphemeralKeyHolder::new(&from_npk);
let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk);
let eph_holder_to = EphemeralKeyHolder::new(&to_npk);
let shared_secret_to = eph_holder_to.calculate_shared_secret_sender(&to_ipk);
let (output, proof) = circuit::execute_and_prove(
&[sender_pre, recipient_pre],
&instruction_data,
&[1, 2],
&produce_random_nonces(2),
&[
(from_npk.clone(), shared_secret_from.clone()),
(to_npk.clone(), shared_secret_to.clone()),
],
&[(from_nsk.unwrap(), from_proof.unwrap())],
&program,
)
.unwrap();
let message = Message::try_from_circuit_output(
vec![],
vec![],
vec![
(
from_npk.clone(),
from_ipk.clone(),
eph_holder_from.generate_ephemeral_public_key(),
),
(
to_npk.clone(),
to_ipk.clone(),
eph_holder_to.generate_ephemeral_public_key(),
),
],
output,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, proof, &[]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok((
self.sequencer_client.send_tx_private(tx).await?,
[shared_secret_from, shared_secret_to],
))
}
pub(crate) async fn private_tx_two_accs_receiver_outer(
&self,
from: AccountId,
to_npk: NullifierPublicKey,
to_ipk: IncomingViewingPublicKey,
instruction_data: InstructionData,
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
program: Program,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let AccountPreparedData {
nsk: from_nsk,
npk: from_npk,
ipk: from_ipk,
auth_acc: sender_pre,
proof: from_proof,
} = self.private_acc_preparation(from, true, true).await?;
let to_acc = nssa_core::account::Account::default();
tx_pre_check(&sender_pre.account, &to_acc)?;
let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk);
let eph_holder = EphemeralKeyHolder::new(&to_npk);
let shared_secret_from = eph_holder.calculate_shared_secret_sender(&from_ipk);
let shared_secret_to = eph_holder.calculate_shared_secret_sender(&to_ipk);
let (output, proof) = circuit::execute_and_prove(
&[sender_pre, recipient_pre],
&instruction_data,
&[1, 2],
&produce_random_nonces(2),
&[
(from_npk.clone(), shared_secret_from.clone()),
(to_npk.clone(), shared_secret_to.clone()),
],
&[(from_nsk.unwrap(), from_proof.unwrap())],
&program,
)
.unwrap();
let message = Message::try_from_circuit_output(
vec![],
vec![],
vec![
(
from_npk.clone(),
from_ipk.clone(),
eph_holder.generate_ephemeral_public_key(),
),
(
to_npk.clone(),
to_ipk.clone(),
eph_holder.generate_ephemeral_public_key(),
),
],
output,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, proof, &[]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok((
self.sequencer_client.send_tx_private(tx).await?,
[shared_secret_from, shared_secret_to],
))
}
pub(crate) async fn deshielded_tx_two_accs(
&self,
from: AccountId,
to: AccountId,
instruction_data: InstructionData,
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
program: Program,
) -> Result<(SendTxResponse, [nssa_core::SharedSecretKey; 1]), ExecutionFailureKind> {
let AccountPreparedData {
nsk: from_nsk,
npk: from_npk,
ipk: from_ipk,
auth_acc: sender_pre,
proof: from_proof,
} = self.private_acc_preparation(from, true, true).await?;
let Ok(to_acc) = self.get_account_public(to).await else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
tx_pre_check(&sender_pre.account, &to_acc)?;
let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, to);
let eph_holder = EphemeralKeyHolder::new(&from_npk);
let shared_secret = eph_holder.calculate_shared_secret_sender(&from_ipk);
let (output, proof) = circuit::execute_and_prove(
&[sender_pre, recipient_pre],
&instruction_data,
&[1, 0],
&produce_random_nonces(1),
&[(from_npk.clone(), shared_secret.clone())],
&[(from_nsk.unwrap(), from_proof.unwrap())],
&program,
)
.unwrap();
let message = Message::try_from_circuit_output(
vec![to],
vec![],
vec![(
from_npk.clone(),
from_ipk.clone(),
eph_holder.generate_ephemeral_public_key(),
)],
output,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, proof, &[]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok((
self.sequencer_client.send_tx_private(tx).await?,
[shared_secret],
))
}
pub(crate) async fn shielded_two_accs_all_init(
&self,
from: AccountId,
to: AccountId,
instruction_data: InstructionData,
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
program: Program,
to_proof: MembershipProof,
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
let Ok(from_acc) = self.get_account_public(from).await else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let AccountPreparedData {
nsk: to_nsk,
npk: to_npk,
ipk: to_ipk,
auth_acc: recipient_pre,
proof: _,
} = self.private_acc_preparation(to, true, false).await?;
tx_pre_check(&from_acc, &recipient_pre.account)?;
let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from);
let eph_holder = EphemeralKeyHolder::new(&to_npk);
let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk);
let (output, proof) = circuit::execute_and_prove(
&[sender_pre, recipient_pre],
&instruction_data,
&[0, 1],
&produce_random_nonces(1),
&[(to_npk.clone(), shared_secret.clone())],
&[(to_nsk.unwrap(), to_proof)],
&program,
)
.unwrap();
let message = Message::try_from_circuit_output(
vec![from],
vec![from_acc.nonce],
vec![(
to_npk.clone(),
to_ipk.clone(),
eph_holder.generate_ephemeral_public_key(),
)],
output,
)
.unwrap();
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
let Some(signing_key) = signing_key else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok((
self.sequencer_client.send_tx_private(tx).await?,
[shared_secret],
))
}
pub(crate) async fn shielded_two_accs_receiver_uninit(
&self,
from: AccountId,
to: AccountId,
instruction_data: InstructionData,
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
program: Program,
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
let Ok(from_acc) = self.get_account_public(from).await else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let AccountPreparedData {
nsk: _,
npk: to_npk,
ipk: to_ipk,
auth_acc: recipient_pre,
proof: _,
} = self.private_acc_preparation(to, false, false).await?;
tx_pre_check(&from_acc, &recipient_pre.account)?;
let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from);
let eph_holder = EphemeralKeyHolder::new(&to_npk);
let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk);
let (output, proof) = circuit::execute_and_prove(
&[sender_pre, recipient_pre],
&instruction_data,
&[0, 2],
&produce_random_nonces(1),
&[(to_npk.clone(), shared_secret.clone())],
&[],
&program,
)
.unwrap();
let message = Message::try_from_circuit_output(
vec![from],
vec![from_acc.nonce],
vec![(
to_npk.clone(),
to_ipk.clone(),
eph_holder.generate_ephemeral_public_key(),
)],
output,
)
.unwrap();
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
let Some(signing_key) = signing_key else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok((
self.sequencer_client.send_tx_private(tx).await?,
[shared_secret],
))
}
pub(crate) async fn shielded_two_accs_receiver_outer(
&self,
from: AccountId,
to_npk: NullifierPublicKey,
to_ipk: IncomingViewingPublicKey,
instruction_data: InstructionData,
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
program: Program,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let Ok(from_acc) = self.get_account_public(from).await else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let to_acc = Account::default();
tx_pre_check(&from_acc, &to_acc)?;
let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from);
let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk);
let eph_holder = EphemeralKeyHolder::new(&to_npk);
let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk);
let (output, proof) = circuit::execute_and_prove(
&[sender_pre, recipient_pre],
&instruction_data,
&[0, 2],
&produce_random_nonces(1),
&[(to_npk.clone(), shared_secret.clone())],
&[],
&program,
)
.unwrap();
let message = Message::try_from_circuit_output(
vec![from],
vec![from_acc.nonce],
vec![(
to_npk.clone(),
to_ipk.clone(),
eph_holder.generate_ephemeral_public_key(),
)],
output,
)
.unwrap();
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
let Some(signing_key) = signing_key else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok(self.sequencer_client.send_tx_private(tx).await?)
}
pub async fn register_account_under_authenticated_transfers_programs_private(
&self,
from: AccountId,
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
let AccountPreparedData {
nsk: _,
npk: from_npk,
ipk: from_ipk,
auth_acc: sender_pre,
proof: _,
} = self.private_acc_preparation(from, false, false).await?;
let eph_holder_from = EphemeralKeyHolder::new(&from_npk);
let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk);
let instruction: u128 = 0;
let (output, proof) = circuit::execute_and_prove(
&[sender_pre],
&Program::serialize_instruction(instruction).unwrap(),
&[2],
&produce_random_nonces(1),
&[(from_npk.clone(), shared_secret_from.clone())],
&[],
&Program::authenticated_transfer_program(),
)
.unwrap();
let message = Message::try_from_circuit_output(
vec![],
vec![],
vec![(
from_npk.clone(),
from_ipk.clone(),
eph_holder_from.generate_ephemeral_public_key(),
)],
output,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, proof, &[]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok((
self.sequencer_client.send_tx_private(tx).await?,
[shared_secret_from],
))
}
}