fix; privacy preserving tx gen 1

This commit is contained in:
Oleksandr Pravdyvyi 2025-09-11 18:32:46 +03:00
parent 65dfbd8bbb
commit 854d96af72
No known key found for this signature in database
GPG Key ID: 9F8955C63C443871
11 changed files with 279 additions and 50 deletions

1
Cargo.lock generated
View File

@ -2333,6 +2333,7 @@ dependencies = [
"lazy_static",
"log",
"nssa",
"nssa-core",
"rand 0.8.5",
"serde",
"serde_json",

View File

@ -147,6 +147,26 @@ impl SequencerClient {
Ok(resp_deser)
}
///Send transaction to sequencer
pub async fn send_tx_private(
&self,
transaction: nssa::PrivacyPreservingTransaction,
) -> Result<SendTxResponse, SequencerClientError> {
let transaction = EncodedTransaction::from(NSSATransaction::PrivacyPreserving(transaction));
let tx_req = SendTxRequest {
transaction: transaction.to_bytes(),
};
let req = serde_json::to_value(tx_req)?;
let resp = self.call_method_with_payload("send_tx", req).await?;
let resp_deser = serde_json::from_value(resp)?;
Ok(resp_deser)
}
///Get genesis id from sequencer
pub async fn get_genesis_id(&self) -> Result<GetGenesisIdResponse, SequencerClientError> {
let genesis_req = GetGenesisIdRequest {};

View File

@ -117,7 +117,7 @@ pub async fn test_success() {
}
pub async fn test_success_move_to_another_account() {
let command = Command::RegisterAccount {};
let command = Command::RegisterAccountPublic {};
let wallet_config = fetch_config().unwrap();
@ -130,10 +130,10 @@ pub async fn test_success_move_to_another_account() {
let mut new_persistent_account_addr = String::new();
for per_acc in persistent_accounts {
if (per_acc.address.to_string() != ACC_RECEIVER)
&& (per_acc.address.to_string() != ACC_SENDER)
if (per_acc.address().to_string() != ACC_RECEIVER)
&& (per_acc.address().to_string() != ACC_SENDER)
{
new_persistent_account_addr = per_acc.address.to_string();
new_persistent_account_addr = per_acc.address().to_string();
}
}

View File

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

View File

@ -23,9 +23,9 @@ pub struct TopSecretKeyHolder {
#[derive(Serialize, Deserialize, Debug, Clone)]
///Private key holder. Produces public keys. Can produce address. Can produce shared secret for recepient.
pub struct PrivateKeyHolder {
pub(crate) nullifier_secret_key: [u8; 32],
pub nullifier_secret_key: [u8; 32],
pub(crate) incoming_viewing_secret_key: Scalar,
pub(crate) outgoing_viewing_secret_key: Scalar,
pub outgoing_viewing_secret_key: Scalar,
}
impl SeedHolder {

View File

@ -13,7 +13,7 @@ pub struct NSSAUserData {
///Map for all user public accounts
pub pub_account_signing_keys: HashMap<nssa::Address, nssa::PrivateKey>,
///Map for all user private accounts
user_private_accounts: HashMap<nssa::Address, KeyChain>,
pub user_private_accounts: HashMap<nssa::Address, (KeyChain, nssa_core::account::Account)>,
}
impl NSSAUserData {
@ -30,10 +30,10 @@ impl NSSAUserData {
}
fn valid_private_key_transaction_pairing_check(
accounts_keys_map: &HashMap<nssa::Address, KeyChain>,
accounts_keys_map: &HashMap<nssa::Address, (KeyChain, nssa_core::account::Account)>,
) -> bool {
let mut check_res = true;
for (addr, key) in accounts_keys_map {
for (addr, (key, _)) in accounts_keys_map {
if nssa::Address::new(key.produce_user_address()) != *addr {
check_res = false;
}
@ -43,7 +43,7 @@ impl NSSAUserData {
pub fn new_with_accounts(
accounts_keys: HashMap<nssa::Address, nssa::PrivateKey>,
accounts_key_chains: HashMap<nssa::Address, KeyChain>,
accounts_key_chains: HashMap<nssa::Address, (KeyChain, nssa_core::account::Account)>,
) -> Result<Self> {
if !Self::valid_public_key_transaction_pairing_check(&accounts_keys) {
anyhow::bail!(
@ -90,15 +90,27 @@ impl NSSAUserData {
let key_chain = KeyChain::new_os_random();
let address = nssa::Address::new(key_chain.produce_user_address());
self.user_private_accounts.insert(address, key_chain);
self.user_private_accounts
.insert(address, (key_chain, nssa_core::account::Account::default()));
address
}
/// Returns the signing key for public transaction signatures
pub fn get_private_account_key_chain(&self, address: &nssa::Address) -> Option<&KeyChain> {
pub fn get_private_account(
&self,
address: &nssa::Address,
) -> Option<&(KeyChain, nssa_core::account::Account)> {
self.user_private_accounts.get(address)
}
/// Returns the signing key for public transaction signatures
pub fn get_private_account_mut(
&mut self,
address: &nssa::Address,
) -> Option<&mut (KeyChain, nssa_core::account::Account)> {
self.user_private_accounts.get_mut(address)
}
}
impl Default for NSSAUserData {
@ -123,9 +135,7 @@ mod tests {
assert!(is_private_key_generated);
let is_key_chain_generated = user_data
.get_private_account_key_chain(&addr_private)
.is_some();
let is_key_chain_generated = user_data.get_private_account(&addr_private).is_some();
assert!(is_key_chain_generated);
}

View File

@ -2,7 +2,7 @@ pub mod address;
pub mod encoding;
pub mod error;
mod merkle_tree;
mod privacy_preserving_transaction;
pub mod privacy_preserving_transaction;
pub mod program;
pub mod public_transaction;
mod signature;

View File

@ -3,7 +3,7 @@ use std::collections::HashMap;
use anyhow::Result;
use key_protocol::key_protocol_core::NSSAUserData;
use crate::config::{PersistentAccountData, WalletConfig};
use crate::config::{InitialAccountData, PersistentAccountData, WalletConfig};
pub struct WalletChainStore {
pub user_data: NSSAUserData,
@ -12,24 +12,40 @@ pub struct WalletChainStore {
impl WalletChainStore {
pub fn new(config: WalletConfig) -> Result<Self> {
let accounts_keys: HashMap<nssa::Address, nssa::PrivateKey> = config
.initial_accounts
.clone()
.into_iter()
.map(|init_acc_data| (init_acc_data.address, init_acc_data.pub_sign_key))
.collect();
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.address, data.pub_sign_key);
}
InitialAccountData::Private(data) => {
private_init_acc_map.insert(data.address, (data.key_chain, data.account));
}
}
}
Ok(Self {
user_data: NSSAUserData::new_with_accounts(accounts_keys, HashMap::new())?,
user_data: NSSAUserData::new_with_accounts(public_init_acc_map, private_init_acc_map)?,
wallet_config: config,
})
}
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.address, acc_data.pub_sign_key);
}
PersistentAccountData::Private(acc_data) => {
self.user_data
.user_private_accounts
.insert(acc_data.address, (acc_data.key_chain, acc_data.account));
}
}
}
}
#[cfg(test)]

View File

@ -1,19 +1,87 @@
use key_protocol::key_management::KeyChain;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitialAccountData {
pub struct InitialAccountDataPublic {
pub address: nssa::Address,
pub account: nssa_core::account::Account,
pub pub_sign_key: nssa::PrivateKey,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistentAccountData {
pub struct PersistentAccountDataPublic {
pub address: nssa::Address,
pub pub_sign_key: nssa::PrivateKey,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitialAccountDataPrivate {
pub address: nssa::Address,
pub account: nssa_core::account::Account,
pub key_chain: KeyChain,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistentAccountDataPrivate {
pub address: nssa::Address,
pub account: nssa_core::account::Account,
pub key_chain: KeyChain,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum InitialAccountData {
Public(InitialAccountDataPublic),
Private(InitialAccountDataPrivate),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PersistentAccountData {
Public(PersistentAccountDataPublic),
Private(PersistentAccountDataPrivate),
}
impl InitialAccountData {
pub fn address(&self) -> nssa::Address {
match &self {
Self::Public(acc) => acc.address,
Self::Private(acc) => acc.address,
}
}
}
impl PersistentAccountData {
pub fn address(&self) -> nssa::Address {
match &self {
Self::Public(acc) => acc.address,
Self::Private(acc) => acc.address,
}
}
}
impl From<InitialAccountDataPublic> for InitialAccountData {
fn from(value: InitialAccountDataPublic) -> Self {
Self::Public(value)
}
}
impl From<InitialAccountDataPrivate> for InitialAccountData {
fn from(value: InitialAccountDataPrivate) -> Self {
Self::Private(value)
}
}
impl From<PersistentAccountDataPublic> for PersistentAccountData {
fn from(value: PersistentAccountDataPublic) -> Self {
Self::Public(value)
}
}
impl From<PersistentAccountDataPrivate> for PersistentAccountData {
fn from(value: PersistentAccountDataPrivate) -> Self {
Self::Private(value)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GasConfig {
/// Gas spent per deploying one byte of data

View File

@ -6,7 +6,10 @@ use nssa::Address;
use crate::{
HOME_DIR_ENV_VAR,
config::{PersistentAccountData, WalletConfig},
config::{
PersistentAccountData, PersistentAccountDataPrivate, PersistentAccountDataPublic,
WalletConfig,
},
};
///Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed.
@ -54,10 +57,24 @@ pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec<PersistentAccou
let mut vec_for_storage = vec![];
for (addr, key) in &user_data.pub_account_signing_keys {
vec_for_storage.push(PersistentAccountData {
vec_for_storage.push(
PersistentAccountDataPublic {
address: *addr,
pub_sign_key: key.clone(),
});
}
.into(),
);
}
for (addr, (key, acc)) in &user_data.user_private_accounts {
vec_for_storage.push(
PersistentAccountDataPrivate {
address: *addr,
account: acc.clone(),
key_chain: key.clone(),
}
.into(),
);
}
vec_for_storage

View File

@ -10,11 +10,11 @@ use common::{
use anyhow::Result;
use chain_storage::WalletChainStore;
use config::WalletConfig;
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
use log::info;
use nssa::Address;
use clap::{Parser, Subcommand};
use nssa_core::account::Account;
use crate::{
helperfunctions::{
@ -31,8 +31,6 @@ pub mod config;
pub mod helperfunctions;
pub mod poller;
//
pub struct WalletCore {
pub storage: WalletChainStore,
pub poller: TxPoller,
@ -74,19 +72,16 @@ impl WalletCore {
Ok(accs_path)
}
pub fn create_new_account(&mut self) -> Address {
pub fn create_new_account_public(&mut self) -> Address {
self.storage
.user_data
.generate_new_public_transaction_private_key()
}
pub fn search_for_initial_account(&self, acc_addr: Address) -> Option<Account> {
for initial_acc in &self.storage.wallet_config.initial_accounts {
if initial_acc.address == acc_addr {
return Some(initial_acc.account.clone());
}
}
None
pub fn create_new_account_private(&mut self) -> Address {
self.storage
.user_data
.generate_new_privacy_preserving_transaction_key_chain()
}
pub async fn send_public_native_token_transfer(
@ -131,6 +126,92 @@ impl WalletCore {
}
}
pub async fn send_private_native_token_transfer(
&self,
from: Address,
to: Address,
balance_to_move: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let from_data = self.storage.user_data.get_private_account(&from);
let to_data = self.storage.user_data.get_private_account(&to);
let Some((from_keys, from_acc)) = from_data else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let Some((to_keys, to_acc)) = to_data else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
if from_acc.balance >= balance_to_move {
let program = nssa::program::Program::authenticated_transfer_program();
let sender_commitment = nssa_core::Commitment::new(
&nssa_core::NullifierPublicKey(from_keys.nullifer_public_key),
from_acc,
);
let sender_pre = nssa_core::account::AccountWithMetadata {
account: from_acc.clone(),
is_authorized: true,
};
let recipient_pre = nssa_core::account::AccountWithMetadata {
account: to_acc.clone(),
is_authorized: false,
};
let eph_holder = EphemeralKeyHolder::new(
to_keys.nullifer_public_key,
from_keys.private_key_holder.outgoing_viewing_secret_key,
from_acc.nonce.try_into().unwrap(),
);
let shared_secret =
eph_holder.calculate_shared_secret_sender(to_keys.incoming_viewing_public_key);
let (output, proof) = nssa::privacy_preserving_transaction::circuit::execute_and_prove(
&[sender_pre, recipient_pre],
&nssa::program::Program::serialize_instruction(balance_to_move).unwrap(),
&[1, 2],
&[from_acc.nonce + 1, to_acc.nonce + 1],
&[
(
nssa_core::NullifierPublicKey(from_keys.nullifer_public_key),
shared_secret,
),
(
nssa_core::NullifierPublicKey(to_keys.nullifer_public_key),
shared_secret,
),
],
&[(
from_keys.private_key_holder.nullifier_secret_key,
state.get_proof_for_commitment(&sender_commitment).unwrap(),
)],
&program,
)
.unwrap();
let message = Message::try_from_circuit_output(
vec![],
vec![],
vec![
(sender_keys.npk(), sender_keys.ivk(), epk_1),
(recipient_keys.npk(), recipient_keys.ivk(), epk_2),
],
output,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, proof, &[]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok(self.sequencer_client.send_tx_public(tx).await?)
} else {
Err(ExecutionFailureKind::InsufficientFundsError)
}
}
///Get account balance
pub async fn get_account_balance(&self, acc: Address) -> Result<u128> {
Ok(self
@ -176,8 +257,10 @@ pub enum Command {
#[arg(long)]
amount: u128,
},
///Register new account
RegisterAccount {},
///Register new public account
RegisterAccountPublic {},
///Register new private account
RegisterAccountPrivate {},
///Fetch transaction by `hash`
FetchTx {
#[arg(short, long)]
@ -225,8 +308,8 @@ pub async fn execute_subcommand(command: Command) -> Result<()> {
info!("Transaction data is {transfer_tx:?}");
}
Command::RegisterAccount {} => {
let addr = wallet_core.create_new_account();
Command::RegisterAccountPublic {} => {
let addr = wallet_core.create_new_account_public();
let key = wallet_core
.storage
@ -236,6 +319,19 @@ pub async fn execute_subcommand(command: Command) -> Result<()> {
info!("Generated new account with addr {addr:#?}");
info!("With key {key:#?}");
}
Command::RegisterAccountPrivate {} => {
let addr = wallet_core.create_new_account_private();
let (key, account) = wallet_core
.storage
.user_data
.get_private_account(&addr)
.unwrap();
info!("Generated new account with addr {addr:#?}");
info!("With key {key:#?}");
info!("With account {account:#?}");
}
Command::FetchTx { tx_hash } => {
let tx_obj = wallet_core
.sequencer_client