Merge branch 'main' into schouhy/add-token-program

This commit is contained in:
Sergio Chouhy 2025-09-29 16:00:53 -03:00
commit 36beca9c7c
44 changed files with 2845 additions and 482 deletions

View File

@ -33,4 +33,4 @@ jobs:
run: chmod 777 ./ci_scripts/lint-ubuntu.sh && ./ci_scripts/lint-ubuntu.sh
- name: test ubuntu-latest
if: success() || failure()
run: chmod 777 ./ci_scripts/test-ubuntu.sh && ./ci_scripts/test-ubuntu.sh
run: chmod 777 ./ci_scripts/test-ubuntu.sh && ./ci_scripts/test-ubuntu.sh

2
.gitignore vendored
View File

@ -6,4 +6,4 @@ data/
.idea/
.vscode/
rocksdb
Cargo.lock
Cargo.lock

View File

@ -41,9 +41,9 @@ ark-bn254 = "0.5.0"
ark-ff = "0.5.0"
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
base64 = "0.22.1"
chrono = "0.4.41"
bip39 = "2.2.0"
hmac-sha512 = "1.1.7"
chrono = "0.4.41"
rocksdb = { version = "0.21.0", default-features = false, features = [
"snappy",

View File

@ -6,5 +6,4 @@ cargo install taplo-cli --locked
cargo fmt -- --check
taplo fmt --check
export RISC0_SKIP_BUILD=1
cargo clippy --workspace --all-targets -- -D warnings
RISC0_SKIP_BUILD=1 cargo clippy --workspace --all-targets -- -D warnings

View File

@ -10,6 +10,10 @@ cd integration_tests
export NSSA_WALLET_HOME_DIR=$(pwd)/configs/debug/wallet/
export RUST_LOG=info
cargo run $(pwd)/configs/debug all
echo "Try test valid proof at least once"
cargo run $(pwd)/configs/debug test_success_private_transfer_to_another_owned_account
echo "Continuing in dev mode"
RISC0_DEV_MODE=1 cargo run $(pwd)/configs/debug all
cd ..
cd nssa/program_methods/guest && cargo test --release

View File

@ -16,6 +16,7 @@ sha2.workspace = true
log.workspace = true
elliptic-curve.workspace = true
hex.workspace = true
nssa-core = { path = "../nssa/core", features = ["host"] }
[dependencies.secp256k1-zkp]
workspace = true

View File

@ -53,6 +53,11 @@ pub struct GetAccountRequest {
pub address: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GetProofByCommitmentRequest {
pub commitment: nssa_core::Commitment,
}
parse_request!(HelloRequest);
parse_request!(RegisterAccountRequest);
parse_request!(SendTxRequest);
@ -63,6 +68,7 @@ parse_request!(GetInitialTestnetAccountsRequest);
parse_request!(GetAccountBalanceRequest);
parse_request!(GetTransactionByHashRequest);
parse_request!(GetAccountsNoncesRequest);
parse_request!(GetProofByCommitmentRequest);
parse_request!(GetAccountRequest);
#[derive(Serialize, Deserialize, Debug)]
@ -115,3 +121,8 @@ pub struct GetTransactionByHashResponse {
pub struct GetAccountResponse {
pub account: nssa::Account,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GetProofByCommitmentResponse {
pub membership_proof: Option<nssa_core::MembershipProof>,
}

View File

@ -9,7 +9,8 @@ use serde_json::Value;
use crate::rpc_primitives::requests::{
GetAccountRequest, GetAccountResponse, GetAccountsNoncesRequest, GetAccountsNoncesResponse,
GetTransactionByHashRequest, GetTransactionByHashResponse,
GetProofByCommitmentRequest, GetProofByCommitmentResponse, GetTransactionByHashRequest,
GetTransactionByHashResponse,
};
use crate::sequencer_client::json::AccountInitialData;
use crate::transaction::{EncodedTransaction, NSSATransaction};
@ -162,6 +163,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 {};
@ -195,4 +216,25 @@ impl SequencerClient {
Ok(resp_deser)
}
///Get proof for commitment
pub async fn get_proof_for_commitment(
&self,
commitment: nssa_core::Commitment,
) -> Result<Option<nssa_core::MembershipProof>, SequencerClientError> {
let acc_req = GetProofByCommitmentRequest { commitment };
let req = serde_json::to_value(acc_req).unwrap();
let resp = self
.call_method_with_payload("get_proof_for_commitment", req)
.await
.unwrap();
let resp_deser = serde_json::from_value::<GetProofByCommitmentResponse>(resp)
.unwrap()
.membership_proof;
Ok(resp_deser)
}
}

View File

@ -15,6 +15,8 @@ tokio.workspace = true
hex.workspace = true
tempfile.workspace = true
nssa-core = { path = "../nssa/core", features = ["host"] }
[dependencies.clap]
features = ["derive", "env"]
workspace = true

View File

@ -16,6 +16,142 @@
"balance": 20000
}
],
"signing_key": [37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37]
}
"initial_commitments": [
{
"npk": [
193,
209,
150,
113,
47,
241,
48,
145,
250,
79,
235,
51,
119,
40,
184,
232,
5,
221,
36,
21,
201,
106,
90,
210,
129,
106,
71,
99,
208,
153,
75,
215
],
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 10000,
"data": [],
"nonce": 0
}
},
{
"npk": [
27,
250,
136,
142,
88,
128,
138,
21,
49,
183,
118,
160,
117,
114,
110,
47,
136,
87,
60,
70,
59,
60,
18,
223,
23,
147,
241,
5,
184,
103,
225,
105
],
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 20000,
"data": [],
"nonce": 0
}
}
],
"signing_key": [
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37
]
}

View File

@ -8,109 +8,540 @@
"seq_poll_retry_delay_millis": 500,
"initial_accounts": [
{
"address": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
"pub_sign_key": [
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
],
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 10000,
"nonce": 0,
"data": []
"Public": {
"address": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
"pub_sign_key": [
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
]
}
},
{
"address": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
"pub_sign_key": [
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
],
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 20000,
"nonce": 0,
"data": []
"Public": {
"address": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
"pub_sign_key": [
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
}
},
{
"Private": {
"address": "6ffe0893c4b2c956fdb769b11fe4e3b2dd36ac4bd0ad90c810844051747c8c04",
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 10000,
"data": [],
"nonce": 0
},
"key_chain": {
"secret_spending_key": [
10,
125,
171,
38,
201,
35,
164,
43,
7,
80,
7,
215,
97,
42,
48,
229,
101,
216,
140,
21,
170,
214,
82,
53,
116,
22,
62,
79,
61,
76,
71,
79
],
"private_key_holder": {
"nullifier_secret_key": [
228,
136,
4,
156,
33,
40,
194,
172,
95,
168,
201,
33,
24,
30,
126,
197,
156,
113,
64,
162,
131,
210,
110,
60,
24,
154,
86,
59,
184,
95,
245,
176
],
"incoming_viewing_secret_key": [
197,
33,
51,
200,
1,
121,
60,
52,
233,
234,
12,
166,
196,
227,
187,
1,
10,
101,
183,
105,
140,
28,
152,
217,
109,
220,
112,
103,
253,
110,
98,
6
],
"outgoing_viewing_secret_key": [
147,
34,
193,
29,
39,
173,
222,
30,
118,
199,
44,
204,
43,
232,
107,
223,
249,
207,
245,
183,
63,
209,
129,
48,
254,
66,
22,
199,
81,
145,
126,
92
]
},
"nullifer_public_key": [
193,
209,
150,
113,
47,
241,
48,
145,
250,
79,
235,
51,
119,
40,
184,
232,
5,
221,
36,
21,
201,
106,
90,
210,
129,
106,
71,
99,
208,
153,
75,
215
],
"incoming_viewing_public_key": [
3,
78,
177,
87,
193,
219,
230,
160,
222,
38,
182,
100,
101,
223,
204,
223,
198,
140,
253,
94,
16,
98,
77,
79,
114,
30,
158,
104,
34,
152,
189,
31,
95
]
}
}
},
{
"Private": {
"address": "4ee9de60e33da96fd72929f1485fb365bcc9c1634dd44e4ba55b1ab96692674b",
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 20000,
"data": [],
"nonce": 0
},
"key_chain": {
"secret_spending_key": [
153,
109,
202,
226,
97,
212,
77,
147,
75,
107,
153,
106,
89,
167,
49,
230,
122,
78,
167,
146,
14,
180,
206,
107,
96,
193,
255,
122,
207,
30,
142,
99
],
"private_key_holder": {
"nullifier_secret_key": [
128,
215,
147,
175,
119,
16,
140,
219,
155,
134,
27,
81,
64,
40,
196,
240,
61,
144,
232,
164,
181,
57,
139,
96,
137,
121,
140,
29,
169,
68,
187,
65
],
"incoming_viewing_secret_key": [
185,
121,
146,
213,
13,
3,
93,
206,
25,
127,
155,
21,
155,
115,
130,
27,
57,
5,
116,
80,
62,
214,
67,
228,
147,
189,
28,
200,
62,
152,
178,
103
],
"outgoing_viewing_secret_key": [
163,
58,
118,
160,
175,
86,
72,
91,
81,
69,
150,
154,
113,
211,
118,
110,
25,
156,
250,
67,
212,
198,
147,
231,
213,
136,
212,
198,
192,
255,
126,
122
]
},
"nullifer_public_key": [
27,
250,
136,
142,
88,
128,
138,
21,
49,
183,
118,
160,
117,
114,
110,
47,
136,
87,
60,
70,
59,
60,
18,
223,
23,
147,
241,
5,
184,
103,
225,
105
],
"incoming_viewing_public_key": [
2,
56,
160,
1,
22,
197,
187,
214,
204,
221,
84,
87,
12,
204,
0,
119,
116,
176,
6,
149,
145,
100,
211,
162,
19,
158,
197,
112,
142,
172,
1,
98,
226
]
}
}
}
]
}
}

