Merge branch 'main' into Pravdyvy/serde-json-removal-from-db

This commit is contained in:
Pravdyvy 2025-10-06 11:43:03 +03:00 committed by GitHub
commit 468b669c14
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
53 changed files with 4036 additions and 710 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,6 +41,7 @@ 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"

8
ci_scripts/test-ubuntu.sh Normal file → Executable file
View File

@ -5,7 +5,15 @@ curl -L https://risczero.com/install | bash
source env.sh
RISC0_DEV_MODE=1 cargo test --release
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"] }
borsh.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

@ -8,12 +8,15 @@ anyhow.workspace = true
env_logger.workspace = true
log.workspace = true
actix.workspace = true
bytemuck = "1.23.2"
actix-web.workspace = true
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();
@ -292,7 +304,629 @@ pub async fn test_get_account() {
assert_eq!(account.nonce, 0);
}
/// This test creates a new token using the token program. After creating the token, the test executes a
/// token transfer to a new account.
pub async fn test_success_token_program() {
let wallet_config = fetch_config().unwrap();
// Create new account for the token definition
wallet::execute_subcommand(Command::RegisterAccountPublic {})
.await
.unwrap();
// Create new account for the token supply holder
wallet::execute_subcommand(Command::RegisterAccountPublic {})
.await
.unwrap();
// Create new account for receiving a token transaction
wallet::execute_subcommand(Command::RegisterAccountPublic {})
.await
.unwrap();
let persistent_accounts = fetch_persistent_accounts().unwrap();
let mut new_persistent_accounts_addr = Vec::new();
for per_acc in persistent_accounts {
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,
}
}
let [definition_addr, supply_addr, recipient_addr] = new_persistent_accounts_addr
.try_into()
.expect("Failed to produce new account, not present in persistent accounts");
// Create new token
let command = Command::CreateNewToken {
definition_addr: definition_addr.to_string(),
supply_addr: supply_addr.to_string(),
name: "A NAME".to_string(),
total_supply: 37,
};
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 seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
// Check the status of the token definition account is the expected after the execution
let definition_acc = seq_client
.get_account(definition_addr.to_string())
.await
.unwrap()
.account;
assert_eq!(definition_acc.program_owner, Program::token().id());
// The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ]
assert_eq!(
definition_acc.data,
vec![
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
);
// Check the status of the token holding account with the total supply is the expected after the execution
let supply_acc = seq_client
.get_account(supply_addr.to_string())
.await
.unwrap()
.account;
// The account must be owned by the token program
assert_eq!(supply_acc.program_owner, Program::token().id());
// The data of a token definition account has the following layout:
// [ 0x01 || corresponding_token_definition_id (32 bytes) || balance (little endian 16 bytes) ]
// First byte of the data equal to 1 means it's a token holding account
assert_eq!(supply_acc.data[0], 1);
// Bytes from 1 to 33 represent the id of the token this account is associated with.
// In this example, this is a token account of the newly created token, so it is expected
// to be equal to the address of the token definition account.
assert_eq!(
&supply_acc.data[1..33],
nssa::AccountId::from(&definition_addr).to_bytes()
);
assert_eq!(
u128::from_le_bytes(supply_acc.data[33..].try_into().unwrap()),
37
);
// Transfer 7 tokens from `supply_acc` to the account at address `recipient_addr`
let command = Command::TransferToken {
sender_addr: supply_addr.to_string(),
recipient_addr: recipient_addr.to_string(),
balance_to_move: 7,
};
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;
// Check the status of the account at `supply_addr` is the expected after the execution
let supply_acc = seq_client
.get_account(supply_addr.to_string())
.await
.unwrap()
.account;
// The account must be owned by the token program
assert_eq!(supply_acc.program_owner, Program::token().id());
// First byte equal to 1 means it's a token holding account
assert_eq!(supply_acc.data[0], 1);
// Bytes from 1 to 33 represent the id of the token this account is associated with.
assert_eq!(
&supply_acc.data[1..33],
nssa::AccountId::from(&definition_addr).to_bytes()
);
assert_eq!(
u128::from_le_bytes(supply_acc.data[33..].try_into().unwrap()),
30
);
// Check the status of the account at `recipient_addr` is the expected after the execution
let recipient_acc = seq_client
.get_account(recipient_addr.to_string())
.await
.unwrap()
.account;
// The account must be owned by the token program
assert_eq!(recipient_acc.program_owner, Program::token().id());
// First byte equal to 1 means it's a token holding account
assert_eq!(recipient_acc.data[0], 1);
// Bytes from 1 to 33 represent the id of the token this account is associated with.
assert_eq!(
&recipient_acc.data[1..33],
nssa::AccountId::from(&definition_addr).to_bytes()
);
assert_eq!(
u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()),
7
);
}
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;
@ -359,6 +993,9 @@ pub async fn main_tests_runner() -> Result<()> {
} = args;
match test_name.as_str() {
"test_success_token_program" => {
test_cleanup_wrap!(home_dir, test_success_token_program);
}
"test_success_move_to_another_account" => {
test_cleanup_wrap!(home_dir, test_success_move_to_another_account);
}
@ -368,12 +1005,54 @@ pub async fn main_tests_runner() -> Result<()> {
"test_failure" => {
test_cleanup_wrap!(home_dir, test_failure);
}
"test_get_account_wallet_command" => {
"test_get_account" => {
test_cleanup_wrap!(home_dir, test_get_account);
}
"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);
}
@ -382,8 +1061,36 @@ pub async fn main_tests_runner() -> Result<()> {
test_cleanup_wrap!(home_dir, test_success);
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

@ -2,6 +2,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;
@ -12,6 +13,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

@ -21,6 +21,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

@ -2,7 +2,7 @@ use crate::program::ProgramId;
use serde::{Deserialize, Serialize};
pub type Nonce = u128;
type Data = Vec<u8>;
pub type Data = Vec<u8>;
/// Account to be used both in public and private contexts
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
@ -14,11 +14,34 @@ pub struct Account {
pub nonce: Nonce,
}
/// A fingerprint of the owner of an account. This can be, for example, an `Address` in case the account
/// is public, or a `NullifierPublicKey` in case the account is private.
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
#[cfg_attr(any(feature = "host", test), derive(Debug))]
pub struct AccountId(pub(super) [u8; 32]);
impl AccountId {
pub fn new(value: [u8; 32]) -> Self {
Self(value)
}
}
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
pub struct AccountWithMetadata {
pub account: Account,
pub is_authorized: bool,
pub account_id: AccountId,
}
#[cfg(feature = "host")]
impl AccountWithMetadata {
pub fn new(account: Account, is_authorized: bool, account_id: impl Into<AccountId>) -> Self {
Self {
account,
is_authorized,
account_id: account_id.into(),
}
}
}
#[cfg(test)]
@ -54,4 +77,21 @@ mod tests {
assert_eq!(new_acc.program_owner, DEFAULT_PROGRAM_ID);
}
#[cfg(feature = "host")]
#[test]
fn test_account_with_metadata_constructor() {
let account = Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 1337,
data: b"testing_account_with_metadata_constructor".to_vec(),
nonce: 0xdeadbeef,
};
let fingerprint = AccountId::new([8; 32]);
let new_acc_with_metadata =
AccountWithMetadata::new(account.clone(), true, fingerprint.clone());
assert_eq!(new_acc_with_metadata.account, account);
assert!(new_acc_with_metadata.is_authorized);
assert_eq!(new_acc_with_metadata.account_id, fingerprint);
}
}

View File

@ -41,7 +41,7 @@ mod tests {
use super::*;
use crate::{
Commitment, Nullifier, NullifierPublicKey,
account::{Account, AccountWithMetadata},
account::{Account, AccountId, AccountWithMetadata},
};
use risc0_zkvm::serde::from_slice;
@ -49,24 +49,26 @@ mod tests {
fn test_privacy_preserving_circuit_output_to_bytes_is_compatible_with_from_slice() {
let output = PrivacyPreservingCircuitOutput {
public_pre_states: vec![
AccountWithMetadata {
account: Account {
AccountWithMetadata::new(
Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 12345678901234567890,
data: b"test data".to_vec(),
nonce: 18446744073709551614,
},
is_authorized: true,
},
AccountWithMetadata {
account: Account {
true,
AccountId::new([0; 32]),
),
AccountWithMetadata::new(
Account {
program_owner: [9, 9, 9, 8, 8, 8, 7, 7],
balance: 123123123456456567112,
data: b"test data".to_vec(),
nonce: 9999999999999999999999,
},
is_authorized: false,
},
false,
AccountId::new([1; 32]),
),
],
public_post_states: vec![Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],

View File

@ -7,6 +7,7 @@ use std::io::Read;
use crate::account::Account;
use crate::account::AccountId;
#[cfg(feature = "host")]
use crate::encryption::shared_key_derivation::Secp256k1Point;
@ -137,6 +138,12 @@ impl Secp256k1Point {
}
}
impl AccountId {
pub fn to_bytes(&self) -> [u8; 32] {
self.0
}
}
#[cfg(test)]
mod tests {
use super::*;

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

@ -1,11 +1,22 @@
use risc0_zkvm::sha::{Impl, Sha256};
use serde::{Deserialize, Serialize};
use crate::Commitment;
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 {
const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/NSSA/v0.1/AccountId/Private/\x00\x00\x00";
let mut bytes = [0; 64];
bytes[0..32].copy_from_slice(PRIVATE_ACCOUNT_ID_PREFIX);
bytes[32..].copy_from_slice(&value.0);
AccountId::new(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap())
}
}
impl AsRef<[u8]> for NullifierPublicKey {
fn as_ref(&self) -> &[u8] {
@ -71,4 +82,21 @@ mod tests {
let npk = NullifierPublicKey::from(&nsk);
assert_eq!(npk, expected_npk);
}
#[test]
fn test_account_id_from_nullifier_public_key() {
let nsk = [
57, 5, 64, 115, 153, 56, 184, 51, 207, 238, 99, 165, 147, 214, 213, 151, 30, 251, 30,
196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28,
];
let npk = NullifierPublicKey::from(&nsk);
let expected_account_id = AccountId::new([
69, 160, 50, 67, 12, 56, 150, 116, 62, 145, 17, 161, 17, 45, 24, 53, 33, 167, 83, 178,
47, 114, 111, 233, 251, 30, 54, 244, 184, 22, 100, 236,
]);
let account_id = AccountId::from(&npk);
assert_eq!(account_id, expected_account_id);
}
}

View File

@ -21,8 +21,8 @@ pub struct ProgramOutput {
pub fn read_nssa_inputs<T: DeserializeOwned>() -> ProgramInput<T> {
let pre_states: Vec<AccountWithMetadata> = env::read();
let words: InstructionData = env::read();
let instruction = T::deserialize(&mut Deserializer::new(words.as_ref())).unwrap();
let instruction_words: InstructionData = env::read();
let instruction = T::deserialize(&mut Deserializer::new(instruction_words.as_ref())).unwrap();
ProgramInput {
pre_states,
instruction,
@ -59,20 +59,23 @@ pub fn validate_execution(
return false;
}
// 3. Ownership change only allowed from default accounts
if pre.account.program_owner != post.program_owner && pre.account != Account::default() {
// 3. Program ownership changes are not allowed
if pre.account.program_owner != post.program_owner {
return false;
}
let account_program_owner = pre.account.program_owner;
// 4. Decreasing balance only allowed if owned by executing program
if post.balance < pre.account.balance && pre.account.program_owner != executing_program_id {
if post.balance < pre.account.balance && account_program_owner != executing_program_id {
return false;
}
// 5. Data changes only allowed if owned by executing program
// 5. Data changes only allowed if owned by executing program or if account pre state has
// default values
if pre.account.data != post.data
&& (executing_program_id != pre.account.program_owner
|| executing_program_id != post.program_owner)
&& pre.account != Account::default()
&& account_program_owner != executing_program_id
{
return false;
}

View File

@ -6,5 +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

@ -1,4 +1,4 @@
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs};
/// A transfer of balance program.
/// To be used both in public and private contexts.

View File

@ -1,12 +1,12 @@
use risc0_zkvm::{guest::env, serde::to_vec};
use nssa_core::{
account::{Account, AccountWithMetadata},
compute_digest_for_path,
encryption::Ciphertext,
program::{validate_execution, ProgramOutput, DEFAULT_PROGRAM_ID},
Commitment, CommitmentSetDigest, EncryptionScheme, Nullifier, NullifierPublicKey,
PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput,
account::{Account, AccountId, AccountWithMetadata},
compute_digest_for_path,
encryption::Ciphertext,
program::{DEFAULT_PROGRAM_ID, ProgramOutput, validate_execution},
};
fn main() {
@ -74,6 +74,10 @@ fn main() {
let new_nonce = private_nonces_iter.next().expect("Missing private nonce");
let (npk, shared_secret) = private_keys_iter.next().expect("Missing keys");
if AccountId::from(npk) != pre_states[i].account_id {
panic!("AccountId mismatch");
}
if visibility_mask[i] == 1 {
// Private account with authentication
let (nsk, membership_proof) =

View File

@ -0,0 +1,554 @@
use nssa_core::{
account::{Account, AccountId, AccountWithMetadata, Data},
program::{ProgramInput, read_nssa_inputs, write_nssa_outputs},
};
// The token program has two functions:
// 1. New token definition.
// Arguments to this function are:
// * Two **default** accounts: [definition_account, holding_account].
// The first default account will be initialized with the token definition account values. The second account will
// be initialized to a token holding account for the new token, holding the entire total supply.
// * An instruction data of 23-bytes, indicating the total supply and the token name, with
// the following layout:
// [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
// The name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
// 2. Token transfer
// Arguments to this function are:
// * Two accounts: [sender_account, recipient_account].
// * An instruction data byte string of length 23, indicating the total supply with the following layout
// [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00].
const TOKEN_DEFINITION_TYPE: u8 = 0;
const TOKEN_DEFINITION_DATA_SIZE: usize = 23;
const TOKEN_HOLDING_TYPE: u8 = 1;
const TOKEN_HOLDING_DATA_SIZE: usize = 49;
struct TokenDefinition {
account_type: u8,
name: [u8; 6],
total_supply: u128,
}
struct TokenHolding {
account_type: u8,
definition_id: AccountId,
balance: u128,
}
impl TokenDefinition {
fn into_data(self) -> Vec<u8> {
let mut bytes = [0; TOKEN_DEFINITION_DATA_SIZE];
bytes[0] = self.account_type;
bytes[1..7].copy_from_slice(&self.name);
bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes());
bytes.into()
}
}
impl TokenHolding {
fn new(definition_id: &AccountId) -> Self {
Self {
account_type: TOKEN_HOLDING_TYPE,
definition_id: definition_id.clone(),
balance: 0,
}
}
fn parse(data: &[u8]) -> Option<Self> {
if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE {
None
} else {
let account_type = data[0];
let definition_id = AccountId::new(data[1..33].try_into().unwrap());
let balance = u128::from_le_bytes(data[33..].try_into().unwrap());
Some(Self {
definition_id,
balance,
account_type,
})
}
}
fn into_data(self) -> Data {
let mut bytes = [0; TOKEN_HOLDING_DATA_SIZE];
bytes[0] = self.account_type;
bytes[1..33].copy_from_slice(&self.definition_id.to_bytes());
bytes[33..].copy_from_slice(&self.balance.to_le_bytes());
bytes.into()
}
}
fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec<Account> {
if pre_states.len() != 2 {
panic!("Invalid number of input accounts");
}
let sender = &pre_states[0];
let recipient = &pre_states[1];
let mut sender_holding =
TokenHolding::parse(&sender.account.data).expect("Invalid sender data");
let mut recipient_holding = if recipient.account == Account::default() {
TokenHolding::new(&sender_holding.definition_id)
} else {
TokenHolding::parse(&recipient.account.data).expect("Invalid recipient data")
};
if sender_holding.definition_id != recipient_holding.definition_id {
panic!("Sender and recipient definition id mismatch");
}
if sender_holding.balance < balance_to_move {
panic!("Insufficient balance");
}
if !sender.is_authorized {
panic!("Sender authorization is missing");
}
sender_holding.balance -= balance_to_move;
recipient_holding.balance = recipient_holding
.balance
.checked_add(balance_to_move)
.expect("Recipient balance overflow.");
let sender_post = {
let mut this = sender.account.clone();
this.data = sender_holding.into_data();
this
};
let recipient_post = {
let mut this = recipient.account.clone();
this.data = recipient_holding.into_data();
this
};
vec![sender_post, recipient_post]
}
fn new_definition(
pre_states: &[AccountWithMetadata],
name: [u8; 6],
total_supply: u128,
) -> Vec<Account> {
if pre_states.len() != 2 {
panic!("Invalid number of input accounts");
}
let definition_target_account = &pre_states[0];
let holding_target_account = &pre_states[1];
if definition_target_account.account != Account::default() {
panic!("Definition target account must have default values");
}
if holding_target_account.account != Account::default() {
panic!("Holding target account must have default values");
}
let token_definition = TokenDefinition {
account_type: TOKEN_DEFINITION_TYPE,
name,
total_supply,
};
let token_holding = TokenHolding {
account_type: TOKEN_HOLDING_TYPE,
definition_id: definition_target_account.account_id.clone(),
balance: total_supply,
};
let mut definition_target_account_post = definition_target_account.account.clone();
definition_target_account_post.data = token_definition.into_data();
let mut holding_target_account_post = holding_target_account.account.clone();
holding_target_account_post.data = token_holding.into_data();
vec![definition_target_account_post, holding_target_account_post]
}
type Instruction = [u8; 23];
fn main() {
let ProgramInput {
pre_states,
instruction,
} = read_nssa_inputs::<Instruction>();
match instruction[0] {
0 => {
// Parse instruction
let total_supply = u128::from_le_bytes(instruction[1..17].try_into().unwrap());
let name: [u8; 6] = instruction[17..].try_into().unwrap();
assert_ne!(name, [0; 6]);
// Execute
let post_states = new_definition(&pre_states, name, total_supply);
write_nssa_outputs(pre_states, post_states);
}
1 => {
// Parse instruction
let balance_to_move = u128::from_le_bytes(instruction[1..17].try_into().unwrap());
let name: [u8; 6] = instruction[17..].try_into().unwrap();
assert_eq!(name, [0; 6]);
// Execute
let post_states = transfer(&pre_states, balance_to_move);
write_nssa_outputs(pre_states, post_states);
}
_ => panic!("Invalid instruction"),
};
}
#[cfg(test)]
mod tests {
use nssa_core::account::{Account, AccountId, AccountWithMetadata};
use crate::{new_definition, transfer, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE};
#[should_panic(expected = "Invalid number of input accounts")]
#[test]
fn test_call_new_definition_with_invalid_number_of_accounts_1() {
let pre_states = vec![AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([1; 32]),
}];
let _post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10);
}
#[should_panic(expected = "Invalid number of input accounts")]
#[test]
fn test_call_new_definition_with_invalid_number_of_accounts_2() {
let pre_states = vec![
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([1; 32]),
},
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([2; 32]),
},
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([3; 32]),
},
];
let _post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10);
}
#[should_panic(expected = "Definition target account must have default values")]
#[test]
fn test_new_definition_non_default_first_account_should_fail() {
let pre_states = vec![
AccountWithMetadata {
account: Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
..Account::default()
},
is_authorized: true,
account_id: AccountId::new([1; 32]),
},
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([2; 32]),
},
];
let _post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10);
}
#[should_panic(expected = "Holding target account must have default values")]
#[test]
fn test_new_definition_non_default_second_account_should_fail() {
let pre_states = vec![
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([1; 32]),
},
AccountWithMetadata {
account: Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
..Account::default()
},
is_authorized: true,
account_id: AccountId::new([2; 32]),
},
];
let _post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10);
}
#[test]
fn test_new_definition_with_valid_inputs_succeeds() {
let pre_states = vec![
AccountWithMetadata {
account: Account::default(),
is_authorized: false,
account_id: AccountId::new([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
]),
},
AccountWithMetadata {
account: Account {
..Account::default()
},
is_authorized: false,
account_id: AccountId::new([2; 32]),
},
];
let post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10);
let [definition_account, holding_account] = post_states.try_into().ok().unwrap();
assert_eq!(
definition_account.data,
vec![
0, 0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
]
);
assert_eq!(
holding_account.data,
vec![
1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
]
);
}
#[should_panic(expected = "Invalid number of input accounts")]
#[test]
fn test_call_transfer_with_invalid_number_of_accounts_1() {
let pre_states = vec![AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([1; 32]),
}];
let _post_states = transfer(&pre_states, 10);
}
#[should_panic(expected = "Invalid number of input accounts")]
#[test]
fn test_call_transfer_with_invalid_number_of_accounts_2() {
let pre_states = vec![
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([1; 32]),
},
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([2; 32]),
},
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([3; 32]),
},
];
let _post_states = transfer(&pre_states, 10);
}
#[should_panic(expected = "Invalid sender data")]
#[test]
fn test_transfer_invalid_instruction_type_should_fail() {
let invalid_type = TOKEN_HOLDING_TYPE ^ 1;
let pre_states = vec![
AccountWithMetadata {
account: Account {
// First byte should be `TOKEN_HOLDING_TYPE` for token holding accounts
data: vec![invalid_type; TOKEN_HOLDING_DATA_SIZE],
..Account::default()
},
is_authorized: true,
account_id: AccountId::new([1; 32]),
},
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([2; 32]),
},
];
let _post_states = transfer(&pre_states, 10);
}
#[should_panic(expected = "Invalid sender data")]
#[test]
fn test_transfer_invalid_data_size_should_fail_1() {
let pre_states = vec![
AccountWithMetadata {
account: Account {
// Data must be of exact length `TOKEN_HOLDING_DATA_SIZE`
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 1],
..Account::default()
},
is_authorized: true,
account_id: AccountId::new([1; 32]),
},
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([2; 32]),
},
];
let _post_states = transfer(&pre_states, 10);
}
#[should_panic(expected = "Invalid sender data")]
#[test]
fn test_transfer_invalid_data_size_should_fail_2() {
let pre_states = vec![
AccountWithMetadata {
account: Account {
// Data must be of exact length `TOKEN_HOLDING_DATA_SIZE`
data: vec![1; TOKEN_HOLDING_DATA_SIZE + 1],
..Account::default()
},
is_authorized: true,
account_id: AccountId::new([1; 32]),
},
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([2; 32]),
},
];
let _post_states = transfer(&pre_states, 10);
}
#[should_panic(expected = "Sender and recipient definition id mismatch")]
#[test]
fn test_transfer_with_different_definition_ids_should_fail() {
let pre_states = vec![
AccountWithMetadata {
account: Account {
data: vec![1; TOKEN_HOLDING_DATA_SIZE],
..Account::default()
},
is_authorized: true,
account_id: AccountId::new([1; 32]),
},
AccountWithMetadata {
account: Account {
data: vec![1]
.into_iter()
.chain(vec![2; TOKEN_HOLDING_DATA_SIZE - 1])
.collect(),
..Account::default()
},
is_authorized: true,
account_id: AccountId::new([2; 32]),
},
];
let _post_states = transfer(&pre_states, 10);
}
#[should_panic(expected = "Insufficient balance")]
#[test]
fn test_transfer_with_insufficient_balance_should_fail() {
let pre_states = vec![
AccountWithMetadata {
account: Account {
// Account with balance 37
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16]
.into_iter()
.chain(u128::to_le_bytes(37))
.collect(),
..Account::default()
},
is_authorized: true,
account_id: AccountId::new([1; 32]),
},
AccountWithMetadata {
account: Account {
data: vec![1; TOKEN_HOLDING_DATA_SIZE],
..Account::default()
},
is_authorized: true,
account_id: AccountId::new([2; 32]),
},
];
// Attempt to transfer 38 tokens
let _post_states = transfer(&pre_states, 38);
}
#[should_panic(expected = "Sender authorization is missing")]
#[test]
fn test_transfer_without_sender_authorization_should_fail() {
let pre_states = vec![
AccountWithMetadata {
account: Account {
// Account with balance 37
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16]
.into_iter()
.chain(u128::to_le_bytes(37))
.collect(),
..Account::default()
},
is_authorized: false,
account_id: AccountId::new([1; 32]),
},
AccountWithMetadata {
account: Account {
data: vec![1; TOKEN_HOLDING_DATA_SIZE],
..Account::default()
},
is_authorized: true,
account_id: AccountId::new([2; 32]),
},
];
let _post_states = transfer(&pre_states, 37);
}
#[test]
fn test_transfer_with_valid_inputs_succeeds() {
let pre_states = vec![
AccountWithMetadata {
account: Account {
// Account with balance 37
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16]
.into_iter()
.chain(u128::to_le_bytes(37))
.collect(),
..Account::default()
},
is_authorized: true,
account_id: AccountId::new([1; 32]),
},
AccountWithMetadata {
account: Account {
// Account with balance 255
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16]
.into_iter()
.chain(u128::to_le_bytes(255))
.collect(),
..Account::default()
},
is_authorized: true,
account_id: AccountId::new([2; 32]),
},
];
let post_states = transfer(&pre_states, 11);
let [sender_post, recipient_post] = post_states.try_into().ok().unwrap();
assert_eq!(
sender_post.data,
vec![
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
);
assert_eq!(
recipient_post.data,
vec![
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
);
}
}

