Merge branch 'main' into schouhy/node-state-reconstruction

This commit is contained in:
Sergio Chouhy 2025-11-03 23:36:42 -03:00
commit 883fdc583a
37 changed files with 2352 additions and 1307 deletions

View File

@ -45,6 +45,7 @@ bip39 = "2.2.0"
hmac-sha512 = "1.1.7" hmac-sha512 = "1.1.7"
chrono = "0.4.41" chrono = "0.4.41"
borsh = "1.5.7" borsh = "1.5.7"
base58 = "0.2.0"
rocksdb = { version = "0.21.0", default-features = false, features = [ rocksdb = { version = "0.21.0", default-features = false, features = [
"snappy", "snappy",

View File

@ -8,3 +8,5 @@ pub mod transaction;
//TODO: Compile only for tests //TODO: Compile only for tests
pub mod test_utils; pub mod test_utils;
pub type HashType = [u8; 32]; pub type HashType = [u8; 32];
pub const PINATA_BASE58: &str = "EfQhKQAkX2FJiwNii2WFQsGndjvF1Mzd7RuVe7QdPLw7";

View File

@ -8,49 +8,49 @@
"port": 3040, "port": 3040,
"initial_accounts": [ "initial_accounts": [
{ {
"addr": "d07ad2e84b27fa00c262f0a1eea0ff35ca0973547e6a106f72f193c2dc838b44", "addr": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy",
"balance": 10000 "balance": 10000
}, },
{ {
"addr": "e7ae77c5ef1a05999344af499fc78a1705398d62ed06cf2e1479f6def89a39bc", "addr": "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw",
"balance": 20000 "balance": 20000
} }
], ],
"initial_commitments": [ "initial_commitments": [
{ {
"npk": [ "npk": [
193, 63,
209, 202,
150, 178,
113, 231,
47, 183,
241, 82,
48, 237,
145, 212,
250, 216,
79,
235,
51,
119,
40,
184,
232,
5,
221, 221,
36, 215,
21, 255,
201,
106,
90,
210,
129,
106,
71,
99,
208,
153, 153,
75, 101,
215 177,
161,
254,
210,
128,
122,
54,
190,
230,
151,
183,
64,
225,
229,
113,
1,
228,
97
], ],
"account": { "account": {
"program_owner": [ "program_owner": [
@ -70,38 +70,38 @@
}, },
{ {
"npk": [ "npk": [
27, 192,
250, 251,
166,
243,
167,
236,
84,
249,
35,
136, 136,
142, 130,
88, 172,
128, 219,
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, 225,
105 161,
139,
229,
89,
243,
125,
194,
213,
209,
30,
23,
174,
100,
244,
124,
74,
140,
47
], ],
"account": { "account": {
"program_owner": [ "program_owner": [
@ -154,4 +154,4 @@
37, 37,
37 37
] ]
} }

View File

@ -9,85 +9,85 @@
"initial_accounts": [ "initial_accounts": [
{ {
"Public": { "Public": {
"address": "d07ad2e84b27fa00c262f0a1eea0ff35ca0973547e6a106f72f193c2dc838b44", "address": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy",
"pub_sign_key": [ "pub_sign_key": [
1, 16,
1, 162,
1, 106,
1, 154,
1, 236,
1, 125,
1, 52,
1, 184,
1, 35,
1, 100,
1, 238,
1, 174,
1, 69,
1, 197,
1, 41,
1, 77,
1, 187,
1, 10,
1, 118,
1, 75,
1, 0,
1, 11,
1, 148,
1, 238,
1, 185,
1, 181,
1, 133,
1, 17,
1, 220,
1, 72,
1, 124,
1 77
] ]
} }
}, },
{ {
"Public": { "Public": {
"address": "e7ae77c5ef1a05999344af499fc78a1705398d62ed06cf2e1479f6def89a39bc", "address": "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw",
"pub_sign_key": [ "pub_sign_key": [
2, 113,
2, 121,
2, 64,
2, 177,
2, 204,
2, 85,
2, 229,
2, 214,
2, 178,
2, 6,
2, 109,
2, 191,
2, 29,
2, 154,
2, 63,
2, 38,
2, 242,
2, 18,
2, 244,
2, 219,
2, 8,
2, 208,
2, 35,
2, 136,
2, 23,
2, 127,
2, 207,
2, 237,
2, 216,
2, 169,
2, 190,
2 27
] ]
} }
}, },
{ {
"Private": { "Private": {
"address": "d360d6b5763f71ac6af56253687fd7d556d5c6c64312e53c0b92ef039a4375df", "address": "3oCG8gqdKLMegw4rRfyaMQvuPHpcASt7xwttsmnZLSkw",
"account": { "account": {
"program_owner": [ "program_owner": [
0, 0,
@ -105,218 +105,218 @@
}, },
"key_chain": { "key_chain": {
"secret_spending_key": [ "secret_spending_key": [
10, 251,
125,
171,
38,
201,
35,
164,
43,
7,
80,
7,
215,
97,
42,
48,
229,
101,
216,
140,
21,
170,
214,
82, 82,
53, 235,
1,
146,
96,
30,
81,
162,
234,
33,
15,
123,
129,
116, 116,
22, 0,
62, 84,
79, 136,
61, 176,
76, 70,
71, 190,
79 224,
161,
54,
134,
142,
154,
1,
18,
251,
242,
189
], ],
"private_key_holder": { "private_key_holder": {
"nullifier_secret_key": [ "nullifier_secret_key": [
228, 29,
136, 250,
4, 10,
187,
35,
123,
180,
250,
246,
97,
216,
153,
44,
156, 156,
33, 16,
40, 93,
194, 241,
172, 26,
95, 174,
168, 219,
201, 72,
33, 84,
24, 34,
30, 247,
126, 112,
197, 101,
156, 217,
113, 243,
64, 189,
162, 173,
131, 75,
210, 20
110,
60,
24,
154,
86,
59,
184,
95,
245,
176
], ],
"incoming_viewing_secret_key": [ "incoming_viewing_secret_key": [
197, 251,
33, 201,
51, 22,
200, 154,
1, 100,
121, 165,
60, 218,
52, 108,
233, 163,
234, 190,
12, 135,
166, 91,
196, 145,
227, 84,
187, 69,
1, 241,
10, 46,
101, 117,
183,
105,
140,
28,
152,
217, 217,
109,
220,
112,
103,
253,
110, 110,
98, 197,
6 248,
91,
193,
14,
104,
88,
103,
67,
153,
182,
158
], ],
"outgoing_viewing_secret_key": [ "outgoing_viewing_secret_key": [
147, 25,
34, 67,
193, 121,
29, 76,
39, 175,
173, 100,
222,
30, 30,
118, 198,
199, 105,
44, 123,
204, 49,
43, 169,
232, 75,
107, 178,
223, 75,
249, 210,
207, 100,
245, 143,
183, 210,
63, 243,
209, 228,
129, 243,
48, 21,
254, 18,
66, 36,
22, 84,
199, 164,
81, 186,
145, 139,
126, 113,
92 214,
12
] ]
}, },
"nullifer_public_key": [ "nullifer_public_key": [
193, 63,
209, 202,
150, 178,
113, 231,
47, 183,
241, 82,
48, 237,
145, 212,
250, 216,
79,
235,
51,
119,
40,
184,
232,
5,
221, 221,
36, 215,
21, 255,
201,
106,
90,
210,
129,
106,
71,
99,
208,
153, 153,
75, 101,
215 177,
161,
254,
210,
128,
122,
54,
190,
230,
151,
183,
64,
225,
229,
113,
1,
228,
97
], ],
"incoming_viewing_public_key": [ "incoming_viewing_public_key": [
3, 3,
78, 235,
139,
131,
237,
177, 177,
87, 122,
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, 189,
31, 6,
95 177,
167,
178,
202,
117,
246,
58,
28,
65,
132,
79,
220,
139,
119,
243,
187,
160,
212,
121,
61,
247,
116,
72,
205
] ]
} }
} }
}, },
{ {
"Private": { "Private": {
"address": "f27087ffc29b99035303697dcf6c8e323b1847d4261e6afd49e0d71c6dfa31ea", "address": "AKTcXgJ1xoynta1Ec7y6Jso1z1JQtHqd7aPQ1h9er6xX",
"account": { "account": {
"program_owner": [ "program_owner": [
0, 0,
@ -334,214 +334,214 @@
}, },
"key_chain": { "key_chain": {
"secret_spending_key": [ "secret_spending_key": [
153, 238,
109, 171,
202, 241,
226, 69,
97, 111,
212, 217,
77, 85,
147, 64,
75, 19,
107, 82,
153, 18,
106, 189,
89, 32,
167, 91,
49,
230,
122,
78, 78,
167, 175,
146,
14,
180,
206,
107, 107,
96, 7,
193, 109,
255, 60,
122, 52,
207, 44,
30, 243,
142, 230,
99 72,
244,
192,
92,
137,
33,
118,
254
], ],
"private_key_holder": { "private_key_holder": {
"nullifier_secret_key": [ "nullifier_secret_key": [
128, 25,
211,
215, 215,
147,
175,
119, 119,
16,
140,
219,
155,
134,
27,
81,
64,
40,
196,
240,
61,
144,
232,
164,
181,
57, 57,
139, 223,
96, 247,
137, 37,
121, 245,
140, 144,
122,
29, 29,
169, 118,
68, 245,
187, 83,
65 228,
23,
9,
101,
120,
88,
33,
238,
207,
128,
61,
110,
2,
89,
62,
164,
13
], ],
"incoming_viewing_secret_key": [ "incoming_viewing_secret_key": [
185, 193,
121, 181,
146, 14,
213, 196,
13, 142,
3, 84,
93, 15,
206, 65,
25, 128,
127, 101,
155, 70,
21, 196,
155, 241,
115, 47,
130, 130,
27, 221,
57, 23,
5, 146,
116, 161,
80, 237,
62, 221,
214, 40,
67, 19,
228, 126,
147, 59,
189, 15,
28, 169,
200, 236,
62, 25,
152, 105,
178, 104,
103 231
], ],
"outgoing_viewing_secret_key": [ "outgoing_viewing_secret_key": [
163, 20,
58, 170,
118, 220,
160, 108,
41,
23,
155,
217,
247,
190,
175, 175,
86, 168,
72, 247,
34,
105,
134,
114,
74,
104,
91, 91,
81,
69,
150,
154,
113,
211, 211,
118, 62,
110,
25,
156,
250,
67,
212,
198,
147,
231,
213,
136,
212,
198,
192,
255,
126, 126,
122 13,
130,
100,
241,
214,
250,
236,
38,
150
] ]
}, },
"nullifer_public_key": [ "nullifer_public_key": [
27, 192,
250, 251,
166,
243,
167,
236,
84,
249,
35,
136, 136,
142, 130,
88, 172,
128, 219,
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, 225,
105 161,
139,
229,
89,
243,
125,
194,
213,
209,
30,
23,
174,
100,
244,
124,
74,
140,
47
], ],
"incoming_viewing_public_key": [ "incoming_viewing_public_key": [
2, 2,
56, 181,
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, 98,
226 93,
216,
241,
241,
110,
58,
198,
119,
174,
250,
184,
1,
204,
200,
173,
44,
238,
37,
247,
170,
156,
100,
254,
116,
242,
28,
183,
187,
77,
255
] ]
} }
} }
} }
] ]
} }

View File

@ -32,18 +32,24 @@ struct Args {
test_name: String, test_name: String,
} }
pub const ACC_SENDER: &str = "d07ad2e84b27fa00c262f0a1eea0ff35ca0973547e6a106f72f193c2dc838b44"; pub const ACC_SENDER: &str = "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy";
pub const ACC_RECEIVER: &str = "e7ae77c5ef1a05999344af499fc78a1705398d62ed06cf2e1479f6def89a39bc"; pub const ACC_RECEIVER: &str = "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw";
pub const ACC_SENDER_PRIVATE: &str = pub const ACC_SENDER_PRIVATE: &str = "3oCG8gqdKLMegw4rRfyaMQvuPHpcASt7xwttsmnZLSkw";
"d360d6b5763f71ac6af56253687fd7d556d5c6c64312e53c0b92ef039a4375df"; pub const ACC_RECEIVER_PRIVATE: &str = "AKTcXgJ1xoynta1Ec7y6Jso1z1JQtHqd7aPQ1h9er6xX";
pub const ACC_RECEIVER_PRIVATE: &str =
"f27087ffc29b99035303697dcf6c8e323b1847d4261e6afd49e0d71c6dfa31ea";
pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12; pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12;
pub const NSSA_PROGRAM_FOR_TEST_DATA_CHANGER: &[u8] = include_bytes!("data_changer.bin"); pub const NSSA_PROGRAM_FOR_TEST_DATA_CHANGER: &[u8] = include_bytes!("data_changer.bin");
fn make_public_account_input_from_str(addr: &str) -> String {
format!("Public/{addr}")
}
fn make_private_account_input_from_str(addr: &str) -> String {
format!("Private/{addr}")
}
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub async fn pre_test( pub async fn pre_test(
home_dir: PathBuf, home_dir: PathBuf,
@ -86,7 +92,7 @@ pub async fn post_test(residual: (ServerHandle, JoinHandle<Result<()>>, TempDir)
seq_http_server_handle.stop(true).await; seq_http_server_handle.stop(true).await;
let wallet_home = wallet::helperfunctions::get_home().unwrap(); let wallet_home = wallet::helperfunctions::get_home().unwrap();
let persistent_data_home = wallet_home.join("curr_accounts.json"); let persistent_data_home = wallet_home.join("storage.json");
//Removing persistent accounts after run to not affect other executions //Removing persistent accounts after run to not affect other executions
//Not necessary an error, if fails as there is tests for failure scenario //Not necessary an error, if fails as there is tests for failure scenario
@ -157,3 +163,20 @@ async fn verify_commitment_is_in_state(
Ok(Some(_)) Ok(Some(_))
) )
} }
#[cfg(test)]
mod tests {
use crate::{make_private_account_input_from_str, make_public_account_input_from_str};
#[test]
fn correct_addr_from_prefix() {
let addr1 = "cafecafe";
let addr2 = "deadbeaf";
let addr1_pub = make_public_account_input_from_str(addr1);
let addr2_priv = make_private_account_input_from_str(addr2);
assert_eq!(addr1_pub, "Public/cafecafe".to_string());
assert_eq!(addr2_priv, "Private/deadbeaf".to_string());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,8 @@ serde.workspace = true
k256.workspace = true k256.workspace = true
sha2.workspace = true sha2.workspace = true
rand.workspace = true rand.workspace = true
hex.workspace = true base58.workspace = true
hex = "0.4.3"
aes-gcm.workspace = true aes-gcm.workspace = true
bip39.workspace = true bip39.workspace = true
hmac-sha512.workspace = true hmac-sha512.workspace = true

View File

@ -55,6 +55,7 @@ impl KeyChain {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use aes_gcm::aead::OsRng; use aes_gcm::aead::OsRng;
use base58::ToBase58;
use k256::AffinePoint; use k256::AffinePoint;
use k256::elliptic_curve::group::GroupEncoding; use k256::elliptic_curve::group::GroupEncoding;
use rand::RngCore; use rand::RngCore;
@ -119,7 +120,7 @@ mod tests {
println!("======Public data======"); println!("======Public data======");
println!(); println!();
println!("Address{:?}", hex::encode(address.value())); println!("Address{:?}", address.value().to_base58());
println!( println!(
"Nulifier public key {:?}", "Nulifier public key {:?}",
hex::encode(nullifer_public_key.to_byte_array()) hex::encode(nullifer_public_key.to_byte_array())

View File

@ -142,5 +142,10 @@ mod tests {
let is_key_chain_generated = user_data.get_private_account(&addr_private).is_some(); let is_key_chain_generated = user_data.get_private_account(&addr_private).is_some();
assert!(is_key_chain_generated); assert!(is_key_chain_generated);
let addr_private_str = addr_private.to_string();
println!("{addr_private_str:#?}");
let key_chain = &user_data.get_private_account(&addr_private).unwrap().0;
println!("{key_chain:#?}");
} }
} }

View File

@ -10,8 +10,9 @@ thiserror = { version = "2.0.12", optional = true }
bytemuck = { version = "1.13", optional = true } bytemuck = { version = "1.13", optional = true }
chacha20 = { version = "0.9", default-features = false } chacha20 = { version = "0.9", default-features = false }
k256 = { version = "0.13.3", optional = true } k256 = { version = "0.13.3", optional = true }
hex = { version = "0.4.3", optional = true } base58 = { version = "0.2.0", optional = true }
anyhow = { version = "1.0.98", optional = true }
[features] [features]
default = [] default = []
host = ["thiserror", "bytemuck", "k256", "hex"] host = ["thiserror", "bytemuck", "k256", "base58", "anyhow"]

View File

@ -3,6 +3,9 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "host")] #[cfg(feature = "host")]
use std::{fmt::Display, str::FromStr}; use std::{fmt::Display, str::FromStr};
#[cfg(feature = "host")]
use base58::{FromBase58, ToBase58};
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[cfg_attr( #[cfg_attr(
any(feature = "host", test), any(feature = "host", test),
@ -31,8 +34,8 @@ impl AsRef<[u8]> for Address {
#[cfg(feature = "host")] #[cfg(feature = "host")]
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum AddressError { pub enum AddressError {
#[error("invalid hex")] #[error("invalid base58")]
InvalidHex(#[from] hex::FromHexError), InvalidBase58(#[from] anyhow::Error),
#[error("invalid length: expected 32 bytes, got {0}")] #[error("invalid length: expected 32 bytes, got {0}")]
InvalidLength(usize), InvalidLength(usize),
} }
@ -41,7 +44,9 @@ pub enum AddressError {
impl FromStr for Address { impl FromStr for Address {
type Err = AddressError; type Err = AddressError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = hex::decode(s)?; let bytes = s
.from_base58()
.map_err(|err| anyhow::anyhow!("Invalid base58 err {err:?}"))?;
if bytes.len() != 32 { if bytes.len() != 32 {
return Err(AddressError::InvalidLength(bytes.len())); return Err(AddressError::InvalidLength(bytes.len()));
} }
@ -54,7 +59,7 @@ impl FromStr for Address {
#[cfg(feature = "host")] #[cfg(feature = "host")]
impl Display for Address { impl Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", hex::encode(self.value)) write!(f, "{}", self.value.to_base58())
} }
} }
@ -65,29 +70,29 @@ mod tests {
#[test] #[test]
fn parse_valid_address() { fn parse_valid_address() {
let hex_str = "00".repeat(32); // 64 hex chars = 32 bytes let base58_str = "11111111111111111111111111111111";
let addr: Address = hex_str.parse().unwrap(); let addr: Address = base58_str.parse().unwrap();
assert_eq!(addr.value, [0u8; 32]); assert_eq!(addr.value, [0u8; 32]);
} }
#[test] #[test]
fn parse_invalid_hex() { fn parse_invalid_base58() {
let hex_str = "zz".repeat(32); // invalid hex chars let base58_str = "00".repeat(32); // invalid base58 chars
let result = hex_str.parse::<Address>().unwrap_err(); let result = base58_str.parse::<Address>().unwrap_err();
assert!(matches!(result, AddressError::InvalidHex(_))); assert!(matches!(result, AddressError::InvalidBase58(_)));
} }
#[test] #[test]
fn parse_wrong_length_short() { fn parse_wrong_length_short() {
let hex_str = "00".repeat(31); // 62 chars = 31 bytes let base58_str = "11".repeat(31); // 62 chars = 31 bytes
let result = hex_str.parse::<Address>().unwrap_err(); let result = base58_str.parse::<Address>().unwrap_err();
assert!(matches!(result, AddressError::InvalidLength(_))); assert!(matches!(result, AddressError::InvalidLength(_)));
} }
#[test] #[test]
fn parse_wrong_length_long() { fn parse_wrong_length_long() {
let hex_str = "00".repeat(33); // 66 chars = 33 bytes let base58_str = "11".repeat(33); // 66 chars = 33 bytes
let result = hex_str.parse::<Address>().unwrap_err(); let result = base58_str.parse::<Address>().unwrap_err();
assert!(matches!(result, AddressError::InvalidLength(_))); assert!(matches!(result, AddressError::InvalidLength(_)));
} }
} }

View File

@ -12,11 +12,20 @@ pub struct ProgramInput<T> {
pub instruction: T, pub instruction: T,
} }
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
pub struct ChainedCall {
pub program_id: ProgramId,
pub instruction_data: InstructionData,
pub account_indices: Vec<usize>,
}
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
pub struct ProgramOutput { pub struct ProgramOutput {
pub pre_states: Vec<AccountWithMetadata>, pub pre_states: Vec<AccountWithMetadata>,
pub post_states: Vec<Account>, pub post_states: Vec<Account>,
pub chained_call: Option<ChainedCall>,
} }
pub fn read_nssa_inputs<T: DeserializeOwned>() -> ProgramInput<T> { pub fn read_nssa_inputs<T: DeserializeOwned>() -> ProgramInput<T> {
@ -33,6 +42,20 @@ pub fn write_nssa_outputs(pre_states: Vec<AccountWithMetadata>, post_states: Vec
let output = ProgramOutput { let output = ProgramOutput {
pre_states, pre_states,
post_states, post_states,
chained_call: None,
};
env::commit(&output);
}
pub fn write_nssa_outputs_with_chained_call(
pre_states: Vec<AccountWithMetadata>,
post_states: Vec<Account>,
chained_call: Option<ChainedCall>,
) {
let output = ProgramOutput {
pre_states,
post_states,
chained_call,
}; };
env::commit(&output); env::commit(&output);
} }
@ -79,9 +102,14 @@ pub fn validate_execution(
{ {
return false; return false;
} }
// 6. If a post state has default program owner, the pre state must have been a default account
if post.program_owner == DEFAULT_PROGRAM_ID && pre.account != Account::default() {
return false;
}
} }
// 6. Total balance is preserved // 7. Total balance is preserved
let total_balance_pre_states: u128 = pre_states.iter().map(|pre| pre.account.balance).sum(); let total_balance_pre_states: u128 = pre_states.iter().map(|pre| pre.account.balance).sum();
let total_balance_post_states: u128 = post_states.iter().map(|post| post.balance).sum(); let total_balance_post_states: u128 = post_states.iter().map(|post| post.balance).sum();
if total_balance_pre_states != total_balance_post_states { if total_balance_pre_states != total_balance_post_states {

View File

@ -27,8 +27,14 @@ fn main() {
let ProgramOutput { let ProgramOutput {
pre_states, pre_states,
post_states, post_states,
chained_call,
} = program_output; } = program_output;
// TODO: implement chained calls for privacy preserving transactions
if chained_call.is_some() {
panic!("Privacy preserving transactions do not support yet chained calls.")
}
// Check that there are no repeated account ids // Check that there are no repeated account ids
if !validate_uniqueness_of_account_ids(&pre_states) { if !validate_uniqueness_of_account_ids(&pre_states) {
panic!("Repeated account ids found") panic!("Repeated account ids found")

View File

@ -1,6 +1,6 @@
use crate::program_methods::{AUTHENTICATED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF}; use crate::program_methods::{AUTHENTICATED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF};
use nssa_core::{ use nssa_core::{
account::{Account, AccountWithMetadata}, account::AccountWithMetadata,
program::{InstructionData, ProgramId, ProgramOutput}, program::{InstructionData, ProgramId, ProgramOutput},
}; };
@ -48,7 +48,7 @@ impl Program {
&self, &self,
pre_states: &[AccountWithMetadata], pre_states: &[AccountWithMetadata],
instruction_data: &InstructionData, instruction_data: &InstructionData,
) -> Result<Vec<Account>, NssaError> { ) -> Result<ProgramOutput, NssaError> {
// Write inputs to the program // Write inputs to the program
let mut env_builder = ExecutorEnv::builder(); let mut env_builder = ExecutorEnv::builder();
env_builder.session_limit(Some(MAX_NUM_CYCLES_PUBLIC_EXECUTION)); env_builder.session_limit(Some(MAX_NUM_CYCLES_PUBLIC_EXECUTION));
@ -62,12 +62,12 @@ impl Program {
.map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?; .map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?;
// Get outputs // Get outputs
let ProgramOutput { post_states, .. } = session_info let program_output = session_info
.journal .journal
.decode() .decode()
.map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?; .map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?;
Ok(post_states) Ok(program_output)
} }
/// Writes inputs to `env_builder` in the order expected by the programs /// Writes inputs to `env_builder` in the order expected by the programs
@ -107,11 +107,11 @@ impl Program {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use nssa_core::account::{Account, AccountId, AccountWithMetadata}; use crate::program_methods::{
use program_methods::{
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID, TOKEN_ELF, AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID, TOKEN_ELF,
TOKEN_ID, TOKEN_ID,
}; };
use nssa_core::account::{Account, AccountId, AccountWithMetadata};
use crate::program::Program; use crate::program::Program;
@ -195,6 +195,15 @@ mod tests {
elf: BURNER_ELF.to_vec(), elf: BURNER_ELF.to_vec(),
} }
} }
pub fn chain_caller() -> Self {
use test_program_methods::{CHAIN_CALLER_ELF, CHAIN_CALLER_ID};
Program {
id: CHAIN_CALLER_ID,
elf: CHAIN_CALLER_ELF.to_vec(),
}
}
} }
#[test] #[test]
@ -221,12 +230,12 @@ mod tests {
balance: balance_to_move, balance: balance_to_move,
..Account::default() ..Account::default()
}; };
let [sender_post, recipient_post] = program let program_output = program
.execute(&[sender, recipient], &instruction_data) .execute(&[sender, recipient], &instruction_data)
.unwrap()
.try_into()
.unwrap(); .unwrap();
let [sender_post, recipient_post] = program_output.post_states.try_into().unwrap();
assert_eq!(sender_post, expected_sender_post); assert_eq!(sender_post, expected_sender_post);
assert_eq!(recipient_post, expected_recipient_post); assert_eq!(recipient_post, expected_recipient_post);
} }

View File

@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
use nssa_core::{ use nssa_core::{
account::{Account, AccountWithMetadata}, account::{Account, AccountWithMetadata},
address::Address, address::Address,
program::validate_execution, program::{DEFAULT_PROGRAM_ID, validate_execution},
}; };
use sha2::{Digest, digest::FixedOutput}; use sha2::{Digest, digest::FixedOutput};
@ -18,6 +18,7 @@ pub struct PublicTransaction {
message: Message, message: Message,
witness_set: WitnessSet, witness_set: WitnessSet,
} }
const MAX_NUMBER_CHAINED_CALLS: usize = 10;
impl PublicTransaction { impl PublicTransaction {
pub fn new(message: Message, witness_set: WitnessSet) -> Self { pub fn new(message: Message, witness_set: WitnessSet) -> Self {
@ -88,7 +89,7 @@ impl PublicTransaction {
} }
// Build pre_states for execution // Build pre_states for execution
let pre_states: Vec<_> = message let mut input_pre_states: Vec<_> = message
.addresses .addresses
.iter() .iter()
.map(|address| { .map(|address| {
@ -100,21 +101,86 @@ impl PublicTransaction {
}) })
.collect(); .collect();
// Check the `program_id` corresponds to a deployed program let mut state_diff: HashMap<Address, Account> = HashMap::new();
let Some(program) = state.programs().get(&message.program_id) else {
return Err(NssaError::InvalidInput("Unknown program".into()));
};
// // Execute program let mut program_id = message.program_id;
let post_states = program.execute(&pre_states, &message.instruction_data)?; let mut instruction_data = message.instruction_data.clone();
// Verify execution corresponds to a well-behaved program. for _i in 0..MAX_NUMBER_CHAINED_CALLS {
// See the # Programs section for the definition of the `validate_execution` method. // Check the `program_id` corresponds to a deployed program
if !validate_execution(&pre_states, &post_states, message.program_id) { let Some(program) = state.programs().get(&program_id) else {
return Err(NssaError::InvalidProgramBehavior); return Err(NssaError::InvalidInput("Unknown program".into()));
};
let mut program_output = program.execute(&input_pre_states, &instruction_data)?;
// This check is equivalent to checking that the program output pre_states coinicide
// with the values in the public state or with any modifications to those values
// during the chain of calls.
if input_pre_states != program_output.pre_states {
return Err(NssaError::InvalidProgramBehavior);
}
// Verify execution corresponds to a well-behaved program.
// See the # Programs section for the definition of the `validate_execution` method.
if !validate_execution(
&program_output.pre_states,
&program_output.post_states,
program_id,
) {
return Err(NssaError::InvalidProgramBehavior);
}
// The invoked program claims the accounts with default program id.
for post in program_output.post_states.iter_mut() {
if post.program_owner == DEFAULT_PROGRAM_ID {
post.program_owner = program_id;
}
}
// Update the state diff
for (pre, post) in program_output
.pre_states
.iter()
.zip(program_output.post_states.iter())
{
state_diff.insert(pre.account_id, post.clone());
}
if let Some(next_chained_call) = program_output.chained_call {
program_id = next_chained_call.program_id;
instruction_data = next_chained_call.instruction_data;
// Build post states with metadata for next call
let mut post_states_with_metadata = Vec::new();
for (pre, post) in program_output
.pre_states
.iter()
.zip(program_output.post_states)
{
let mut post_with_metadata = pre.clone();
post_with_metadata.account = post.clone();
post_states_with_metadata.push(post_with_metadata);
}
input_pre_states = next_chained_call
.account_indices
.iter()
.map(|&i| {
post_states_with_metadata
.get(i)
.ok_or_else(|| {
NssaError::InvalidInput("Invalid account indices".into())
})
.cloned()
})
.collect::<Result<Vec<_>, NssaError>>()?;
} else {
break;
};
} }
Ok(message.addresses.iter().cloned().zip(post_states).collect()) Ok(state_diff)
} }
} }

View File

@ -6,9 +6,7 @@ use crate::{
}; };
use nssa_core::{ use nssa_core::{
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier, Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier,
account::Account, account::Account, address::Address, program::ProgramId,
address::Address,
program::{DEFAULT_PROGRAM_ID, ProgramId},
}; };
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@ -114,10 +112,6 @@ impl V02State {
let current_account = self.get_account_by_address_mut(address); let current_account = self.get_account_by_address_mut(address);
*current_account = post; *current_account = post;
// The invoked program claims the accounts with default program id.
if current_account.program_owner == DEFAULT_PROGRAM_ID {
current_account.program_owner = tx.message().program_id;
}
} }
for address in tx.signer_addresses() { for address in tx.signer_addresses() {
@ -263,6 +257,7 @@ pub mod tests {
Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
account::{Account, AccountId, AccountWithMetadata, Nonce}, account::{Account, AccountId, AccountWithMetadata, Nonce},
encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar}, encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar},
program::ProgramId,
}; };
fn transfer_transaction( fn transfer_transaction(
@ -436,7 +431,7 @@ pub mod tests {
} }
#[test] #[test]
fn transition_from_chained_authenticated_transfer_program_invocations() { fn transition_from_sequence_of_authenticated_transfer_program_invocations() {
let key1 = PrivateKey::try_new([8; 32]).unwrap(); let key1 = PrivateKey::try_new([8; 32]).unwrap();
let address1 = Address::from(&PublicKey::new_from_private_key(&key1)); let address1 = Address::from(&PublicKey::new_from_private_key(&key1));
let key2 = PrivateKey::try_new([2; 32]).unwrap(); let key2 = PrivateKey::try_new([2; 32]).unwrap();
@ -475,6 +470,7 @@ pub mod tests {
self.insert_program(Program::data_changer()); self.insert_program(Program::data_changer());
self.insert_program(Program::minter()); self.insert_program(Program::minter());
self.insert_program(Program::burner()); self.insert_program(Program::burner());
self.insert_program(Program::chain_caller());
self self
} }
@ -2045,4 +2041,80 @@ pub mod tests {
assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
} }
#[test]
fn test_claiming_mechanism() {
let program = Program::authenticated_transfer_program();
let key = PrivateKey::try_new([1; 32]).unwrap();
let address = Address::from(&PublicKey::new_from_private_key(&key));
let initial_balance = 100;
let initial_data = [(address, initial_balance)];
let mut state =
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let from = address;
let from_key = key;
let to = Address::new([2; 32]);
let amount: u128 = 37;
// Check the recipient is an uninitialized account
assert_eq!(state.get_account_by_address(&to), Account::default());
let expected_recipient_post = Account {
program_owner: program.id(),
balance: amount,
..Account::default()
};
let message =
public_transaction::Message::try_new(program.id(), vec![from, to], vec![0], amount)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
let recipient_post = state.get_account_by_address(&to);
assert_eq!(recipient_post, expected_recipient_post);
}
#[test]
fn test_chained_call() {
let program = Program::chain_caller();
let key = PrivateKey::try_new([1; 32]).unwrap();
let address = Address::from(&PublicKey::new_from_private_key(&key));
let initial_balance = 100;
let initial_data = [(address, initial_balance)];
let mut state =
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
let from = address;
let from_key = key;
let to = Address::new([2; 32]);
let amount: u128 = 37;
let instruction: (u128, ProgramId) =
(amount, Program::authenticated_transfer_program().id());
let expected_to_post = Account {
program_owner: Program::chain_caller().id(),
balance: amount,
..Account::default()
};
let message = public_transaction::Message::try_new(
program.id(),
vec![to, from], //The chain_caller program permutes the account order in the chain call
vec![0],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
let from_post = state.get_account_by_address(&from);
let to_post = state.get_account_by_address(&to);
assert_eq!(from_post.balance, initial_balance - amount);
assert_eq!(to_post, expected_to_post);
}
} }

View File

@ -1824,6 +1824,8 @@ name = "programs"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"nssa-core", "nssa-core",
"risc0-zkvm",
"serde",
] ]
[[package]] [[package]]

View File

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

View File

@ -0,0 +1,34 @@
use nssa_core::program::{
ChainedCall, ProgramId, ProgramInput, read_nssa_inputs, write_nssa_outputs_with_chained_call,
};
use risc0_zkvm::serde::to_vec;
type Instruction = (u128, ProgramId);
/// A program that calls another program.
/// It permutes the order of the input accounts on the subsequent call
fn main() {
let ProgramInput {
pre_states,
instruction: (balance, program_id),
} = read_nssa_inputs::<Instruction>();
let [sender_pre, receiver_pre] = match pre_states.try_into() {
Ok(array) => array,
Err(_) => return,
};
let instruction_data = to_vec(&balance).unwrap();
let chained_call = Some(ChainedCall {
program_id,
instruction_data,
account_indices: vec![1, 0], // <- Account order permutation here
});
write_nssa_outputs_with_chained_call(
vec![sender_pre.clone(), receiver_pre.clone()],
vec![sender_pre.account, receiver_pre.account],
chained_call,
);
}

View File

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
hex.workspace = true base58.workspace = true
anyhow.workspace = true anyhow.workspace = true
serde.workspace = true serde.workspace = true
rand.workspace = true rand.workspace = true

View File

@ -1,6 +1,8 @@
use std::fmt::Display; use std::fmt::Display;
use anyhow::Result; use anyhow::Result;
#[cfg(feature = "testnet")]
use common::PINATA_BASE58;
use common::{ use common::{
HashType, HashType,
block::HashableBlockData, block::HashableBlockData,
@ -82,7 +84,7 @@ impl SequencerCore {
let mut state = nssa::V02State::new_with_genesis_accounts(&init_accs, &initial_commitments); let mut state = nssa::V02State::new_with_genesis_accounts(&init_accs, &initial_commitments);
#[cfg(feature = "testnet")] #[cfg(feature = "testnet")]
state.add_pinata_program("cafe".repeat(16).parse().unwrap()); state.add_pinata_program(PINATA_BASE58.parse().unwrap());
let mut this = Self { let mut this = Self {
state, state,
@ -239,6 +241,7 @@ impl SequencerCore {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use base58::{FromBase58, ToBase58};
use common::test_utils::sequencer_sign_key_for_testing; use common::test_utils::sequencer_sign_key_for_testing;
use nssa::PrivateKey; use nssa::PrivateKey;
@ -273,23 +276,23 @@ mod tests {
} }
fn setup_sequencer_config() -> SequencerConfig { fn setup_sequencer_config() -> SequencerConfig {
let acc1_addr = vec![ let acc1_addr: Vec<u8> = vec![
208, 122, 210, 232, 75, 39, 250, 0, 194, 98, 240, 161, 238, 160, 255, 53, 202, 9, 115, 208, 122, 210, 232, 75, 39, 250, 0, 194, 98, 240, 161, 238, 160, 255, 53, 202, 9, 115,
84, 126, 106, 16, 111, 114, 241, 147, 194, 220, 131, 139, 68, 84, 126, 106, 16, 111, 114, 241, 147, 194, 220, 131, 139, 68,
]; ];
let acc2_addr = vec![ let acc2_addr: Vec<u8> = vec![
231, 174, 119, 197, 239, 26, 5, 153, 147, 68, 175, 73, 159, 199, 138, 23, 5, 57, 141, 231, 174, 119, 197, 239, 26, 5, 153, 147, 68, 175, 73, 159, 199, 138, 23, 5, 57, 141,
98, 237, 6, 207, 46, 20, 121, 246, 222, 248, 154, 57, 188, 98, 237, 6, 207, 46, 20, 121, 246, 222, 248, 154, 57, 188,
]; ];
let initial_acc1 = AccountInitialData { let initial_acc1 = AccountInitialData {
addr: hex::encode(acc1_addr), addr: acc1_addr.to_base58(),
balance: 10000, balance: 10000,
}; };
let initial_acc2 = AccountInitialData { let initial_acc2 = AccountInitialData {
addr: hex::encode(acc2_addr), addr: acc2_addr.to_base58(),
balance: 20000, balance: 20000,
}; };
@ -324,11 +327,17 @@ mod tests {
assert_eq!(sequencer.sequencer_config.max_num_tx_in_block, 10); assert_eq!(sequencer.sequencer_config.max_num_tx_in_block, 10);
assert_eq!(sequencer.sequencer_config.port, 8080); assert_eq!(sequencer.sequencer_config.port, 8080);
let acc1_addr = hex::decode(config.initial_accounts[0].addr.clone()) let acc1_addr = config.initial_accounts[0]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
let acc2_addr = hex::decode(config.initial_accounts[1].addr.clone()) let acc2_addr = config.initial_accounts[1]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
@ -348,23 +357,23 @@ mod tests {
#[test] #[test]
fn test_start_different_intial_accounts_balances() { fn test_start_different_intial_accounts_balances() {
let acc1_addr = vec![ let acc1_addr: Vec<u8> = vec![
27, 132, 197, 86, 123, 18, 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30, 24, 27, 132, 197, 86, 123, 18, 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30, 24,
52, 96, 72, 25, 255, 156, 23, 245, 233, 213, 221, 7, 143, 52, 96, 72, 25, 255, 156, 23, 245, 233, 213, 221, 7, 143,
]; ];
let acc2_addr = vec![ let acc2_addr: Vec<u8> = vec![
77, 75, 108, 209, 54, 16, 50, 202, 155, 210, 174, 185, 217, 0, 170, 77, 69, 217, 234, 77, 75, 108, 209, 54, 16, 50, 202, 155, 210, 174, 185, 217, 0, 170, 77, 69, 217, 234,
216, 10, 201, 66, 51, 116, 196, 81, 167, 37, 77, 7, 102, 216, 10, 201, 66, 51, 116, 196, 81, 167, 37, 77, 7, 102,
]; ];
let initial_acc1 = AccountInitialData { let initial_acc1 = AccountInitialData {
addr: hex::encode(acc1_addr), addr: acc1_addr.to_base58(),
balance: 10000, balance: 10000,
}; };
let initial_acc2 = AccountInitialData { let initial_acc2 = AccountInitialData {
addr: hex::encode(acc2_addr), addr: acc2_addr.to_base58(),
balance: 20000, balance: 20000,
}; };
@ -373,11 +382,17 @@ mod tests {
let config = setup_sequencer_config_variable_initial_accounts(initial_accounts); let config = setup_sequencer_config_variable_initial_accounts(initial_accounts);
let sequencer = SequencerCore::start_from_config(config.clone()); let sequencer = SequencerCore::start_from_config(config.clone());
let acc1_addr = hex::decode(config.initial_accounts[0].addr.clone()) let acc1_addr = config.initial_accounts[0]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
let acc2_addr = hex::decode(config.initial_accounts[1].addr.clone()) let acc2_addr = config.initial_accounts[1]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
@ -418,11 +433,17 @@ mod tests {
common_setup(&mut sequencer); common_setup(&mut sequencer);
let acc1 = hex::decode(sequencer.sequencer_config.initial_accounts[0].addr.clone()) let acc1 = sequencer.sequencer_config.initial_accounts[0]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
let acc2 = hex::decode(sequencer.sequencer_config.initial_accounts[1].addr.clone()) let acc2 = sequencer.sequencer_config.initial_accounts[1]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
@ -444,11 +465,17 @@ mod tests {
common_setup(&mut sequencer); common_setup(&mut sequencer);
let acc1 = hex::decode(sequencer.sequencer_config.initial_accounts[0].addr.clone()) let acc1 = sequencer.sequencer_config.initial_accounts[0]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
let acc2 = hex::decode(sequencer.sequencer_config.initial_accounts[1].addr.clone()) let acc2 = sequencer.sequencer_config.initial_accounts[1]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
@ -480,11 +507,17 @@ mod tests {
common_setup(&mut sequencer); common_setup(&mut sequencer);
let acc1 = hex::decode(sequencer.sequencer_config.initial_accounts[0].addr.clone()) let acc1 = sequencer.sequencer_config.initial_accounts[0]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
let acc2 = hex::decode(sequencer.sequencer_config.initial_accounts[1].addr.clone()) let acc2 = sequencer.sequencer_config.initial_accounts[1]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
@ -516,11 +549,17 @@ mod tests {
common_setup(&mut sequencer); common_setup(&mut sequencer);
let acc1 = hex::decode(sequencer.sequencer_config.initial_accounts[0].addr.clone()) let acc1 = sequencer.sequencer_config.initial_accounts[0]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
let acc2 = hex::decode(sequencer.sequencer_config.initial_accounts[1].addr.clone()) let acc2 = sequencer.sequencer_config.initial_accounts[1]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
@ -606,11 +645,17 @@ mod tests {
common_setup(&mut sequencer); common_setup(&mut sequencer);
let acc1 = hex::decode(sequencer.sequencer_config.initial_accounts[0].addr.clone()) let acc1 = sequencer.sequencer_config.initial_accounts[0]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
let acc2 = hex::decode(sequencer.sequencer_config.initial_accounts[1].addr.clone()) let acc2 = sequencer.sequencer_config.initial_accounts[1]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
@ -647,11 +692,17 @@ mod tests {
common_setup(&mut sequencer); common_setup(&mut sequencer);
let acc1 = hex::decode(sequencer.sequencer_config.initial_accounts[0].addr.clone()) let acc1 = sequencer.sequencer_config.initial_accounts[0]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
let acc2 = hex::decode(sequencer.sequencer_config.initial_accounts[1].addr.clone()) let acc2 = sequencer.sequencer_config.initial_accounts[1]
.addr
.clone()
.from_base58()
.unwrap() .unwrap()
.try_into() .try_into()
.unwrap(); .unwrap();
@ -688,14 +739,8 @@ mod tests {
#[test] #[test]
fn test_restart_from_storage() { fn test_restart_from_storage() {
let config = setup_sequencer_config(); let config = setup_sequencer_config();
let acc1_addr = hex::decode(config.initial_accounts[0].addr.clone()) let acc1_addr: nssa::Address = config.initial_accounts[0].addr.parse().unwrap();
.unwrap() let acc2_addr: nssa::Address = config.initial_accounts[1].addr.parse().unwrap();
.try_into()
.unwrap();
let acc2_addr = hex::decode(config.initial_accounts[1].addr.clone())
.unwrap()
.try_into()
.unwrap();
let balance_to_move = 13; let balance_to_move = 13;
// In the following code block a transaction will be processed that moves `balance_to_move` // In the following code block a transaction will be processed that moves `balance_to_move`
@ -706,9 +751,9 @@ mod tests {
let signing_key = PrivateKey::try_new([1; 32]).unwrap(); let signing_key = PrivateKey::try_new([1; 32]).unwrap();
let tx = common::test_utils::create_transaction_native_token_transfer( let tx = common::test_utils::create_transaction_native_token_transfer(
acc1_addr, *acc1_addr.value(),
0, 0,
acc2_addr, *acc2_addr.value(),
balance_to_move, balance_to_move,
signing_key, signing_key,
); );
@ -727,14 +772,8 @@ mod tests {
// Instantiating a new sequencer from the same config. This should load the existing block // Instantiating a new sequencer from the same config. This should load the existing block
// with the above transaction and update the state to reflect that. // with the above transaction and update the state to reflect that.
let sequencer = SequencerCore::start_from_config(config.clone()); let sequencer = SequencerCore::start_from_config(config.clone());
let balance_acc_1 = sequencer let balance_acc_1 = sequencer.state.get_account_by_address(&acc1_addr).balance;
.state let balance_acc_2 = sequencer.state.get_account_by_address(&acc2_addr).balance;
.get_account_by_address(&nssa::Address::new(acc1_addr))
.balance;
let balance_acc_2 = sequencer
.state
.get_account_by_address(&nssa::Address::new(acc2_addr))
.balance;
// Balances should be consistent with the stored block // Balances should be consistent with the stored block
assert_eq!( assert_eq!(

View File

@ -0,0 +1,74 @@
use std::path::Path;
use block_store::SequecerBlockStore;
use common::block::HashableBlockData;
use nssa::{self, Address};
use rand::{RngCore, rngs::OsRng};
use crate::config::AccountInitialData;
pub mod block_store;
pub struct SequecerChainStore {
pub state: nssa::V02State,
pub block_store: SequecerBlockStore,
}
impl SequecerChainStore {
pub fn new_with_genesis(
home_dir: &Path,
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
.iter()
.map(|acc_data| (acc_data.addr.parse().unwrap(), acc_data.balance))
.collect();
#[cfg(not(feature = "testnet"))]
let state = nssa::V02State::new_with_genesis_accounts(&init_accs, initial_commitments);
#[cfg(feature = "testnet")]
let state = {
use common::PINATA_BASE58;
let mut this =
nssa::V02State::new_with_genesis_accounts(&init_accs, initial_commitments);
this.add_pinata_program(PINATA_BASE58.parse().unwrap());
this
};
let mut data = [0; 32];
let mut prev_block_hash = [0; 32];
if is_genesis_random {
OsRng.fill_bytes(&mut data);
OsRng.fill_bytes(&mut prev_block_hash);
}
let curr_time = chrono::Utc::now().timestamp_millis() as u64;
let hashable_data = HashableBlockData {
block_id: genesis_id,
transactions: vec![],
prev_block_hash,
timestamp: curr_time,
};
let genesis_block = hashable_data.into_block(&signing_key);
//Sequencer should panic if unable to open db,
//as fixing this issue may require actions non-native to program scope
let block_store = SequecerBlockStore::open_db_with_genesis(
&home_dir.join("rocksdb"),
Some(genesis_block),
signing_key,
)
.unwrap();
Self { state, block_store }
}
}

View File

@ -10,7 +10,8 @@ log.workspace = true
serde.workspace = true serde.workspace = true
actix-cors.workspace = true actix-cors.workspace = true
futures.workspace = true futures.workspace = true
hex.workspace = true base58.workspace = true
hex = "0.4.3"
tempfile.workspace = true tempfile.workspace = true
base64.workspace = true base64.workspace = true

View File

@ -1,6 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use actix_web::Error as HttpError; use actix_web::Error as HttpError;
use base58::FromBase58;
use base64::{Engine, engine::general_purpose}; use base64::{Engine, engine::general_purpose};
use nssa::{self, program::Program}; use nssa::{self, program::Program};
use sequencer_core::config::AccountInitialData; use sequencer_core::config::AccountInitialData;
@ -160,8 +161,10 @@ impl JsonHandler {
/// The address must be a valid hex string of the correct length. /// The address must be a valid hex string of the correct length.
async fn process_get_account_balance(&self, request: Request) -> Result<Value, RpcErr> { async fn process_get_account_balance(&self, request: Request) -> Result<Value, RpcErr> {
let get_account_req = GetAccountBalanceRequest::parse(Some(request.params))?; let get_account_req = GetAccountBalanceRequest::parse(Some(request.params))?;
let address_bytes = hex::decode(get_account_req.address) let address_bytes = get_account_req
.map_err(|_| RpcError::invalid_params("invalid hex".to_string()))?; .address
.from_base58()
.map_err(|_| RpcError::invalid_params("invalid base58".to_string()))?;
let address = nssa::Address::new( let address = nssa::Address::new(
address_bytes address_bytes
.try_into() .try_into()
@ -307,6 +310,7 @@ mod tests {
use std::sync::Arc; use std::sync::Arc;
use crate::{JsonHandler, rpc_handler}; use crate::{JsonHandler, rpc_handler};
use base58::ToBase58;
use base64::{Engine, engine::general_purpose}; use base64::{Engine, engine::general_purpose};
use common::{ use common::{
rpc_primitives::RpcPollingConfig, test_utils::sequencer_sign_key_for_testing, rpc_primitives::RpcPollingConfig, test_utils::sequencer_sign_key_for_testing,
@ -324,23 +328,23 @@ mod tests {
fn sequencer_config_for_tests() -> SequencerConfig { fn sequencer_config_for_tests() -> SequencerConfig {
let tempdir = tempdir().unwrap(); let tempdir = tempdir().unwrap();
let home = tempdir.path().to_path_buf(); let home = tempdir.path().to_path_buf();
let acc1_addr = vec![ let acc1_addr: Vec<u8> = vec![
208, 122, 210, 232, 75, 39, 250, 0, 194, 98, 240, 161, 238, 160, 255, 53, 202, 9, 115, 208, 122, 210, 232, 75, 39, 250, 0, 194, 98, 240, 161, 238, 160, 255, 53, 202, 9, 115,
84, 126, 106, 16, 111, 114, 241, 147, 194, 220, 131, 139, 68, 84, 126, 106, 16, 111, 114, 241, 147, 194, 220, 131, 139, 68,
]; ];
let acc2_addr = vec![ let acc2_addr: Vec<u8> = vec![
231, 174, 119, 197, 239, 26, 5, 153, 147, 68, 175, 73, 159, 199, 138, 23, 5, 57, 141, 231, 174, 119, 197, 239, 26, 5, 153, 147, 68, 175, 73, 159, 199, 138, 23, 5, 57, 141,
98, 237, 6, 207, 46, 20, 121, 246, 222, 248, 154, 57, 188, 98, 237, 6, 207, 46, 20, 121, 246, 222, 248, 154, 57, 188,
]; ];
let initial_acc1 = AccountInitialData { let initial_acc1 = AccountInitialData {
addr: hex::encode(acc1_addr), addr: acc1_addr.to_base58(),
balance: 10000, balance: 10000,
}; };
let initial_acc2 = AccountInitialData { let initial_acc2 = AccountInitialData {
addr: hex::encode(acc2_addr), addr: acc2_addr.to_base58(),
balance: 20000, balance: 20000,
}; };
@ -425,7 +429,7 @@ mod tests {
let request = serde_json::json!({ let request = serde_json::json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "get_account_balance", "method": "get_account_balance",
"params": { "address": "efac".repeat(16) }, "params": { "address": "11".repeat(16) },
"id": 1 "id": 1
}); });
let expected_response = serde_json::json!({ let expected_response = serde_json::json!({
@ -442,12 +446,12 @@ mod tests {
} }
#[actix_web::test] #[actix_web::test]
async fn test_get_account_balance_for_invalid_hex() { async fn test_get_account_balance_for_invalid_base58() {
let (json_handler, _, _) = components_for_tests(); let (json_handler, _, _) = components_for_tests();
let request = serde_json::json!({ let request = serde_json::json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "get_account_balance", "method": "get_account_balance",
"params": { "address": "not_a_valid_hex" }, "params": { "address": "not_a_valid_base58" },
"id": 1 "id": 1
}); });
let expected_response = serde_json::json!({ let expected_response = serde_json::json!({
@ -456,7 +460,7 @@ mod tests {
"error": { "error": {
"code": -32602, "code": -32602,
"message": "Invalid params", "message": "Invalid params",
"data": "invalid hex" "data": "invalid base58"
} }
}); });
let response = call_rpc_handler_with_json(json_handler, request).await; let response = call_rpc_handler_with_json(json_handler, request).await;
@ -518,7 +522,7 @@ mod tests {
let request = serde_json::json!({ let request = serde_json::json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "get_accounts_nonces", "method": "get_accounts_nonces",
"params": { "addresses": ["efac".repeat(16)] }, "params": { "addresses": ["11".repeat(16)] },
"id": 1 "id": 1
}); });
let expected_response = serde_json::json!({ let expected_response = serde_json::json!({
@ -566,7 +570,7 @@ mod tests {
let request = serde_json::json!({ let request = serde_json::json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "get_account", "method": "get_account",
"params": { "address": "efac".repeat(16) }, "params": { "address": "11".repeat(16) },
"id": 1 "id": 1
}); });
let expected_response = serde_json::json!({ let expected_response = serde_json::json!({

View File

@ -8,49 +8,49 @@
"port": 3040, "port": 3040,
"initial_accounts": [ "initial_accounts": [
{ {
"addr": "d07ad2e84b27fa00c262f0a1eea0ff35ca0973547e6a106f72f193c2dc838b44", "addr": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy",
"balance": 10000 "balance": 10000
}, },
{ {
"addr": "e7ae77c5ef1a05999344af499fc78a1705398d62ed06cf2e1479f6def89a39bc", "addr": "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw",
"balance": 20000 "balance": 20000
} }
], ],
"initial_commitments": [ "initial_commitments": [
{ {
"npk": [ "npk": [
193, 63,
209, 202,
150, 178,
113, 231,
47, 183,
241, 82,
48, 237,
145, 212,
250, 216,
79,
235,
51,
119,
40,
184,
232,
5,
221, 221,
36, 215,
21, 255,
201,
106,
90,
210,
129,
106,
71,
99,
208,
153, 153,
75, 101,
215 177,
161,
254,
210,
128,
122,
54,
190,
230,
151,
183,
64,
225,
229,
113,
1,
228,
97
], ],
"account": { "account": {
"program_owner": [ "program_owner": [
@ -70,38 +70,38 @@
}, },
{ {
"npk": [ "npk": [
27, 192,
250, 251,
166,
243,
167,
236,
84,
249,
35,
136, 136,
142, 130,
88, 172,
128, 219,
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, 225,
105 161,
139,
229,
89,
243,
125,
194,
213,
209,
30,
23,
174,
100,
244,
124,
74,
140,
47
], ],
"account": { "account": {
"program_owner": [ "program_owner": [
@ -154,4 +154,4 @@
37, 37,
37 37
] ]
} }

View File

@ -16,7 +16,8 @@ nssa-core = { path = "../nssa/core" }
base64.workspace = true base64.workspace = true
bytemuck = "1.23.2" bytemuck = "1.23.2"
borsh.workspace = true borsh.workspace = true
hex.workspace = true base58.workspace = true
hex = "0.4.3"
rand.workspace = true rand.workspace = true
[dependencies.key_protocol] [dependencies.key_protocol]

View File

@ -75,19 +75,91 @@ mod tests {
use tempfile::tempdir; use tempfile::tempdir;
fn create_initial_accounts() -> Vec<InitialAccountData> { fn create_initial_accounts() -> Vec<InitialAccountData> {
let initial_acc1 = serde_json::from_str(r#"{ let initial_acc1 = serde_json::from_str(
r#"{
"Public": { "Public": {
"address": "d07ad2e84b27fa00c262f0a1eea0ff35ca0973547e6a106f72f193c2dc838b44", "address": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy",
"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] "pub_sign_key": [
16,
162,
106,
154,
236,
125,
52,
184,
35,
100,
238,
174,
69,
197,
41,
77,
187,
10,
118,
75,
0,
11,
148,
238,
185,
181,
133,
17,
220,
72,
124,
77
]
} }
}"#).unwrap(); }"#,
)
.unwrap();
let initial_acc2 = serde_json::from_str(r#"{ let initial_acc2 = serde_json::from_str(
r#"{
"Public": { "Public": {
"address": "e7ae77c5ef1a05999344af499fc78a1705398d62ed06cf2e1479f6def89a39bc", "address": "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw",
"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] "pub_sign_key": [
113,
121,
64,
177,
204,
85,
229,
214,
178,
6,
109,
191,
29,
154,
63,
38,
242,
18,
244,
219,
8,
208,
35,
136,
23,
127,
207,
237,
216,
169,
190,
27
]
} }
}"#).unwrap(); }"#,
)
.unwrap();
let initial_accounts = vec![initial_acc1, initial_acc2]; let initial_accounts = vec![initial_acc1, initial_acc2];

View File

@ -1,213 +1,117 @@
use std::str::FromStr;
use anyhow::Result; use anyhow::Result;
use base58::ToBase58;
use clap::Subcommand; use clap::Subcommand;
use common::transaction::NSSATransaction; use nssa::{Account, Address, program::Program};
use nssa::Address; use serde::Serialize;
use crate::{ use crate::{
SubcommandReturnValue, WalletCore, cli::WalletSubcommand, helperfunctions::HumanReadableAccount, SubcommandReturnValue, WalletCore,
cli::WalletSubcommand,
helperfunctions::{AddressPrivacyKind, HumanReadableAccount, parse_addr_with_privacy_prefix},
parse_block_range,
}; };
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 {
#[allow(unused)]
account_type: u8,
name: [u8; 6],
total_supply: u128,
}
struct TokenHolding {
#[allow(unused)]
account_type: u8,
definition_id: Address,
balance: u128,
}
impl TokenDefinition {
fn parse(data: &[u8]) -> Option<Self> {
if data.len() != TOKEN_DEFINITION_DATA_SIZE || data[0] != TOKEN_DEFINITION_TYPE {
None
} else {
let account_type = data[0];
let name = data[1..7].try_into().unwrap();
let total_supply = u128::from_le_bytes(data[7..].try_into().unwrap());
Some(Self {
account_type,
name,
total_supply,
})
}
}
}
impl TokenHolding {
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 = Address::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,
})
}
}
}
///Represents generic chain CLI subcommand ///Represents generic chain CLI subcommand
#[derive(Subcommand, Debug, Clone)] #[derive(Subcommand, Debug, Clone)]
pub enum AccountSubcommand { pub enum AccountSubcommand {
///Get ///Get account data
Get {
///Flag to get raw account data
#[arg(short, long)]
raw: bool,
///Valid 32 byte base58 string with privacy prefix
#[arg(short, long)]
addr: String,
},
///Produce new public or private account
#[command(subcommand)] #[command(subcommand)]
Get(GetSubcommand), New(NewSubcommand),
///Fetch ///Sync private accounts
#[command(subcommand)] SyncPrivate {},
Fetch(FetchSubcommand),
///Register
#[command(subcommand)]
Register(RegisterSubcommand),
}
///Represents generic getter CLI subcommand
#[derive(Subcommand, Debug, Clone)]
pub enum GetSubcommand {
///Get account `addr` balance
PublicAccountBalance {
#[arg(short, long)]
addr: String,
},
///Get account `addr` nonce
PublicAccountNonce {
#[arg(short, long)]
addr: String,
},
///Get account at address `addr`
PublicAccount {
#[arg(short, long)]
addr: String,
},
///Get private account with `addr` from storage
PrivateAccount {
#[arg(short, long)]
addr: String,
},
}
///Represents generic getter CLI subcommand
#[derive(Subcommand, Debug, Clone)]
pub enum FetchSubcommand {
///Fetch transaction by `hash`
Tx {
#[arg(short, long)]
tx_hash: String,
},
///Claim account `acc_addr` generated in transaction `tx_hash`, using secret `sh_secret` at ciphertext id `ciph_id`
PrivateAccount {
///tx_hash - valid 32 byte hex string
#[arg(long)]
tx_hash: String,
///acc_addr - valid 32 byte hex string
#[arg(long)]
acc_addr: String,
///output_id - id of the output in the transaction
#[arg(long)]
output_id: usize,
},
} }
///Represents generic register CLI subcommand ///Represents generic register CLI subcommand
#[derive(Subcommand, Debug, Clone)] #[derive(Subcommand, Debug, Clone)]
pub enum RegisterSubcommand { pub enum NewSubcommand {
///Register new public account ///Register new public account
Public {}, Public {},
///Register new private account ///Register new private account
Private {}, Private {},
} }
impl WalletSubcommand for GetSubcommand { impl WalletSubcommand for NewSubcommand {
async fn handle_subcommand( async fn handle_subcommand(
self, self,
wallet_core: &mut WalletCore, wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> { ) -> Result<SubcommandReturnValue> {
match self { match self {
GetSubcommand::PublicAccountBalance { addr } => { NewSubcommand::Public {} => {
let addr = Address::from_str(&addr)?;
let balance = wallet_core.get_account_balance(addr).await?;
println!("Accounts {addr} balance is {balance}");
Ok(SubcommandReturnValue::Empty)
}
GetSubcommand::PublicAccountNonce { addr } => {
let addr = Address::from_str(&addr)?;
let nonce = wallet_core.get_accounts_nonces(vec![addr]).await?[0];
println!("Accounts {addr} nonce is {nonce}");
Ok(SubcommandReturnValue::Empty)
}
GetSubcommand::PublicAccount { addr } => {
let addr: Address = addr.parse()?;
let account = wallet_core.get_account_public(addr).await?;
let account_hr: HumanReadableAccount = account.clone().into();
println!("{}", serde_json::to_string(&account_hr).unwrap());
Ok(SubcommandReturnValue::Account(account))
}
GetSubcommand::PrivateAccount { addr } => {
let addr: Address = addr.parse()?;
if let Some(account) = wallet_core.get_account_private(&addr) {
println!("{}", serde_json::to_string(&account).unwrap());
} else {
println!("Private account not found.");
}
Ok(SubcommandReturnValue::Empty)
}
}
}
}
impl WalletSubcommand for FetchSubcommand {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
FetchSubcommand::Tx { tx_hash } => {
let tx_obj = wallet_core
.sequencer_client
.get_transaction_by_hash(tx_hash)
.await?;
println!("Transaction object {tx_obj:#?}");
Ok(SubcommandReturnValue::Empty)
}
FetchSubcommand::PrivateAccount {
tx_hash,
acc_addr,
output_id: ciph_id,
} => {
let acc_addr: Address = acc_addr.parse().unwrap();
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().await?;
println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::Empty)
}
}
}
}
impl WalletSubcommand for RegisterSubcommand {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
RegisterSubcommand::Public {} => {
let addr = wallet_core.create_new_account_public(); let addr = wallet_core.create_new_account_public();
println!("Generated new account with addr {addr}"); println!("Generated new account with addr Public/{addr}");
let path = wallet_core.store_persistent_accounts().await?; let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::RegisterAccount { addr }) Ok(SubcommandReturnValue::RegisterAccount { addr })
} }
RegisterSubcommand::Private {} => { NewSubcommand::Private {} => {
let addr = wallet_core.create_new_account_private(); let addr = wallet_core.create_new_account_private();
let (key, _) = wallet_core let (key, _) = wallet_core
@ -216,14 +120,17 @@ impl WalletSubcommand for RegisterSubcommand {
.get_private_account(&addr) .get_private_account(&addr)
.unwrap(); .unwrap();
println!("Generated new account with addr {addr}"); println!(
println!("With npk {}", hex::encode(&key.nullifer_public_key)); "Generated new account with addr Private/{}",
addr.to_bytes().to_base58()
);
println!("With npk {}", hex::encode(key.nullifer_public_key.0));
println!( println!(
"With ipk {}", "With ipk {}",
hex::encode(key.incoming_viewing_public_key.to_bytes()) hex::encode(key.incoming_viewing_public_key.to_bytes())
); );
let path = wallet_core.store_persistent_accounts().await?; let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");
@ -233,20 +140,155 @@ impl WalletSubcommand for RegisterSubcommand {
} }
} }
#[derive(Debug, Serialize)]
pub struct AuthenticatedTransferAccountView {
pub balance: u128,
}
impl From<nssa::Account> for AuthenticatedTransferAccountView {
fn from(value: nssa::Account) -> Self {
Self {
balance: value.balance,
}
}
}
#[derive(Debug, Serialize)]
pub struct TokedDefinitionAccountView {
pub account_type: String,
pub name: String,
pub total_supply: u128,
}
impl From<TokenDefinition> for TokedDefinitionAccountView {
fn from(value: TokenDefinition) -> Self {
Self {
account_type: "Token definition".to_string(),
name: hex::encode(value.name),
total_supply: value.total_supply,
}
}
}
#[derive(Debug, Serialize)]
pub struct TokedHoldingAccountView {
pub account_type: String,
pub definition_id: String,
pub balance: u128,
}
impl From<TokenHolding> for TokedHoldingAccountView {
fn from(value: TokenHolding) -> Self {
Self {
account_type: "Token holding".to_string(),
definition_id: value.definition_id.to_string(),
balance: value.balance,
}
}
}
impl WalletSubcommand for AccountSubcommand { impl WalletSubcommand for AccountSubcommand {
async fn handle_subcommand( async fn handle_subcommand(
self, self,
wallet_core: &mut WalletCore, wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> { ) -> Result<SubcommandReturnValue> {
match self { match self {
AccountSubcommand::Get(get_subcommand) => { AccountSubcommand::Get { raw, addr } => {
get_subcommand.handle_subcommand(wallet_core).await let (addr, addr_kind) = parse_addr_with_privacy_prefix(&addr)?;
let addr = addr.parse()?;
let account = match addr_kind {
AddressPrivacyKind::Public => wallet_core.get_account_public(addr).await?,
AddressPrivacyKind::Private => wallet_core
.get_account_private(&addr)
.ok_or(anyhow::anyhow!("Private account not found in storage"))?,
};
if account == Account::default() {
println!("Account is Uninitialized");
return Ok(SubcommandReturnValue::Empty);
}
if raw {
let account_hr: HumanReadableAccount = account.clone().into();
println!("{}", serde_json::to_string(&account_hr).unwrap());
return Ok(SubcommandReturnValue::Empty);
}
let auth_tr_prog_id = Program::authenticated_transfer_program().id();
let token_prog_id = Program::token().id();
let acc_view = match &account.program_owner {
_ if account.program_owner == auth_tr_prog_id => {
let acc_view: AuthenticatedTransferAccountView = account.into();
println!("Account owned by authenticated transfer program");
serde_json::to_string(&acc_view)?
}
_ if account.program_owner == token_prog_id => {
if let Some(token_def) = TokenDefinition::parse(&account.data) {
let acc_view: TokedDefinitionAccountView = token_def.into();
println!("Definition account owned by token program");
serde_json::to_string(&acc_view)?
} else if let Some(token_hold) = TokenHolding::parse(&account.data) {
let acc_view: TokedHoldingAccountView = token_hold.into();
println!("Holding account owned by token program");
serde_json::to_string(&acc_view)?
} else {
anyhow::bail!("Invalid data for account {addr:#?} with token program");
}
}
_ => {
let account_hr: HumanReadableAccount = account.clone().into();
serde_json::to_string(&account_hr).unwrap()
}
};
println!("{}", acc_view);
Ok(SubcommandReturnValue::Empty)
} }
AccountSubcommand::Fetch(fetch_subcommand) => { AccountSubcommand::New(new_subcommand) => {
fetch_subcommand.handle_subcommand(wallet_core).await new_subcommand.handle_subcommand(wallet_core).await
} }
AccountSubcommand::Register(register_subcommand) => { AccountSubcommand::SyncPrivate {} => {
register_subcommand.handle_subcommand(wallet_core).await let last_synced_block = wallet_core.last_synced_block;
let curr_last_block = wallet_core
.sequencer_client
.get_last_block()
.await?
.last_block;
if !wallet_core
.storage
.user_data
.user_private_accounts
.is_empty()
{
parse_block_range(
last_synced_block + 1,
curr_last_block,
wallet_core.sequencer_client.clone(),
wallet_core,
)
.await?;
} else {
wallet_core.last_synced_block = curr_last_block;
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent data at {path:#?}");
}
Ok(SubcommandReturnValue::SyncedToBlock(curr_last_block))
} }
} }
} }

View File

@ -6,12 +6,16 @@ use crate::{SubcommandReturnValue, WalletCore, cli::WalletSubcommand};
///Represents generic chain CLI subcommand ///Represents generic chain CLI subcommand
#[derive(Subcommand, Debug, Clone)] #[derive(Subcommand, Debug, Clone)]
pub enum ChainSubcommand { pub enum ChainSubcommand {
GetLatestBlockId {}, ///Get current block id from sequencer
GetBlockAtId { CurrentBlockId {},
///Get block at id from sequencer
Block {
#[arg(short, long)] #[arg(short, long)]
id: u64, id: u64,
}, },
GetTransactionAtHash { ///Get transaction at hash from sequencer
Transaction {
///hash - valid 32 byte hex string
#[arg(short, long)] #[arg(short, long)]
hash: String, hash: String,
}, },
@ -23,17 +27,17 @@ impl WalletSubcommand for ChainSubcommand {
wallet_core: &mut WalletCore, wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> { ) -> Result<SubcommandReturnValue> {
match self { match self {
ChainSubcommand::GetLatestBlockId {} => { ChainSubcommand::CurrentBlockId {} => {
let latest_block_res = wallet_core.sequencer_client.get_last_block().await?; let latest_block_res = wallet_core.sequencer_client.get_last_block().await?;
println!("Last block id is {}", latest_block_res.last_block); println!("Last block id is {}", latest_block_res.last_block);
} }
ChainSubcommand::GetBlockAtId { id } => { ChainSubcommand::Block { id } => {
let block_res = wallet_core.sequencer_client.get_block(id).await?; let block_res = wallet_core.sequencer_client.get_block(id).await?;
println!("Last block id is {:#?}", block_res.block); println!("Last block id is {:#?}", block_res.block);
} }
ChainSubcommand::GetTransactionAtHash { hash } => { ChainSubcommand::Transaction { hash } => {
let tx_res = wallet_core let tx_res = wallet_core
.sequencer_client .sequencer_client
.get_transaction_by_hash(hash) .get_transaction_by_hash(hash)

View File

@ -3,7 +3,193 @@ use clap::Subcommand;
use common::transaction::NSSATransaction; use common::transaction::NSSATransaction;
use nssa::Address; use nssa::Address;
use crate::{SubcommandReturnValue, WalletCore, cli::WalletSubcommand}; use crate::{
SubcommandReturnValue, WalletCore,
cli::WalletSubcommand,
helperfunctions::{AddressPrivacyKind, parse_addr_with_privacy_prefix},
};
///Represents generic CLI subcommand for a wallet working with native token transfer program
#[derive(Subcommand, Debug, Clone)]
pub enum AuthTransferSubcommand {
///Initialize account under authenticated transfer program
Init {
///addr - valid 32 byte base58 string with privacy prefix
#[arg(long)]
addr: String,
},
///Send native tokens from one account to another with variable privacy
///
///If receiver is private, then `to` and (`to_npk` , `to_ipk`) is a mutually exclusive patterns.
///
///First is used for owned accounts, second otherwise.
Send {
///from - valid 32 byte base58 string with privacy prefix
#[arg(long)]
from: String,
///to - valid 32 byte base58 string with privacy prefix
#[arg(long)]
to: Option<String>,
///to_npk - valid 32 byte hex string
#[arg(long)]
to_npk: Option<String>,
///to_ipk - valid 33 byte hex string
#[arg(long)]
to_ipk: Option<String>,
///amount - amount of balance to move
#[arg(long)]
amount: u128,
},
}
impl WalletSubcommand for AuthTransferSubcommand {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
AuthTransferSubcommand::Init { addr } => {
let (addr, addr_privacy) = parse_addr_with_privacy_prefix(&addr)?;
match addr_privacy {
AddressPrivacyKind::Public => {
let addr = addr.parse()?;
let res = wallet_core
.register_account_under_authenticated_transfers_programs(addr)
.await?;
println!("Results of tx send is {res:#?}");
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_data().await?;
println!("Stored persistent accounts at {path:#?}");
}
AddressPrivacyKind::Private => {
let addr = addr.parse()?;
let (res, [secret]) = wallet_core
.register_account_under_authenticated_transfers_programs_private(addr)
.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 acc_decode_data = vec![(secret, addr)];
wallet_core.decode_insert_privacy_preserving_transaction_results(
tx,
&acc_decode_data,
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
}
}
Ok(SubcommandReturnValue::Empty)
}
AuthTransferSubcommand::Send {
from,
to,
to_npk,
to_ipk,
amount,
} => {
let underlying_subcommand = match (to, to_npk, to_ipk) {
(None, None, None) => {
anyhow::bail!(
"Provide either account address of receiver or their public keys"
);
}
(Some(_), Some(_), Some(_)) => {
anyhow::bail!(
"Provide only one variant: either account address of receiver or their public keys"
);
}
(_, Some(_), None) | (_, None, Some(_)) => {
anyhow::bail!("List of public keys is uncomplete");
}
(Some(to), None, None) => {
let (from, from_privacy) = parse_addr_with_privacy_prefix(&from)?;
let (to, to_privacy) = parse_addr_with_privacy_prefix(&to)?;
match (from_privacy, to_privacy) {
(AddressPrivacyKind::Public, AddressPrivacyKind::Public) => {
NativeTokenTransferProgramSubcommand::Public { from, to, amount }
}
(AddressPrivacyKind::Private, AddressPrivacyKind::Private) => {
NativeTokenTransferProgramSubcommand::Private(
NativeTokenTransferProgramSubcommandPrivate::PrivateOwned {
from,
to,
amount,
},
)
}
(AddressPrivacyKind::Private, AddressPrivacyKind::Public) => {
NativeTokenTransferProgramSubcommand::Deshielded {
from,
to,
amount,
}
}
(AddressPrivacyKind::Public, AddressPrivacyKind::Private) => {
NativeTokenTransferProgramSubcommand::Shielded(
NativeTokenTransferProgramSubcommandShielded::ShieldedOwned {
from,
to,
amount,
},
)
}
}
}
(None, Some(to_npk), Some(to_ipk)) => {
let (from, from_privacy) = parse_addr_with_privacy_prefix(&from)?;
match from_privacy {
AddressPrivacyKind::Private => {
NativeTokenTransferProgramSubcommand::Private(
NativeTokenTransferProgramSubcommandPrivate::PrivateForeign {
from,
to_npk,
to_ipk,
amount,
},
)
}
AddressPrivacyKind::Public => {
NativeTokenTransferProgramSubcommand::Shielded(
NativeTokenTransferProgramSubcommandShielded::ShieldedForeign {
from,
to_npk,
to_ipk,
amount,
},
)
}
}
}
};
underlying_subcommand.handle_subcommand(wallet_core).await
}
}
}
}
///Represents generic CLI subcommand for a wallet working with native token transfer program ///Represents generic CLI subcommand for a wallet working with native token transfer program
#[derive(Subcommand, Debug, Clone)] #[derive(Subcommand, Debug, Clone)]
@ -158,7 +344,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
)?; )?;
} }
let path = wallet_core.store_persistent_accounts().await?; let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");
@ -202,7 +388,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
)?; )?;
} }
let path = wallet_core.store_persistent_accounts().await?; let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");
@ -252,7 +438,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
)?; )?;
} }
let path = wallet_core.store_persistent_accounts().await?; let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");
@ -285,7 +471,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
let tx_hash = res.tx_hash; let tx_hash = res.tx_hash;
let path = wallet_core.store_persistent_accounts().await?; let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");
@ -331,7 +517,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand {
)?; )?;
} }
let path = wallet_core.store_persistent_accounts().await?; let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");
@ -351,7 +537,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand {
println!("Transaction data is {transfer_tx:?}"); println!("Transaction data is {transfer_tx:?}");
let path = wallet_core.store_persistent_accounts().await?; let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");

View File

@ -1,9 +1,59 @@
use anyhow::Result; use anyhow::Result;
use clap::Subcommand; use clap::Subcommand;
use common::transaction::NSSATransaction; use common::{PINATA_BASE58, transaction::NSSATransaction};
use log::info; use log::info;
use crate::{SubcommandReturnValue, WalletCore, cli::WalletSubcommand}; use crate::{
SubcommandReturnValue, WalletCore,
cli::WalletSubcommand,
helperfunctions::{AddressPrivacyKind, parse_addr_with_privacy_prefix},
};
///Represents generic CLI subcommand for a wallet working with pinata program
#[derive(Subcommand, Debug, Clone)]
pub enum PinataProgramAgnosticSubcommand {
///Claim pinata
Claim {
///to_addr - valid 32 byte base58 string with privacy prefix
#[arg(long)]
to_addr: String,
///solution - solution to pinata challenge
#[arg(long)]
solution: u128,
},
}
impl WalletSubcommand for PinataProgramAgnosticSubcommand {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
let underlying_subcommand = match self {
PinataProgramAgnosticSubcommand::Claim { to_addr, solution } => {
let (to_addr, to_addr_privacy) = parse_addr_with_privacy_prefix(&to_addr)?;
match to_addr_privacy {
AddressPrivacyKind::Public => {
PinataProgramSubcommand::Public(PinataProgramSubcommandPublic::Claim {
pinata_addr: PINATA_BASE58.to_string(),
winner_addr: to_addr,
solution,
})
}
AddressPrivacyKind::Private => PinataProgramSubcommand::Private(
PinataProgramSubcommandPrivate::ClaimPrivateOwned {
pinata_addr: PINATA_BASE58.to_string(),
winner_addr: to_addr,
solution,
},
),
}
}
};
underlying_subcommand.handle_subcommand(wallet_core).await
}
}
///Represents generic CLI subcommand for a wallet working with pinata program ///Represents generic CLI subcommand for a wallet working with pinata program
#[derive(Subcommand, Debug, Clone)] #[derive(Subcommand, Debug, Clone)]
@ -131,7 +181,7 @@ impl WalletSubcommand for PinataProgramSubcommandPrivate {
)?; )?;
} }
let path = wallet_core.store_persistent_accounts().await?; let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");

View File

@ -3,7 +3,197 @@ use clap::Subcommand;
use common::transaction::NSSATransaction; use common::transaction::NSSATransaction;
use nssa::Address; use nssa::Address;
use crate::{SubcommandReturnValue, WalletCore, cli::WalletSubcommand}; use crate::{
SubcommandReturnValue, WalletCore,
cli::WalletSubcommand,
helperfunctions::{AddressPrivacyKind, parse_addr_with_privacy_prefix},
};
///Represents generic CLI subcommand for a wallet working with token program
#[derive(Subcommand, Debug, Clone)]
pub enum TokenProgramAgnosticSubcommand {
///Produce a new token
///
///Currently the only supported privacy options is for public definition
New {
///definition_addr - valid 32 byte base58 string with privacy prefix
#[arg(long)]
definition_addr: String,
///supply_addr - valid 32 byte base58 string with privacy prefix
#[arg(long)]
supply_addr: String,
#[arg(short, long)]
name: String,
#[arg(short, long)]
total_supply: u128,
},
///Send tokens from one account to another with variable privacy
///
///If receiver is private, then `to` and (`to_npk` , `to_ipk`) is a mutually exclusive patterns.
///
///First is used for owned accounts, second otherwise.
Send {
///from - valid 32 byte base58 string with privacy prefix
#[arg(long)]
from: String,
///to - valid 32 byte base58 string with privacy prefix
#[arg(long)]
to: Option<String>,
///to_npk - valid 32 byte hex string
#[arg(long)]
to_npk: Option<String>,
///to_ipk - valid 33 byte hex string
#[arg(long)]
to_ipk: Option<String>,
///amount - amount of balance to move
#[arg(long)]
amount: u128,
},
}
impl WalletSubcommand for TokenProgramAgnosticSubcommand {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
TokenProgramAgnosticSubcommand::New {
definition_addr,
supply_addr,
name,
total_supply,
} => {
let (definition_addr, definition_addr_privacy) =
parse_addr_with_privacy_prefix(&definition_addr)?;
let (supply_addr, supply_addr_privacy) =
parse_addr_with_privacy_prefix(&supply_addr)?;
let underlying_subcommand = match (definition_addr_privacy, supply_addr_privacy) {
(AddressPrivacyKind::Public, AddressPrivacyKind::Public) => {
TokenProgramSubcommand::Public(
TokenProgramSubcommandPublic::CreateNewToken {
definition_addr,
supply_addr,
name,
total_supply,
},
)
}
(AddressPrivacyKind::Public, AddressPrivacyKind::Private) => {
TokenProgramSubcommand::Private(
TokenProgramSubcommandPrivate::CreateNewTokenPrivateOwned {
definition_addr,
supply_addr,
name,
total_supply,
},
)
}
(AddressPrivacyKind::Private, AddressPrivacyKind::Private) => {
//ToDo: maybe implement this one. It is not immediately clear why definition should be private.
anyhow::bail!("Unavailable privacy pairing")
}
(AddressPrivacyKind::Private, AddressPrivacyKind::Public) => {
//ToDo: Probably valid. If definition is not public, but supply is it is very suspicious.
anyhow::bail!("Unavailable privacy pairing")
}
};
underlying_subcommand.handle_subcommand(wallet_core).await
}
TokenProgramAgnosticSubcommand::Send {
from,
to,
to_npk,
to_ipk,
amount,
} => {
let underlying_subcommand = match (to, to_npk, to_ipk) {
(None, None, None) => {
anyhow::bail!(
"Provide either account address of receiver or their public keys"
);
}
(Some(_), Some(_), Some(_)) => {
anyhow::bail!(
"Provide only one variant: either account address of receiver or their public keys"
);
}
(_, Some(_), None) | (_, None, Some(_)) => {
anyhow::bail!("List of public keys is uncomplete");
}
(Some(to), None, None) => {
let (from, from_privacy) = parse_addr_with_privacy_prefix(&from)?;
let (to, to_privacy) = parse_addr_with_privacy_prefix(&to)?;
match (from_privacy, to_privacy) {
(AddressPrivacyKind::Public, AddressPrivacyKind::Public) => {
TokenProgramSubcommand::Public(
TokenProgramSubcommandPublic::TransferToken {
sender_addr: from,
recipient_addr: to,
balance_to_move: amount,
},
)
}
(AddressPrivacyKind::Private, AddressPrivacyKind::Private) => {
TokenProgramSubcommand::Private(
TokenProgramSubcommandPrivate::TransferTokenPrivateOwned {
sender_addr: from,
recipient_addr: to,
balance_to_move: amount,
},
)
}
(AddressPrivacyKind::Private, AddressPrivacyKind::Public) => {
TokenProgramSubcommand::Deshielded(
TokenProgramSubcommandDeshielded::TransferTokenDeshielded {
sender_addr: from,
recipient_addr: to,
balance_to_move: amount,
},
)
}
(AddressPrivacyKind::Public, AddressPrivacyKind::Private) => {
TokenProgramSubcommand::Shielded(
TokenProgramSubcommandShielded::TransferTokenShieldedOwned {
sender_addr: from,
recipient_addr: to,
balance_to_move: amount,
},
)
}
}
}
(None, Some(to_npk), Some(to_ipk)) => {
let (from, from_privacy) = parse_addr_with_privacy_prefix(&from)?;
match from_privacy {
AddressPrivacyKind::Private => TokenProgramSubcommand::Private(
TokenProgramSubcommandPrivate::TransferTokenPrivateForeign {
sender_addr: from,
recipient_npk: to_npk,
recipient_ipk: to_ipk,
balance_to_move: amount,
},
),
AddressPrivacyKind::Public => TokenProgramSubcommand::Shielded(
TokenProgramSubcommandShielded::TransferTokenShieldedForeign {
sender_addr: from,
recipient_npk: to_npk,
recipient_ipk: to_ipk,
balance_to_move: amount,
},
),
}
}
};
underlying_subcommand.handle_subcommand(wallet_core).await
}
}
}
}
///Represents generic CLI subcommand for a wallet working with token_program ///Represents generic CLI subcommand for a wallet working with token_program
#[derive(Subcommand, Debug, Clone)] #[derive(Subcommand, Debug, Clone)]
@ -221,7 +411,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
)?; )?;
} }
let path = wallet_core.store_persistent_accounts().await?; let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");
@ -278,7 +468,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
)?; )?;
} }
let path = wallet_core.store_persistent_accounts().await?; let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");
@ -328,7 +518,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
)?; )?;
} }
let path = wallet_core.store_persistent_accounts().await?; let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");
@ -376,7 +566,7 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded {
)?; )?;
} }
let path = wallet_core.store_persistent_accounts().await?; let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");
@ -431,7 +621,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
println!("Transaction data is {:?}", tx.message); println!("Transaction data is {:?}", tx.message);
} }
let path = wallet_core.store_persistent_accounts().await?; let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");
@ -485,7 +675,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
)?; )?;
} }
let path = wallet_core.store_persistent_accounts().await?; let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}"); println!("Stored persistent accounts at {path:#?}");

View File

@ -46,6 +46,12 @@ pub enum PersistentAccountData {
Private(PersistentAccountDataPrivate), Private(PersistentAccountDataPrivate),
} }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistentStorage {
pub accounts: Vec<PersistentAccountData>,
pub last_synced_block: u64,
}
impl InitialAccountData { impl InitialAccountData {
pub fn address(&self) -> nssa::Address { pub fn address(&self) -> nssa::Address {
match &self { match &self {

View File

@ -12,8 +12,7 @@ use serde::Serialize;
use crate::{ use crate::{
HOME_DIR_ENV_VAR, HOME_DIR_ENV_VAR,
config::{ config::{
PersistentAccountData, PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, WalletConfig,
WalletConfig,
}, },
}; };
@ -30,21 +29,24 @@ pub async fn fetch_config() -> Result<WalletConfig> {
Ok(serde_json::from_slice(&config_contents)?) Ok(serde_json::from_slice(&config_contents)?)
} }
/// Fetch list of accounts stored at `NSSA_WALLET_HOME_DIR/curr_accounts.json` /// Fetch data stored at `NSSA_WALLET_HOME_DIR/storage.json`
/// ///
/// If file not present, it is considered as empty list of persistent accounts /// If file not present, it is considered as empty list of persistent accounts
pub async fn fetch_persistent_accounts() -> Result<Vec<PersistentAccountData>> { pub async fn fetch_persistent_storage() -> Result<PersistentStorage> {
let home = get_home()?; let home = get_home()?;
let accs_path = home.join("curr_accounts.json"); let accs_path = home.join("storage.json");
let mut persistent_accounts_content = vec![]; let mut storage_content = vec![];
match tokio::fs::File::open(accs_path).await { match tokio::fs::File::open(accs_path).await {
Ok(mut file) => { Ok(mut file) => {
file.read_to_end(&mut persistent_accounts_content).await?; file.read_to_end(&mut storage_content).await?;
Ok(serde_json::from_slice(&persistent_accounts_content)?) Ok(serde_json::from_slice(&storage_content)?)
} }
Err(err) => match err.kind() { Err(err) => match err.kind() {
std::io::ErrorKind::NotFound => Ok(vec![]), std::io::ErrorKind::NotFound => Ok(PersistentStorage {
accounts: vec![],
last_synced_block: 0,
}),
_ => { _ => {
anyhow::bail!("IO error {err:#?}"); anyhow::bail!("IO error {err:#?}");
} }
@ -52,8 +54,11 @@ pub async fn fetch_persistent_accounts() -> Result<Vec<PersistentAccountData>> {
} }
} }
/// Produces a list of accounts for storage /// Produces data for storage
pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec<PersistentAccountData> { pub fn produce_data_for_storage(
user_data: &NSSAUserData,
last_synced_block: u64,
) -> PersistentStorage {
let mut vec_for_storage = vec![]; let mut vec_for_storage = vec![];
for (addr, key) in &user_data.pub_account_signing_keys { for (addr, key) in &user_data.pub_account_signing_keys {
@ -77,7 +82,10 @@ pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec<PersistentAccou
); );
} }
vec_for_storage PersistentStorage {
accounts: vec_for_storage,
last_synced_block,
}
} }
pub(crate) fn produce_random_nonces(size: usize) -> Vec<Nonce> { pub(crate) fn produce_random_nonces(size: usize) -> Vec<Nonce> {
@ -86,6 +94,30 @@ pub(crate) fn produce_random_nonces(size: usize) -> Vec<Nonce> {
result.into_iter().map(Nonce::from_le_bytes).collect() result.into_iter().map(Nonce::from_le_bytes).collect()
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AddressPrivacyKind {
Public,
Private,
}
pub(crate) fn parse_addr_with_privacy_prefix(
addr_base58: &str,
) -> Result<(String, AddressPrivacyKind)> {
if addr_base58.starts_with("Public/") {
Ok((
addr_base58.strip_prefix("Public/").unwrap().to_string(),
AddressPrivacyKind::Public,
))
} else if addr_base58.starts_with("Private/") {
Ok((
addr_base58.strip_prefix("Private/").unwrap().to_string(),
AddressPrivacyKind::Private,
))
} else {
anyhow::bail!("Unsupported privacy kind, available variants is Public/ and Private/");
}
}
/// Human-readable representation of an account. /// Human-readable representation of an account.
#[derive(Serialize)] #[derive(Serialize)]
pub(crate) struct HumanReadableAccount { pub(crate) struct HumanReadableAccount {
@ -126,4 +158,20 @@ mod tests {
std::env::remove_var(HOME_DIR_ENV_VAR); std::env::remove_var(HOME_DIR_ENV_VAR);
} }
} }
#[test]
fn test_addr_parse_with_privacy() {
let addr_base58 = "Public/BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy";
let (_, addr_kind) = parse_addr_with_privacy_prefix(addr_base58).unwrap();
assert_eq!(addr_kind, AddressPrivacyKind::Public);
let addr_base58 = "Private/BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy";
let (_, addr_kind) = parse_addr_with_privacy_prefix(addr_base58).unwrap();
assert_eq!(addr_kind, AddressPrivacyKind::Private);
let addr_base58 = "asdsada/BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy";
assert!(parse_addr_with_privacy_prefix(addr_base58).is_err());
}
} }

View File

@ -20,16 +20,18 @@ use clap::{Parser, Subcommand};
use nssa_core::{Commitment, MembershipProof}; use nssa_core::{Commitment, MembershipProof};
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use crate::cli::{ use crate::{
WalletSubcommand, account::AccountSubcommand, chain::ChainSubcommand, cli::{
native_token_transfer_program::NativeTokenTransferProgramSubcommand, WalletSubcommand, account::AccountSubcommand, chain::ChainSubcommand,
pinata_program::PinataProgramSubcommand, native_token_transfer_program::AuthTransferSubcommand,
pinata_program::PinataProgramAgnosticSubcommand,
token_program::TokenProgramAgnosticSubcommand,
},
config::PersistentStorage,
helperfunctions::fetch_persistent_storage,
}; };
use crate::{ use crate::{
cli::token_program::TokenProgramSubcommand, helperfunctions::{fetch_config, get_home, produce_data_for_storage},
helperfunctions::{
fetch_config, fetch_persistent_accounts, get_home, produce_data_for_storage,
},
poller::TxPoller, poller::TxPoller,
}; };
@ -49,6 +51,7 @@ pub struct WalletCore {
pub storage: WalletChainStore, pub storage: WalletChainStore,
pub poller: TxPoller, pub poller: TxPoller,
pub sequencer_client: Arc<SequencerClient>, pub sequencer_client: Arc<SequencerClient>,
pub last_synced_block: u64,
} }
impl WalletCore { impl WalletCore {
@ -58,7 +61,10 @@ impl WalletCore {
let mut storage = WalletChainStore::new(config)?; let mut storage = WalletChainStore::new(config)?;
let persistent_accounts = fetch_persistent_accounts().await?; let PersistentStorage {
accounts: persistent_accounts,
last_synced_block,
} = fetch_persistent_storage().await?;
for pers_acc_data in persistent_accounts { for pers_acc_data in persistent_accounts {
storage.insert_account_data(pers_acc_data); storage.insert_account_data(pers_acc_data);
} }
@ -67,23 +73,24 @@ impl WalletCore {
storage, storage,
poller: tx_poller, poller: tx_poller,
sequencer_client: client.clone(), sequencer_client: client.clone(),
last_synced_block,
}) })
} }
///Store persistent accounts at home ///Store persistent data at home
pub async fn store_persistent_accounts(&self) -> Result<PathBuf> { pub async fn store_persistent_data(&self) -> Result<PathBuf> {
let home = get_home()?; let home = get_home()?;
let accs_path = home.join("curr_accounts.json"); let storage_path = home.join("storage.json");
let data = produce_data_for_storage(&self.storage.user_data); let data = produce_data_for_storage(&self.storage.user_data, self.last_synced_block);
let accs = serde_json::to_vec_pretty(&data)?; let storage = serde_json::to_vec_pretty(&data)?;
let mut accs_file = tokio::fs::File::create(accs_path.as_path()).await?; let mut storage_file = tokio::fs::File::create(storage_path.as_path()).await?;
accs_file.write_all(&accs).await?; storage_file.write_all(&storage).await?;
info!("Stored accounts data at {accs_path:#?}"); info!("Stored data at {storage_path:#?}");
Ok(accs_path) Ok(storage_path)
} }
pub fn create_new_account_public(&mut self) -> Address { pub fn create_new_account_public(&mut self) -> Address {
@ -191,28 +198,32 @@ impl WalletCore {
#[derive(Subcommand, Debug, Clone)] #[derive(Subcommand, Debug, Clone)]
#[clap(about)] #[clap(about)]
pub enum Command { pub enum Command {
///Transfer command ///Authenticated transfer subcommand
#[command(subcommand)] #[command(subcommand)]
Transfer(NativeTokenTransferProgramSubcommand), AuthTransfer(AuthTransferSubcommand),
///Chain command ///Generic chain info subcommand
#[command(subcommand)] #[command(subcommand)]
Chain(ChainSubcommand), ChainInfo(ChainSubcommand),
///Chain command ///Account view and sync subcommand
#[command(subcommand)] #[command(subcommand)]
Account(AccountSubcommand), Account(AccountSubcommand),
///Pinata command ///Pinata program interaction subcommand
#[command(subcommand)] #[command(subcommand)]
PinataProgram(PinataProgramSubcommand), Pinata(PinataProgramAgnosticSubcommand),
///Token command ///Token program interaction subcommand
#[command(subcommand)] #[command(subcommand)]
TokenProgram(TokenProgramSubcommand), Token(TokenProgramAgnosticSubcommand),
AuthenticatedTransferInitializePublicAccount {}, /// Check the wallet can connect to the node and builtin local programs
// Check the wallet can connect to the node and builtin local programs /// match the remote versions
// match the remote versions
CheckHealth {}, CheckHealth {},
} }
///To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config ///To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config
///
/// All account adresses must be valid 32 byte base58 strings.
///
/// All account addresses must be provided as {privacy_prefix}/{addr},
/// where valid options for `privacy_prefix` is `Public` and `Private`
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[clap(version, about)] #[clap(version, about)]
pub struct Args { pub struct Args {
@ -230,6 +241,7 @@ pub enum SubcommandReturnValue {
RegisterAccount { addr: nssa::Address }, RegisterAccount { addr: nssa::Address },
Account(nssa::Account), Account(nssa::Account),
Empty, Empty,
SyncedToBlock(u64),
} }
pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValue> { pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValue> {
@ -237,12 +249,12 @@ pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValu
let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?; let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?;
let subcommand_ret = match command { let subcommand_ret = match command {
Command::Transfer(transfer_subcommand) => { Command::AuthTransfer(transfer_subcommand) => {
transfer_subcommand transfer_subcommand
.handle_subcommand(&mut wallet_core) .handle_subcommand(&mut wallet_core)
.await? .await?
} }
Command::Chain(chain_subcommand) => { Command::ChainInfo(chain_subcommand) => {
chain_subcommand.handle_subcommand(&mut wallet_core).await? chain_subcommand.handle_subcommand(&mut wallet_core).await?
} }
Command::Account(account_subcommand) => { Command::Account(account_subcommand) => {
@ -250,7 +262,7 @@ pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValu
.handle_subcommand(&mut wallet_core) .handle_subcommand(&mut wallet_core)
.await? .await?
} }
Command::PinataProgram(pinata_subcommand) => { Command::Pinata(pinata_subcommand) => {
pinata_subcommand pinata_subcommand
.handle_subcommand(&mut wallet_core) .handle_subcommand(&mut wallet_core)
.await? .await?
@ -285,26 +297,7 @@ pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValu
SubcommandReturnValue::Empty SubcommandReturnValue::Empty
} }
Command::AuthenticatedTransferInitializePublicAccount {} => { Command::Token(token_subcommand) => {
let addr = wallet_core.create_new_account_public();
println!("Generated new account with addr {addr}");
let path = wallet_core.store_persistent_accounts().await?;
println!("Stored persistent accounts at {path:#?}");
let res = wallet_core
.register_account_under_authenticated_transfers_programs(addr)
.await?;
println!("Results of tx send is {res:#?}");
let _transfer_tx = wallet_core.poll_native_token_transfer(res.tx_hash).await?;
SubcommandReturnValue::RegisterAccount { addr }
}
Command::TokenProgram(token_subcommand) => {
token_subcommand.handle_subcommand(&mut wallet_core).await? token_subcommand.handle_subcommand(&mut wallet_core).await?
} }
}; };
@ -312,6 +305,80 @@ pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValu
Ok(subcommand_ret) Ok(subcommand_ret)
} }
pub async fn parse_block_range(
start: u64,
stop: u64,
seq_client: Arc<SequencerClient>,
wallet_core: &mut WalletCore,
) -> Result<()> {
for block_id in start..(stop + 1) {
let block =
borsh::from_slice::<HashableBlockData>(&seq_client.get_block(block_id).await?.block)?;
for tx in block.transactions {
let nssa_tx = NSSATransaction::try_from(&tx)?;
if let NSSATransaction::PrivacyPreserving(tx) = nssa_tx {
let mut affected_accounts = vec![];
for (acc_addr, (key_chain, _)) in
&wallet_core.storage.user_data.user_private_accounts
{
let view_tag = EncryptedAccountData::compute_view_tag(
key_chain.nullifer_public_key.clone(),
key_chain.incoming_viewing_public_key.clone(),
);
for (ciph_id, encrypted_data) in tx
.message()
.encrypted_private_post_states
.iter()
.enumerate()
{
if encrypted_data.view_tag == view_tag {
let ciphertext = &encrypted_data.ciphertext;
let commitment = &tx.message.new_commitments[ciph_id];
let shared_secret = key_chain
.calculate_shared_secret_receiver(encrypted_data.epk.clone());
let res_acc = nssa_core::EncryptionScheme::decrypt(
ciphertext,
&shared_secret,
commitment,
ciph_id as u32,
);
if let Some(res_acc) = res_acc {
println!(
"Received new account for addr {acc_addr:#?} with account object {res_acc:#?}"
);
affected_accounts.push((*acc_addr, res_acc));
}
}
}
}
for (affected_addr, new_acc) in affected_accounts {
wallet_core
.storage
.insert_private_account_data(affected_addr, new_acc);
}
}
}
wallet_core.last_synced_block = block_id;
wallet_core.store_persistent_data().await?;
println!(
"Block at id {block_id} with timestamp {} parsed",
block.timestamp
);
}
Ok(())
}
pub async fn execute_continious_run() -> Result<()> { pub async fn execute_continious_run() -> Result<()> {
let config = fetch_config().await?; let config = fetch_config().await?;
let seq_client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?); let seq_client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?);
@ -321,70 +388,13 @@ pub async fn execute_continious_run() -> Result<()> {
let mut curr_last_block = latest_block_num; let mut curr_last_block = latest_block_num;
loop { loop {
for block_id in curr_last_block..(latest_block_num + 1) { parse_block_range(
let block = borsh::from_slice::<HashableBlockData>( curr_last_block,
&seq_client.get_block(block_id).await?.block, latest_block_num,
)?; seq_client.clone(),
&mut wallet_core,
for tx in block.transactions { )
let nssa_tx = NSSATransaction::try_from(&tx)?; .await?;
if let NSSATransaction::PrivacyPreserving(tx) = nssa_tx {
let mut affected_accounts = vec![];
for (acc_addr, (key_chain, _)) in
&wallet_core.storage.user_data.user_private_accounts
{
let view_tag = EncryptedAccountData::compute_view_tag(
key_chain.nullifer_public_key.clone(),
key_chain.incoming_viewing_public_key.clone(),
);
for (ciph_id, encrypted_data) in tx
.message()
.encrypted_private_post_states
.iter()
.enumerate()
{
if encrypted_data.view_tag == view_tag {
let ciphertext = &encrypted_data.ciphertext;
let commitment = &tx.message.new_commitments[ciph_id];
let shared_secret = key_chain
.calculate_shared_secret_receiver(encrypted_data.epk.clone());
let res_acc = nssa_core::EncryptionScheme::decrypt(
ciphertext,
&shared_secret,
commitment,
ciph_id as u32,
);
if let Some(res_acc) = res_acc {
println!(
"Received new account for addr {acc_addr:#?} with account object {res_acc:#?}"
);
affected_accounts.push((*acc_addr, res_acc));
}
}
}
}
for (affected_addr, new_acc) in affected_accounts {
wallet_core
.storage
.insert_private_account_data(affected_addr, new_acc);
}
}
}
wallet_core.store_persistent_accounts().await?;
println!(
"Block at id {block_id} with timestamp {} parsed",
block.timestamp
);
}
curr_last_block = latest_block_num + 1; curr_last_block = latest_block_num + 1;

View File

@ -537,4 +537,53 @@ impl WalletCore {
Ok(self.sequencer_client.send_tx_private(tx).await?) Ok(self.sequencer_client.send_tx_private(tx).await?)
} }
pub async fn register_account_under_authenticated_transfers_programs_private(
&self,
from: Address,
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
let AccountPreparedData {
nsk: _,
npk: from_npk,
ipk: from_ipk,
auth_acc: sender_pre,
proof: _,
} = self.private_acc_preparation(from, false, false).await?;
let eph_holder_from = EphemeralKeyHolder::new(&from_npk);
let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk);
let instruction: u128 = 0;
let (output, proof) = circuit::execute_and_prove(
&[sender_pre],
&Program::serialize_instruction(instruction).unwrap(),
&[2],
&produce_random_nonces(1),
&[(from_npk.clone(), shared_secret_from.clone())],
&[],
&Program::authenticated_transfer_program(),
)
.unwrap();
let message = Message::try_from_circuit_output(
vec![],
vec![],
vec![(
from_npk.clone(),
from_ipk.clone(),
eph_holder_from.generate_ephemeral_public_key(),
)],
output,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, proof, &[]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok((
self.sequencer_client.send_tx_private(tx).await?,
[shared_secret_from],
))
}
} }