View File

@ -6,13 +6,15 @@ use clap::Parser;
use common::sequencer_client::SequencerClient;
use log::{info, warn};
use nssa::program::Program;
use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point};
use sequencer_core::config::SequencerConfig;
use sequencer_runner::startup_sequencer;
use tempfile::TempDir;
use tokio::task::JoinHandle;
use wallet::{
Command,
helperfunctions::{fetch_config, fetch_persistent_accounts},
Command, SubcommandReturnValue, WalletCore,
config::PersistentAccountData,
helperfunctions::{fetch_config, fetch_persistent_accounts, produce_account_addr_from_hex},
};
#[derive(Parser, Debug)]
@ -27,6 +29,11 @@ struct Args {
pub const ACC_SENDER: &str = "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f";
pub const ACC_RECEIVER: &str = "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766";
pub const ACC_SENDER_PRIVATE: &str =
"6ffe0893c4b2c956fdb769b11fe4e3b2dd36ac4bd0ad90c810844051747c8c04";
pub const ACC_RECEIVER_PRIVATE: &str =
"4ee9de60e33da96fd72929f1485fb365bcc9c1634dd44e4ba55b1ab96692674b";
pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12;
#[allow(clippy::type_complexity)]
@ -83,7 +90,8 @@ pub async fn post_test(residual: (ServerHandle, JoinHandle<Result<()>>, TempDir)
}
pub async fn test_success() {
let command = Command::SendNativeTokenTransfer {
info!("test_success");
let command = Command::SendNativeTokenTransferPublic {
from: ACC_SENDER.to_string(),
to: ACC_RECEIVER.to_string(),
amount: 100,
@ -118,7 +126,8 @@ pub async fn test_success() {
}
pub async fn test_success_move_to_another_account() {
let command = Command::RegisterAccount {};
info!("test_success_move_to_another_account");
let command = Command::RegisterAccountPublic {};
let wallet_config = fetch_config().unwrap();
@ -131,10 +140,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();
}
}
@ -142,7 +151,7 @@ pub async fn test_success_move_to_another_account() {
panic!("Failed to produce new account, not present in persistent accounts");
}
let command = Command::SendNativeTokenTransfer {
let command = Command::SendNativeTokenTransferPublic {
from: ACC_SENDER.to_string(),
to: new_persistent_account_addr.clone(),
amount: 100,
@ -173,7 +182,8 @@ pub async fn test_success_move_to_another_account() {
}
pub async fn test_failure() {
let command = Command::SendNativeTokenTransfer {
info!("test_failure");
let command = Command::SendNativeTokenTransferPublic {
from: ACC_SENDER.to_string(),
to: ACC_RECEIVER.to_string(),
amount: 1000000,
@ -210,7 +220,8 @@ pub async fn test_failure() {
}
pub async fn test_success_two_transactions() {
let command = Command::SendNativeTokenTransfer {
info!("test_success_two_transactions");
let command = Command::SendNativeTokenTransferPublic {
from: ACC_SENDER.to_string(),
to: ACC_RECEIVER.to_string(),
amount: 100,
@ -243,7 +254,7 @@ pub async fn test_success_two_transactions() {
info!("First TX Success!");
let command = Command::SendNativeTokenTransfer {
let command = Command::SendNativeTokenTransferPublic {
from: ACC_SENDER.to_string(),
to: ACC_RECEIVER.to_string(),
amount: 100,
@ -274,6 +285,7 @@ pub async fn test_success_two_transactions() {
}
pub async fn test_get_account() {
info!("test_get_account");
let wallet_config = fetch_config().unwrap();
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
@ -298,15 +310,15 @@ pub async fn test_success_token_program() {
let wallet_config = fetch_config().unwrap();
// Create new account for the token definition
wallet::execute_subcommand(Command::RegisterAccount {})
wallet::execute_subcommand(Command::RegisterAccountPublic {})
.await
.unwrap();
// Create new account for the token supply holder
wallet::execute_subcommand(Command::RegisterAccount {})
wallet::execute_subcommand(Command::RegisterAccountPublic {})
.await
.unwrap();
// Create new account for receiving a token transaction
wallet::execute_subcommand(Command::RegisterAccount {})
wallet::execute_subcommand(Command::RegisterAccountPublic {})
.await
.unwrap();
@ -315,10 +327,15 @@ pub async fn test_success_token_program() {
let mut new_persistent_accounts_addr = Vec::new();
for per_acc in persistent_accounts {
if (per_acc.address.to_string() != ACC_RECEIVER)
&& (per_acc.address.to_string() != ACC_SENDER)
{
new_persistent_accounts_addr.push(per_acc.address);
match per_acc {
PersistentAccountData::Public(per_acc) => {
if (per_acc.address.to_string() != ACC_RECEIVER)
&& (per_acc.address.to_string() != ACC_SENDER)
{
new_persistent_accounts_addr.push(per_acc.address);
}
}
_ => continue,
}
}
@ -433,7 +450,483 @@ pub async fn test_success_token_program() {
);
}
pub async fn test_success_private_transfer_to_another_owned_account() {
info!("test_success_private_transfer_to_another_owned_account");
let command = Command::SendNativeTokenTransferPrivate {
from: ACC_SENDER_PRIVATE.to_string(),
to: ACC_RECEIVER_PRIVATE.to_string(),
amount: 100,
};
let from = produce_account_addr_from_hex(ACC_SENDER_PRIVATE.to_string()).unwrap();
let to = produce_account_addr_from_hex(ACC_RECEIVER_PRIVATE.to_string()).unwrap();
let wallet_config = fetch_config().unwrap();
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
let mut wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap();
wallet::execute_subcommand(command).await.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment1 = {
let from_acc = wallet_storage
.storage
.user_data
.get_private_account_mut(&from)
.unwrap();
from_acc.1.program_owner = nssa::program::Program::authenticated_transfer_program().id();
from_acc.1.balance -= 100;
from_acc.1.nonce += 1;
nssa_core::Commitment::new(&from_acc.0.nullifer_public_key, &from_acc.1)
};
let new_commitment2 = {
let to_acc = wallet_storage
.storage
.user_data
.get_private_account_mut(&to)
.unwrap();
to_acc.1.program_owner = nssa::program::Program::authenticated_transfer_program().id();
to_acc.1.balance += 100;
to_acc.1.nonce += 1;
nssa_core::Commitment::new(&to_acc.0.nullifer_public_key, &to_acc.1)
};
let proof1 = seq_client
.get_proof_for_commitment(new_commitment1)
.await
.unwrap()
.unwrap();
let proof2 = seq_client
.get_proof_for_commitment(new_commitment2)
.await
.unwrap()
.unwrap();
println!("New proof is {proof1:#?}");
println!("New proof is {proof2:#?}");
info!("Success!");
}
pub async fn test_success_private_transfer_to_another_foreign_account() {
info!("test_success_private_transfer_to_another_foreign_account");
let to_npk_orig = NullifierPublicKey([42; 32]);
let to_npk = hex::encode(to_npk_orig.0);
let to_ipk = Secp256k1Point::from_scalar(to_npk_orig.0);
let command = Command::SendNativeTokenTransferPrivateForeignAccount {
from: ACC_SENDER_PRIVATE.to_string(),
to_npk,
to_ipk: hex::encode(to_ipk.0),
amount: 100,
};
let from = produce_account_addr_from_hex(ACC_SENDER_PRIVATE.to_string()).unwrap();
let wallet_config = fetch_config().unwrap();
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
let mut wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap();
let sub_ret = wallet::execute_subcommand(command).await.unwrap();
println!("SUB RET is {sub_ret:#?}");
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment1 = {
let from_acc = wallet_storage
.storage
.user_data
.get_private_account_mut(&from)
.unwrap();
from_acc.1.program_owner = nssa::program::Program::authenticated_transfer_program().id();
from_acc.1.balance -= 100;
from_acc.1.nonce += 1;
nssa_core::Commitment::new(&from_acc.0.nullifer_public_key, &from_acc.1)
};
let new_commitment2 = {
let to_acc = nssa_core::account::Account {
program_owner: nssa::program::Program::authenticated_transfer_program().id(),
balance: 100,
data: vec![],
nonce: 1,
};
nssa_core::Commitment::new(&to_npk_orig, &to_acc)
};
let proof1 = seq_client
.get_proof_for_commitment(new_commitment1)
.await
.unwrap()
.unwrap();
let proof2 = seq_client
.get_proof_for_commitment(new_commitment2)
.await
.unwrap()
.unwrap();
println!("New proof is {proof1:#?}");
println!("New proof is {proof2:#?}");
info!("Success!");
}
pub async fn test_success_private_transfer_to_another_owned_account_claiming_path() {
info!("test_success_private_transfer_to_another_owned_account_claiming_path");
let command = Command::RegisterAccountPrivate {};
let sub_ret = wallet::execute_subcommand(command).await.unwrap();
let SubcommandReturnValue::RegisterAccount { addr: to_addr } = sub_ret else {
panic!("FAILED TO REGISTER ACCOUNT");
};
let wallet_config = fetch_config().unwrap();
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
let mut wallet_storage =
WalletCore::start_from_config_update_chain(wallet_config.clone()).unwrap();
let (to_keys, mut to_acc) = wallet_storage
.storage
.user_data
.user_private_accounts
.get(&to_addr)
.cloned()
.unwrap();
let command = Command::SendNativeTokenTransferPrivateForeignAccount {
from: ACC_SENDER_PRIVATE.to_string(),
to_npk: hex::encode(to_keys.nullifer_public_key.0),
to_ipk: hex::encode(to_keys.incoming_viewing_public_key.0),
amount: 100,
};
let from = produce_account_addr_from_hex(ACC_SENDER_PRIVATE.to_string()).unwrap();
let sub_ret = wallet::execute_subcommand(command).await.unwrap();
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else {
panic!("FAILED TO SEND TX");
};
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment1 = {
let from_acc = wallet_storage
.storage
.user_data
.get_private_account_mut(&from)
.unwrap();
from_acc.1.program_owner = nssa::program::Program::authenticated_transfer_program().id();
from_acc.1.balance -= 100;
from_acc.1.nonce += 1;
nssa_core::Commitment::new(&from_acc.0.nullifer_public_key, &from_acc.1)
};
let new_commitment2 = {
to_acc.program_owner = nssa::program::Program::authenticated_transfer_program().id();
to_acc.balance = 100;
to_acc.nonce = 1;
nssa_core::Commitment::new(&to_keys.nullifer_public_key, &to_acc)
};
let proof1 = seq_client
.get_proof_for_commitment(new_commitment1)
.await
.unwrap()
.unwrap();
let proof2 = seq_client
.get_proof_for_commitment(new_commitment2)
.await
.unwrap()
.unwrap();
println!("New proof is {proof1:#?}");
println!("New proof is {proof2:#?}");
let command = Command::ClaimPrivateAccount {
tx_hash,
acc_addr: hex::encode(to_addr),
ciph_id: 1,
};
wallet::execute_subcommand(command).await.unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap();
let (_, to_res_acc) = wallet_storage
.storage
.user_data
.get_private_account(&to_addr)
.unwrap();
assert_eq!(to_res_acc.balance, 100);
info!("Success!");
}
pub async fn test_success_deshielded_transfer_to_another_account() {
info!("test_success_deshielded_transfer_to_another_account");
let command = Command::SendNativeTokenTransferDeshielded {
from: ACC_SENDER_PRIVATE.to_string(),
to: ACC_RECEIVER.to_string(),
amount: 100,
};
let from = produce_account_addr_from_hex(ACC_SENDER_PRIVATE.to_string()).unwrap();
let wallet_config = fetch_config().unwrap();
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
let mut wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap();
wallet::execute_subcommand(command).await.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment1 = {
let from_acc = wallet_storage
.storage
.user_data
.get_private_account_mut(&from)
.unwrap();
from_acc.1.program_owner = nssa::program::Program::authenticated_transfer_program().id();
from_acc.1.balance -= 100;
from_acc.1.nonce += 1;
nssa_core::Commitment::new(&from_acc.0.nullifer_public_key, &from_acc.1)
};
let proof1 = seq_client
.get_proof_for_commitment(new_commitment1)
.await
.unwrap()
.unwrap();
let acc_2_balance = seq_client
.get_account_balance(ACC_RECEIVER.to_string())
.await
.unwrap();
println!("New proof is {proof1:#?}");
assert_eq!(acc_2_balance.balance, 20100);
info!("Success!");
}
pub async fn test_success_shielded_transfer_to_another_owned_account() {
info!("test_success_shielded_transfer_to_another_owned_account");
let command = Command::SendNativeTokenTransferShielded {
from: ACC_SENDER.to_string(),
to: ACC_RECEIVER_PRIVATE.to_string(),
amount: 100,
};
let to = produce_account_addr_from_hex(ACC_RECEIVER_PRIVATE.to_string()).unwrap();
let wallet_config = fetch_config().unwrap();
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
let mut wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap();
wallet::execute_subcommand(command).await.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment2 = {
let to_acc = wallet_storage
.storage
.user_data
.get_private_account_mut(&to)
.unwrap();
to_acc.1.program_owner = nssa::program::Program::authenticated_transfer_program().id();
to_acc.1.balance += 100;
to_acc.1.nonce += 1;
nssa_core::Commitment::new(&to_acc.0.nullifer_public_key, &to_acc.1)
};
let acc_1_balance = seq_client
.get_account_balance(ACC_SENDER.to_string())
.await
.unwrap();
let proof2 = seq_client
.get_proof_for_commitment(new_commitment2)
.await
.unwrap()
.unwrap();
assert_eq!(acc_1_balance.balance, 9900);
println!("New proof is {proof2:#?}");
info!("Success!");
}
pub async fn test_success_shielded_transfer_to_another_foreign_account() {
info!("test_success_shielded_transfer_to_another_foreign_account");
let to_npk_orig = NullifierPublicKey([42; 32]);
let to_npk = hex::encode(to_npk_orig.0);
let to_ipk = Secp256k1Point::from_scalar(to_npk_orig.0);
let command = Command::SendNativeTokenTransferShieldedForeignAccount {
from: ACC_SENDER.to_string(),
to_npk,
to_ipk: hex::encode(to_ipk.0),
amount: 100,
};
let wallet_config = fetch_config().unwrap();
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
wallet::execute_subcommand(command).await.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment2 = {
let to_acc = nssa_core::account::Account {
program_owner: nssa::program::Program::authenticated_transfer_program().id(),
balance: 100,
data: vec![],
nonce: 1,
};
nssa_core::Commitment::new(&to_npk_orig, &to_acc)
};
let acc_1_balance = seq_client
.get_account_balance(ACC_SENDER.to_string())
.await
.unwrap();
let proof2 = seq_client
.get_proof_for_commitment(new_commitment2)
.await
.unwrap()
.unwrap();
assert_eq!(acc_1_balance.balance, 9900);
println!("New proof is {proof2:#?}");
info!("Success!");
}
pub async fn test_success_shielded_transfer_to_another_owned_account_claiming_path() {
info!("test_success_shielded_transfer_to_another_owned_account_claiming_path");
let command = Command::RegisterAccountPrivate {};
let sub_ret = wallet::execute_subcommand(command).await.unwrap();
let SubcommandReturnValue::RegisterAccount { addr: to_addr } = sub_ret else {
panic!("FAILED TO REGISTER ACCOUNT");
};
let wallet_config = fetch_config().unwrap();
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()).unwrap();
let (to_keys, mut to_acc) = wallet_storage
.storage
.user_data
.user_private_accounts
.get(&to_addr)
.cloned()
.unwrap();
let command = Command::SendNativeTokenTransferShieldedForeignAccount {
from: ACC_SENDER.to_string(),
to_npk: hex::encode(to_keys.nullifer_public_key.0),
to_ipk: hex::encode(to_keys.incoming_viewing_public_key.0),
amount: 100,
};
let sub_ret = wallet::execute_subcommand(command).await.unwrap();
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else {
panic!("FAILED TO SEND TX");
};
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment2 = {
to_acc.program_owner = nssa::program::Program::authenticated_transfer_program().id();
to_acc.balance = 100;
to_acc.nonce = 1;
nssa_core::Commitment::new(&to_keys.nullifer_public_key, &to_acc)
};
let acc_1_balance = seq_client
.get_account_balance(ACC_SENDER.to_string())
.await
.unwrap();
let proof2 = seq_client
.get_proof_for_commitment(new_commitment2)
.await
.unwrap()
.unwrap();
assert_eq!(acc_1_balance.balance, 9900);
println!("New proof is {proof2:#?}");
let command = Command::ClaimPrivateAccount {
tx_hash,
acc_addr: hex::encode(to_addr),
ciph_id: 0,
};
wallet::execute_subcommand(command).await.unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap();
let (_, to_res_acc) = wallet_storage
.storage
.user_data
.get_private_account(&to_addr)
.unwrap();
assert_eq!(to_res_acc.balance, 100);
info!("Success!");
}
pub async fn test_pinata() {
info!("test_pinata");
let pinata_addr = "cafe".repeat(16);
let pinata_prize = 150;
let solution = 989106;
@ -518,6 +1011,48 @@ pub async fn main_tests_runner() -> Result<()> {
"test_success_two_transactions" => {
test_cleanup_wrap!(home_dir, test_success_two_transactions);
}
"test_success_private_transfer_to_another_owned_account" => {
test_cleanup_wrap!(
home_dir,
test_success_private_transfer_to_another_owned_account
);
}
"test_success_private_transfer_to_another_foreign_account" => {
test_cleanup_wrap!(
home_dir,
test_success_private_transfer_to_another_foreign_account
);
}
"test_success_private_transfer_to_another_owned_account_claiming_path" => {
test_cleanup_wrap!(
home_dir,
test_success_private_transfer_to_another_owned_account_claiming_path
);
}
"test_success_deshielded_transfer_to_another_account" => {
test_cleanup_wrap!(
home_dir,
test_success_deshielded_transfer_to_another_account
);
}
"test_success_shielded_transfer_to_another_owned_account" => {
test_cleanup_wrap!(
home_dir,
test_success_shielded_transfer_to_another_owned_account
);
}
"test_success_shielded_transfer_to_another_foreign_account" => {
test_cleanup_wrap!(
home_dir,
test_success_shielded_transfer_to_another_foreign_account
);
}
"test_success_shielded_transfer_to_another_owned_account_claiming_path" => {
test_cleanup_wrap!(
home_dir,
test_success_shielded_transfer_to_another_owned_account_claiming_path
);
}
"test_pinata" => {
test_cleanup_wrap!(home_dir, test_pinata);
}
@ -527,8 +1062,35 @@ pub async fn main_tests_runner() -> Result<()> {
test_cleanup_wrap!(home_dir, test_failure);
test_cleanup_wrap!(home_dir, test_success_two_transactions);
test_cleanup_wrap!(home_dir, test_success_token_program);
test_cleanup_wrap!(
home_dir,
test_success_private_transfer_to_another_owned_account
);
test_cleanup_wrap!(
home_dir,
test_success_private_transfer_to_another_foreign_account
);
test_cleanup_wrap!(
home_dir,
test_success_deshielded_transfer_to_another_account
);
test_cleanup_wrap!(
home_dir,
test_success_shielded_transfer_to_another_owned_account
);
test_cleanup_wrap!(
home_dir,
test_success_shielded_transfer_to_another_foreign_account
);
test_cleanup_wrap!(
home_dir,
test_success_private_transfer_to_another_owned_account_claiming_path
);
test_cleanup_wrap!(
home_dir,
test_success_shielded_transfer_to_another_owned_account_claiming_path
);
test_cleanup_wrap!(home_dir, test_pinata);
test_cleanup_wrap!(home_dir, test_get_account);
}
_ => {
anyhow::bail!("Unknown test name");

View File

@ -3,6 +3,7 @@ use nssa_core::{
NullifierPublicKey, SharedSecretKey,
encryption::{EphemeralPublicKey, EphemeralSecretKey, IncomingViewingPublicKey},
};
use rand::{RngCore, rngs::OsRng};
use sha2::Digest;
use crate::key_management::secret_holders::OutgoingViewingSecretKey;
@ -13,6 +14,17 @@ pub struct EphemeralKeyHolder {
ephemeral_secret_key: EphemeralSecretKey,
}
pub fn produce_one_sided_shared_secret_receiver(
ipk: &IncomingViewingPublicKey,
) -> (SharedSecretKey, EphemeralPublicKey) {
let mut esk = [0; 32];
OsRng.fill_bytes(&mut esk);
(
SharedSecretKey::new(&esk, ipk),
EphemeralPublicKey::from_scalar(esk),
)
}
impl EphemeralKeyHolder {
pub fn new(
receiver_nullifier_public_key: NullifierPublicKey,

View File

@ -22,6 +22,18 @@ pub struct KeyChain {
pub incoming_viewing_public_key: IncomingViewingPublicKey,
}
pub fn produce_user_address_foreign_account(
npk: &NullifierPublicKey,
ipk: &IncomingViewingPublicKey,
) -> [u8; 32] {
let mut hasher = sha2::Sha256::new();
hasher.update(npk);
hasher.update(ipk.to_bytes());
<TreeHashType>::from(hasher.finalize_fixed())
}
impl KeyChain {
pub fn new_os_random() -> Self {
//Currently dropping SeedHolder at the end of initialization.

View File

@ -26,9 +26,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

@ -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

@ -5,7 +5,7 @@ edition = "2024"
[dependencies]
thiserror = "2.0.12"
risc0-zkvm = "3.0.3"
risc0-zkvm = { version = "3.0.3", features = ['std'] }
nssa-core = { path = "core", features = ["host"] }
program-methods = { path = "program_methods" }
serde = "1.0.219"

View File

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
risc0-zkvm = { version = "3.0.3" }
risc0-zkvm = { version = "3.0.3", features = ['std'] }
serde = { version = "1.0", default-features = false }
thiserror = { version = "2.0.12", optional = true }
bytemuck = { version = "1.13", optional = true }

View File

@ -6,7 +6,7 @@ use risc0_zkvm::sha::{Impl, Sha256};
use serde::{Deserialize, Serialize};
#[cfg(feature = "host")]
pub(crate) mod shared_key_derivation;
pub mod shared_key_derivation;
#[cfg(feature = "host")]
pub use shared_key_derivation::{EphemeralPublicKey, EphemeralSecretKey, IncomingViewingPublicKey};
@ -16,7 +16,7 @@ use crate::{Commitment, account::Account};
pub type Scalar = [u8; 32];
#[derive(Serialize, Deserialize, Clone)]
pub struct SharedSecretKey([u8; 32]);
pub struct SharedSecretKey(pub [u8; 32]);
pub struct EncryptionScheme;

View File

@ -11,7 +11,7 @@ use k256::{
use crate::{SharedSecretKey, encryption::Scalar};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Secp256k1Point(pub(crate) Vec<u8>);
pub struct Secp256k1Point(pub Vec<u8>);
impl Secp256k1Point {
pub fn from_scalar(value: Scalar) -> Secp256k1Point {

View File

@ -5,7 +5,7 @@ use crate::{Commitment, account::AccountId};
#[derive(Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, Hash))]
pub struct NullifierPublicKey(pub(super) [u8; 32]);
pub struct NullifierPublicKey(pub [u8; 32]);
impl From<&NullifierPublicKey> for AccountId {
fn from(value: &NullifierPublicKey) -> Self {

View File

@ -6,6 +6,6 @@ edition = "2024"
[workspace]
[dependencies]
risc0-zkvm = { version = "3.0.3", default-features = false, features = ['std'] }
risc0-zkvm = { version = "3.0.3", features = ['std'] }
nssa-core = { path = "../../core" }
serde = { version = "1.0.219", default-features = false }

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

@ -11,9 +11,9 @@ pub type ViewTag = u8;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncryptedAccountData {
pub(crate) ciphertext: Ciphertext,
pub(crate) epk: EphemeralPublicKey,
pub(crate) view_tag: ViewTag,
pub ciphertext: Ciphertext,
pub epk: EphemeralPublicKey,
pub view_tag: ViewTag,
}
impl EncryptedAccountData {
@ -47,8 +47,8 @@ pub struct Message {
pub(crate) public_addresses: Vec<Address>,
pub(crate) nonces: Vec<Nonce>,
pub(crate) public_post_states: Vec<Account>,
pub(crate) encrypted_private_post_states: Vec<EncryptedAccountData>,
pub(crate) new_commitments: Vec<Commitment>,
pub encrypted_private_post_states: Vec<EncryptedAccountData>,
pub new_commitments: Vec<Commitment>,
pub(crate) new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
}

View File

@ -15,7 +15,7 @@ use super::witness_set::WitnessSet;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrivacyPreservingTransaction {
message: Message,
pub message: Message,
witness_set: WitnessSet,
}

View File

@ -141,7 +141,7 @@ pub mod tests {
fn state_for_tests() -> V01State {
let (_, _, addr1, addr2) = keys_for_tests();
let initial_data = [(addr1, 10000), (addr2, 20000)];
V01State::new_with_genesis_accounts(&initial_data)
V01State::new_with_genesis_accounts(&initial_data, &[])
}
fn transaction_for_tests() -> PublicTransaction {

View File

@ -64,7 +64,10 @@ pub struct V01State {
}
impl V01State {
pub fn new_with_genesis_accounts(initial_data: &[(Address, u128)]) -> Self {
pub fn new_with_genesis_accounts(
initial_data: &[(Address, u128)],
initial_commitments: &[nssa_core::Commitment],
) -> Self {
let authenticated_transfer_program = Program::authenticated_transfer_program();
let public_state = initial_data
.iter()
@ -79,9 +82,12 @@ impl V01State {
})
.collect();
let mut private_state = CommitmentSet::with_capacity(32);
private_state.extend(initial_commitments);
let mut this = Self {
public_state,
private_state: (CommitmentSet::with_capacity(32), NullifierSet::new()),
private_state: (private_state, NullifierSet::new()),
builtin_programs: HashMap::new(),
};
@ -301,7 +307,7 @@ pub mod tests {
this
};
let state = V01State::new_with_genesis_accounts(&initial_data);
let state = V01State::new_with_genesis_accounts(&initial_data, &[]);
assert_eq!(state.public_state, expected_public_state);
assert_eq!(state.builtin_programs, expected_builtin_programs);
@ -309,7 +315,7 @@ pub mod tests {
#[test]
fn test_insert_program() {
let mut state = V01State::new_with_genesis_accounts(&[]);
let mut state = V01State::new_with_genesis_accounts(&[], &[]);
let program_to_insert = Program::simple_balance_transfer();
let program_id = program_to_insert.id();
assert!(!state.builtin_programs.contains_key(&program_id));
@ -324,7 +330,7 @@ pub mod tests {
let key = PrivateKey::try_new([1; 32]).unwrap();
let addr = Address::from(&PublicKey::new_from_private_key(&key));
let initial_data = [(addr, 100u128)];
let state = V01State::new_with_genesis_accounts(&initial_data);
let state = V01State::new_with_genesis_accounts(&initial_data, &[]);
let expected_account = state.public_state.get(&addr).unwrap();
let account = state.get_account_by_address(&addr);
@ -335,7 +341,7 @@ pub mod tests {
#[test]
fn test_get_account_by_address_default_account() {
let addr2 = Address::new([0; 32]);
let state = V01State::new_with_genesis_accounts(&[]);
let state = V01State::new_with_genesis_accounts(&[], &[]);
let expected_account = Account::default();
let account = state.get_account_by_address(&addr2);
@ -345,7 +351,7 @@ pub mod tests {
#[test]
fn test_builtin_programs_getter() {
let state = V01State::new_with_genesis_accounts(&[]);
let state = V01State::new_with_genesis_accounts(&[], &[]);
let builtin_programs = state.builtin_programs();
@ -357,7 +363,7 @@ pub mod tests {
let key = PrivateKey::try_new([1; 32]).unwrap();
let address = Address::from(&PublicKey::new_from_private_key(&key));
let initial_data = [(address, 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let mut state = V01State::new_with_genesis_accounts(&initial_data, &[]);
let from = address;
let to = Address::new([2; 32]);
assert_eq!(state.get_account_by_address(&to), Account::default());
@ -377,7 +383,7 @@ pub mod tests {
let key = PrivateKey::try_new([1; 32]).unwrap();
let address = Address::from(&PublicKey::new_from_private_key(&key));
let initial_data = [(address, 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let mut state = V01State::new_with_genesis_accounts(&initial_data, &[]);
let from = address;
let from_key = key;
let to = Address::new([2; 32]);
@ -401,7 +407,7 @@ pub mod tests {
let address1 = Address::from(&PublicKey::new_from_private_key(&key1));
let address2 = Address::from(&PublicKey::new_from_private_key(&key2));
let initial_data = [(address1, 100), (address2, 200)];
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let mut state = V01State::new_with_genesis_accounts(&initial_data, &[]);
let from = address2;
let from_key = key2;
let to = address1;
@ -424,7 +430,7 @@ pub mod tests {
let key2 = PrivateKey::try_new([2; 32]).unwrap();
let address2 = Address::from(&PublicKey::new_from_private_key(&key2));
let initial_data = [(address1, 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let mut state = V01State::new_with_genesis_accounts(&initial_data, &[]);
let address3 = Address::new([3; 32]);
let balance_to_move = 5;
@ -508,7 +514,8 @@ pub mod tests {
#[test]
fn test_program_should_fail_if_modifies_nonces() {
let initial_data = [(Address::new([1; 32]), 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let mut state =
V01State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let addresses = vec![Address::new([1; 32])];
let program_id = Program::nonce_changer_program().id();
let message =
@ -524,7 +531,8 @@ pub mod tests {
#[test]
fn test_program_should_fail_if_output_accounts_exceed_inputs() {
let initial_data = [(Address::new([1; 32]), 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let mut state =
V01State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let addresses = vec![Address::new([1; 32])];
let program_id = Program::extra_output_program().id();
let message =
@ -540,7 +548,8 @@ pub mod tests {
#[test]
fn test_program_should_fail_with_missing_output_accounts() {
let initial_data = [(Address::new([1; 32]), 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let mut state =
V01State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let addresses = vec![Address::new([1; 32]), Address::new([2; 32])];
let program_id = Program::missing_output_program().id();
let message =
@ -556,7 +565,8 @@ pub mod tests {
#[test]
fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_program_owner() {
let initial_data = [(Address::new([1; 32]), 0)];
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let mut state =
V01State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let address = Address::new([1; 32]);
let account = state.get_account_by_address(&address);
// Assert the target account only differs from the default account in the program owner field
@ -578,7 +588,7 @@ pub mod tests {
#[test]
fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_balance() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data)
let mut state = V01State::new_with_genesis_accounts(&initial_data, &[])
.with_test_programs()
.with_non_default_accounts_but_default_program_owners();
let address = Address::new([255; 32]);
@ -602,7 +612,7 @@ pub mod tests {
#[test]
fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_nonce() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data)
let mut state = V01State::new_with_genesis_accounts(&initial_data, &[])
.with_test_programs()
.with_non_default_accounts_but_default_program_owners();
let address = Address::new([254; 32]);
@ -626,7 +636,7 @@ pub mod tests {
#[test]
fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_data() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data)
let mut state = V01State::new_with_genesis_accounts(&initial_data, &[])
.with_test_programs()
.with_non_default_accounts_but_default_program_owners();
let address = Address::new([253; 32]);
@ -650,7 +660,8 @@ pub mod tests {
#[test]
fn test_program_should_fail_if_transfers_balance_from_non_owned_account() {
let initial_data = [(Address::new([1; 32]), 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let mut state =
V01State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let sender_address = Address::new([1; 32]);
let receiver_address = Address::new([2; 32]);
let balance_to_move: u128 = 1;
@ -677,7 +688,7 @@ pub mod tests {
#[test]
fn test_program_should_fail_if_modifies_data_of_non_owned_account() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data)
let mut state = V01State::new_with_genesis_accounts(&initial_data, &[])
.with_test_programs()
.with_non_default_accounts_but_default_program_owners();
let address = Address::new([255; 32]);
@ -701,7 +712,8 @@ pub mod tests {
#[test]
fn test_program_should_fail_if_does_not_preserve_total_balance_by_minting() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let mut state =
V01State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let address = Address::new([1; 32]);
let program_id = Program::minter().id();
@ -718,7 +730,7 @@ pub mod tests {
#[test]
fn test_program_should_fail_if_does_not_preserve_total_balance_by_burning() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data)
let mut state = V01State::new_with_genesis_accounts(&initial_data, &[])
.with_test_programs()
.with_account_owned_by_burner_program();
let program_id = Program::burner().id();
@ -942,7 +954,7 @@ pub mod tests {
let sender_keys = test_public_account_keys_1();
let recipient_keys = test_private_account_keys_1();
let mut state = V01State::new_with_genesis_accounts(&[(sender_keys.address(), 200)]);
let mut state = V01State::new_with_genesis_accounts(&[(sender_keys.address(), 200)], &[]);
let balance_to_move = 37;
@ -988,7 +1000,7 @@ pub mod tests {
};
let recipient_keys = test_private_account_keys_2();
let mut state = V01State::new_with_genesis_accounts(&[])
let mut state = V01State::new_with_genesis_accounts(&[], &[])
.with_private_account(&sender_keys, &sender_private_account);
let balance_to_move = 37;
@ -1053,10 +1065,10 @@ pub mod tests {
};
let recipient_keys = test_public_account_keys_1();
let recipient_initial_balance = 400;
let mut state = V01State::new_with_genesis_accounts(&[(
recipient_keys.address(),
recipient_initial_balance,
)])
let mut state = V01State::new_with_genesis_accounts(
&[(recipient_keys.address(), recipient_initial_balance)],
&[],
)
.with_private_account(&sender_keys, &sender_private_account);
let balance_to_move = 37;

View File

@ -11,6 +11,7 @@ rand.workspace = true
tempfile.workspace = true
chrono.workspace = true
log.workspace = true
nssa-core = { path = "../nssa/core", features = ["host"] }
[dependencies.storage]
path = "../storage"

View File

@ -9,6 +9,13 @@ pub struct AccountInitialData {
pub balance: u128,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
///Helperstruct to initialize commitments
pub struct CommitmentsInitialData {
pub npk: nssa_core::NullifierPublicKey,
pub account: nssa_core::account::Account,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct SequencerConfig {
///Home dir of sequencer storage
@ -27,6 +34,8 @@ pub struct SequencerConfig {
pub port: u16,
///List of initial accounts data
pub initial_accounts: Vec<AccountInitialData>,
///List of initial commitments
pub initial_commitments: Vec<CommitmentsInitialData>,
///Sequencer own signing key
pub signing_key: [u8; 32],
}

View File

@ -49,12 +49,27 @@ impl std::error::Error for TransactionMalformationErrorKind {}
impl SequencerCore {
pub fn start_from_config(config: SequencerConfig) -> Self {
let mut initial_commitments = vec![];
for init_comm_data in config.initial_commitments.clone() {
let npk = init_comm_data.npk;
let mut acc = init_comm_data.account;
acc.program_owner = nssa::program::Program::authenticated_transfer_program().id();
let comm = nssa_core::Commitment::new(&npk, &acc);
initial_commitments.push(comm);
}
Self {
store: SequecerChainStore::new_with_genesis(
&config.home,
config.genesis_id,
config.is_genesis_random,
&config.initial_accounts,
&initial_commitments,
nssa::PrivateKey::try_new(config.signing_key).unwrap(),
),
mempool: MemPool::default(),
@ -209,6 +224,7 @@ mod tests {
block_create_timeout_millis: 1000,
port: 8080,
initial_accounts,
initial_commitments: vec![],
signing_key: *sequencer_sign_key_for_testing().value(),
}
}

View File

@ -20,6 +20,7 @@ impl SequecerChainStore {
genesis_id: u64,
is_genesis_random: bool,
initial_accounts: &[AccountInitialData],
initial_commitments: &[nssa_core::Commitment],
signing_key: nssa::PrivateKey,
) -> Self {
let init_accs: Vec<(Address, u128)> = initial_accounts
@ -28,11 +29,12 @@ impl SequecerChainStore {
.collect();
#[cfg(not(feature = "testnet"))]
let state = nssa::V01State::new_with_genesis_accounts(&init_accs);
let state = nssa::V01State::new_with_genesis_accounts(&init_accs, initial_commitments);
#[cfg(feature = "testnet")]
let state = {
let mut this = nssa::V01State::new_with_genesis_accounts(&init_accs);
let mut this =
nssa::V01State::new_with_genesis_accounts(&init_accs, initial_commitments);
this.add_pinata_program("cafe".repeat(16).parse().unwrap());
this
};

View File

@ -12,6 +12,7 @@ actix-cors.workspace = true
futures.workspace = true
hex.workspace = true
tempfile.workspace = true
nssa-core = { path = "../nssa/core", features = ["host"] }
base64.workspace = true
actix-web.workspace = true

View File

@ -14,7 +14,8 @@ use common::{
requests::{
GetAccountBalanceRequest, GetAccountBalanceResponse, GetAccountRequest,
GetAccountResponse, GetAccountsNoncesRequest, GetAccountsNoncesResponse,
GetInitialTestnetAccountsRequest, GetTransactionByHashRequest,
GetInitialTestnetAccountsRequest, GetProofByCommitmentRequest,
GetProofByCommitmentResponse, GetTransactionByHashRequest,
GetTransactionByHashResponse,
},
},
@ -38,6 +39,7 @@ pub const GET_ACCOUNT_BALANCE: &str = "get_account_balance";
pub const GET_TRANSACTION_BY_HASH: &str = "get_transaction_by_hash";
pub const GET_ACCOUNTS_NONCES: &str = "get_accounts_nonces";
pub const GET_ACCOUNT: &str = "get_account";
pub const GET_PROOF_FOR_COMMITMENT: &str = "get_proof_for_commitment";
pub const HELLO_FROM_SEQUENCER: &str = "HELLO_FROM_SEQUENCER";
@ -250,6 +252,21 @@ impl JsonHandler {
respond(helperstruct)
}
/// Returns the commitment proof, corresponding to commitment
async fn process_get_proof_by_commitment(&self, request: Request) -> Result<Value, RpcErr> {
let get_proof_req = GetProofByCommitmentRequest::parse(Some(request.params))?;
let membership_proof = {
let state = self.sequencer_state.lock().await;
state
.store
.state
.get_proof_for_commitment(&get_proof_req.commitment)
};
let helperstruct = GetProofByCommitmentResponse { membership_proof };
respond(helperstruct)
}
pub async fn process_request_internal(&self, request: Request) -> Result<Value, RpcErr> {
match request.method.as_ref() {
HELLO => self.process_temp_hello(request).await,
@ -262,6 +279,7 @@ impl JsonHandler {
GET_ACCOUNTS_NONCES => self.process_get_accounts_nonces(request).await,
GET_ACCOUNT => self.process_get_account(request).await,
GET_TRANSACTION_BY_HASH => self.process_get_transaction_by_hash(request).await,
GET_PROOF_FOR_COMMITMENT => self.process_get_proof_by_commitment(request).await,
_ => Err(RpcErr(RpcError::method_not_found(request.method))),
}
}
@ -320,6 +338,7 @@ mod tests {
block_create_timeout_millis: 1000,
port: 8080,
initial_accounts,
initial_commitments: vec![],
signing_key: *sequencer_sign_key_for_testing().value(),
}
}

View File

@ -14,7 +14,9 @@ tempfile.workspace = true
clap.workspace = true
nssa-core = { path = "../nssa/core" }
base64.workspace = true
k256 = { version = "0.13.3" }
bytemuck = "1.23.2"
hex.workspace = true
[dependencies.key_protocol]
path = "../key_protocol"

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,23 +12,50 @@ 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) {
pub fn insert_private_account_data(
&mut self,
addr: nssa::Address,
account: nssa_core::account::Account,
) {
self.user_data
.pub_account_signing_keys
.insert(acc_data.address, acc_data.pub_sign_key);
.user_private_accounts
.entry(addr)
.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.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));
}
}
}
}
@ -42,24 +69,16 @@ mod tests {
fn create_initial_accounts() -> Vec<InitialAccountData> {
let initial_acc1 = serde_json::from_str(r#"{
"address": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
"pub_sign_key": [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],
"account": {
"program_owner": [0,0,0,0,0,0,0,0],
"balance": 100,
"nonce": 0,
"data": []
"Public": {
"address": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
"pub_sign_key": [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]
}
}"#).unwrap();
let initial_acc2 = serde_json::from_str(r#"{
"address": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
"pub_sign_key": [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
"account": {
"program_owner": [0,0,0,0,0,0,0,0],
"balance": 100,
"nonce": 0,
"data": []
"Public": {
"address": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
"pub_sign_key": [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
}
}"#).unwrap();

View File

@ -1,19 +1,93 @@
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,
}
//Big difference in enum variants sizes
//however it is improbable, that we will have that much accounts, that it will substantialy affect memory
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum InitialAccountData {
Public(InitialAccountDataPublic),
Private(InitialAccountDataPrivate),
}
//Big difference in enum variants sizes
//however it is improbable, that we will have that much accounts, that it will substantialy affect memory
#[allow(clippy::large_enum_variant)]
#[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

@ -8,7 +8,10 @@ use serde::Serialize;
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.
@ -56,10 +59,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 {
address: *addr,
pub_sign_key: key.clone(),
});
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

@ -11,10 +11,9 @@ use anyhow::Result;
use chain_storage::WalletChainStore;
use config::WalletConfig;
use log::info;
use nssa::Address;
use nssa::{Account, Address};
use clap::{Parser, Subcommand};
use nssa_core::account::Account;
use crate::{
helperfunctions::{
@ -24,12 +23,15 @@ use crate::{
poller::TxPoller,
};
//
pub const HOME_DIR_ENV_VAR: &str = "NSSA_WALLET_HOME_DIR";
pub mod chain_storage;
pub mod config;
pub mod helperfunctions;
pub mod poller;
pub mod token_transfers;
pub struct WalletCore {
pub storage: WalletChainStore,
@ -72,19 +74,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 claim_pinata(
@ -105,48 +104,6 @@ impl WalletCore {
Ok(self.sequencer_client.send_tx_public(tx).await?)
}
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_new_token_definition(
&self,
definition_address: Address,
@ -229,7 +186,7 @@ impl WalletCore {
}
///Poll transactions
pub async fn poll_public_native_token_transfer(&self, hash: String) -> Result<NSSATransaction> {
pub async fn poll_native_token_transfer(&self, hash: String) -> Result<NSSATransaction> {
let transaction_encoded = self.poller.poll_tx(hash).await?;
let tx_base64_decode = BASE64.decode(transaction_encoded)?;
let pub_tx = EncodedTransaction::from_bytes(tx_base64_decode);
@ -243,7 +200,9 @@ impl WalletCore {
#[clap(about)]
pub enum Command {
///Send native token transfer from `from` to `to` for `amount`
SendNativeTokenTransfer {
///
/// Public operation
SendNativeTokenTransferPublic {
///from - valid 32 byte hex string
#[arg(long)]
from: String,
@ -254,8 +213,98 @@ pub enum Command {
#[arg(long)]
amount: u128,
},
///Register new account
RegisterAccount {},
///Send native token transfer from `from` to `to` for `amount`
///
/// Private operation
SendNativeTokenTransferPrivate {
///from - valid 32 byte hex string
#[arg(long)]
from: String,
///to - valid 32 byte hex string
#[arg(long)]
to: String,
///amount - amount of balance to move
#[arg(long)]
amount: u128,
},
///Send native token transfer from `from` to `to` for `amount`
///
/// Private operation
SendNativeTokenTransferPrivateForeignAccount {
///from - valid 32 byte hex string
#[arg(long)]
from: String,
///to_npk - valid 32 byte hex string
#[arg(long)]
to_npk: String,
///to_ipk - valid 33 byte hex string
#[arg(long)]
to_ipk: String,
///amount - amount of balance to move
#[arg(long)]
amount: u128,
},
///Send native token transfer from `from` to `to` for `amount`
///
/// Deshielded operation
SendNativeTokenTransferDeshielded {
///from - valid 32 byte hex string
#[arg(long)]
from: String,
///to - valid 32 byte hex string
#[arg(long)]
to: String,
///amount - amount of balance to move
#[arg(long)]
amount: u128,
},
///Send native token transfer from `from` to `to` for `amount`
///
/// Shielded operation
SendNativeTokenTransferShielded {
///from - valid 32 byte hex string
#[arg(long)]
from: String,
///to - valid 32 byte hex string
#[arg(long)]
to: String,
///amount - amount of balance to move
#[arg(long)]
amount: u128,
},
///Send native token transfer from `from` to `to` for `amount`
///
/// Shielded operation
SendNativeTokenTransferShieldedForeignAccount {
///from - valid 32 byte hex string
#[arg(long)]
from: String,
///to_npk - valid 32 byte hex string
#[arg(long)]
to_npk: String,
///to_ipk - valid 33 byte hex string
#[arg(long)]
to_ipk: String,
///amount - amount of balance to move
#[arg(long)]
amount: u128,
},
///Claim account `acc_addr` generated in transaction `tx_hash`, using secret `sh_secret` at ciphertext id `ciph_id`
ClaimPrivateAccount {
///tx_hash - valid 32 byte hex string
#[arg(long)]
tx_hash: String,
///acc_addr - valid 32 byte hex string
#[arg(long)]
acc_addr: String,
///ciph_id - id of cipher in transaction
#[arg(long)]
ciph_id: usize,
},
///Register new public account
RegisterAccountPublic {},
///Register new private account
RegisterAccountPrivate {},
///Fetch transaction by `hash`
FetchTx {
#[arg(short, long)]
@ -320,12 +369,19 @@ pub struct Args {
pub command: Command,
}
pub async fn execute_subcommand(command: Command) -> Result<()> {
#[derive(Debug, Clone)]
pub enum SubcommandReturnValue {
PrivacyPreservingTransfer { tx_hash: String },
RegisterAccount { addr: nssa::Address },
Empty,
}
pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValue> {
let wallet_config = fetch_config()?;
let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config)?;
match command {
Command::SendNativeTokenTransfer { from, to, amount } => {
let subcommand_ret = match command {
Command::SendNativeTokenTransferPublic { from, to, amount } => {
let from = produce_account_addr_from_hex(from)?;
let to = produce_account_addr_from_hex(to)?;
@ -335,17 +391,336 @@ pub async fn execute_subcommand(command: Command) -> Result<()> {
println!("Results of tx send is {res:#?}");
let transfer_tx = wallet_core
.poll_public_native_token_transfer(res.tx_hash)
.await?;
let transfer_tx = wallet_core.poll_native_token_transfer(res.tx_hash).await?;
println!("Transaction data is {transfer_tx:?}");
let path = wallet_core.store_persistent_accounts()?;
println!("Stored persistent accounts at {path:#?}");
SubcommandReturnValue::Empty
}
Command::RegisterAccount {} => {
let addr = wallet_core.create_new_account();
wallet_core.store_persistent_accounts()?;
Command::SendNativeTokenTransferPrivate { from, to, amount } => {
let from = produce_account_addr_from_hex(from)?;
let to = produce_account_addr_from_hex(to)?;
let (res, secret) = wallet_core
.send_private_native_token_transfer(from, to, amount)
.await?;
println!("Results of tx send is {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let from_ebc = tx.message.encrypted_private_post_states[0].clone();
let from_comm = tx.message.new_commitments[0].clone();
let to_ebc = tx.message.encrypted_private_post_states[1].clone();
let to_comm = tx.message.new_commitments[1].clone();
let res_acc_from = nssa_core::EncryptionScheme::decrypt(
&from_ebc.ciphertext,
&secret,
&from_comm,
0,
)
.unwrap();
let res_acc_to =
nssa_core::EncryptionScheme::decrypt(&to_ebc.ciphertext, &secret, &to_comm, 1)
.unwrap();
println!("Received new from acc {res_acc_from:#?}");
println!("Received new to acc {res_acc_to:#?}");
println!("Transaction data is {:?}", tx.message);
wallet_core
.storage
.insert_private_account_data(from, res_acc_from);
wallet_core
.storage
.insert_private_account_data(to, res_acc_to);
}
let path = wallet_core.store_persistent_accounts()?;
println!("Stored persistent accounts at {path:#?}");
SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }
}
Command::SendNativeTokenTransferPrivateForeignAccount {
from,
to_npk,
to_ipk,
amount,
} => {
let from = produce_account_addr_from_hex(from)?;
let to_npk_res = hex::decode(to_npk)?;
let mut to_npk = [0; 32];
to_npk.copy_from_slice(&to_npk_res);
let to_npk = nssa_core::NullifierPublicKey(to_npk);
let to_ipk_res = hex::decode(to_ipk)?;
let mut to_ipk = [0u8; 33];
to_ipk.copy_from_slice(&to_ipk_res);
let to_ipk =
nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_ipk.to_vec());
let (res, secret) = wallet_core
.send_private_native_token_transfer_outer_account(from, to_npk, to_ipk, amount)
.await?;
println!("Results of tx send is {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let from_ebc = tx.message.encrypted_private_post_states[0].clone();
let from_comm = tx.message.new_commitments[0].clone();
let to_ebc = tx.message.encrypted_private_post_states[1].clone();
let to_comm = tx.message.new_commitments[1].clone();
let res_acc_from = nssa_core::EncryptionScheme::decrypt(
&from_ebc.ciphertext,
&secret,
&from_comm,
0,
)
.unwrap();
let res_acc_to =
nssa_core::EncryptionScheme::decrypt(&to_ebc.ciphertext, &secret, &to_comm, 1)
.unwrap();
println!("RES acc {res_acc_from:#?}");
println!("RES acc to {res_acc_to:#?}");
println!("Transaction data is {:?}", tx.message);
wallet_core
.storage
.insert_private_account_data(from, res_acc_from);
}
let path = wallet_core.store_persistent_accounts()?;
println!("Stored persistent accounts at {path:#?}");
SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }
}
Command::SendNativeTokenTransferDeshielded { from, to, amount } => {
let from = produce_account_addr_from_hex(from)?;
let to = produce_account_addr_from_hex(to)?;
let (res, secret) = wallet_core
.send_deshielded_native_token_transfer(from, to, amount)
.await?;
println!("Results of tx send is {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let from_ebc = tx.message.encrypted_private_post_states[0].clone();
let from_comm = tx.message.new_commitments[0].clone();
let res_acc_from = nssa_core::EncryptionScheme::decrypt(
&from_ebc.ciphertext,
&secret,
&from_comm,
0,
)
.unwrap();
println!("RES acc {res_acc_from:#?}");
println!("Transaction data is {:?}", tx.message);
wallet_core
.storage
.insert_private_account_data(from, res_acc_from);
}
let path = wallet_core.store_persistent_accounts()?;
println!("Stored persistent accounts at {path:#?}");
SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }
}
Command::SendNativeTokenTransferShielded { from, to, amount } => {
let from = produce_account_addr_from_hex(from)?;
let to = produce_account_addr_from_hex(to)?;
let (res, secret) = wallet_core
.send_shiedled_native_token_transfer(from, to, amount)
.await?;
println!("Results of tx send is {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let to_ebc = tx.message.encrypted_private_post_states[0].clone();
let to_comm = tx.message.new_commitments[0].clone();
let res_acc_to =
nssa_core::EncryptionScheme::decrypt(&to_ebc.ciphertext, &secret, &to_comm, 0)
.unwrap();
println!("RES acc to {res_acc_to:#?}");
println!("Transaction data is {:?}", tx.message);
}
let path = wallet_core.store_persistent_accounts()?;
println!("Stored persistent accounts at {path:#?}");
SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }
}
Command::SendNativeTokenTransferShieldedForeignAccount {
from,
to_npk,
to_ipk,
amount,
} => {
let from = produce_account_addr_from_hex(from)?;
let to_npk_res = hex::decode(to_npk)?;
let mut to_npk = [0; 32];
to_npk.copy_from_slice(&to_npk_res);
let to_npk = nssa_core::NullifierPublicKey(to_npk);
let to_ipk_res = hex::decode(to_ipk)?;
let mut to_ipk = [0u8; 33];
to_ipk.copy_from_slice(&to_ipk_res);
let to_ipk =
nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_ipk.to_vec());
let (res, secret) = wallet_core
.send_shielded_native_token_transfer_maybe_outer_account(
from, to_npk, to_ipk, amount,
)
.await?;
println!("Results of tx send is {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let to_ebc = tx.message.encrypted_private_post_states[0].clone();
let to_comm = tx.message.new_commitments[0].clone();
let res_acc_to =
nssa_core::EncryptionScheme::decrypt(&to_ebc.ciphertext, &secret, &to_comm, 0)
.unwrap();
println!("RES acc to {res_acc_to:#?}");
println!("Transaction data is {:?}", tx.message);
}
let path = wallet_core.store_persistent_accounts()?;
println!("Stored persistent accounts at {path:#?}");
SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }
}
Command::ClaimPrivateAccount {
tx_hash,
acc_addr,
ciph_id,
} => {
let acc_addr = produce_account_addr_from_hex(acc_addr)?;
let account_key_chain = wallet_core
.storage
.user_data
.user_private_accounts
.get(&acc_addr);
let Some((account_key_chain, _)) = account_key_chain else {
anyhow::bail!("Account not found");
};
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let to_ebc = tx.message.encrypted_private_post_states[ciph_id].clone();
let to_comm = tx.message.new_commitments[ciph_id].clone();
let shared_secret = account_key_chain.calculate_shared_secret_receiver(to_ebc.epk);
let res_acc_to = nssa_core::EncryptionScheme::decrypt(
&to_ebc.ciphertext,
&shared_secret,
&to_comm,
ciph_id as u32,
)
.unwrap();
println!("RES acc to {res_acc_to:#?}");
println!("Transaction data is {:?}", tx.message);
wallet_core
.storage
.insert_private_account_data(acc_addr, res_acc_to);
}
let path = wallet_core.store_persistent_accounts()?;
println!("Stored persistent accounts at {path:#?}");
SubcommandReturnValue::Empty
}
Command::RegisterAccountPublic {} => {
let addr = wallet_core.create_new_account_public();
println!("Generated new account with addr {addr}");
let path = wallet_core.store_persistent_accounts()?;
println!("Stored persistent accounts at {path:#?}");
SubcommandReturnValue::RegisterAccount { addr }
}
Command::RegisterAccountPrivate {} => {
let addr = wallet_core.create_new_account_private();
let (key, account) = wallet_core
.storage
.user_data
.get_private_account(&addr)
.unwrap();
println!("Generated new account with addr {addr:#?}");
println!("With key {key:#?}");
println!("With account {account:#?}");
let path = wallet_core.store_persistent_accounts()?;
println!("Stored persistent accounts at {path:#?}");
SubcommandReturnValue::RegisterAccount { addr }
}
Command::FetchTx { tx_hash } => {
let tx_obj = wallet_core
@ -354,23 +729,31 @@ pub async fn execute_subcommand(command: Command) -> Result<()> {
.await?;
println!("Transaction object {tx_obj:#?}");
SubcommandReturnValue::Empty
}
Command::GetAccountBalance { addr } => {
let addr = Address::from_str(&addr)?;
let balance = wallet_core.get_account_balance(addr).await?;
println!("Accounts {addr} balance is {balance}");
SubcommandReturnValue::Empty
}
Command::GetAccountNonce { addr } => {
let addr = Address::from_str(&addr)?;
let nonce = wallet_core.get_accounts_nonces(vec![addr]).await?[0];
println!("Accounts {addr} nonce is {nonce}");
SubcommandReturnValue::Empty
}
Command::GetAccount { addr } => {
let addr: Address = addr.parse()?;
let account: HumanReadableAccount = wallet_core.get_account(addr).await?.into();
println!("{}", serde_json::to_string(&account).unwrap());
SubcommandReturnValue::Empty
}
Command::CreateNewToken {
definition_addr,
@ -393,6 +776,7 @@ pub async fn execute_subcommand(command: Command) -> Result<()> {
total_supply,
)
.await?;
SubcommandReturnValue::Empty
}
Command::TransferToken {
sender_addr,
@ -406,6 +790,7 @@ pub async fn execute_subcommand(command: Command) -> Result<()> {
balance_to_move,
)
.await?;
SubcommandReturnValue::Empty
}
Command::ClaimPinata {
pinata_addr,
@ -420,8 +805,10 @@ pub async fn execute_subcommand(command: Command) -> Result<()> {
)
.await?;
info!("Results of tx send is {res:#?}");
}
}
Ok(())
SubcommandReturnValue::Empty
}
};
Ok(subcommand_ret)
}

View File

@ -0,0 +1,102 @@
use common::{ExecutionFailureKind, sequencer_client::json::SendTxResponse};
use k256::elliptic_curve::rand_core::{OsRng, RngCore};
use nssa::Address;
use nssa_core::{SharedSecretKey, encryption::EphemeralPublicKey};
use crate::WalletCore;
impl WalletCore {
pub async fn send_deshielded_native_token_transfer(
&self,
from: Address,
to: Address,
balance_to_move: u128,
) -> Result<(SendTxResponse, nssa_core::SharedSecretKey), ExecutionFailureKind> {
let from_data = self.storage.user_data.get_private_account(&from).cloned();
let to_data = self.get_account(to).await;
let Some((from_keys, mut from_acc)) = from_data else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let Ok(to_acc) = to_data else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
if from_acc.balance >= balance_to_move {
let program = nssa::program::Program::authenticated_transfer_program();
from_acc.program_owner = program.id();
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,
account_id: (&from_keys.nullifer_public_key).into(),
};
let recipient_pre = nssa_core::account::AccountWithMetadata {
account: to_acc.clone(),
is_authorized: false,
account_id: (&to).into(),
};
//Move into different function
let mut esk = [0; 32];
OsRng.fill_bytes(&mut esk);
let shared_secret = SharedSecretKey::new(&esk, &from_keys.incoming_viewing_public_key);
let epk = EphemeralPublicKey::from_scalar(esk);
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],
vec![],
vec![(
from_keys.nullifer_public_key.clone(),
from_keys.incoming_viewing_public_key.clone(),
epk,
)],
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,
))
} else {
Err(ExecutionFailureKind::InsufficientFundsError)
}
}
}

View File

@ -0,0 +1,4 @@
pub mod deshielded;
pub mod private;
pub mod public;
pub mod shielded;

View File

@ -0,0 +1,234 @@
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_private_native_token_transfer_outer_account(
&self,
from: Address,
to_npk: nssa_core::NullifierPublicKey,
to_ipk: nssa_core::encryption::IncomingViewingPublicKey,
balance_to_move: u128,
) -> Result<(SendTxResponse, nssa_core::SharedSecretKey), ExecutionFailureKind> {
let from_data = self.storage.user_data.get_private_account(&from).cloned();
let Some((from_keys, mut from_acc)) = from_data else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let to_acc = nssa_core::account::Account::default();
if from_acc.balance >= balance_to_move {
let program = nssa::program::Program::authenticated_transfer_program();
from_acc.program_owner = program.id();
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,
account_id: (&from_keys.nullifer_public_key).into(),
};
let recipient_pre = nssa_core::account::AccountWithMetadata {
account: to_acc.clone(),
is_authorized: false,
account_id: (&to_npk).into(),
};
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.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![],
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?,
shared_secret,
))
} else {
Err(ExecutionFailureKind::InsufficientFundsError)
}
}
pub async fn send_private_native_token_transfer(
&self,
from: Address,
to: Address,
balance_to_move: u128,
) -> Result<(SendTxResponse, nssa_core::SharedSecretKey), ExecutionFailureKind> {
let from_data = self.storage.user_data.get_private_account(&from).cloned();
let to_data = self.storage.user_data.get_private_account(&to).cloned();
let Some((from_keys, mut from_acc)) = from_data else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let Some((to_keys, mut to_acc)) = to_data else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let to_npk = to_keys.nullifer_public_key.clone();
let to_ipk = to_keys.incoming_viewing_public_key.clone();
if from_acc.balance >= balance_to_move {
let program = nssa::program::Program::authenticated_transfer_program();
from_acc.program_owner = program.id();
to_acc.program_owner = program.id();
let sender_commitment =
nssa_core::Commitment::new(&from_keys.nullifer_public_key, &from_acc);
let receiver_commitment =
nssa_core::Commitment::new(&to_keys.nullifer_public_key, &to_acc);
let sender_pre = nssa_core::account::AccountWithMetadata {
account: from_acc.clone(),
is_authorized: true,
account_id: (&from_keys.nullifer_public_key).into(),
};
let recipient_pre = nssa_core::account::AccountWithMetadata {
account: to_acc.clone(),
is_authorized: true,
account_id: (&to_npk).into(),
};
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, 1],
&[from_acc.nonce + 1, to_acc.nonce + 1],
&[
(from_keys.nullifer_public_key.clone(), shared_secret.clone()),
(to_npk.clone(), shared_secret.clone()),
],
&[
(
from_keys.private_key_holder.nullifier_secret_key,
self.sequencer_client
.get_proof_for_commitment(sender_commitment)
.await
.unwrap()
.unwrap(),
),
(
to_keys.private_key_holder.nullifier_secret_key,
self.sequencer_client
.get_proof_for_commitment(receiver_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?,
shared_secret,
))
} else {
Err(ExecutionFailureKind::InsufficientFundsError)
}
}
}

View File

@ -0,0 +1,48 @@
use common::{ExecutionFailureKind, sequencer_client::json::SendTxResponse};
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)
}
}
}

View File

@ -0,0 +1,181 @@
use common::{ExecutionFailureKind, sequencer_client::json::SendTxResponse};
use key_protocol::key_management::ephemeral_key_holder::produce_one_sided_shared_secret_receiver;
use nssa::Address;
use crate::WalletCore;
impl WalletCore {
pub async fn send_shiedled_native_token_transfer(
&self,
from: Address,
to: Address,
balance_to_move: u128,
) -> Result<(SendTxResponse, nssa_core::SharedSecretKey), ExecutionFailureKind> {
let from_data = self.get_account(from).await;
let to_data = self.storage.user_data.get_private_account(&to).cloned();
let Ok(from_acc) = from_data else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let Some((to_keys, mut to_acc)) = to_data else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let to_npk = to_keys.nullifer_public_key.clone();
let to_ipk = to_keys.incoming_viewing_public_key.clone();
if from_acc.balance >= balance_to_move {
let program = nssa::program::Program::authenticated_transfer_program();
to_acc.program_owner = program.id();
let receiver_commitment =
nssa_core::Commitment::new(&to_keys.nullifer_public_key, &to_acc);
let sender_pre = nssa_core::account::AccountWithMetadata {
account: from_acc.clone(),
is_authorized: true,
account_id: (&from).into(),
};
let recipient_pre = nssa_core::account::AccountWithMetadata {
account: to_acc.clone(),
is_authorized: true,
account_id: (&to_npk).into(),
};
let (shared_secret, epk) = produce_one_sided_shared_secret_receiver(&to_ipk);
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, 1],
&[to_acc.nonce + 1],
&[(to_npk.clone(), shared_secret.clone())],
&[(
to_keys.private_key_holder.nullifier_secret_key,
self.sequencer_client
.get_proof_for_commitment(receiver_commitment)
.await
.unwrap()
.unwrap(),
)],
&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(), epk)],
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?,
shared_secret,
))
} 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,
) -> Result<(SendTxResponse, nssa_core::SharedSecretKey), ExecutionFailureKind> {
let from_data = self.get_account(from).await;
let Ok(from_acc) = from_data else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
let to_acc = nssa_core::account::Account::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,
account_id: (&from).into(),
};
let recipient_pre = nssa_core::account::AccountWithMetadata {
account: to_acc.clone(),
is_authorized: false,
account_id: (&to_npk).into(),
};
let (shared_secret, epk) = produce_one_sided_shared_secret_receiver(&to_ipk);
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.clone())],
&[],
&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(), epk)],
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?,
shared_secret,
))
} else {
Err(ExecutionFailureKind::InsufficientFundsError)
}
}
}