View File

@ -1,6 +1,8 @@
use std::{fmt::Display, str::FromStr};
use nssa_core::account::AccountId;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::signature::PublicKey;
@ -81,8 +83,21 @@ impl<'de> Deserialize<'de> for Address {
}
}
impl From<&Address> for AccountId {
fn from(address: &Address) -> Self {
const PUBLIC_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/NSSA/v0.1/AccountId/Public/\x00\x00\x00\x00";
let mut hasher = Sha256::new();
hasher.update(PUBLIC_ACCOUNT_ID_PREFIX);
hasher.update(address.value);
AccountId::new(hasher.finalize().into())
}
}
#[cfg(test)]
mod tests {
use nssa_core::account::AccountId;
use crate::{Address, address::AddressError};
#[test]
@ -112,4 +127,17 @@ mod tests {
let result = hex_str.parse::<Address>().unwrap_err();
assert!(matches!(result, AddressError::InvalidLength(_)));
}
#[test]
fn test_account_id_from_address() {
let address: Address = "37".repeat(32).parse().unwrap();
let expected_account_id = AccountId::new([
93, 223, 66, 245, 78, 230, 157, 188, 110, 161, 134, 255, 137, 177, 220, 88, 37, 44,
243, 91, 236, 4, 36, 147, 185, 112, 21, 49, 234, 4, 107, 185,
]);
let account_id = AccountId::from(&address);
assert_eq!(account_id, expected_account_id);
}
}

