feat: shielded+deshielded token transfers

This commit is contained in:
Oleksandr Pravdyvyi 2025-09-16 14:53:00 +03:00
parent ae70baf5c4
commit 08526d47bf
No known key found for this signature in database
GPG Key ID: 9F8955C63C443871
4 changed files with 344 additions and 188 deletions

View File

@ -1,8 +1,4 @@
use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead};
use common::TreeHashType;
use elliptic_curve::group::GroupEncoding;
use elliptic_curve::point::AffineCoordinates;
use k256::AffinePoint;
use log::info;
use nssa_core::{
NullifierPublicKey, SharedSecretKey,

View File

@ -25,9 +25,9 @@ pub type OutgoingViewingSecretKey = Scalar;
#[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: NullifierSecretKey,
pub nullifier_secret_key: NullifierSecretKey,
pub(crate) incoming_viewing_secret_key: IncomingViewingSecretKey,
pub(crate) outgoing_viewing_secret_key: OutgoingViewingSecretKey,
pub outgoing_viewing_secret_key: OutgoingViewingSecretKey,
}
impl SeedHolder {

View File

@ -2,17 +2,13 @@ use std::{fs::File, io::Write, path::PathBuf, str::FromStr, sync::Arc};
use base64::Engine;
use common::{
ExecutionFailureKind,
sequencer_client::{SequencerClient, json::SendTxResponse},
sequencer_client::SequencerClient,
transaction::{EncodedTransaction, NSSATransaction},
};
use anyhow::Result;
use chain_storage::WalletChainStore;
use config::WalletConfig;
use k256::elliptic_curve::group::GroupEncoding;
use k256::elliptic_curve::sec1::ToEncodedPoint;
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
use log::info;
use nssa::Address;
@ -32,6 +28,7 @@ pub mod chain_storage;
pub mod config;
pub mod helperfunctions;
pub mod poller;
pub mod token_transfers;
pub struct WalletCore {
pub storage: WalletChainStore,
@ -86,183 +83,6 @@ impl WalletCore {
.generate_new_privacy_preserving_transaction_key_chain()
}
pub async fn send_public_native_token_transfer(
&self,
from: Address,
to: Address,
balance_to_move: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let Ok(balance) = self.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 {
return Err(ExecutionFailureKind::SequencerError);
};
let addresses = vec![from, to];
let program_id = nssa::program::Program::authenticated_transfer_program().id();
let message = nssa::public_transaction::Message::try_new(
program_id,
addresses,
nonces,
balance_to_move,
)
.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 =
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?)
} else {
Err(ExecutionFailureKind::InsufficientFundsError)
}
}
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),
nssa_core::SharedSecretKey(
shared_secret.to_bytes().as_slice().try_into().unwrap(),
),
),
(
nssa_core::NullifierPublicKey(to_keys.nullifer_public_key),
nssa_core::SharedSecretKey(
shared_secret.to_bytes().as_slice().try_into().unwrap(),
),
),
],
&[(
from_keys.private_key_holder.nullifier_secret_key,
self.sequencer_client
.get_proof_for_commitment(sender_commitment)
.await
.unwrap()
.unwrap(),
)],
&program,
)
.unwrap();
let message =
nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output(
vec![],
vec![],
vec![
(
nssa_core::NullifierPublicKey(from_keys.nullifer_public_key),
nssa_core::encryption::shared_key_derivation::Secp256k1Point(
from_keys
.incoming_viewing_public_key
.to_encoded_point(true)
.as_bytes()
.to_vec(),
),
nssa_core::encryption::shared_key_derivation::Secp256k1Point(
eph_holder
.generate_ephemeral_public_key()
.to_encoded_point(true)
.as_bytes()
.to_vec(),
),
),
(
nssa_core::NullifierPublicKey(to_keys.nullifer_public_key),
nssa_core::encryption::shared_key_derivation::Secp256k1Point(
to_keys
.incoming_viewing_public_key
.to_encoded_point(true)
.as_bytes()
.to_vec(),
),
nssa_core::encryption::shared_key_derivation::Secp256k1Point(
eph_holder
.generate_ephemeral_public_key()
.to_encoded_point(true)
.as_bytes()
.to_vec(),
),
),
],
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?)
} else {
Err(ExecutionFailureKind::InsufficientFundsError)
}
}
///Get account balance
pub async fn get_account_balance(&self, acc: Address) -> Result<u128> {
Ok(self

View File

@ -0,0 +1,340 @@
use common::{ExecutionFailureKind, sequencer_client::json::SendTxResponse};
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
use nssa::Address;
use crate::WalletCore;
impl WalletCore {
pub async fn send_public_native_token_transfer(
&self,
from: Address,
to: Address,
balance_to_move: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let Ok(balance) = self.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 {
return Err(ExecutionFailureKind::SequencerError);
};
let addresses = vec![from, to];
let program_id = nssa::program::Program::authenticated_transfer_program().id();
let message = nssa::public_transaction::Message::try_new(
program_id,
addresses,
nonces,
balance_to_move,
)
.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 =
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?)
} else {
Err(ExecutionFailureKind::InsufficientFundsError)
}
}
pub async fn send_private_native_token_transfer_maybe_outer_account(
&self,
from: Address,
to_npk: nssa_core::NullifierPublicKey,
to_ipk: nssa_core::encryption::IncomingViewingPublicKey,
balance_to_move: u128,
to_acc: Option<nssa_core::account::Account>,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let from_data = self.storage.user_data.get_private_account(&from);
let Some((from_keys, from_acc)) = from_data else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let to_acc = to_acc.unwrap_or_default();
if from_acc.balance >= balance_to_move {
let program = nssa::program::Program::authenticated_transfer_program();
let sender_commitment =
nssa_core::Commitment::new(&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_npk.clone(),
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_ipk.clone());
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],
&[
(from_keys.nullifer_public_key.clone(), shared_secret.clone()),
(to_npk.clone(), shared_secret),
],
&[(
from_keys.private_key_holder.nullifier_secret_key,
self.sequencer_client
.get_proof_for_commitment(sender_commitment)
.await
.unwrap()
.unwrap(),
)],
&program,
)
.unwrap();
let message =
nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output(
vec![],
vec![],
vec![
(
from_keys.nullifer_public_key.clone(),
from_keys.incoming_viewing_public_key.clone(),
eph_holder.generate_ephemeral_public_key(),
),
(
to_npk.clone(),
to_ipk.clone(),
eph_holder.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?)
} else {
Err(ExecutionFailureKind::InsufficientFundsError)
}
}
pub async fn send_shielded_native_token_transfer_maybe_outer_account(
&self,
from: Address,
to_npk: nssa_core::NullifierPublicKey,
to_ipk: nssa_core::encryption::IncomingViewingPublicKey,
balance_to_move: u128,
to_acc: Option<nssa_core::account::Account>,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let from_data = self.storage.user_data.get_private_account(&from);
let Some((from_keys, from_acc)) = from_data else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let to_acc = to_acc.unwrap_or_default();
if from_acc.balance >= balance_to_move {
let program = nssa::program::Program::authenticated_transfer_program();
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_npk.clone(),
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_ipk.clone());
let (output, proof) = nssa::privacy_preserving_transaction::circuit::execute_and_prove(
&[sender_pre, recipient_pre],
&nssa::program::Program::serialize_instruction(balance_to_move).unwrap(),
&[0, 2],
&[to_acc.nonce + 1],
&[(to_npk.clone(), shared_secret)],
&[],
&program,
)
.unwrap();
let message =
nssa::privacy_preserving_transaction::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 =
nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message(
&message,
proof,
&[signing_key],
);
let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new(
message,
witness_set,
);
Ok(self.sequencer_client.send_tx_private(tx).await?)
} else {
Err(ExecutionFailureKind::InsufficientFundsError)
}
}
pub async fn send_deshielded_native_token_transfer_maybe_outer_account(
&self,
from: Address,
to_addr: Address,
to_npk: nssa_core::NullifierPublicKey,
to_ipk: nssa_core::encryption::IncomingViewingPublicKey,
balance_to_move: u128,
to_acc: Option<nssa_core::account::Account>,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let from_data = self.storage.user_data.get_private_account(&from);
let Some((from_keys, from_acc)) = from_data else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let to_acc = to_acc.unwrap_or_default();
if from_acc.balance >= balance_to_move {
let program = nssa::program::Program::authenticated_transfer_program();
let sender_commitment =
nssa_core::Commitment::new(&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_npk.clone(),
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_ipk.clone());
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, 0],
&[from_acc.nonce + 1],
&[(from_keys.nullifer_public_key.clone(), shared_secret.clone())],
&[(
from_keys.private_key_holder.nullifier_secret_key,
self.sequencer_client
.get_proof_for_commitment(sender_commitment)
.await
.unwrap()
.unwrap(),
)],
&program,
)
.unwrap();
let message =
nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output(
vec![to_addr],
vec![],
vec![(
from_keys.nullifer_public_key.clone(),
from_keys.incoming_viewing_public_key.clone(),
eph_holder.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?)
} else {
Err(ExecutionFailureKind::InsufficientFundsError)
}
}
pub async fn send_private_native_token_transfer(
&self,
from: Address,
to: Address,
balance_to_move: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let to_data = self.storage.user_data.get_private_account(&to);
let Some((to_keys, to_acc)) = to_data else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
self.send_private_native_token_transfer_maybe_outer_account(
from,
to_keys.nullifer_public_key.clone(),
to_keys.incoming_viewing_public_key.clone(),
balance_to_move,
Some(to_acc.clone()),
)
.await
}
}