Merge pull request #141 from vacp2p/Pravdyvy/cli-refactor

CLI refactor
This commit is contained in:
Pravdyvy 2025-11-03 15:35:30 +02:00 committed by GitHub
commit aa36cc09fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 2022 additions and 1258 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -55,6 +55,7 @@ impl KeyChain {
#[cfg(test)]
mod tests {
use aes_gcm::aead::OsRng;
use base58::ToBase58;
use k256::AffinePoint;
use k256::elliptic_curve::group::GroupEncoding;
use rand::RngCore;
@ -119,7 +120,7 @@ mod tests {
println!("======Public data======");
println!();
println!("Address{:?}", hex::encode(address.value()));
println!("Address{:?}", address.value().to_base58());
println!(
"Nulifier public key {:?}",
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();
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 }
chacha20 = { version = "0.9", default-features = false }
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]
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")]
use std::{fmt::Display, str::FromStr};
#[cfg(feature = "host")]
use base58::{FromBase58, ToBase58};
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(
any(feature = "host", test),
@ -31,8 +34,8 @@ impl AsRef<[u8]> for Address {
#[cfg(feature = "host")]
#[derive(Debug, thiserror::Error)]
pub enum AddressError {
#[error("invalid hex")]
InvalidHex(#[from] hex::FromHexError),
#[error("invalid base58")]
InvalidBase58(#[from] anyhow::Error),
#[error("invalid length: expected 32 bytes, got {0}")]
InvalidLength(usize),
}
@ -41,7 +44,9 @@ pub enum AddressError {
impl FromStr for Address {
type Err = AddressError;
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 {
return Err(AddressError::InvalidLength(bytes.len()));
}
@ -54,7 +59,7 @@ impl FromStr for Address {
#[cfg(feature = "host")]
impl Display for Address {
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]
fn parse_valid_address() {
let hex_str = "00".repeat(32); // 64 hex chars = 32 bytes
let addr: Address = hex_str.parse().unwrap();
let base58_str = "11111111111111111111111111111111";
let addr: Address = base58_str.parse().unwrap();
assert_eq!(addr.value, [0u8; 32]);
}
#[test]
fn parse_invalid_hex() {
let hex_str = "zz".repeat(32); // invalid hex chars
let result = hex_str.parse::<Address>().unwrap_err();
assert!(matches!(result, AddressError::InvalidHex(_)));
fn parse_invalid_base58() {
let base58_str = "00".repeat(32); // invalid base58 chars
let result = base58_str.parse::<Address>().unwrap_err();
assert!(matches!(result, AddressError::InvalidBase58(_)));
}
#[test]
fn parse_wrong_length_short() {
let hex_str = "00".repeat(31); // 62 chars = 31 bytes
let result = hex_str.parse::<Address>().unwrap_err();
let base58_str = "11".repeat(31); // 62 chars = 31 bytes
let result = base58_str.parse::<Address>().unwrap_err();
assert!(matches!(result, AddressError::InvalidLength(_)));
}
#[test]
fn parse_wrong_length_long() {
let hex_str = "00".repeat(33); // 66 chars = 33 bytes
let result = hex_str.parse::<Address>().unwrap_err();
let base58_str = "11".repeat(33); // 66 chars = 33 bytes
let result = base58_str.parse::<Address>().unwrap_err();
assert!(matches!(result, AddressError::InvalidLength(_)));
}
}

View File

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

View File

@ -194,6 +194,7 @@ impl SequencerCore {
#[cfg(test)]
mod tests {
use base58::{FromBase58, ToBase58};
use common::test_utils::sequencer_sign_key_for_testing;
use crate::config::AccountInitialData;
@ -227,23 +228,23 @@ mod tests {
}
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,
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,
98, 237, 6, 207, 46, 20, 121, 246, 222, 248, 154, 57, 188,
];
let initial_acc1 = AccountInitialData {
addr: hex::encode(acc1_addr),
addr: acc1_addr.to_base58(),
balance: 10000,
};
let initial_acc2 = AccountInitialData {
addr: hex::encode(acc2_addr),
addr: acc2_addr.to_base58(),
balance: 20000,
};
@ -278,11 +279,17 @@ mod tests {
assert_eq!(sequencer.sequencer_config.max_num_tx_in_block, 10);
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()
.try_into()
.unwrap();
let acc2_addr = hex::decode(config.initial_accounts[1].addr.clone())
let acc2_addr = config.initial_accounts[1]
.addr
.clone()
.from_base58()
.unwrap()
.try_into()
.unwrap();
@ -304,23 +311,23 @@ mod tests {
#[test]
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,
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,
216, 10, 201, 66, 51, 116, 196, 81, 167, 37, 77, 7, 102,
];
let initial_acc1 = AccountInitialData {
addr: hex::encode(acc1_addr),
addr: acc1_addr.to_base58(),
balance: 10000,
};
let initial_acc2 = AccountInitialData {
addr: hex::encode(acc2_addr),
addr: acc2_addr.to_base58(),
balance: 20000,
};
@ -329,11 +336,17 @@ mod tests {
let config = setup_sequencer_config_variable_initial_accounts(initial_accounts);
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()
.try_into()
.unwrap();
let acc2_addr = hex::decode(config.initial_accounts[1].addr.clone())
let acc2_addr = config.initial_accounts[1]
.addr
.clone()
.from_base58()
.unwrap()
.try_into()
.unwrap();
@ -376,11 +389,17 @@ mod tests {
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()
.try_into()
.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()
.try_into()
.unwrap();
@ -402,11 +421,17 @@ mod tests {
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()
.try_into()
.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()
.try_into()
.unwrap();
@ -438,11 +463,17 @@ mod tests {
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()
.try_into()
.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()
.try_into()
.unwrap();
@ -474,11 +505,17 @@ mod tests {
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()
.try_into()
.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()
.try_into()
.unwrap();
@ -566,11 +603,17 @@ mod tests {
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()
.try_into()
.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()
.try_into()
.unwrap();
@ -608,11 +651,17 @@ mod tests {
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()
.try_into()
.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()
.try_into()
.unwrap();

View File

@ -33,9 +33,11 @@ impl SequecerChainStore {
#[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("cafe".repeat(16).parse().unwrap());
this.add_pinata_program(PINATA_BASE58.parse().unwrap());
this
};

View File

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

View File

@ -1,6 +1,7 @@
use std::collections::HashMap;
use actix_web::Error as HttpError;
use base58::FromBase58;
use base64::{Engine, engine::general_purpose};
use nssa::{self, program::Program};
use sequencer_core::config::AccountInitialData;
@ -163,8 +164,10 @@ impl JsonHandler {
/// The address must be a valid hex string of the correct length.
async fn process_get_account_balance(&self, request: Request) -> Result<Value, RpcErr> {
let get_account_req = GetAccountBalanceRequest::parse(Some(request.params))?;
let address_bytes = hex::decode(get_account_req.address)
.map_err(|_| RpcError::invalid_params("invalid hex".to_string()))?;
let address_bytes = get_account_req
.address
.from_base58()
.map_err(|_| RpcError::invalid_params("invalid base58".to_string()))?;
let address = nssa::Address::new(
address_bytes
.try_into()
@ -312,6 +315,7 @@ mod tests {
use std::sync::Arc;
use crate::{JsonHandler, rpc_handler};
use base58::ToBase58;
use base64::{Engine, engine::general_purpose};
use common::{
rpc_primitives::RpcPollingConfig, test_utils::sequencer_sign_key_for_testing,
@ -329,23 +333,23 @@ mod tests {
fn sequencer_config_for_tests() -> SequencerConfig {
let tempdir = tempdir().unwrap();
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,
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,
98, 237, 6, 207, 46, 20, 121, 246, 222, 248, 154, 57, 188,
];
let initial_acc1 = AccountInitialData {
addr: hex::encode(acc1_addr),
addr: acc1_addr.to_base58(),
balance: 10000,
};
let initial_acc2 = AccountInitialData {
addr: hex::encode(acc2_addr),
addr: acc2_addr.to_base58(),
balance: 20000,
};
@ -430,7 +434,7 @@ mod tests {
let request = serde_json::json!({
"jsonrpc": "2.0",
"method": "get_account_balance",
"params": { "address": "efac".repeat(16) },
"params": { "address": "11".repeat(16) },
"id": 1
});
let expected_response = serde_json::json!({
@ -447,12 +451,12 @@ mod tests {
}
#[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 request = serde_json::json!({
"jsonrpc": "2.0",
"method": "get_account_balance",
"params": { "address": "not_a_valid_hex" },
"params": { "address": "not_a_valid_base58" },
"id": 1
});
let expected_response = serde_json::json!({
@ -461,7 +465,7 @@ mod tests {
"error": {
"code": -32602,
"message": "Invalid params",
"data": "invalid hex"
"data": "invalid base58"
}
});
let response = call_rpc_handler_with_json(json_handler, request).await;
@ -523,7 +527,7 @@ mod tests {
let request = serde_json::json!({
"jsonrpc": "2.0",
"method": "get_accounts_nonces",
"params": { "addresses": ["efac".repeat(16)] },
"params": { "addresses": ["11".repeat(16)] },
"id": 1
});
let expected_response = serde_json::json!({
@ -571,7 +575,7 @@ mod tests {
let request = serde_json::json!({
"jsonrpc": "2.0",
"method": "get_account",
"params": { "address": "efac".repeat(16) },
"params": { "address": "11".repeat(16) },
"id": 1
});
let expected_response = serde_json::json!({

View File

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

View File

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

View File

@ -75,19 +75,91 @@ mod tests {
use tempfile::tempdir;
fn create_initial_accounts() -> Vec<InitialAccountData> {
let initial_acc1 = serde_json::from_str(r#"{
let initial_acc1 = serde_json::from_str(
r#"{
"Public": {
"address": "d07ad2e84b27fa00c262f0a1eea0ff35ca0973547e6a106f72f193c2dc838b44",
"pub_sign_key": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
"address": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy",
"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": {
"address": "e7ae77c5ef1a05999344af499fc78a1705398d62ed06cf2e1479f6def89a39bc",
"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]
"address": "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw",
"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];

View File

@ -1,213 +1,117 @@
use std::str::FromStr;
use anyhow::Result;
use base58::ToBase58;
use clap::Subcommand;
use common::transaction::NSSATransaction;
use nssa::Address;
use nssa::{Account, Address, program::Program};
use serde::Serialize;
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
#[derive(Subcommand, Debug, Clone)]
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)]
Get(GetSubcommand),
///Fetch
#[command(subcommand)]
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,
},
New(NewSubcommand),
///Sync private accounts
SyncPrivate {},
}
///Represents generic register CLI subcommand
#[derive(Subcommand, Debug, Clone)]
pub enum RegisterSubcommand {
pub enum NewSubcommand {
///Register new public account
Public {},
///Register new private account
Private {},
}
impl WalletSubcommand for GetSubcommand {
impl WalletSubcommand for NewSubcommand {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
GetSubcommand::PublicAccountBalance { addr } => {
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 {} => {
NewSubcommand::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:#?}");
Ok(SubcommandReturnValue::RegisterAccount { addr })
}
RegisterSubcommand::Private {} => {
NewSubcommand::Private {} => {
let addr = wallet_core.create_new_account_private();
let (key, _) = wallet_core
@ -216,14 +120,17 @@ impl WalletSubcommand for RegisterSubcommand {
.get_private_account(&addr)
.unwrap();
println!("Generated new account with addr {addr}");
println!("With npk {}", hex::encode(&key.nullifer_public_key));
println!(
"Generated new account with addr Private/{}",
addr.to_bytes().to_base58()
);
println!("With npk {}", hex::encode(key.nullifer_public_key.0));
println!(
"With ipk {}",
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:#?}");
@ -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 {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
AccountSubcommand::Get(get_subcommand) => {
get_subcommand.handle_subcommand(wallet_core).await
AccountSubcommand::Get { raw, addr } => {
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) => {
fetch_subcommand.handle_subcommand(wallet_core).await
AccountSubcommand::New(new_subcommand) => {
new_subcommand.handle_subcommand(wallet_core).await
}
AccountSubcommand::Register(register_subcommand) => {
register_subcommand.handle_subcommand(wallet_core).await
AccountSubcommand::SyncPrivate {} => {
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
#[derive(Subcommand, Debug, Clone)]
pub enum ChainSubcommand {
GetLatestBlockId {},
GetBlockAtId {
///Get current block id from sequencer
CurrentBlockId {},
///Get block at id from sequencer
Block {
#[arg(short, long)]
id: u64,
},
GetTransactionAtHash {
///Get transaction at hash from sequencer
Transaction {
///hash - valid 32 byte hex string
#[arg(short, long)]
hash: String,
},
@ -23,17 +27,17 @@ impl WalletSubcommand for ChainSubcommand {
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
ChainSubcommand::GetLatestBlockId {} => {
ChainSubcommand::CurrentBlockId {} => {
let latest_block_res = wallet_core.sequencer_client.get_last_block().await?;
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?;
println!("Last block id is {:#?}", block_res.block);
}
ChainSubcommand::GetTransactionAtHash { hash } => {
ChainSubcommand::Transaction { hash } => {
let tx_res = wallet_core
.sequencer_client
.get_transaction_by_hash(hash)

View File

@ -3,7 +3,193 @@ use clap::Subcommand;
use common::transaction::NSSATransaction;
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
#[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:#?}");
@ -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:#?}");
@ -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:#?}");
@ -285,7 +471,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
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:#?}");
@ -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:#?}");
@ -351,7 +537,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand {
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:#?}");

View File

@ -1,9 +1,59 @@
use anyhow::Result;
use clap::Subcommand;
use common::transaction::NSSATransaction;
use common::{PINATA_BASE58, transaction::NSSATransaction};
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
#[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:#?}");

View File

@ -3,7 +3,197 @@ use clap::Subcommand;
use common::transaction::NSSATransaction;
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
#[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:#?}");
@ -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:#?}");
@ -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:#?}");
@ -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:#?}");
@ -431,7 +621,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
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:#?}");
@ -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:#?}");

View File

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

View File

@ -12,8 +12,7 @@ use serde::Serialize;
use crate::{
HOME_DIR_ENV_VAR,
config::{
PersistentAccountData, PersistentAccountDataPrivate, PersistentAccountDataPublic,
WalletConfig,
PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, WalletConfig,
},
};
@ -30,21 +29,24 @@ pub async fn fetch_config() -> Result<WalletConfig> {
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
pub async fn fetch_persistent_accounts() -> Result<Vec<PersistentAccountData>> {
pub async fn fetch_persistent_storage() -> Result<PersistentStorage> {
let home = get_home()?;
let accs_path = home.join("curr_accounts.json");
let mut persistent_accounts_content = vec![];
let accs_path = home.join("storage.json");
let mut storage_content = vec![];
match tokio::fs::File::open(accs_path).await {
Ok(mut file) => {
file.read_to_end(&mut persistent_accounts_content).await?;
Ok(serde_json::from_slice(&persistent_accounts_content)?)
file.read_to_end(&mut storage_content).await?;
Ok(serde_json::from_slice(&storage_content)?)
}
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:#?}");
}
@ -52,8 +54,11 @@ pub async fn fetch_persistent_accounts() -> Result<Vec<PersistentAccountData>> {
}
}
/// Produces a list of accounts for storage
pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec<PersistentAccountData> {
/// Produces data for storage
pub fn produce_data_for_storage(
user_data: &NSSAUserData,
last_synced_block: u64,
) -> PersistentStorage {
let mut vec_for_storage = vec![];
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> {
@ -86,6 +94,30 @@ pub(crate) fn produce_random_nonces(size: usize) -> Vec<Nonce> {
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.
#[derive(Serialize)]
pub(crate) struct HumanReadableAccount {
@ -126,4 +158,20 @@ mod tests {
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 tokio::io::AsyncWriteExt;
use crate::cli::{
WalletSubcommand, account::AccountSubcommand, chain::ChainSubcommand,
native_token_transfer_program::NativeTokenTransferProgramSubcommand,
pinata_program::PinataProgramSubcommand,
use crate::{
cli::{
WalletSubcommand, account::AccountSubcommand, chain::ChainSubcommand,
native_token_transfer_program::AuthTransferSubcommand,
pinata_program::PinataProgramAgnosticSubcommand,
token_program::TokenProgramAgnosticSubcommand,
},
config::PersistentStorage,
helperfunctions::fetch_persistent_storage,
};
use crate::{
cli::token_program::TokenProgramSubcommand,
helperfunctions::{
fetch_config, fetch_persistent_accounts, get_home, produce_data_for_storage,
},
helperfunctions::{fetch_config, get_home, produce_data_for_storage},
poller::TxPoller,
};
@ -49,6 +51,7 @@ pub struct WalletCore {
pub storage: WalletChainStore,
pub poller: TxPoller,
pub sequencer_client: Arc<SequencerClient>,
pub last_synced_block: u64,
}
impl WalletCore {
@ -58,7 +61,10 @@ impl WalletCore {
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 {
storage.insert_account_data(pers_acc_data);
}
@ -67,23 +73,24 @@ impl WalletCore {
storage,
poller: tx_poller,
sequencer_client: client.clone(),
last_synced_block,
})
}
///Store persistent accounts at home
pub async fn store_persistent_accounts(&self) -> Result<PathBuf> {
///Store persistent data at home
pub async fn store_persistent_data(&self) -> Result<PathBuf> {
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 accs = serde_json::to_vec_pretty(&data)?;
let data = produce_data_for_storage(&self.storage.user_data, self.last_synced_block);
let storage = serde_json::to_vec_pretty(&data)?;
let mut accs_file = tokio::fs::File::create(accs_path.as_path()).await?;
accs_file.write_all(&accs).await?;
let mut storage_file = tokio::fs::File::create(storage_path.as_path()).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 {
@ -191,28 +198,32 @@ impl WalletCore {
#[derive(Subcommand, Debug, Clone)]
#[clap(about)]
pub enum Command {
///Transfer command
///Authenticated transfer subcommand
#[command(subcommand)]
Transfer(NativeTokenTransferProgramSubcommand),
///Chain command
AuthTransfer(AuthTransferSubcommand),
///Generic chain info subcommand
#[command(subcommand)]
Chain(ChainSubcommand),
///Chain command
ChainInfo(ChainSubcommand),
///Account view and sync subcommand
#[command(subcommand)]
Account(AccountSubcommand),
///Pinata command
///Pinata program interaction subcommand
#[command(subcommand)]
PinataProgram(PinataProgramSubcommand),
///Token command
Pinata(PinataProgramAgnosticSubcommand),
///Token program interaction subcommand
#[command(subcommand)]
TokenProgram(TokenProgramSubcommand),
AuthenticatedTransferInitializePublicAccount {},
// Check the wallet can connect to the node and builtin local programs
// match the remote versions
Token(TokenProgramAgnosticSubcommand),
/// Check the wallet can connect to the node and builtin local programs
/// match the remote versions
CheckHealth {},
}
///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)]
#[clap(version, about)]
pub struct Args {
@ -230,6 +241,7 @@ pub enum SubcommandReturnValue {
RegisterAccount { addr: nssa::Address },
Account(nssa::Account),
Empty,
SyncedToBlock(u64),
}
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 subcommand_ret = match command {
Command::Transfer(transfer_subcommand) => {
Command::AuthTransfer(transfer_subcommand) => {
transfer_subcommand
.handle_subcommand(&mut wallet_core)
.await?
}
Command::Chain(chain_subcommand) => {
Command::ChainInfo(chain_subcommand) => {
chain_subcommand.handle_subcommand(&mut wallet_core).await?
}
Command::Account(account_subcommand) => {
@ -250,7 +262,7 @@ pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValu
.handle_subcommand(&mut wallet_core)
.await?
}
Command::PinataProgram(pinata_subcommand) => {
Command::Pinata(pinata_subcommand) => {
pinata_subcommand
.handle_subcommand(&mut wallet_core)
.await?
@ -285,26 +297,7 @@ pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValu
SubcommandReturnValue::Empty
}
Command::AuthenticatedTransferInitializePublicAccount {} => {
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) => {
Command::Token(token_subcommand) => {
token_subcommand.handle_subcommand(&mut wallet_core).await?
}
};
@ -312,6 +305,80 @@ pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValu
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<()> {
let config = fetch_config().await?;
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;
loop {
for block_id in curr_last_block..(latest_block_num + 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.store_persistent_accounts().await?;
println!(
"Block at id {block_id} with timestamp {} parsed",
block.timestamp
);
}
parse_block_range(
curr_last_block,
latest_block_num,
seq_client.clone(),
&mut wallet_core,
)
.await?;
curr_last_block = latest_block_num + 1;

View File

@ -537,4 +537,53 @@ impl WalletCore {
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],
))
}
}