View File

@ -2,14 +2,14 @@ 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;
mod state;
pub use address::Address;
pub use nssa_core::account::Account;
pub use nssa_core::account::{Account, AccountId};
pub use privacy_preserving_transaction::{
PrivacyPreservingTransaction, circuit::execute_and_prove,
};

View File

@ -92,7 +92,7 @@ impl Proof {
mod tests {
use nssa_core::{
Commitment, EncryptionScheme, Nullifier,
account::{Account, AccountWithMetadata},
account::{Account, AccountId, AccountWithMetadata},
};
use crate::{
@ -108,20 +108,23 @@ mod tests {
#[test]
fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() {
let recipient_keys = test_private_account_keys_1();
let program = Program::authenticated_transfer_program();
let sender = AccountWithMetadata {
account: Account {
let sender = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
true,
AccountId::new([0; 32]),
);
let recipient = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
let recipient = AccountWithMetadata::new(
Account::default(),
false,
AccountId::from(&recipient_keys.npk()),
);
let balance_to_move: u128 = 37;
@ -140,7 +143,6 @@ mod tests {
};
let expected_sender_pre = sender.clone();
let recipient_keys = test_private_account_keys_1();
let esk = [3; 32];
let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk());
@ -179,23 +181,26 @@ mod tests {
#[test]
fn prove_privacy_preserving_execution_circuit_fully_private() {
let program = Program::authenticated_transfer_program();
let sender_pre = AccountWithMetadata {
account: Account {
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let sender_pre = AccountWithMetadata::new(
Account {
balance: 100,
nonce: 0xdeadbeef,
program_owner: program.id(),
data: vec![],
},
is_authorized: true,
};
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
true,
AccountId::from(&sender_keys.npk()),
);
let commitment_sender = Commitment::new(&sender_keys.npk(), &sender_pre.account);
let recipient = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
let recipient = AccountWithMetadata::new(
Account::default(),
false,
AccountId::from(&recipient_keys.npk()),
);
let balance_to_move: u128 = 37;
let mut commitment_set = CommitmentSet::with_capacity(2);

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,
}
@ -90,9 +90,12 @@ impl PrivacyPreservingTransaction {
let public_pre_states: Vec<_> = message
.public_addresses
.iter()
.map(|address| AccountWithMetadata {
account: state.get_account_by_address(address),
is_authorized: signer_addresses.contains(address),
.map(|address| {
AccountWithMetadata::new(
state.get_account_by_address(address),
signer_addresses.contains(address),
address,
)
})
.collect();

View File

@ -3,7 +3,8 @@ use nssa_core::{
program::{InstructionData, ProgramId, ProgramOutput},
};
use program_methods::{
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID,
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID, TOKEN_ELF,
TOKEN_ID,
};
use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec};
use serde::Serialize;
@ -75,6 +76,13 @@ impl Program {
elf: AUTHENTICATED_TRANSFER_ELF,
}
}
pub fn token() -> Self {
Self {
id: TOKEN_ID,
elf: TOKEN_ELF,
}
}
}
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
@ -89,7 +97,7 @@ impl Program {
#[cfg(test)]
mod tests {
use nssa_core::account::{Account, AccountWithMetadata};
use nssa_core::account::{Account, AccountId, AccountWithMetadata};
use crate::program::Program;
@ -180,17 +188,16 @@ mod tests {
let program = Program::simple_balance_transfer();
let balance_to_move: u128 = 11223344556677;
let instruction_data = Program::serialize_instruction(balance_to_move).unwrap();
let sender = AccountWithMetadata {
account: Account {
let sender = AccountWithMetadata::new(
Account {
balance: 77665544332211,
..Account::default()
},
is_authorized: false,
};
let recipient = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
true,
AccountId::new([0; 32]),
);
let recipient =
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
let expected_sender_post = Account {
balance: 77665544332211 - balance_to_move,

View File

@ -91,9 +91,12 @@ impl PublicTransaction {
let pre_states: Vec<_> = message
.addresses
.iter()
.map(|address| AccountWithMetadata {
account: state.get_account_by_address(address),
is_authorized: signer_addresses.contains(address),
.map(|address| {
AccountWithMetadata::new(
state.get_account_by_address(address),
signer_addresses.contains(address),
address,
)
})
.collect();
@ -138,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,13 +82,17 @@ 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(),
};
this.insert_program(Program::authenticated_transfer_program());
this.insert_program(Program::token());
this
}
@ -242,7 +249,7 @@ pub mod tests {
use nssa_core::{
Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
account::{Account, AccountWithMetadata, Nonce},
account::{Account, AccountId, AccountWithMetadata, Nonce},
encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar},
};
@ -269,14 +276,14 @@ pub mod tests {
let addr1 = Address::from(&PublicKey::new_from_private_key(&key1));
let addr2 = Address::from(&PublicKey::new_from_private_key(&key2));
let initial_data = [(addr1, 100u128), (addr2, 151u128)];
let program = Program::authenticated_transfer_program();
let authenticated_transfers_program = Program::authenticated_transfer_program();
let expected_public_state = {
let mut this = HashMap::new();
this.insert(
addr1,
Account {
balance: 100,
program_owner: program.id(),
program_owner: authenticated_transfers_program.id(),
..Account::default()
},
);
@ -284,7 +291,7 @@ pub mod tests {
addr2,
Account {
balance: 151,
program_owner: program.id(),
program_owner: authenticated_transfers_program.id(),
..Account::default()
},
);
@ -292,11 +299,15 @@ pub mod tests {
};
let expected_builtin_programs = {
let mut this = HashMap::new();
this.insert(program.id(), program);
this.insert(
authenticated_transfers_program.id(),
authenticated_transfers_program,
);
this.insert(Program::token().id(), Program::token());
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);
@ -304,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));
@ -319,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);
@ -330,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);
@ -340,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();
@ -352,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());
@ -372,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]);
@ -396,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;
@ -419,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;
@ -503,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 =
@ -519,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 =
@ -535,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 =
@ -551,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
@ -573,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]);
@ -597,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]);
@ -621,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]);
@ -645,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;
@ -672,12 +688,13 @@ 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).with_test_programs();
let address = Address::new([1; 32]);
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]);
let program_id = Program::data_changer().id();
// Consider the extreme case where the target account is the default account
assert_eq!(state.get_account_by_address(&address), Account::default());
assert_ne!(state.get_account_by_address(&address), Account::default());
assert_ne!(
state.get_account_by_address(&address).program_owner,
program_id
@ -695,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();
@ -712,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();
@ -789,17 +807,15 @@ pub mod tests {
balance_to_move: u128,
state: &V01State,
) -> PrivacyPreservingTransaction {
let sender = AccountWithMetadata {
account: state.get_account_by_address(&sender_keys.address()),
is_authorized: true,
};
let sender = AccountWithMetadata::new(
state.get_account_by_address(&sender_keys.address()),
true,
&sender_keys.address(),
);
let sender_nonce = sender.account.nonce;
let recipient = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
let recipient = AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
let esk = [3; 32];
let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk());
@ -838,14 +854,10 @@ pub mod tests {
) -> PrivacyPreservingTransaction {
let program = Program::authenticated_transfer_program();
let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account);
let sender_pre = AccountWithMetadata {
account: sender_private_account.clone(),
is_authorized: true,
};
let recipient_pre = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
let sender_pre =
AccountWithMetadata::new(sender_private_account.clone(), true, &sender_keys.npk());
let recipient_pre =
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
let esk_1 = [3; 32];
let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk());
@ -898,14 +910,13 @@ pub mod tests {
) -> PrivacyPreservingTransaction {
let program = Program::authenticated_transfer_program();
let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account);
let sender_pre = AccountWithMetadata {
account: sender_private_account.clone(),
is_authorized: true,
};
let recipient_pre = AccountWithMetadata {
account: state.get_account_by_address(recipient_address),
is_authorized: false,
};
let sender_pre =
AccountWithMetadata::new(sender_private_account.clone(), true, &sender_keys.npk());
let recipient_pre = AccountWithMetadata::new(
state.get_account_by_address(recipient_address),
false,
recipient_address,
);
let esk = [3; 32];
let shared_secret = SharedSecretKey::new(&esk, &sender_keys.ivk());
@ -943,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;
@ -989,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;
@ -1054,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;
@ -1114,14 +1125,15 @@ pub mod tests {
#[test]
fn test_burner_program_should_fail_in_privacy_preserving_circuit() {
let program = Program::burner();
let public_account = AccountWithMetadata {
account: Account {
let public_account = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
true,
AccountId::new([0; 32]),
);
let result = execute_and_prove(
&[public_account],
@ -1139,14 +1151,15 @@ pub mod tests {
#[test]
fn test_minter_program_should_fail_in_privacy_preserving_circuit() {
let program = Program::minter();
let public_account = AccountWithMetadata {
account: Account {
let public_account = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
true,
AccountId::new([0; 32]),
);
let result = execute_and_prove(
&[public_account],
@ -1164,14 +1177,15 @@ pub mod tests {
#[test]
fn test_nonce_changer_program_should_fail_in_privacy_preserving_circuit() {
let program = Program::nonce_changer_program();
let public_account = AccountWithMetadata {
account: Account {
let public_account = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
true,
AccountId::new([0; 32]),
);
let result = execute_and_prove(
&[public_account],
@ -1189,14 +1203,15 @@ pub mod tests {
#[test]
fn test_data_changer_program_should_fail_for_non_owned_account_in_privacy_preserving_circuit() {
let program = Program::data_changer();
let public_account = AccountWithMetadata {
account: Account {
let public_account = AccountWithMetadata::new(
Account {
program_owner: [0, 1, 2, 3, 4, 5, 6, 7],
balance: 0,
..Account::default()
},
is_authorized: true,
};
true,
AccountId::new([0; 32]),
);
let result = execute_and_prove(
&[public_account],
@ -1214,14 +1229,15 @@ pub mod tests {
#[test]
fn test_extra_output_program_should_fail_in_privacy_preserving_circuit() {
let program = Program::extra_output_program();
let public_account = AccountWithMetadata {
account: Account {
let public_account = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
true,
AccountId::new([0; 32]),
);
let result = execute_and_prove(
&[public_account],
@ -1239,22 +1255,24 @@ pub mod tests {
#[test]
fn test_missing_output_program_should_fail_in_privacy_preserving_circuit() {
let program = Program::missing_output_program();
let public_account_1 = AccountWithMetadata {
account: Account {
let public_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
let public_account_2 = AccountWithMetadata {
account: Account {
true,
AccountId::new([0; 32]),
);
let public_account_2 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
true,
AccountId::new([1; 32]),
);
let result = execute_and_prove(
&[public_account_1, public_account_2],
@ -1272,14 +1290,15 @@ pub mod tests {
#[test]
fn test_program_owner_changer_should_fail_in_privacy_preserving_circuit() {
let program = Program::program_owner_changer();
let public_account = AccountWithMetadata {
account: Account {
let public_account = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
true,
AccountId::new([0; 32]),
);
let result = execute_and_prove(
&[public_account],
@ -1297,22 +1316,24 @@ pub mod tests {
#[test]
fn test_transfer_from_non_owned_account_should_fail_in_privacy_preserving_circuit() {
let program = Program::simple_balance_transfer();
let public_account_1 = AccountWithMetadata {
account: Account {
let public_account_1 = AccountWithMetadata::new(
Account {
program_owner: [0, 1, 2, 3, 4, 5, 6, 7],
balance: 100,
..Account::default()
},
is_authorized: true,
};
let public_account_2 = AccountWithMetadata {
account: Account {
true,
AccountId::new([0; 32]),
);
let public_account_2 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
true,
AccountId::new([1; 32]),
);
let result = execute_and_prove(
&[public_account_1, public_account_2],
@ -1330,22 +1351,24 @@ pub mod tests {
#[test]
fn test_circuit_fails_if_visibility_masks_have_incorrect_lenght() {
let program = Program::simple_balance_transfer();
let public_account_1 = AccountWithMetadata {
account: Account {
let public_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let public_account_2 = AccountWithMetadata {
account: Account {
true,
AccountId::new([0; 32]),
);
let public_account_2 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
true,
AccountId::new([1; 32]),
);
// Setting only one visibility mask for a circuit execution with two pre_state accounts.
let visibility_mask = [0];
@ -1367,18 +1390,17 @@ pub mod tests {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
let private_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
true,
AccountId::new([0; 32]),
);
let private_account_2 =
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
// Setting only one nonce for an execution with two private accounts.
let private_account_nonces = [0xdeadbeef1];
@ -1408,18 +1430,17 @@ pub mod tests {
fn test_circuit_fails_if_insufficient_keys_are_provided() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let private_account_1 = AccountWithMetadata {
account: Account {
let private_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
true,
AccountId::new([0; 32]),
);
let private_account_2 =
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
// Setting only one key for an execution with two private accounts.
let private_account_keys = [(
@ -1444,18 +1465,17 @@ pub mod tests {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
let private_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
true,
AccountId::new([0; 32]),
);
let private_account_2 =
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
// Setting no auth key for an execution with one non default private accounts.
let private_account_auth = [];
@ -1486,18 +1506,17 @@ pub mod tests {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
let private_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
true,
AccountId::new([0; 32]),
);
let private_account_2 =
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
let private_account_keys = [
// First private account is the sender
@ -1535,22 +1554,24 @@ pub mod tests {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
let private_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account {
true,
AccountId::new([0; 32]),
);
let private_account_2 = AccountWithMetadata::new(
Account {
// Non default balance
balance: 1,
..Account::default()
},
is_authorized: false,
};
false,
AccountId::new([1; 32]),
);
let result = execute_and_prove(
&[private_account_1, private_account_2],
@ -1580,22 +1601,24 @@ pub mod tests {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
let private_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account {
true,
AccountId::new([0; 32]),
);
let private_account_2 = AccountWithMetadata::new(
Account {
// Non default program_owner
program_owner: [0, 1, 2, 3, 4, 5, 6, 7],
..Account::default()
},
is_authorized: false,
};
false,
AccountId::new([1; 32]),
);
let result = execute_and_prove(
&[private_account_1, private_account_2],
@ -1624,22 +1647,24 @@ pub mod tests {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
let private_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account {
true,
AccountId::new([0; 32]),
);
let private_account_2 = AccountWithMetadata::new(
Account {
// Non default data
data: b"hola mundo".to_vec(),
..Account::default()
},
is_authorized: false,
};
false,
AccountId::new([1; 32]),
);
let result = execute_and_prove(
&[private_account_1, private_account_2],
@ -1668,22 +1693,24 @@ pub mod tests {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
let private_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account {
true,
AccountId::new([0; 32]),
);
let private_account_2 = AccountWithMetadata::new(
Account {
// Non default nonce
nonce: 0xdeadbeef,
..Account::default()
},
is_authorized: false,
};
false,
AccountId::new([1; 32]),
);
let result = execute_and_prove(
&[private_account_1, private_account_2],
@ -1713,19 +1740,21 @@ pub mod tests {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
let private_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
true,
AccountId::new([0; 32]),
);
let private_account_2 = AccountWithMetadata::new(
Account::default(),
// This should be set to false in normal circumstances
is_authorized: true,
};
true,
AccountId::new([1; 32]),
);
let result = execute_and_prove(
&[private_account_1, private_account_2],
@ -1752,18 +1781,17 @@ pub mod tests {
#[test]
fn test_circuit_should_fail_with_invalid_visibility_mask_value() {
let program = Program::simple_balance_transfer();
let public_account_1 = AccountWithMetadata {
account: Account {
let public_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let public_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
true,
AccountId::new([0; 32]),
);
let public_account_2 =
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
let visibility_mask = [0, 3];
let result = execute_and_prove(
@ -1784,18 +1812,17 @@ pub mod tests {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
let private_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
true,
AccountId::new([0; 32]),
);
let private_account_2 =
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
// Setting three new private account nonces for a circuit execution with only two private
// accounts.
@ -1827,18 +1854,17 @@ pub mod tests {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
let private_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
true,
AccountId::new([0; 32]),
);
let private_account_2 =
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
// Setting three private account keys for a circuit execution with only two private
// accounts.
@ -1874,18 +1900,17 @@ pub mod tests {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
let private_account_1 = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
true,
AccountId::new([0; 32]),
);
let private_account_2 =
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
// Setting two private account keys for a circuit execution with only one non default
// private account (visibility mask equal to 1 means that auth keys are expected).

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,8 +14,10 @@ tempfile.workspace = true
clap.workspace = true
nssa-core = { path = "../nssa/core" }
base64.workspace = true
k256 = { version = "0.13.3" }
bytemuck = "1.23.2"
borsh.workspace = true
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,63 @@ impl WalletCore {
Ok(self.sequencer_client.send_tx_public(tx).await?)
}
pub async fn send_public_native_token_transfer(
pub async fn send_new_token_definition(
&self,
from: Address,
to: Address,
balance_to_move: u128,
definition_address: Address,
supply_address: Address,
name: [u8; 6],
total_supply: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let Ok(balance) = self.get_account_balance(from).await else {
return Err(ExecutionFailureKind::SequencerError);
};
let addresses = vec![definition_address, supply_address];
let program_id = nssa::program::Program::token().id();
// Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
let mut instruction = [0; 23];
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
instruction[17..].copy_from_slice(&name);
let message =
nssa::public_transaction::Message::try_new(program_id, addresses, vec![], instruction)
.unwrap();
if balance >= balance_to_move {
let Ok(nonces) = self.get_accounts_nonces(vec![from]).await else {
return Err(ExecutionFailureKind::SequencerError);
};
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
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 tx = nssa::PublicTransaction::new(message, witness_set);
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)
}
Ok(self.sequencer_client.send_tx_public(tx).await?)
}
pub async fn send_transfer_token_transaction(
&self,
sender_address: Address,
recipient_address: Address,
amount: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let addresses = vec![sender_address, recipient_address];
let program_id = nssa::program::Program::token().id();
// Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00].
let mut instruction = [0; 23];
instruction[0] = 0x01;
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
let Ok(nonces) = self.get_accounts_nonces(vec![sender_address]).await else {
return Err(ExecutionFailureKind::SequencerError);
};
let message =
nssa::public_transaction::Message::try_new(program_id, addresses, nonces, instruction)
.unwrap();
let Some(signing_key) = self
.storage
.user_data
.get_pub_account_signing_key(&sender_address)
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?)
}
///Get account balance
pub async fn get_account_balance(&self, acc: Address) -> Result<u128> {
Ok(self
@ -172,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 = borsh::from_slice::<EncodedTransaction>(&tx_base64_decode).unwrap();
@ -186,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,
@ -197,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)]
@ -219,6 +325,26 @@ pub enum Command {
#[arg(short, long)]
addr: String,
},
//Create a new token using the token program
CreateNewToken {
#[arg(short, long)]
definition_addr: String,
#[arg(short, long)]
supply_addr: String,
#[arg(short, long)]
name: String,
#[arg(short, long)]
total_supply: u128,
},
//Transfer tokens using the token program
TransferToken {
#[arg(short, long)]
sender_addr: String,
#[arg(short, long)]
recipient_addr: String,
#[arg(short, long)]
balance_to_move: u128,
},
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
// Claim piñata prize
ClaimPinata {
@ -243,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)?;
@ -258,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
@ -277,23 +729,68 @@ 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,
supply_addr,
name,
total_supply,
} => {
let name = name.as_bytes();
if name.len() > 6 {
// TODO: return error
panic!();
}
let mut name_bytes = [0; 6];
name_bytes[..name.len()].copy_from_slice(name);
wallet_core
.send_new_token_definition(
definition_addr.parse().unwrap(),
supply_addr.parse().unwrap(),
name_bytes,
total_supply,
)
.await?;
SubcommandReturnValue::Empty
}
Command::TransferToken {
sender_addr,
recipient_addr,
balance_to_move,
} => {
wallet_core
.send_transfer_token_transaction(
sender_addr.parse().unwrap(),
recipient_addr.parse().unwrap(),
balance_to_move,
)
.await?;
SubcommandReturnValue::Empty
}
Command::ClaimPinata {
pinata_addr,
@ -308,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)
}
}
}