Merge branch 'Pravdyvy/state-transition-token-transfer' into schouhy/prevent-replay-attacks-with-nonces

fix test
This commit is contained in:
Sergio Chouhy 2025-07-29 12:50:30 -03:00
commit 966a5c9a04
18 changed files with 908 additions and 1027 deletions

1
Cargo.lock generated
View File

@ -19,6 +19,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"tiny-keccak",
"utxo",
]

View File

@ -16,6 +16,7 @@ elliptic-curve.workspace = true
hex.workspace = true
aes-gcm.workspace = true
lazy_static.workspace = true
tiny-keccak.workspace = true
[dependencies.utxo]
path = "../utxo"

View File

@ -16,7 +16,7 @@ use crate::key_management::{
pub type PublicKey = AffinePoint;
pub type AccountAddress = TreeHashType;
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct Account {
pub key_holder: AddressKeyHolder,
pub address: AccountAddress,
@ -24,7 +24,7 @@ pub struct Account {
pub utxos: HashMap<TreeHashType, UTXO>,
}
#[derive(Serialize, Deserialize, Clone)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AccountForSerialization {
pub key_holder: AddressKeyHolder,
pub address: AccountAddress,

View File

@ -7,6 +7,7 @@ use log::info;
use rand::{rngs::OsRng, RngCore};
use secret_holders::{SeedHolder, TopSecretKeyHolder, UTXOSecretKeyHolder};
use serde::{Deserialize, Serialize};
use tiny_keccak::{Hasher, Keccak};
use crate::account_core::PublicKey;
pub type PublicAccountSigningKey = [u8; 32];
@ -15,7 +16,7 @@ pub mod constants_types;
pub mod ephemeral_key_holder;
pub mod secret_holders;
#[derive(Serialize, Deserialize, Clone)]
#[derive(Serialize, Deserialize, Clone, Debug)]
///Entrypoint to key management
pub struct AddressKeyHolder {
//Will be useful in future
@ -37,7 +38,6 @@ impl AddressKeyHolder {
let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder();
let address = utxo_secret_key_holder.generate_address();
let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key();
let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key();
@ -47,6 +47,17 @@ impl AddressKeyHolder {
bytes
};
//Address is a Keccak(verification_key)
let field_bytes = FieldBytes::from_slice(&pub_account_signing_key);
let signing_key = SigningKey::from_bytes(field_bytes).unwrap();
let verifying_key = signing_key.verifying_key();
let mut address = [0; 32];
let mut keccak_hasher = Keccak::v256();
keccak_hasher.update(&verifying_key.to_sec1_bytes());
keccak_hasher.finalize(&mut address);
Self {
top_secret_key_holder,
utxo_secret_key_holder,
@ -332,6 +343,21 @@ mod tests {
);
}
#[test]
fn test_address_key_equal_keccak_pub_sign_key() {
let address_key_holder = AddressKeyHolder::new_os_random();
let signing_key = address_key_holder.get_pub_account_signing_key();
let verifying_key = signing_key.verifying_key();
let mut address = [0; 32];
let mut keccak_hasher = Keccak::v256();
keccak_hasher.update(&verifying_key.to_sec1_bytes());
keccak_hasher.finalize(&mut address);
assert_eq!(address, address_key_holder.address);
}
#[test]
fn key_generation_test() {
let seed_holder = SeedHolder::new_os_random();
@ -339,10 +365,26 @@ mod tests {
let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder();
let address = utxo_secret_key_holder.generate_address();
let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key();
let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key();
let pub_account_signing_key = {
let mut bytes = [0; 32];
OsRng.fill_bytes(&mut bytes);
bytes
};
//Address is a Keccak(verification_key)
let field_bytes = FieldBytes::from_slice(&pub_account_signing_key);
let signing_key = SigningKey::from_bytes(field_bytes).unwrap();
let verifying_key = signing_key.verifying_key();
let mut address = [0; 32];
let mut keccak_hasher = Keccak::v256();
keccak_hasher.update(&verifying_key.to_sec1_bytes());
keccak_hasher.finalize(&mut address);
println!("======Prerequisites======");
println!();

View File

@ -98,16 +98,4 @@ impl UTXOSecretKeyHolder {
pub fn generate_viewing_public_key(&self) -> AffinePoint {
(AffinePoint::GENERATOR * self.viewing_secret_key).into()
}
pub fn generate_address(&self) -> TreeHashType {
let npk = self.generate_nullifier_public_key();
let vpk = self.generate_viewing_public_key();
let mut hasher = sha2::Sha256::new();
hasher.update(serde_json::to_vec(&npk).unwrap());
hasher.update(serde_json::to_vec(&vpk).unwrap());
<TreeHashType>::from(hasher.finalize_fixed())
}
}

View File

@ -290,6 +290,10 @@ impl AuthenticatedTransaction {
&self.transaction
}
pub fn into_transaction(self) -> Transaction {
self.transaction
}
/// Returns the precomputed hash over the body of the transaction
pub fn hash(&self) -> &TransactionHash {
&self.hash
@ -421,4 +425,11 @@ mod tests {
let authenticated_tx = transaction.clone().into_authenticated().unwrap();
assert_eq!(authenticated_tx.hash(), &transaction.body.hash());
}
#[test]
fn test_authenticated_transaction_into_transaction() {
let transaction = test_transaction();
let authenticated_tx = transaction.clone().into_authenticated().unwrap();
assert_eq!(authenticated_tx.into_transaction(), transaction);
}
}

View File

@ -304,6 +304,248 @@ mod tests {
use std::path::PathBuf;
use tempfile::tempdir;
fn create_initial_accounts() -> Vec<Account> {
let initial_acc1 = serde_json::from_str(r#"{
"address": [
244,
55,
238,
205,
74,
115,
179,
192,
65,
186,
166,
169,
221,
45,
6,
57,
200,
65,
195,
70,
118,
252,
206,
100,
215,
250,
72,
230,
19,
71,
217,
249
],
"balance": 100,
"key_holder": {
"address": [
244,
55,
238,
205,
74,
115,
179,
192,
65,
186,
166,
169,
221,
45,
6,
57,
200,
65,
195,
70,
118,
252,
206,
100,
215,
250,
72,
230,
19,
71,
217,
249
],
"nullifer_public_key": "03A340BECA9FAAB444CED0140681D72EA1318B5C611704FEE017DA9836B17DB718",
"pub_account_signing_key": [
244,
88,
134,
61,
35,
209,
229,
101,
85,
35,
140,
140,
192,
226,
83,
83,
190,
189,
110,
8,
89,
127,
147,
142,
157,
204,
51,
109,
189,
92,
144,
68
],
"top_secret_key_holder": {
"secret_spending_key": "7BC46784DB1BC67825D8F029436846712BFDF9B5D79EA3AB11D39A52B9B229D4"
},
"utxo_secret_key_holder": {
"nullifier_secret_key": "BB54A8D3C9C51B82C431082D1845A74677B0EF829A11B517E1D9885DE3139506",
"viewing_secret_key": "AD923E92F6A5683E30140CEAB2702AFB665330C1EE4EFA70FAF29767B6B52BAF"
},
"viewing_public_key": "0361220C5D277E7A1709340FD31A52600C1432B9C45B9BCF88A43581D58824A8B6"
},
"utxos": {}
}"#).unwrap();
let initial_acc2 = serde_json::from_str(r#"{
"address": [
72,
169,
70,
237,
1,
96,
35,
157,
25,
15,
83,
18,
52,
206,
202,
63,
48,
59,
173,
76,
78,
7,
254,
229,
28,
45,
194,
79,
6,
89,
58,
85
],
"balance": 200,
"key_holder": {
"address": [
72,
169,
70,
237,
1,
96,
35,
157,
25,
15,
83,
18,
52,
206,
202,
63,
48,
59,
173,
76,
78,
7,
254,
229,
28,
45,
194,
79,
6,
89,
58,
85
],
"nullifer_public_key": "02172F50274DE67C4087C344F5D58E11DF761D90285B095060E0994FAA6BCDE271",
"pub_account_signing_key": [
136,
105,
9,
53,
180,
145,
64,
5,
235,
174,
62,
211,
206,
116,
185,
24,
214,
62,
244,
64,
224,
59,
120,
150,
30,
249,
160,
46,
189,
254,
47,
244
],
"top_secret_key_holder": {
"secret_spending_key": "80A186737C8D38B4288A03F0F589957D9C040D79C19F3E0CC4BA80F8494E5179"
},
"utxo_secret_key_holder": {
"nullifier_secret_key": "746928E63F0984F6F4818933493CE9C067562D9CB932FDC06D82C86CDF6D7122",
"viewing_secret_key": "89176CF4BC9E673807643FD52110EF99D4894335AFB10D881AC0B5041FE1FCB7"
},
"viewing_public_key": "026072A8F83FEC3472E30CDD4767683F30B91661D25B1040AD9A5FC2E01D659F99"
},
"utxos": {}
}"#).unwrap();
let initial_accounts = vec![initial_acc1, initial_acc2];
initial_accounts
}
fn create_genesis_block() -> Block {
Block {
block_id: 0,
@ -362,6 +604,7 @@ mod tests {
port: 8000,
gas_config: create_sample_gas_config(),
shapshot_frequency_in_blocks: 1,
initial_accounts: create_initial_accounts(),
}
}

View File

@ -1,5 +1,6 @@
use std::path::PathBuf;
use accounts::account_core::Account;
use serde::{Deserialize, Serialize};
use zkvm::gas_calculator::GasCalculator;
@ -51,4 +52,6 @@ pub struct NodeConfig {
pub gas_config: GasConfig,
///Frequency of snapshots
pub shapshot_frequency_in_blocks: u64,
///Initial accounts for wallet
pub initial_accounts: Vec<Account>,
}

View File

@ -486,7 +486,7 @@ impl NodeCore {
secret_r,
sc_addr,
state_changes,
nonce: 1
nonce: 1,
};
let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key();
@ -761,7 +761,7 @@ impl NodeCore {
secret_r,
sc_addr,
state_changes,
nonce: 1
nonce: 1,
};
let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key();
@ -851,7 +851,7 @@ impl NodeCore {
secret_r,
sc_addr,
state_changes,
nonce: 1
nonce: 1,
};
let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key();

View File

@ -13,5 +13,241 @@
"gas_limit_deploy": 30000000,
"gas_limit_runtime": 30000000
},
"shapshot_frequency_in_blocks": 10
"shapshot_frequency_in_blocks": 10,
"initial_accounts": [
{
"address": [
13,
150,
223,
204,
65,
64,
25,
56,
12,
157,
222,
12,
211,
220,
229,
170,
201,
15,
181,
68,
59,
248,
113,
16,
135,
65,
174,
175,
222,
85,
42,
215
],
"balance": 10000,
"key_holder": {
"address": [
13,
150,
223,
204,
65,
64,
25,
56,
12,
157,
222,
12,
211,
220,
229,
170,
201,
15,
181,
68,
59,
248,
113,
16,
135,
65,
174,
175,
222,
85,
42,
215
],
"nullifer_public_key": "03A340BECA9FAAB444CED0140681D72EA1318B5C611704FEE017DA9836B17DB718",
"pub_account_signing_key": [
133,
143,
177,
187,
252,
66,
237,
236,
234,
252,
244,
138,
5,
151,
3,
99,
217,
231,
112,
217,
77,
211,
58,
218,
176,
68,
99,
53,
152,
228,
198,
190
],
"top_secret_key_holder": {
"secret_spending_key": "7BC46784DB1BC67825D8F029436846712BFDF9B5D79EA3AB11D39A52B9B229D4"
},
"utxo_secret_key_holder": {
"nullifier_secret_key": "BB54A8D3C9C51B82C431082D1845A74677B0EF829A11B517E1D9885DE3139506",
"viewing_secret_key": "AD923E92F6A5683E30140CEAB2702AFB665330C1EE4EFA70FAF29767B6B52BAF"
},
"viewing_public_key": "0361220C5D277E7A1709340FD31A52600C1432B9C45B9BCF88A43581D58824A8B6"
},
"utxos": {}
},
{
"address": [
151,
72,
112,
233,
190,
141,
10,
192,
138,
168,
59,
63,
199,
167,
166,
134,
41,
29,
135,
50,
80,
138,
186,
152,
179,
96,
128,
243,
156,
44,
243,
100
],
"balance": 20000,
"key_holder": {
"address": [
151,
72,
112,
233,
190,
141,
10,
192,
138,
168,
59,
63,
199,
167,
166,
134,
41,
29,
135,
50,
80,
138,
186,
152,
179,
96,
128,
243,
156,
44,
243,
100
],
"nullifer_public_key": "02172F50274DE67C4087C344F5D58E11DF761D90285B095060E0994FAA6BCDE271",
"pub_account_signing_key": [
54,
90,
62,
225,
71,
225,
228,
148,
143,
53,
210,
23,
137,
158,
171,
156,
48,
7,
139,
52,
117,
242,
214,
7,
99,
29,
122,
184,
59,
116,
144,
107
],
"top_secret_key_holder": {
"secret_spending_key": "80A186737C8D38B4288A03F0F589957D9C040D79C19F3E0CC4BA80F8494E5179"
},
"utxo_secret_key_holder": {
"nullifier_secret_key": "746928E63F0984F6F4818933493CE9C067562D9CB932FDC06D82C86CDF6D7122",
"viewing_secret_key": "89176CF4BC9E673807643FD52110EF99D4894335AFB10D881AC0B5041FE1FCB7"
},
"viewing_public_key": "026072A8F83FEC3472E30CDD4767683F30B91661D25B1040AD9A5FC2E01D659F99"
},
"utxos": {}
}
]
}

View File

@ -15,7 +15,7 @@ pub fn create_public_transaction_payload(
secret_r: [u8; 32],
sc_addr: String,
state_changes: (serde_json::Value, usize),
nonce: u64
nonce: u64,
) -> TransactionBody {
TransactionBody {
tx_kind: TxKind::Public,

View File

@ -1,7 +1,14 @@
use accounts::account_core::AccountForSerialization;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Serialize, Deserialize, Clone)]
///Helperstruct for account serialization
pub struct AccountInitialData {
///Hex encoded `AccountAddress`
pub addr: String,
pub balance: u64,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct SequencerConfig {
///Home dir of sequencer storage
@ -19,5 +26,5 @@ pub struct SequencerConfig {
///Port to listen
pub port: u16,
///List of initial accounts data
pub initial_accounts: Vec<AccountForSerialization>,
pub initial_accounts: Vec<AccountInitialData>,
}

View File

@ -28,7 +28,7 @@ pub struct SequencerCore {
pub chain_height: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TransactionMalformationErrorKind {
PublicTransactionChangedPrivateData { tx: TreeHashType },
PrivateTransactionChangedPublicData { tx: TreeHashType },
@ -143,7 +143,7 @@ impl SequencerCore {
_ => {}
};
//Correct sender check
//Native transfers checks
if let Ok(native_transfer_action) =
serde_json::from_slice::<PublicNativeTokenSend>(execution_input)
{
@ -152,6 +152,7 @@ impl SequencerCore {
keccak_hasher.update(&tx.transaction().public_key.to_sec1_bytes());
keccak_hasher.finalize(&mut output);
//Correct sender check
if native_transfer_action.from != output {
return Err(TransactionMalformationErrorKind::IncorrectSender);
}
@ -239,7 +240,7 @@ impl SequencerCore {
return Err(TransactionMalformationErrorKind::NonceMismatch { tx: tx_hash });
}
//Balance check
//Balance move
if let Ok(native_transfer_action) =
serde_json::from_slice::<PublicNativeTokenSend>(execution_input)
{
@ -252,21 +253,21 @@ impl SequencerCore {
.acc_store
.get_account_balance(&native_transfer_action.to);
if from_balance >= native_transfer_action.balance_to_move {
self.store.acc_store.set_account_balance(
&native_transfer_action.from,
from_balance - native_transfer_action.balance_to_move,
);
self.store.acc_store.set_account_balance(
&native_transfer_action.to,
to_balance + native_transfer_action.balance_to_move,
);
// Update nonce
let _new_nonce = self.store.acc_store.increase_nonce(&signer_addres);
} else {
//Balance check
if from_balance < native_transfer_action.balance_to_move {
return Err(TransactionMalformationErrorKind::BalanceMismatch { tx: tx_hash });
}
self.store.acc_store.set_account_balance(
&native_transfer_action.from,
from_balance - native_transfer_action.balance_to_move,
);
self.store.acc_store.set_account_balance(
&native_transfer_action.to,
to_balance + native_transfer_action.balance_to_move,
);
self.store.acc_store.increase_nonce(&signer_addres);
}
for utxo_comm in utxo_commitments_created_hashes {
@ -300,9 +301,16 @@ impl SequencerCore {
.mempool
.pop_size(self.sequencer_config.max_num_tx_in_block);
for tx in &transactions {
self.execute_check_transaction_on_state(tx);
}
let valid_transactions = transactions
.into_iter()
.filter_map(|mempool_tx| {
if self.execute_check_transaction_on_state(&mempool_tx).is_ok() {
Some(mempool_tx.auth_tx.into_transaction())
} else {
None
}
})
.collect();
let prev_block_hash = self
.store
@ -313,10 +321,7 @@ impl SequencerCore {
let hashable_data = HashableBlockData {
block_id: new_block_height,
prev_block_id: self.chain_height,
transactions: transactions
.into_iter()
.map(|tx_mem| tx_mem.auth_tx.transaction().clone())
.collect(),
transactions: valid_transactions,
data: vec![],
prev_block_hash,
};
@ -333,17 +338,19 @@ impl SequencerCore {
#[cfg(test)]
mod tests {
use crate::config::AccountInitialData;
use super::*;
use std::path::PathBuf;
use accounts::account_core::AccountForSerialization;
use common::transaction::{SignaturePrivateKey, Transaction, TransactionBody, TxKind};
use k256::{ecdsa::SigningKey, FieldBytes};
use mempool_transaction::MempoolTransaction;
use rand::Rng;
use secp256k1_zkp::Tweak;
fn setup_sequencer_config_variable_initial_accounts(
initial_accounts: Vec<AccountForSerialization>,
initial_accounts: Vec<AccountInitialData>,
) -> SequencerConfig {
let mut rng = rand::thread_rng();
let random_u8: u8 = rng.gen();
@ -363,241 +370,25 @@ mod tests {
}
fn setup_sequencer_config() -> SequencerConfig {
let initial_acc1 = serde_json::from_str(r#"{
"address": [
244,
55,
238,
205,
74,
115,
179,
192,
65,
186,
166,
169,
221,
45,
6,
57,
200,
65,
195,
70,
118,
252,
206,
100,
215,
250,
72,
230,
19,
71,
217,
249
],
"balance": 10,
"key_holder": {
"address": [
244,
55,
238,
205,
74,
115,
179,
192,
65,
186,
166,
169,
221,
45,
6,
57,
200,
65,
195,
70,
118,
252,
206,
100,
215,
250,
72,
230,
19,
71,
217,
249
],
"nullifer_public_key": "03A340BECA9FAAB444CED0140681D72EA1318B5C611704FEE017DA9836B17DB718",
"pub_account_signing_key": [
244,
88,
134,
61,
35,
209,
229,
101,
85,
35,
140,
140,
192,
226,
83,
83,
190,
189,
110,
8,
89,
127,
147,
142,
157,
204,
51,
109,
189,
92,
144,
68
],
"top_secret_key_holder": {
"secret_spending_key": "7BC46784DB1BC67825D8F029436846712BFDF9B5D79EA3AB11D39A52B9B229D4"
},
"utxo_secret_key_holder": {
"nullifier_secret_key": "BB54A8D3C9C51B82C431082D1845A74677B0EF829A11B517E1D9885DE3139506",
"viewing_secret_key": "AD923E92F6A5683E30140CEAB2702AFB665330C1EE4EFA70FAF29767B6B52BAF"
},
"viewing_public_key": "0361220C5D277E7A1709340FD31A52600C1432B9C45B9BCF88A43581D58824A8B6"
},
"utxos": {}
}"#).unwrap();
let acc1_addr = vec![
13, 150, 223, 204, 65, 64, 25, 56, 12, 157, 222, 12, 211, 220, 229, 170, 201, 15, 181,
68, 59, 248, 113, 16, 135, 65, 174, 175, 222, 85, 42, 215,
];
let initial_acc2 = serde_json::from_str(r#"{
"address": [
72,
169,
70,
237,
1,
96,
35,
157,
25,
15,
83,
18,
52,
206,
202,
63,
48,
59,
173,
76,
78,
7,
254,
229,
28,
45,
194,
79,
6,
89,
58,
85
],
"balance": 100,
"key_holder": {
"address": [
72,
169,
70,
237,
1,
96,
35,
157,
25,
15,
83,
18,
52,
206,
202,
63,
48,
59,
173,
76,
78,
7,
254,
229,
28,
45,
194,
79,
6,
89,
58,
85
],
"nullifer_public_key": "02172F50274DE67C4087C344F5D58E11DF761D90285B095060E0994FAA6BCDE271",
"pub_account_signing_key": [
136,
105,
9,
53,
180,
145,
64,
5,
235,
174,
62,
211,
206,
116,
185,
24,
214,
62,
244,
64,
224,
59,
120,
150,
30,
249,
160,
46,
189,
254,
47,
244
],
"top_secret_key_holder": {
"secret_spending_key": "80A186737C8D38B4288A03F0F589957D9C040D79C19F3E0CC4BA80F8494E5179"
},
"utxo_secret_key_holder": {
"nullifier_secret_key": "746928E63F0984F6F4818933493CE9C067562D9CB932FDC06D82C86CDF6D7122",
"viewing_secret_key": "89176CF4BC9E673807643FD52110EF99D4894335AFB10D881AC0B5041FE1FCB7"
},
"viewing_public_key": "026072A8F83FEC3472E30CDD4767683F30B91661D25B1040AD9A5FC2E01D659F99"
},
"utxos": {}
}"#).unwrap();
let acc2_addr = vec![
151, 72, 112, 233, 190, 141, 10, 192, 138, 168, 59, 63, 199, 167, 166, 134, 41, 29,
135, 50, 80, 138, 186, 152, 179, 96, 128, 243, 156, 44, 243, 100,
];
let initial_acc1 = AccountInitialData {
addr: hex::encode(acc1_addr),
balance: 10000,
};
let initial_acc2 = AccountInitialData {
addr: hex::encode(acc2_addr),
balance: 20000,
};
let initial_accounts = vec![initial_acc1, initial_acc2];
@ -631,6 +422,61 @@ mod tests {
Transaction::new(body, SignaturePrivateKey::random(&mut rng))
}
fn create_dummy_transaction_native_token_transfer(
from: [u8; 32],
nonce: u64,
to: [u8; 32],
balance_to_move: u64,
signing_key: SigningKey,
) -> Transaction {
let mut rng = rand::thread_rng();
let native_token_transfer = PublicNativeTokenSend {
from,
to,
balance_to_move,
};
let body = TransactionBody {
tx_kind: TxKind::Public,
execution_input: serde_json::to_vec(&native_token_transfer).unwrap(),
execution_output: vec![],
utxo_commitments_spent_hashes: vec![],
utxo_commitments_created_hashes: vec![],
nullifier_created_hashes: vec![],
execution_proof_private: "".to_string(),
encoded_data: vec![],
ephemeral_pub_key: vec![10, 11, 12],
commitment: vec![],
tweak: Tweak::new(&mut rng),
secret_r: [0; 32],
sc_addr: "sc_addr".to_string(),
state_changes: (serde_json::Value::Null, 0),
nonce,
};
Transaction::new(body, signing_key)
}
fn create_signing_key_for_account1() -> SigningKey {
let pub_sign_key_acc1 = [
133, 143, 177, 187, 252, 66, 237, 236, 234, 252, 244, 138, 5, 151, 3, 99, 217, 231,
112, 217, 77, 211, 58, 218, 176, 68, 99, 53, 152, 228, 198, 190,
];
let field_bytes = FieldBytes::from_slice(&pub_sign_key_acc1);
SigningKey::from_bytes(field_bytes).unwrap()
}
fn create_signing_key_for_account2() -> SigningKey {
let pub_sign_key_acc2 = [
54, 90, 62, 225, 71, 225, 228, 148, 143, 53, 210, 23, 137, 158, 171, 156, 48, 7, 139,
52, 117, 242, 214, 7, 99, 29, 122, 184, 59, 116, 144, 107,
];
let field_bytes = FieldBytes::from_slice(&pub_sign_key_acc2);
SigningKey::from_bytes(field_bytes).unwrap()
}
fn common_setup(sequencer: &mut SequencerCore) {
let tx = create_dummy_transaction(vec![[9; 32]], vec![[7; 32]], vec![[8; 32]]);
let mempool_tx = MempoolTransaction {
@ -652,259 +498,49 @@ mod tests {
assert_eq!(sequencer.sequencer_config.max_num_tx_in_block, 10);
assert_eq!(sequencer.sequencer_config.port, 8080);
let acc1_addr = config.initial_accounts[0].address;
let acc2_addr = config.initial_accounts[1].address;
let acc1_addr = hex::decode(config.initial_accounts[0].addr.clone())
.unwrap()
.try_into()
.unwrap();
let acc2_addr = hex::decode(config.initial_accounts[1].addr.clone())
.unwrap()
.try_into()
.unwrap();
assert!(sequencer.store.acc_store.contains_account(&acc1_addr));
assert!(sequencer.store.acc_store.contains_account(&acc2_addr));
assert_eq!(
10,
10000,
sequencer.store.acc_store.get_account_balance(&acc1_addr)
);
assert_eq!(
100,
20000,
sequencer.store.acc_store.get_account_balance(&acc2_addr)
);
}
#[test]
fn test_start_different_intial_accounts_balances() {
let initial_acc1 = serde_json::from_str(r#"{
"address": [
244,
55,
238,
205,
74,
115,
179,
192,
65,
186,
166,
169,
221,
45,
6,
57,
200,
65,
195,
70,
118,
252,
206,
100,
215,
250,
72,
230,
19,
71,
217,
249
],
"balance": 1000,
"key_holder": {
"address": [
244,
55,
238,
205,
74,
115,
179,
192,
65,
186,
166,
169,
221,
45,
6,
57,
200,
65,
195,
70,
118,
252,
206,
100,
215,
250,
72,
230,
19,
71,
217,
249
],
"nullifer_public_key": "03A340BECA9FAAB444CED0140681D72EA1318B5C611704FEE017DA9836B17DB718",
"pub_account_signing_key": [
244,
88,
134,
61,
35,
209,
229,
101,
85,
35,
140,
140,
192,
226,
83,
83,
190,
189,
110,
8,
89,
127,
147,
142,
157,
204,
51,
109,
189,
92,
144,
68
],
"top_secret_key_holder": {
"secret_spending_key": "7BC46784DB1BC67825D8F029436846712BFDF9B5D79EA3AB11D39A52B9B229D4"
},
"utxo_secret_key_holder": {
"nullifier_secret_key": "BB54A8D3C9C51B82C431082D1845A74677B0EF829A11B517E1D9885DE3139506",
"viewing_secret_key": "AD923E92F6A5683E30140CEAB2702AFB665330C1EE4EFA70FAF29767B6B52BAF"
},
"viewing_public_key": "0361220C5D277E7A1709340FD31A52600C1432B9C45B9BCF88A43581D58824A8B6"
},
"utxos": {}
}"#).unwrap();
let acc1_addr = vec![
13, 150, 223, 204, 65, 64, 25, 56, 12, 157, 222, 12, 211, 220, 229, 170, 201, 15, 181,
68, 59, 248, 113, 16, 135, 65, 174, 175, 222, 42, 42, 42,
];
let initial_acc2 = serde_json::from_str(r#"{
"address": [
72,
169,
70,
237,
1,
96,
35,
157,
25,
15,
83,
18,
52,
206,
202,
63,
48,
59,
173,
76,
78,
7,
254,
229,
28,
45,
194,
79,
6,
89,
58,
85
],
"balance": 1000,
"key_holder": {
"address": [
72,
169,
70,
237,
1,
96,
35,
157,
25,
15,
83,
18,
52,
206,
202,
63,
48,
59,
173,
76,
78,
7,
254,
229,
28,
45,
194,
79,
6,
89,
58,
85
],
"nullifer_public_key": "02172F50274DE67C4087C344F5D58E11DF761D90285B095060E0994FAA6BCDE271",
"pub_account_signing_key": [
136,
105,
9,
53,
180,
145,
64,
5,
235,
174,
62,
211,
206,
116,
185,
24,
214,
62,
244,
64,
224,
59,
120,
150,
30,
249,
160,
46,
189,
254,
47,
244
],
"top_secret_key_holder": {
"secret_spending_key": "80A186737C8D38B4288A03F0F589957D9C040D79C19F3E0CC4BA80F8494E5179"
},
"utxo_secret_key_holder": {
"nullifier_secret_key": "746928E63F0984F6F4818933493CE9C067562D9CB932FDC06D82C86CDF6D7122",
"viewing_secret_key": "89176CF4BC9E673807643FD52110EF99D4894335AFB10D881AC0B5041FE1FCB7"
},
"viewing_public_key": "026072A8F83FEC3472E30CDD4767683F30B91661D25B1040AD9A5FC2E01D659F99"
},
"utxos": {}
}"#).unwrap();
let acc2_addr = vec![
151, 72, 112, 233, 190, 141, 10, 192, 138, 168, 59, 63, 199, 167, 166, 134, 41, 29,
135, 50, 80, 138, 186, 152, 179, 96, 128, 243, 156, 42, 42, 42,
];
let initial_acc1 = AccountInitialData {
addr: hex::encode(acc1_addr),
balance: 10000,
};
let initial_acc2 = AccountInitialData {
addr: hex::encode(acc2_addr),
balance: 20000,
};
let initial_accounts = vec![initial_acc1, initial_acc2];
@ -913,8 +549,14 @@ mod tests {
let config = setup_sequencer_config_variable_initial_accounts(initial_accounts);
let sequencer = SequencerCore::start_from_config(config.clone());
let acc1_addr = config.initial_accounts[0].address;
let acc2_addr = config.initial_accounts[1].address;
let acc1_addr = hex::decode(config.initial_accounts[0].addr.clone())
.unwrap()
.try_into()
.unwrap();
let acc2_addr = hex::decode(config.initial_accounts[1].addr.clone())
.unwrap()
.try_into()
.unwrap();
assert!(sequencer.store.acc_store.contains_account(&acc1_addr));
assert!(sequencer.store.acc_store.contains_account(&acc2_addr));
@ -922,11 +564,11 @@ mod tests {
assert_eq!(sequencer.store.acc_store.len(), intial_accounts_len);
assert_eq!(
1000,
10000,
sequencer.store.acc_store.get_account_balance(&acc1_addr)
);
assert_eq!(
1000,
20000,
sequencer.store.acc_store.get_account_balance(&acc2_addr)
);
}
@ -956,6 +598,124 @@ mod tests {
assert!(result.is_ok());
}
#[test]
fn test_transaction_pre_check_native_transfer_valid() {
let config = setup_sequencer_config();
let mut sequencer = SequencerCore::start_from_config(config);
common_setup(&mut sequencer);
let acc1 = hex::decode(sequencer.sequencer_config.initial_accounts[0].addr.clone())
.unwrap()
.try_into()
.unwrap();
let acc2 = hex::decode(sequencer.sequencer_config.initial_accounts[1].addr.clone())
.unwrap()
.try_into()
.unwrap();
let sign_key1 = create_signing_key_for_account1();
let tx = create_dummy_transaction_native_token_transfer(acc1, 0, acc2, 10, sign_key1);
let tx_roots = sequencer.get_tree_roots();
let result = sequencer.transaction_pre_check(tx, tx_roots);
assert!(result.is_ok());
}
#[test]
fn test_transaction_pre_check_native_transfer_other_signature() {
let config = setup_sequencer_config();
let mut sequencer = SequencerCore::start_from_config(config);
common_setup(&mut sequencer);
let acc1 = hex::decode(sequencer.sequencer_config.initial_accounts[0].addr.clone())
.unwrap()
.try_into()
.unwrap();
let acc2 = hex::decode(sequencer.sequencer_config.initial_accounts[1].addr.clone())
.unwrap()
.try_into()
.unwrap();
let sign_key2 = create_signing_key_for_account2();
let tx = create_dummy_transaction_native_token_transfer(acc1, 0, acc2, 10, sign_key2);
let tx_roots = sequencer.get_tree_roots();
let result = sequencer.transaction_pre_check(tx, tx_roots);
assert_eq!(
result.err().unwrap(),
TransactionMalformationErrorKind::IncorrectSender
);
}
#[test]
fn test_transaction_pre_check_native_transfer_sent_too_much() {
let config = setup_sequencer_config();
let mut sequencer = SequencerCore::start_from_config(config);
common_setup(&mut sequencer);
let acc1 = hex::decode(sequencer.sequencer_config.initial_accounts[0].addr.clone())
.unwrap()
.try_into()
.unwrap();
let acc2 = hex::decode(sequencer.sequencer_config.initial_accounts[1].addr.clone())
.unwrap()
.try_into()
.unwrap();
let sign_key1 = create_signing_key_for_account1();
let tx = create_dummy_transaction_native_token_transfer(acc1, 0, acc2, 10000000, sign_key1);
let tx_roots = sequencer.get_tree_roots();
let result = sequencer.transaction_pre_check(tx, tx_roots);
//Passed pre-check
assert!(result.is_ok());
let result = sequencer.execute_check_transaction_on_state(&result.unwrap().into());
let is_failed_at_balance_mismatch = matches!(
result.err().unwrap(),
TransactionMalformationErrorKind::BalanceMismatch { tx: _ }
);
assert!(is_failed_at_balance_mismatch);
}
#[test]
fn test_transaction_execute_native_transfer() {
let config = setup_sequencer_config();
let mut sequencer = SequencerCore::start_from_config(config);
common_setup(&mut sequencer);
let acc1 = hex::decode(sequencer.sequencer_config.initial_accounts[0].addr.clone())
.unwrap()
.try_into()
.unwrap();
let acc2 = hex::decode(sequencer.sequencer_config.initial_accounts[1].addr.clone())
.unwrap()
.try_into()
.unwrap();
let sign_key1 = create_signing_key_for_account1();
let tx = create_dummy_transaction_native_token_transfer(acc1, 0, acc2, 100, sign_key1);
sequencer
.execute_check_transaction_on_state(&tx.into_authenticated().unwrap().into())
.unwrap();
let bal_from = sequencer.store.acc_store.get_account_balance(&acc1);
let bal_to = sequencer.store.acc_store.get_account_balance(&acc2);
assert_eq!(bal_from, 9900);
assert_eq!(bal_to, 20100);
}
#[test]
fn test_push_tx_into_mempool_fails_mempool_full() {
let config = SequencerConfig {
@ -1020,7 +780,20 @@ mod tests {
let config = setup_sequencer_config();
let mut sequencer = SequencerCore::start_from_config(config);
let tx = create_dummy_transaction(vec![[94; 32]], vec![[7; 32]], vec![[8; 32]]);
common_setup(&mut sequencer);
let acc1 = hex::decode(sequencer.sequencer_config.initial_accounts[0].addr.clone())
.unwrap()
.try_into()
.unwrap();
let acc2 = hex::decode(sequencer.sequencer_config.initial_accounts[1].addr.clone())
.unwrap()
.try_into()
.unwrap();
let sign_key1 = create_signing_key_for_account1();
let tx = create_dummy_transaction_native_token_transfer(acc1, 0, acc2, 100, sign_key1);
// The transaction should be included the first time
let tx_mempool_original = MempoolTransaction {

View File

@ -84,18 +84,22 @@ impl SequencerAccountsStore {
///Update `account_addr` balance,
///
/// returns 0, if account address not found, otherwise returns previous balance
///
/// Also, if account was not previously found, sets it with zero balance
pub fn set_account_balance(&mut self, account_addr: &AccountAddress, new_balance: u64) -> u64 {
let acc_data = self.accounts.get_mut(account_addr);
acc_data
.map(|data| {
let old_balance = data.balance;
if let Some(acc_data) = acc_data {
let old_balance = acc_data.balance;
data.balance = new_balance;
acc_data.balance = new_balance;
old_balance
})
.unwrap_or(0)
old_balance
} else {
self.register_account(*account_addr);
0
}
}
pub fn increase_nonce(&mut self, account_addr: &AccountAddress) -> Option<u64> {
@ -278,4 +282,15 @@ mod tests {
assert!(seq_acc_store.is_empty());
}
#[test]
fn account_sequencer_store_set_balance_to_unknown_account() {
let mut seq_acc_store = SequencerAccountsStore::default();
let ret = seq_acc_store.set_account_balance(&[1; 32], 100);
assert_eq!(ret, 0);
assert!(seq_acc_store.contains_account(&[1; 32]));
assert_eq!(seq_acc_store.get_account_balance(&[1; 32]), 0);
}
}

View File

@ -95,7 +95,7 @@ mod tests {
secret_r: Default::default(),
sc_addr: Default::default(),
state_changes: Default::default(),
nonce: 1
nonce: 1,
};
let tx = Transaction::new(body, SignaturePrivateKey::from_slice(&[1; 32]).unwrap());
(

View File

@ -1,6 +1,5 @@
use std::{collections::HashSet, path::Path};
use accounts::account_core::AccountForSerialization;
use accounts_store::SequencerAccountsStore;
use block_store::SequecerBlockStore;
use common::{
@ -10,6 +9,8 @@ use common::{
};
use rand::{rngs::OsRng, RngCore};
use crate::config::AccountInitialData;
pub mod accounts_store;
pub mod block_store;
@ -26,14 +27,22 @@ impl SequecerChainStore {
home_dir: &Path,
genesis_id: u64,
is_genesis_random: bool,
initial_accounts: &[AccountForSerialization],
initial_accounts: &[AccountInitialData],
) -> Self {
let accs_pregenerated: Vec<_> = initial_accounts
let init_accs: Vec<_> = initial_accounts
.iter()
.map(|acc| (acc.address, acc.balance))
.map(|acc_data| {
(
hex::decode(acc_data.addr.clone())
.unwrap()
.try_into()
.unwrap(),
acc_data.balance,
)
})
.collect();
let acc_store = SequencerAccountsStore::new(&accs_pregenerated);
let acc_store = SequencerAccountsStore::new(&init_accs);
let nullifier_store = HashSet::new();
let utxo_commitments_store = UTXOCommitmentsMerkleTree::new(vec![]);
let pub_tx_store = PublicTransactionMerkleTree::new(vec![]);

View File

@ -1,5 +1,5 @@
use accounts::account_core::AccountForSerialization;
use actix_web::Error as HttpError;
use sequencer_core::config::AccountInitialData;
use serde_json::Value;
use common::{
@ -149,13 +149,13 @@ impl JsonHandler {
let _get_initial_testnet_accounts_request =
GetInitialTestnetAccountsRequest::parse(Some(request.params))?;
let accounts_for_serialization: Vec<AccountForSerialization> = {
let initial_accounts: Vec<AccountInitialData> = {
let state = self.sequencer_state.lock().await;
state.sequencer_config.initial_accounts.clone()
};
respond(accounts_for_serialization)
respond(initial_accounts)
}
/// Returns the balance of the account at the given address.
@ -217,12 +217,14 @@ mod tests {
use std::sync::Arc;
use crate::{rpc_handler, JsonHandler};
use accounts::account_core::Account;
use common::{
rpc_primitives::RpcPollingConfig,
transaction::{SignaturePrivateKey, Transaction, TransactionBody},
};
use sequencer_core::{config::SequencerConfig, SequencerCore};
use sequencer_core::{
config::{AccountInitialData, SequencerConfig},
SequencerCore,
};
use serde_json::Value;
use tempfile::tempdir;
use tokio::sync::Mutex;
@ -230,241 +232,25 @@ mod tests {
fn sequencer_config_for_tests() -> SequencerConfig {
let tempdir = tempdir().unwrap();
let home = tempdir.path().to_path_buf();
let initial_acc1 = serde_json::from_str(r#"{
"address": [
244,
55,
238,
205,
74,
115,
179,
192,
65,
186,
166,
169,
221,
45,
6,
57,
200,
65,
195,
70,
118,
252,
206,
100,
215,
250,
72,
230,
19,
71,
217,
249
],
"balance": 100,
"key_holder": {
"address": [
244,
55,
238,
205,
74,
115,
179,
192,
65,
186,
166,
169,
221,
45,
6,
57,
200,
65,
195,
70,
118,
252,
206,
100,
215,
250,
72,
230,
19,
71,
217,
249
],
"nullifer_public_key": "03A340BECA9FAAB444CED0140681D72EA1318B5C611704FEE017DA9836B17DB718",
"pub_account_signing_key": [
244,
88,
134,
61,
35,
209,
229,
101,
85,
35,
140,
140,
192,
226,
83,
83,
190,
189,
110,
8,
89,
127,
147,
142,
157,
204,
51,
109,
189,
92,
144,
68
],
"top_secret_key_holder": {
"secret_spending_key": "7BC46784DB1BC67825D8F029436846712BFDF9B5D79EA3AB11D39A52B9B229D4"
},
"utxo_secret_key_holder": {
"nullifier_secret_key": "BB54A8D3C9C51B82C431082D1845A74677B0EF829A11B517E1D9885DE3139506",
"viewing_secret_key": "AD923E92F6A5683E30140CEAB2702AFB665330C1EE4EFA70FAF29767B6B52BAF"
},
"viewing_public_key": "0361220C5D277E7A1709340FD31A52600C1432B9C45B9BCF88A43581D58824A8B6"
},
"utxos": {}
}"#).unwrap();
let acc1_addr = vec![
13, 150, 223, 204, 65, 64, 25, 56, 12, 157, 222, 12, 211, 220, 229, 170, 201, 15, 181,
68, 59, 248, 113, 16, 135, 65, 174, 175, 222, 85, 42, 215,
];
let initial_acc2 = serde_json::from_str(r#"{
"address": [
72,
169,
70,
237,
1,
96,
35,
157,
25,
15,
83,
18,
52,
206,
202,
63,
48,
59,
173,
76,
78,
7,
254,
229,
28,
45,
194,
79,
6,
89,
58,
85
],
"balance": 200,
"key_holder": {
"address": [
72,
169,
70,
237,
1,
96,
35,
157,
25,
15,
83,
18,
52,
206,
202,
63,
48,
59,
173,
76,
78,
7,
254,
229,
28,
45,
194,
79,
6,
89,
58,
85
],
"nullifer_public_key": "02172F50274DE67C4087C344F5D58E11DF761D90285B095060E0994FAA6BCDE271",
"pub_account_signing_key": [
136,
105,
9,
53,
180,
145,
64,
5,
235,
174,
62,
211,
206,
116,
185,
24,
214,
62,
244,
64,
224,
59,
120,
150,
30,
249,
160,
46,
189,
254,
47,
244
],
"top_secret_key_holder": {
"secret_spending_key": "80A186737C8D38B4288A03F0F589957D9C040D79C19F3E0CC4BA80F8494E5179"
},
"utxo_secret_key_holder": {
"nullifier_secret_key": "746928E63F0984F6F4818933493CE9C067562D9CB932FDC06D82C86CDF6D7122",
"viewing_secret_key": "89176CF4BC9E673807643FD52110EF99D4894335AFB10D881AC0B5041FE1FCB7"
},
"viewing_public_key": "026072A8F83FEC3472E30CDD4767683F30B91661D25B1040AD9A5FC2E01D659F99"
},
"utxos": {}
}"#).unwrap();
let acc2_addr = vec![
151, 72, 112, 233, 190, 141, 10, 192, 138, 168, 59, 63, 199, 167, 166, 134, 41, 29,
135, 50, 80, 138, 186, 152, 179, 96, 128, 243, 156, 44, 243, 100,
];
let initial_acc1 = AccountInitialData {
addr: hex::encode(acc1_addr),
balance: 10000,
};
let initial_acc2 = AccountInitialData {
addr: hex::encode(acc2_addr),
balance: 20000,
};
let initial_accounts = vec![initial_acc1, initial_acc2];
@ -480,19 +266,15 @@ mod tests {
}
}
fn json_handler_for_tests() -> (JsonHandler, Vec<Account>) {
fn json_handler_for_tests() -> (JsonHandler, Vec<AccountInitialData>) {
let config = sequencer_config_for_tests();
let mut sequencer_core = SequencerCore::start_from_config(config);
let initial_accounts = sequencer_core
.sequencer_config
.initial_accounts
.iter()
.map(|acc_ser| acc_ser.clone().into())
.collect();
let initial_accounts = sequencer_core.sequencer_config.initial_accounts.clone();
let tx_body = TransactionBody {
tx_kind: common::transaction::TxKind::Public,
tx_kind: common::transaction::TxKind::Shielded,
execution_input: Default::default(),
execution_output: Default::default(),
utxo_commitments_spent_hashes: Default::default(),
@ -506,9 +288,10 @@ mod tests {
secret_r: Default::default(),
sc_addr: Default::default(),
state_changes: Default::default(),
nonce: 1,
nonce: 0,
};
let tx = Transaction::new(tx_body, SignaturePrivateKey::from_slice(&[1; 32]).unwrap());
println!("{:?}", tx.body().hash());
sequencer_core
.push_tx_into_mempool_pre_check(tx, [[0; 32]; 2])
@ -621,7 +404,7 @@ mod tests {
async fn test_get_account_balance_for_existing_account() {
let (json_handler, initial_accounts) = json_handler_for_tests();
let acc1_addr = hex::encode(initial_accounts[0].address);
let acc1_addr = initial_accounts[0].addr.clone();
let request = serde_json::json!({
"jsonrpc": "2.0",
@ -633,7 +416,7 @@ mod tests {
"id": 1,
"jsonrpc": "2.0",
"result": {
"balance": 100
"balance": 10000
}
});
@ -718,9 +501,10 @@ mod tests {
let request = serde_json::json!({
"jsonrpc": "2.0",
"method": "get_transaction_by_hash",
"params": { "hash": "ca8e38269c0137d27cbe7c55d240a834b46e86e236578b9a1a3a25b3dabc5709" },
"params": { "hash": "b70b861373b99a509b27a0c61d6340762c9a0c0026520921d92218712efc3bca"},
"id": 1
});
let expected_response = serde_json::json!({
"id": 1,
"jsonrpc": "2.0",
@ -738,12 +522,13 @@ mod tests {
"secret_r": vec![0; 32],
"state_changes": [null, 0],
"tweak": "0".repeat(64),
"tx_kind": "Public",
"tx_kind": "Shielded",
"utxo_commitments_created_hashes": [],
"utxo_commitments_spent_hashes": []
"utxo_commitments_spent_hashes": [],
"nonce": 0,
},
"public_key": "3056301006072A8648CE3D020106052B8104000A034200041B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F70BEAF8F588B541507FED6A642C5AB42DFDF8120A7F639DE5122D47A69A8E8D1",
"signature": "28CB6CA744864340A3441CB48D5700690F90130DE0760EE5C640F85F4285C5FD2BD7D0E270EC2AC82E4124999E63659AA9C33CF378F959EDF4E50F2626EA3B99"
"signature": "50AEACE783026D0B6CE221474A928A05A99E2942246DF1C79162C08175F80744746E4A00D6C7F70BDBCF6D7B3668334396B9378FB0F58DC2E8A9B13F3BF001C4"
}
}
});

View File

@ -7,239 +7,6 @@
"block_create_timeout_millis": 10000,
"port": 3040,
"initial_accounts": [
{
"address": [
244,
55,
238,
205,
74,
115,
179,
192,
65,
186,
166,
169,
221,
45,
6,
57,
200,
65,
195,
70,
118,
252,
206,
100,
215,
250,
72,
230,
19,
71,
217,
249
],
"balance": 100,
"key_holder": {
"address": [
244,
55,
238,
205,
74,
115,
179,
192,
65,
186,
166,
169,
221,
45,
6,
57,
200,
65,
195,
70,
118,
252,
206,
100,
215,
250,
72,
230,
19,
71,
217,
249
],
"nullifer_public_key": "03A340BECA9FAAB444CED0140681D72EA1318B5C611704FEE017DA9836B17DB718",
"pub_account_signing_key": [
244,
88,
134,
61,
35,
209,
229,
101,
85,
35,
140,
140,
192,
226,
83,
83,
190,
189,
110,
8,
89,
127,
147,
142,
157,
204,
51,
109,
189,
92,
144,
68
],
"top_secret_key_holder": {
"secret_spending_key": "7BC46784DB1BC67825D8F029436846712BFDF9B5D79EA3AB11D39A52B9B229D4"
},
"utxo_secret_key_holder": {
"nullifier_secret_key": "BB54A8D3C9C51B82C431082D1845A74677B0EF829A11B517E1D9885DE3139506",
"viewing_secret_key": "AD923E92F6A5683E30140CEAB2702AFB665330C1EE4EFA70FAF29767B6B52BAF"
},
"viewing_public_key": "0361220C5D277E7A1709340FD31A52600C1432B9C45B9BCF88A43581D58824A8B6"
},
"utxos": {}
},
{
"address": [
72,
169,
70,
237,
1,
96,
35,
157,
25,
15,
83,
18,
52,
206,
202,
63,
48,
59,
173,
76,
78,
7,
254,
229,
28,
45,
194,
79,
6,
89,
58,
85
],
"balance": 1000,
"key_holder": {
"address": [
72,
169,
70,
237,
1,
96,
35,
157,
25,
15,
83,
18,
52,
206,
202,
63,
48,
59,
173,
76,
78,
7,
254,
229,
28,
45,
194,
79,
6,
89,
58,
85
],
"nullifer_public_key": "02172F50274DE67C4087C344F5D58E11DF761D90285B095060E0994FAA6BCDE271",
"pub_account_signing_key": [
136,
105,
9,
53,
180,
145,
64,
5,
235,
174,
62,
211,
206,
116,
185,
24,
214,
62,
244,
64,
224,
59,
120,
150,
30,
249,
160,
46,
189,
254,
47,
244
],
"top_secret_key_holder": {
"secret_spending_key": "80A186737C8D38B4288A03F0F589957D9C040D79C19F3E0CC4BA80F8494E5179"
},
"utxo_secret_key_holder": {
"nullifier_secret_key": "746928E63F0984F6F4818933493CE9C067562D9CB932FDC06D82C86CDF6D7122",
"viewing_secret_key": "89176CF4BC9E673807643FD52110EF99D4894335AFB10D881AC0B5041FE1FCB7"
},
"viewing_public_key": "026072A8F83FEC3472E30CDD4767683F30B91661D25B1040AD9A5FC2E01D659F99"
},
"utxos": {}
}
]
}