diff --git a/Cargo.toml b/Cargo.toml index 02192ca..8053d62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ ark-bn254 = "0.5.0" ark-ff = "0.5.0" tiny-keccak = { version = "2.0.2", features = ["keccak"] } base64 = "0.22.1" +chrono = "0.4.41" rocksdb = { version = "0.21.0", default-features = false, features = [ "snappy", diff --git a/common/src/block.rs b/common/src/block.rs index 2e4e9b9..64bce30 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -1,41 +1,56 @@ +use rs_merkle::Hasher; use std::io::{Cursor, Read}; -use rs_merkle::Hasher; - -use crate::merkle_tree_public::hasher::OwnHasher; -use nssa; +use crate::{OwnHasher, transaction::EncodedTransaction}; pub type BlockHash = [u8; 32]; pub type BlockId = u64; +pub type TimeStamp = u64; + +#[derive(Debug, Clone)] +pub struct BlockHeader { + pub block_id: BlockId, + pub prev_block_hash: BlockHash, + pub hash: BlockHash, + pub timestamp: TimeStamp, + pub signature: nssa::Signature, +} + +#[derive(Debug, Clone)] +pub struct BlockBody { + pub transactions: Vec, +} #[derive(Debug, Clone)] pub struct Block { - pub block_id: BlockId, - pub prev_block_id: BlockId, - pub prev_block_hash: BlockHash, - pub hash: BlockHash, - pub transactions: Vec, + pub header: BlockHeader, + pub body: BlockBody, } #[derive(Debug, PartialEq, Eq)] pub struct HashableBlockData { pub block_id: BlockId, - pub prev_block_id: BlockId, pub prev_block_hash: BlockHash, - pub transactions: Vec, + pub timestamp: TimeStamp, + pub transactions: Vec, } -impl From for Block { - fn from(value: HashableBlockData) -> Self { - let data = value.to_bytes(); - let hash = OwnHasher::hash(&data); - - Self { - block_id: value.block_id, - prev_block_id: value.prev_block_id, - hash, - transactions: value.transactions, - prev_block_hash: value.prev_block_hash, +impl HashableBlockData { + pub fn into_block(self, signing_key: &nssa::PrivateKey) -> Block { + let data_bytes = self.to_bytes(); + let signature = nssa::Signature::new(signing_key, &data_bytes); + let hash = OwnHasher::hash(&data_bytes); + Block { + header: BlockHeader { + block_id: self.block_id, + prev_block_hash: self.prev_block_hash, + hash, + timestamp: self.timestamp, + signature, + }, + body: BlockBody { + transactions: self.transactions, + }, } } } @@ -43,10 +58,10 @@ impl From for Block { impl From for HashableBlockData { fn from(value: Block) -> Self { Self { - block_id: value.block_id, - prev_block_id: value.prev_block_id, - prev_block_hash: value.prev_block_hash, - transactions: value.transactions, + block_id: value.header.block_id, + prev_block_hash: value.header.prev_block_hash, + timestamp: value.header.timestamp, + transactions: value.body.transactions, } } } @@ -55,11 +70,15 @@ impl HashableBlockData { pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); bytes.extend_from_slice(&self.block_id.to_le_bytes()); - bytes.extend_from_slice(&self.prev_block_id.to_le_bytes()); bytes.extend_from_slice(&self.prev_block_hash); + bytes.extend_from_slice(&self.timestamp.to_le_bytes()); let num_transactions: u32 = self.transactions.len() as u32; bytes.extend_from_slice(&num_transactions.to_le_bytes()); for tx in &self.transactions { + let transaction_bytes = tx.to_bytes(); + let num_transaction_bytes: u32 = transaction_bytes.len() as u32; + + bytes.extend_from_slice(&num_transaction_bytes.to_le_bytes()); bytes.extend_from_slice(&tx.to_bytes()); } bytes @@ -70,37 +89,47 @@ impl HashableBlockData { let mut cursor = Cursor::new(data); let block_id = u64_from_cursor(&mut cursor); - let prev_block_id = u64_from_cursor(&mut cursor); let mut prev_block_hash = [0u8; 32]; cursor.read_exact(&mut prev_block_hash).unwrap(); + let timestamp = u64_from_cursor(&mut cursor); + let num_transactions = u32_from_cursor(&mut cursor) as usize; let mut transactions = Vec::with_capacity(num_transactions); for _ in 0..num_transactions { - let tx = nssa::PublicTransaction::from_cursor(&mut cursor).unwrap(); + let tx_len = u32_from_cursor(&mut cursor) as usize; + let mut tx_bytes = Vec::with_capacity(tx_len); + + for _ in 0..tx_len { + let mut buff = [0; 1]; + cursor.read_exact(&mut buff).unwrap(); + tx_bytes.push(buff[0]); + } + + let tx = EncodedTransaction::from_bytes(tx_bytes); transactions.push(tx); } Self { block_id, - prev_block_id, prev_block_hash, + timestamp, transactions, } } } // TODO: Improve error handling. Remove unwraps. -fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> u32 { +pub fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> u32 { let mut word_buf = [0u8; 4]; cursor.read_exact(&mut word_buf).unwrap(); u32::from_le_bytes(word_buf) } // TODO: Improve error handling. Remove unwraps. -fn u64_from_cursor(cursor: &mut Cursor<&[u8]>) -> u64 { +pub fn u64_from_cursor(cursor: &mut Cursor<&[u8]>) -> u64 { let mut word_buf = [0u8; 8]; cursor.read_exact(&mut word_buf).unwrap(); u64::from_le_bytes(word_buf) diff --git a/common/src/commitment.rs b/common/src/commitment.rs index 0874bc7..a905ad1 100644 --- a/common/src/commitment.rs +++ b/common/src/commitment.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::merkle_tree_public::CommitmentHashType; +use crate::CommitmentHashType; #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)] pub struct Commitment { diff --git a/common/src/execution_input.rs b/common/src/execution_input.rs index c4f2141..08ff598 100644 --- a/common/src/execution_input.rs +++ b/common/src/execution_input.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::merkle_tree_public::TreeHashType; +use crate::TreeHashType; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PublicNativeTokenSend { diff --git a/common/src/lib.rs b/common/src/lib.rs index 3b2ddff..67d628d 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,10 +1,10 @@ -use merkle_tree_public::TreeHashType; +use rs_merkle::Hasher; use serde::Deserialize; +use sha2::{Digest, Sha256, digest::FixedOutput}; pub mod block; pub mod commitment; pub mod execution_input; -pub mod merkle_tree_public; pub mod nullifier; pub mod rpc_primitives; pub mod sequencer_client; @@ -16,6 +16,25 @@ pub mod test_utils; use rpc_primitives::errors::RpcError; +pub type TreeHashType = [u8; 32]; +pub type CommitmentHashType = Vec; + +#[derive(Debug, Clone)] +///Our own hasher. +/// Currently it is SHA256 hasher wrapper. May change in a future. +pub struct OwnHasher {} + +impl Hasher for OwnHasher { + type Hash = TreeHashType; + + fn hash(data: &[u8]) -> TreeHashType { + let mut hasher = Sha256::new(); + + hasher.update(data); + ::from(hasher.finalize_fixed()) + } +} + ///Account id on blockchain pub type AccountId = TreeHashType; diff --git a/common/src/merkle_tree_public/hasher.rs b/common/src/merkle_tree_public/hasher.rs deleted file mode 100644 index ed9443a..0000000 --- a/common/src/merkle_tree_public/hasher.rs +++ /dev/null @@ -1,20 +0,0 @@ -use rs_merkle::Hasher; -use sha2::{Digest, Sha256, digest::FixedOutput}; - -use super::TreeHashType; - -#[derive(Debug, Clone)] -///Our own hasher. -/// Currently it is SHA256 hasher wrapper. May change in a future. -pub struct OwnHasher {} - -impl Hasher for OwnHasher { - type Hash = TreeHashType; - - fn hash(data: &[u8]) -> TreeHashType { - let mut hasher = Sha256::new(); - - hasher.update(data); - ::from(hasher.finalize_fixed()) - } -} diff --git a/common/src/merkle_tree_public/merkle_tree.rs b/common/src/merkle_tree_public/merkle_tree.rs deleted file mode 100644 index 8911344..0000000 --- a/common/src/merkle_tree_public/merkle_tree.rs +++ /dev/null @@ -1,312 +0,0 @@ -use std::{collections::HashMap, fmt, marker::PhantomData}; - -use rs_merkle::{MerkleProof, MerkleTree}; -use serde::{ - Deserialize, Deserializer, Serialize, - de::{SeqAccess, Visitor}, - ser::SerializeSeq, -}; - -use crate::{transaction::Transaction, utxo_commitment::UTXOCommitment}; - -use super::{TreeHashType, hasher::OwnHasher, tree_leav_item::TreeLeavItem}; - -#[derive(Clone)] -pub struct HashStorageMerkleTree { - leaves: HashMap, - hash_to_id_map: HashMap, - tree: MerkleTree, -} - -impl Serialize for HashStorageMerkleTree { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut vector = self.leaves.iter().collect::>(); - vector.sort_by(|a, b| a.0.cmp(b.0)); - - let mut seq = serializer.serialize_seq(Some(self.leaves.len()))?; - for element in vector.iter() { - seq.serialize_element(element.1)?; - } - seq.end() - } -} - -struct HashStorageMerkleTreeDeserializer { - marker: PhantomData HashStorageMerkleTree>, -} - -impl HashStorageMerkleTreeDeserializer { - fn new() -> Self { - HashStorageMerkleTreeDeserializer { - marker: PhantomData, - } - } -} - -impl<'de, Leav: TreeLeavItem + Clone + Deserialize<'de>> Visitor<'de> - for HashStorageMerkleTreeDeserializer -{ - type Value = HashStorageMerkleTree; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("HashStorageMerkleTree key value sequence.") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let mut vector = vec![]; - - loop { - let opt_key = seq.next_element::()?; - if let Some(value) = opt_key { - vector.push(value); - } else { - break; - } - } - - Ok(HashStorageMerkleTree::new(vector)) - } -} - -impl<'de, Leav: TreeLeavItem + Clone + Deserialize<'de>> serde::Deserialize<'de> - for HashStorageMerkleTree -{ - fn deserialize>(deserializer: D) -> Result { - deserializer.deserialize_seq(HashStorageMerkleTreeDeserializer::new()) - } -} - -pub type PublicTransactionMerkleTree = HashStorageMerkleTree; - -pub type UTXOCommitmentsMerkleTree = HashStorageMerkleTree; - -impl HashStorageMerkleTree { - pub fn new(leaves_vec: Vec) -> Self { - let mut leaves_map = HashMap::new(); - let mut hash_to_id_map = HashMap::new(); - - let leaves_hashed: Vec = leaves_vec - .iter() - .enumerate() - .map(|(id, tx)| { - leaves_map.insert(id, tx.clone()); - hash_to_id_map.insert(tx.hash(), id); - tx.hash() - }) - .collect(); - Self { - leaves: leaves_map, - hash_to_id_map, - tree: MerkleTree::from_leaves(&leaves_hashed), - } - } - - pub fn get_tx(&self, hash: TreeHashType) -> Option<&Leav> { - self.hash_to_id_map - .get(&hash) - .and_then(|id| self.leaves.get(id)) - } - - pub fn get_root(&self) -> Option { - self.tree.root() - } - - pub fn get_proof(&self, hash: TreeHashType) -> Option> { - self.hash_to_id_map - .get(&hash) - .map(|id| self.tree.proof(&[*id])) - } - - pub fn get_proof_multiple(&self, hashes: &[TreeHashType]) -> Option> { - let ids_opt: Vec> = hashes - .iter() - .map(|hash| self.hash_to_id_map.get(hash)) - .collect(); - - let is_valid = ids_opt.iter().all(|el| el.is_some()); - - if is_valid { - let ids: Vec = ids_opt.into_iter().map(|el| *el.unwrap()).collect(); - - Some(self.tree.proof(&ids)) - } else { - None - } - } - - pub fn add_tx(&mut self, tx: &Leav) { - let last = self.leaves.len(); - - self.leaves.insert(last, tx.clone()); - self.hash_to_id_map.insert(tx.hash(), last); - - self.tree.insert(tx.hash()); - - self.tree.commit(); - } - - pub fn add_tx_multiple(&mut self, txs: Vec) { - for tx in txs.iter() { - let last = self.leaves.len(); - - self.leaves.insert(last, tx.clone()); - self.hash_to_id_map.insert(tx.hash(), last); - } - - self.tree - .append(&mut txs.iter().map(|tx| tx.hash()).collect()); - - self.tree.commit(); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - // Mock implementation of TreeLeavItem trait for testing - #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] - struct MockTransaction { - pub hash: TreeHashType, - } - - impl TreeLeavItem for MockTransaction { - fn hash(&self) -> TreeHashType { - self.hash - } - } - - fn get_first_32_bytes(s: &str) -> [u8; 32] { - let mut buffer = [0u8; 32]; - let bytes = s.as_bytes(); - let len = std::cmp::min(32, bytes.len()); - - buffer[..len].copy_from_slice(&bytes[..len]); - buffer - } - - #[test] - fn test_new_merkle_tree() { - let tx1 = MockTransaction { - hash: get_first_32_bytes("tx1"), - }; - let tx2 = MockTransaction { - hash: get_first_32_bytes("tx2"), - }; - - let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]); - - assert_eq!(tree.leaves.len(), 2); - assert!(tree.get_root().is_some()); - } - - #[test] - fn test_new_merkle_tree_serialize() { - let tx1 = MockTransaction { - hash: get_first_32_bytes("tx1"), - }; - let tx2 = MockTransaction { - hash: get_first_32_bytes("tx2"), - }; - - let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]); - - let binding = serde_json::to_vec(&tree).unwrap(); - - let obj: HashStorageMerkleTree = serde_json::from_slice(&binding).unwrap(); - - assert_eq!(tree.leaves, obj.leaves); - assert_eq!(tree.hash_to_id_map, obj.hash_to_id_map); - assert_eq!(tree.tree.root(), obj.tree.root()); - } - - #[test] - fn test_get_tx() { - let tx1 = MockTransaction { - hash: get_first_32_bytes("tx1"), - }; - let tx2 = MockTransaction { - hash: get_first_32_bytes("tx2"), - }; - - let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]); - - assert_eq!(tree.get_tx(tx1.hash()), Some(&tx1)); - assert_eq!(tree.get_tx(tx2.hash()), Some(&tx2)); - } - - #[test] - fn test_get_proof() { - let tx1 = MockTransaction { - hash: get_first_32_bytes("tx1"), - }; - let tx2 = MockTransaction { - hash: get_first_32_bytes("tx2"), - }; - - let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]); - - let proof = tree.get_proof(tx1.hash()); - assert!(proof.is_some()); - } - - #[test] - fn test_add_tx() { - let tx1 = MockTransaction { - hash: get_first_32_bytes("tx1"), - }; - let tx2 = MockTransaction { - hash: get_first_32_bytes("tx2"), - }; - - let mut tree = HashStorageMerkleTree::new(vec![tx1.clone()]); - - tree.add_tx(&tx2); - assert_eq!(tree.leaves.len(), 2); - assert_eq!(tree.get_tx(tx2.hash()), Some(&tx2)); - } - - #[test] - fn test_add_tx_multiple() { - let tx1 = MockTransaction { - hash: get_first_32_bytes("tx1"), - }; - let tx2 = MockTransaction { - hash: get_first_32_bytes("tx2"), - }; - let tx3 = MockTransaction { - hash: get_first_32_bytes("tx3"), - }; - - let mut tree = HashStorageMerkleTree::new(vec![tx1.clone()]); - tree.add_tx_multiple(vec![tx2.clone(), tx3.clone()]); - - assert_eq!(tree.leaves.len(), 3); - assert_eq!(tree.get_tx(tx2.hash()), Some(&tx2)); - assert_eq!(tree.get_tx(tx3.hash()), Some(&tx3)); - } - - #[test] - fn test_get_proof_multiple() { - let tx1 = MockTransaction { - hash: get_first_32_bytes("tx1"), - }; - let tx2 = MockTransaction { - hash: get_first_32_bytes("tx2"), - }; - let tx3 = MockTransaction { - hash: get_first_32_bytes("tx3"), - }; - - let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone(), tx3.clone()]); - let proof = tree.get_proof_multiple(&[tx1.hash(), tx2.hash()]); - - assert!(proof.is_some()); - } -} diff --git a/common/src/merkle_tree_public/mod.rs b/common/src/merkle_tree_public/mod.rs deleted file mode 100644 index f1fc89e..0000000 --- a/common/src/merkle_tree_public/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod hasher; -pub mod merkle_tree; -pub mod tree_leav_item; - -pub type TreeHashType = [u8; 32]; -pub type CommitmentHashType = Vec; diff --git a/common/src/merkle_tree_public/tree_leav_item.rs b/common/src/merkle_tree_public/tree_leav_item.rs deleted file mode 100644 index 3b919a8..0000000 --- a/common/src/merkle_tree_public/tree_leav_item.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::{transaction::Transaction, utxo_commitment::UTXOCommitment}; - -use super::TreeHashType; - -pub trait TreeLeavItem { - fn hash(&self) -> TreeHashType; -} - -impl TreeLeavItem for Transaction { - fn hash(&self) -> TreeHashType { - self.body().hash() - } -} - -impl TreeLeavItem for UTXOCommitment { - fn hash(&self) -> TreeHashType { - self.hash - } -} diff --git a/common/src/nullifier.rs b/common/src/nullifier.rs index 2c95741..f277efd 100644 --- a/common/src/nullifier.rs +++ b/common/src/nullifier.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::merkle_tree_public::TreeHashType; +use crate::TreeHashType; //ToDo: Update Nullifier model, when it is clear #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)] diff --git a/common/src/rpc_primitives/requests.rs b/common/src/rpc_primitives/requests.rs index a566ee2..f62d567 100644 --- a/common/src/rpc_primitives/requests.rs +++ b/common/src/rpc_primitives/requests.rs @@ -49,7 +49,7 @@ pub struct GetAccountsNoncesRequest { } #[derive(Serialize, Deserialize, Debug)] -pub struct GetAccountDataRequest { +pub struct GetAccountRequest { pub address: String, } @@ -63,7 +63,7 @@ parse_request!(GetInitialTestnetAccountsRequest); parse_request!(GetAccountBalanceRequest); parse_request!(GetTransactionByHashRequest); parse_request!(GetAccountsNoncesRequest); -parse_request!(GetAccountDataRequest); +parse_request!(GetAccountRequest); #[derive(Serialize, Deserialize, Debug)] pub struct HelloResponse { @@ -112,9 +112,6 @@ pub struct GetTransactionByHashResponse { } #[derive(Serialize, Deserialize, Debug)] -pub struct GetAccountDataResponse { - pub balance: u128, - pub nonce: u128, - pub program_owner: [u32; 8], - pub data: Vec, +pub struct GetAccountResponse { + pub account: nssa::Account, } diff --git a/common/src/sequencer_client/mod.rs b/common/src/sequencer_client/mod.rs index ad66d82..92bcd62 100644 --- a/common/src/sequencer_client/mod.rs +++ b/common/src/sequencer_client/mod.rs @@ -8,10 +8,11 @@ use reqwest::Client; use serde_json::Value; use crate::rpc_primitives::requests::{ - GetAccountsNoncesRequest, GetAccountsNoncesResponse, GetTransactionByHashRequest, - GetTransactionByHashResponse, + GetAccountRequest, GetAccountResponse, GetAccountsNoncesRequest, GetAccountsNoncesResponse, + GetTransactionByHashRequest, GetTransactionByHashResponse, }; use crate::sequencer_client::json::AccountInitialData; +use crate::transaction::{EncodedTransaction, NSSATransaction}; use crate::{SequencerClientError, SequencerRpcError}; pub mod json; @@ -108,6 +109,21 @@ impl SequencerClient { Ok(resp_deser) } + pub async fn get_account( + &self, + address: String, + ) -> Result { + let block_req = GetAccountRequest { address }; + + let req = serde_json::to_value(block_req)?; + + let resp = self.call_method_with_payload("get_account", req).await?; + + let resp_deser = serde_json::from_value(resp)?; + + Ok(resp_deser) + } + ///Get transaction details for `hash`. pub async fn get_transaction_by_hash( &self, @@ -127,10 +143,12 @@ impl SequencerClient { } ///Send transaction to sequencer - pub async fn send_tx( + pub async fn send_tx_public( &self, transaction: nssa::PublicTransaction, ) -> Result { + let transaction = EncodedTransaction::from(NSSATransaction::Public(transaction)); + let tx_req = SendTxRequest { transaction: transaction.to_bytes(), }; diff --git a/common/src/test_utils.rs b/common/src/test_utils.rs index b956cd7..9d68db8 100644 --- a/common/src/test_utils.rs +++ b/common/src/test_utils.rs @@ -1,6 +1,13 @@ -use nssa; +use crate::{ + block::{Block, HashableBlockData}, + transaction::{EncodedTransaction, NSSATransaction}, +}; -use crate::block::{Block, HashableBlockData}; +//Helpers + +pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey { + nssa::PrivateKey::try_new([37; 32]).unwrap() +} //Dummy producers @@ -10,23 +17,23 @@ use crate::block::{Block, HashableBlockData}; /// /// `prev_hash` - hash of previous block, provide None for genesis /// -/// `transactions` - vector of `Transaction` objects +/// `transactions` - vector of `EncodedTransaction` objects pub fn produce_dummy_block( id: u64, prev_hash: Option<[u8; 32]>, - transactions: Vec, + transactions: Vec, ) -> Block { let block_data = HashableBlockData { block_id: id, - prev_block_id: id.saturating_sub(1), prev_block_hash: prev_hash.unwrap_or_default(), + timestamp: id * 100, transactions, }; - block_data.into() + block_data.into_block(&sequencer_sign_key_for_testing()) } -pub fn produce_dummy_empty_transaction() -> nssa::PublicTransaction { +pub fn produce_dummy_empty_transaction() -> EncodedTransaction { let program_id = nssa::program::Program::authenticated_transfer_program().id(); let addresses = vec![]; let nonces = vec![]; @@ -36,7 +43,10 @@ pub fn produce_dummy_empty_transaction() -> nssa::PublicTransaction { .unwrap(); let private_key = nssa::PrivateKey::try_new([1; 32]).unwrap(); let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&private_key]); - nssa::PublicTransaction::new(message, witness_set) + + let nssa_tx = nssa::PublicTransaction::new(message, witness_set); + + EncodedTransaction::from(NSSATransaction::Public(nssa_tx)) } pub fn create_transaction_native_token_transfer( @@ -45,7 +55,7 @@ pub fn create_transaction_native_token_transfer( to: [u8; 32], balance_to_move: u128, signing_key: nssa::PrivateKey, -) -> nssa::PublicTransaction { +) -> EncodedTransaction { let addresses = vec![nssa::Address::new(from), nssa::Address::new(to)]; let nonces = vec![nonce]; let program_id = nssa::program::Program::authenticated_transfer_program().id(); @@ -53,5 +63,8 @@ pub fn create_transaction_native_token_transfer( nssa::public_transaction::Message::try_new(program_id, addresses, nonces, balance_to_move) .unwrap(); let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&signing_key]); - nssa::PublicTransaction::new(message, witness_set) + + let nssa_tx = nssa::PublicTransaction::new(message, witness_set); + + EncodedTransaction::from(NSSATransaction::Public(nssa_tx)) } diff --git a/common/src/transaction.rs b/common/src/transaction.rs index 1d0ccec..d196687 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -1,22 +1,34 @@ -use k256::ecdsa::{ - Signature, SigningKey, VerifyingKey, - signature::{Signer, Verifier}, -}; +use k256::ecdsa::{Signature, SigningKey, VerifyingKey}; use log::info; -use secp256k1_zkp::{PedersenCommitment, Tweak}; use serde::{Deserialize, Serialize}; use sha2::{Digest, digest::FixedOutput}; -use crate::merkle_tree_public::TreeHashType; - use elliptic_curve::{ consts::{B0, B1}, generic_array::GenericArray, }; use sha2::digest::typenum::{UInt, UTerm}; -use crate::TransactionSignatureError; +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum NSSATransaction { + Public(nssa::PublicTransaction), + PrivacyPreserving(nssa::PrivacyPreservingTransaction), +} + +impl From for NSSATransaction { + fn from(value: nssa::PublicTransaction) -> Self { + Self::Public(value) + } +} + +impl From for NSSATransaction { + fn from(value: nssa::PrivacyPreservingTransaction) -> Self { + Self::PrivacyPreserving(value) + } +} + +use crate::TreeHashType; pub type CipherText = Vec; pub type Nonce = GenericArray, B1>, B0>, B0>>; @@ -25,39 +37,45 @@ pub type Tag = u8; #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] pub enum TxKind { Public, - Private, - Shielded, - Deshielded, + PrivacyPreserving, } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] ///General transaction object -pub struct TransactionBody { +pub struct EncodedTransaction { pub tx_kind: TxKind, - ///Tx input data (public part) - pub execution_input: Vec, - ///Tx output data (public_part) - pub execution_output: Vec, - ///Tx input utxo commitments - pub utxo_commitments_spent_hashes: Vec, - ///Tx output utxo commitments - pub utxo_commitments_created_hashes: Vec, - ///Tx output nullifiers - pub nullifier_created_hashes: Vec, - ///Execution proof (private part) - pub execution_proof_private: String, ///Encoded blobs of data - pub encoded_data: Vec<(CipherText, Vec, Tag)>, - ///Transaction senders ephemeral pub key - pub ephemeral_pub_key: Vec, - ///Public (Pedersen) commitment - pub commitment: Vec, - ///tweak - pub tweak: Tweak, - ///secret_r - pub secret_r: [u8; 32], - ///Hex-encoded address of a smart contract account called - pub sc_addr: String, + pub encoded_transaction_data: Vec, +} + +impl From for EncodedTransaction { + fn from(value: NSSATransaction) -> Self { + match value { + NSSATransaction::Public(tx) => Self { + tx_kind: TxKind::Public, + encoded_transaction_data: tx.to_bytes(), + }, + NSSATransaction::PrivacyPreserving(tx) => Self { + tx_kind: TxKind::PrivacyPreserving, + encoded_transaction_data: tx.to_bytes(), + }, + } + } +} + +impl TryFrom<&EncodedTransaction> for NSSATransaction { + type Error = nssa::error::NssaError; + + fn try_from(value: &EncodedTransaction) -> Result { + match value.tx_kind { + TxKind::Public => nssa::PublicTransaction::from_bytes(&value.encoded_transaction_data) + .map(|tx| tx.into()), + TxKind::PrivacyPreserving => { + nssa::PrivacyPreservingTransaction::from_bytes(&value.encoded_transaction_data) + .map(|tx| tx.into()) + } + } + } } #[derive(Debug, Serialize, Deserialize)] @@ -153,7 +171,7 @@ impl ActionData { } } -impl TransactionBody { +impl EncodedTransaction { /// Computes and returns the SHA-256 hash of the JSON-serialized representation of `self`. pub fn hash(&self) -> TreeHashType { let bytes_to_hash = self.to_bytes(); @@ -162,180 +180,48 @@ impl TransactionBody { TreeHashType::from(hasher.finalize_fixed()) } - fn to_bytes(&self) -> Vec { + pub fn to_bytes(&self) -> Vec { // TODO: Remove `unwrap` by implementing a `to_bytes` method // that deterministically encodes all transaction fields to bytes // and guarantees serialization will succeed. serde_json::to_vec(&self).unwrap() } + pub fn from_bytes(bytes: Vec) -> Self { + serde_json::from_slice(&bytes).unwrap() + } + pub fn log(&self) { info!("Transaction hash is {:?}", hex::encode(self.hash())); info!("Transaction tx_kind is {:?}", self.tx_kind); - info!("Transaction execution_input is {:?}", { - if let Ok(action) = serde_json::from_slice::(&self.execution_input) { - action.into_hexed_print() - } else { - "".to_string() - } - }); - info!("Transaction execution_output is {:?}", { - if let Ok(action) = serde_json::from_slice::(&self.execution_output) { - action.into_hexed_print() - } else { - "".to_string() - } - }); - info!( - "Transaction utxo_commitments_spent_hashes is {:?}", - self.utxo_commitments_spent_hashes - .iter() - .map(|val| hex::encode(*val)) - .collect::>() - ); - info!( - "Transaction utxo_commitments_created_hashes is {:?}", - self.utxo_commitments_created_hashes - .iter() - .map(|val| hex::encode(*val)) - .collect::>() - ); - info!( - "Transaction nullifier_created_hashes is {:?}", - self.nullifier_created_hashes - .iter() - .map(|val| hex::encode(*val)) - .collect::>() - ); - info!( - "Transaction encoded_data is {:?}", - self.encoded_data - .iter() - .map(|val| (hex::encode(val.0.clone()), hex::encode(val.1.clone()))) - .collect::>() - ); - info!( - "Transaction ephemeral_pub_key is {:?}", - hex::encode(self.ephemeral_pub_key.clone()) - ); } } -type TransactionHash = [u8; 32]; pub type TransactionSignature = Signature; pub type SignaturePublicKey = VerifyingKey; pub type SignaturePrivateKey = SigningKey; -/// A container for a transaction body with a signature. -/// Meant to be sent through the network to the sequencer -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -pub struct Transaction { - body: TransactionBody, - pub signature: TransactionSignature, - pub public_key: VerifyingKey, -} - -impl Transaction { - /// Returns a new transaction signed with the provided `private_key`. - /// The signature is generated over the hash of the body as computed by `body.hash()` - pub fn new(body: TransactionBody, private_key: SigningKey) -> Transaction { - let signature: TransactionSignature = private_key.sign(&body.to_bytes()); - let public_key = VerifyingKey::from(&private_key); - Self { - body, - signature, - public_key, - } - } - - /// Converts the transaction into an `AuthenticatedTransaction` by verifying its signature. - /// Returns an error if the signature verification fails. - pub fn into_authenticated(self) -> Result { - let hash = self.body.hash(); - - self.public_key - .verify(&self.body.to_bytes(), &self.signature) - .map_err(|_| TransactionSignatureError::InvalidSignature)?; - - Ok(AuthenticatedTransaction { - hash, - transaction: self, - }) - } - - /// Returns the body of the transaction - pub fn body(&self) -> &TransactionBody { - &self.body - } -} - -/// A transaction with a valid signature over the hash of its body. -/// Can only be constructed from an `Transaction` -/// if the signature is valid -#[derive(Debug, Clone)] -pub struct AuthenticatedTransaction { - hash: TransactionHash, - transaction: Transaction, -} - -impl AuthenticatedTransaction { - /// Returns the underlying transaction - pub fn transaction(&self) -> &Transaction { - &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 - } -} - #[cfg(test)] mod tests { - use super::*; - use k256::{FieldBytes, ecdsa::signature::Signer}; - use secp256k1_zkp::{Tweak, constants::SECRET_KEY_SIZE}; use sha2::{Digest, digest::FixedOutput}; use crate::{ - merkle_tree_public::TreeHashType, - transaction::{Transaction, TransactionBody, TxKind}, + TreeHashType, + transaction::{EncodedTransaction, TxKind}, }; - fn test_transaction_body() -> TransactionBody { - TransactionBody { + fn test_transaction_body() -> EncodedTransaction { + EncodedTransaction { tx_kind: TxKind::Public, - execution_input: vec![1, 2, 3, 4], - execution_output: vec![5, 6, 7, 8], - utxo_commitments_spent_hashes: vec![[9; 32], [10; 32], [11; 32], [12; 32]], - utxo_commitments_created_hashes: vec![[13; 32]], - nullifier_created_hashes: vec![[0; 32], [1; 32], [2; 32], [3; 32]], - execution_proof_private: "loremipsum".to_string(), - encoded_data: vec![(vec![255, 255, 255], vec![254, 254, 254], 1)], - ephemeral_pub_key: vec![5; 32], - commitment: vec![], - tweak: Tweak::from_slice(&[7; SECRET_KEY_SIZE]).unwrap(), - secret_r: [8; 32], - sc_addr: "someAddress".to_string(), + encoded_transaction_data: vec![1, 2, 3, 4], } } - fn test_transaction() -> Transaction { - let body = test_transaction_body(); - let key_bytes = FieldBytes::from_slice(&[37; 32]); - let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); - Transaction::new(body, private_key) - } - #[test] fn test_transaction_hash_is_sha256_of_json_bytes() { let body = test_transaction_body(); let expected_hash = { - let data = serde_json::to_vec(&body).unwrap(); + let data = body.to_bytes(); let mut hasher = sha2::Sha256::new(); hasher.update(&data); TreeHashType::from(hasher.finalize_fixed()) @@ -347,83 +233,12 @@ mod tests { } #[test] - fn test_transaction_constructor() { + fn test_to_bytes_from_bytes() { let body = test_transaction_body(); - let key_bytes = FieldBytes::from_slice(&[37; 32]); - let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); - let transaction = Transaction::new(body.clone(), private_key.clone()); - assert_eq!( - transaction.public_key, - SignaturePublicKey::from(&private_key) - ); - assert_eq!(transaction.body, body); - } - #[test] - fn test_transaction_body_getter() { - let body = test_transaction_body(); - let key_bytes = FieldBytes::from_slice(&[37; 32]); - let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); - let transaction = Transaction::new(body.clone(), private_key.clone()); - assert_eq!(transaction.body(), &body); - } + let body_bytes = body.to_bytes(); + let body_new = EncodedTransaction::from_bytes(body_bytes); - #[test] - fn test_into_authenticated_succeeds_for_valid_signature() { - let transaction = test_transaction(); - let authenticated_tx = transaction.clone().into_authenticated().unwrap(); - - let signature = authenticated_tx.transaction().signature; - let hash = authenticated_tx.hash(); - - assert_eq!(authenticated_tx.transaction(), &transaction); - assert_eq!(hash, &transaction.body.hash()); - assert!( - authenticated_tx - .transaction() - .public_key - .verify(&transaction.body.to_bytes(), &signature) - .is_ok() - ); - } - - #[test] - fn test_into_authenticated_fails_for_invalid_signature() { - let body = test_transaction_body(); - let key_bytes = FieldBytes::from_slice(&[37; 32]); - let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); - let transaction = { - let mut this = Transaction::new(body, private_key.clone()); - // Modify the signature to make it invalid - // We do this by changing it to the signature of something else - this.signature = private_key.sign(b"deadbeef"); - this - }; - - matches!( - transaction.into_authenticated(), - Err(TransactionSignatureError::InvalidSignature) - ); - } - - #[test] - fn test_authenticated_transaction_getter() { - let transaction = test_transaction(); - let authenticated_tx = transaction.clone().into_authenticated().unwrap(); - assert_eq!(authenticated_tx.transaction(), &transaction); - } - - #[test] - fn test_authenticated_transaction_hash_getter() { - let transaction = test_transaction(); - 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); + assert_eq!(body, body_new); } } diff --git a/common/src/utxo_commitment.rs b/common/src/utxo_commitment.rs index 75c0543..1b42421 100644 --- a/common/src/utxo_commitment.rs +++ b/common/src/utxo_commitment.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::merkle_tree_public::TreeHashType; +use crate::TreeHashType; //ToDo: Update UTXO Commitment model, when it is clear #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 16e1b2e..a01935f 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -29,3 +29,6 @@ path = "../wallet" [dependencies.common] path = "../common" + +[dependencies.nssa] +path = "../nssa" diff --git a/integration_tests/configs/debug/sequencer/sequencer_config.json b/integration_tests/configs/debug/sequencer/sequencer_config.json index 37bfd0a..3317a11 100644 --- a/integration_tests/configs/debug/sequencer/sequencer_config.json +++ b/integration_tests/configs/debug/sequencer/sequencer_config.json @@ -15,5 +15,7 @@ "addr": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766", "balance": 20000 } - ] + ], + "signing_key": [37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, +37, 37, 37, 37, 37, 37] } diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 9b7b970..5c14ea2 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -5,6 +5,7 @@ use anyhow::Result; use clap::Parser; use common::sequencer_client::SequencerClient; use log::{info, warn}; +use nssa::program::Program; use sequencer_core::config::SequencerConfig; use sequencer_runner::startup_sequencer; use tempfile::TempDir; @@ -272,6 +273,36 @@ pub async fn test_success_two_transactions() { info!("Second TX Success!"); } +pub async fn test_get_account_wallet_command() { + let command = Command::GetAccount { + addr: ACC_SENDER.to_string(), + }; + + let wallet_config = fetch_config().unwrap(); + + let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); + + wallet::execute_subcommand(command).await.unwrap(); + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct account"); + let account = seq_client + .get_account(ACC_SENDER.to_string()) + .await + .unwrap() + .account; + + assert_eq!( + account.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!(account.balance, 10000); + assert!(account.data.is_empty()); + assert_eq!(account.nonce, 0); +} + macro_rules! test_cleanup_wrap { ($home_dir:ident, $test_func:ident) => {{ let res = pre_test($home_dir.clone()).await.unwrap(); @@ -304,6 +335,9 @@ pub async fn main_tests_runner() -> Result<()> { "test_failure" => { test_cleanup_wrap!(home_dir, test_failure); } + "test_get_account_wallet_command" => { + test_cleanup_wrap!(home_dir, test_get_account_wallet_command); + } "test_success_two_transactions" => { test_cleanup_wrap!(home_dir, test_success_two_transactions); } @@ -312,6 +346,7 @@ pub async fn main_tests_runner() -> Result<()> { test_cleanup_wrap!(home_dir, test_success); test_cleanup_wrap!(home_dir, test_failure); test_cleanup_wrap!(home_dir, test_success_two_transactions); + test_cleanup_wrap!(home_dir, test_get_account_wallet_command); } _ => { anyhow::bail!("Unknown test name"); diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index c6b2948..87e4540 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -1,4 +1,4 @@ -use common::merkle_tree_public::TreeHashType; +use common::TreeHashType; use elliptic_curve::PrimeField; use k256::{AffinePoint, FieldBytes, Scalar}; use rand::{RngCore, rngs::OsRng}; diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 398bfc6..158a660 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -27,11 +27,13 @@ fn main() { // Check that the program is well behaved. // See the # Programs section for the definition of the `validate_execution` method. - validate_execution(&pre_states, &post_states, program_id); + if !validate_execution(&pre_states, &post_states, program_id) { + panic!("Bad behaved program"); + } let n_accounts = pre_states.len(); if visibility_mask.len() != n_accounts { - panic!(); + panic!("Invalid visibility mask length"); } // These lists will be the public outputs of this circuit @@ -54,7 +56,9 @@ fn main() { public_pre_states.push(pre_states[i].clone()); let mut post = post_states[i].clone(); - post.nonce += 1; + if pre_states[i].is_authorized { + post.nonce += 1; + } if post.program_owner == DEFAULT_PROGRAM_ID { // Claim account post.program_owner = program_id; @@ -135,7 +139,7 @@ fn main() { } if private_keys_iter.next().is_some() { - panic!("Too many private accounts keys."); + panic!("Too many private account keys."); } if private_auth_iter.next().is_some() { diff --git a/nssa/src/encoding/mod.rs b/nssa/src/encoding/mod.rs new file mode 100644 index 0000000..5ff45e2 --- /dev/null +++ b/nssa/src/encoding/mod.rs @@ -0,0 +1,2 @@ +pub mod privacy_preserving_transaction; +pub mod public_transaction; diff --git a/nssa/src/privacy_preserving_transaction/encoding.rs b/nssa/src/encoding/privacy_preserving_transaction.rs similarity index 67% rename from nssa/src/privacy_preserving_transaction/encoding.rs rename to nssa/src/encoding/privacy_preserving_transaction.rs index b5d4950..2e5ea14 100644 --- a/nssa/src/privacy_preserving_transaction/encoding.rs +++ b/nssa/src/encoding/privacy_preserving_transaction.rs @@ -7,16 +7,20 @@ use nssa_core::{ }; use crate::{ - Address, error::NssaError, privacy_preserving_transaction::message::EncryptedAccountData, + Address, PrivacyPreservingTransaction, PublicKey, Signature, + error::NssaError, + privacy_preserving_transaction::{ + circuit::Proof, + message::{EncryptedAccountData, Message}, + witness_set::WitnessSet, + }, }; -use super::message::Message; - const MESSAGE_ENCODING_PREFIX_LEN: usize = 22; const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"\x01/NSSA/v0.1/TxMessage/"; impl EncryptedAccountData { - pub(crate) fn to_bytes(&self) -> Vec { + pub fn to_bytes(&self) -> Vec { let mut bytes = self.ciphertext.to_bytes(); bytes.extend_from_slice(&self.epk.to_bytes()); bytes.push(self.view_tag); @@ -168,3 +172,86 @@ impl Message { }) } } + +impl WitnessSet { + pub(crate) fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + let size = self.signatures_and_public_keys().len() as u32; + bytes.extend_from_slice(&size.to_le_bytes()); + for (signature, public_key) in self.signatures_and_public_keys() { + bytes.extend_from_slice(signature.to_bytes()); + bytes.extend_from_slice(public_key.to_bytes()); + } + bytes.extend_from_slice(&self.proof.to_bytes()); + bytes + } + + pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + let num_signatures: u32 = { + let mut buf = [0u8; 4]; + cursor.read_exact(&mut buf)?; + u32::from_le_bytes(buf) + }; + let mut signatures_and_public_keys = Vec::with_capacity(num_signatures as usize); + for _i in 0..num_signatures { + let signature = Signature::from_cursor(cursor)?; + let public_key = PublicKey::from_cursor(cursor)?; + signatures_and_public_keys.push((signature, public_key)) + } + let proof = Proof::from_cursor(cursor)?; + Ok(Self { + signatures_and_public_keys, + proof, + }) + } +} + +impl PrivacyPreservingTransaction { + pub fn to_bytes(&self) -> Vec { + let mut bytes = self.message().to_bytes(); + bytes.extend_from_slice(&self.witness_set().to_bytes()); + bytes + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = Cursor::new(bytes); + Self::from_cursor(&mut cursor) + } + + pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + let message = Message::from_cursor(cursor)?; + let witness_set = WitnessSet::from_cursor(cursor)?; + Ok(PrivacyPreservingTransaction::new(message, witness_set)) + } +} + +impl Proof { + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + let proof_len = self.0.len() as u32; + bytes.extend_from_slice(&proof_len.to_le_bytes()); + bytes.extend_from_slice(&self.0); + bytes + } + + pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + let proof_len = u32_from_cursor(cursor) as usize; + let mut proof = Vec::with_capacity(proof_len); + + for _ in 0..proof_len { + let mut one_byte_buf = [0u8]; + + cursor.read_exact(&mut one_byte_buf)?; + + proof.push(one_byte_buf[0]); + } + Ok(Self(proof)) + } +} + +// TODO: Improve error handling. Remove unwraps. +pub fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> u32 { + let mut word_buf = [0u8; 4]; + cursor.read_exact(&mut word_buf).unwrap(); + u32::from_le_bytes(word_buf) +} diff --git a/nssa/src/public_transaction/encoding.rs b/nssa/src/encoding/public_transaction.rs similarity index 100% rename from nssa/src/public_transaction/encoding.rs rename to nssa/src/encoding/public_transaction.rs diff --git a/nssa/src/error.rs b/nssa/src/error.rs index 1d6d6ad..0e85789 100644 --- a/nssa/src/error.rs +++ b/nssa/src/error.rs @@ -45,4 +45,7 @@ pub enum NssaError { #[error("Invalid privacy preserving execution circuit proof")] InvalidPrivacyPreservingProof, + + #[error("Circuit proving error")] + CircuitProvingError(String), } diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index ed88047..afa4980 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -1,4 +1,5 @@ pub mod address; +pub mod encoding; pub mod error; mod merkle_tree; mod privacy_preserving_transaction; @@ -8,6 +9,7 @@ mod signature; mod state; pub use address::Address; +pub use nssa_core::account::Account; pub use privacy_preserving_transaction::{ PrivacyPreservingTransaction, circuit::execute_and_prove, }; diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index d8e5701..8575bfe 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -12,15 +12,7 @@ use program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT /// Proof of the privacy preserving execution circuit #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Proof(Vec); - -impl Proof { - pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool { - let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap(); - let receipt = Receipt::new(inner, circuit_output.to_bytes()); - receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok() - } -} +pub struct Proof(pub(crate) Vec); /// Generates a proof of the execution of a NSSA program inside the privacy preserving execution /// circuit @@ -55,7 +47,9 @@ pub fn execute_and_prove( env_builder.write(&circuit_input).unwrap(); let env = env_builder.build().unwrap(); let prover = default_prover(); - let prove_info = prover.prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF).unwrap(); + let prove_info = prover + .prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF) + .map_err(|e| NssaError::CircuitProvingError(e.to_string()))?; let proof = Proof(borsh::to_vec(&prove_info.receipt.inner)?); @@ -86,6 +80,14 @@ fn execute_and_prove_program( .receipt) } +impl Proof { + pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool { + let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap(); + let receipt = Receipt::new(inner, circuit_output.to_bytes()); + receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok() + } +} + #[cfg(test)] mod tests { use nssa_core::{ @@ -110,6 +112,7 @@ mod tests { let program = Program::authenticated_transfer_program(); let sender = AccountWithMetadata::new( Account { + program_owner: program.id(), balance: 100, ..Account::default() }, @@ -177,6 +180,7 @@ mod tests { #[test] fn prove_privacy_preserving_execution_circuit_fully_private() { + let program = Program::authenticated_transfer_program(); let sender_keys = test_private_account_keys_1(); let recipient_keys = test_private_account_keys_2(); @@ -184,7 +188,8 @@ mod tests { Account { balance: 100, nonce: 0xdeadbeef, - ..Account::default() + program_owner: program.id(), + data: vec![], }, true, AccountId::from(&sender_keys.npk()), diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index 54fc94b..c74c077 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -1,4 +1,3 @@ -mod encoding; pub mod message; pub mod transaction; pub mod witness_set; diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 6992b2f..0a8e124 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -170,3 +170,42 @@ fn n_unique(data: &[T]) -> usize { let set: HashSet<&T> = data.iter().collect(); set.len() } + +#[cfg(test)] +mod tests { + use crate::{ + Address, PrivacyPreservingTransaction, PrivateKey, PublicKey, + privacy_preserving_transaction::{ + circuit::Proof, message::tests::message_for_tests, witness_set::WitnessSet, + }, + }; + + fn keys_for_tests() -> (PrivateKey, PrivateKey, Address, Address) { + let key1 = PrivateKey::try_new([1; 32]).unwrap(); + let key2 = PrivateKey::try_new([2; 32]).unwrap(); + let addr1 = Address::from(&PublicKey::new_from_private_key(&key1)); + let addr2 = Address::from(&PublicKey::new_from_private_key(&key2)); + (key1, key2, addr1, addr2) + } + + fn proof_for_tests() -> Proof { + Proof(vec![1, 2, 3, 4, 5]) + } + + fn transaction_for_tests() -> PrivacyPreservingTransaction { + let (key1, key2, _, _) = keys_for_tests(); + + let message = message_for_tests(); + + let witness_set = WitnessSet::for_message(&message, proof_for_tests(), &[&key1, &key2]); + PrivacyPreservingTransaction::new(message, witness_set) + } + + #[test] + fn test_privacy_preserving_transaction_encoding_bytes_roundtrip() { + let tx = transaction_for_tests(); + let bytes = tx.to_bytes(); + let tx_from_bytes = PrivacyPreservingTransaction::from_bytes(&bytes).unwrap(); + assert_eq!(tx, tx_from_bytes); + } +} diff --git a/nssa/src/privacy_preserving_transaction/witness_set.rs b/nssa/src/privacy_preserving_transaction/witness_set.rs index fe897ce..9fc587e 100644 --- a/nssa/src/privacy_preserving_transaction/witness_set.rs +++ b/nssa/src/privacy_preserving_transaction/witness_set.rs @@ -5,8 +5,8 @@ use crate::{ #[derive(Debug, Clone, PartialEq, Eq)] pub struct WitnessSet { - pub(super) signatures_and_public_keys: Vec<(Signature, PublicKey)>, - pub(super) proof: Proof, + pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>, + pub(crate) proof: Proof, } impl WitnessSet { diff --git a/nssa/src/public_transaction/mod.rs b/nssa/src/public_transaction/mod.rs index 9ae24cf..278e9df 100644 --- a/nssa/src/public_transaction/mod.rs +++ b/nssa/src/public_transaction/mod.rs @@ -1,4 +1,3 @@ -mod encoding; mod message; mod transaction; mod witness_set; diff --git a/nssa/src/public_transaction/witness_set.rs b/nssa/src/public_transaction/witness_set.rs index f0e5c6b..e5095ba 100644 --- a/nssa/src/public_transaction/witness_set.rs +++ b/nssa/src/public_transaction/witness_set.rs @@ -2,7 +2,7 @@ use crate::{PrivateKey, PublicKey, Signature, public_transaction::Message}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct WitnessSet { - pub(super) signatures_and_public_keys: Vec<(Signature, PublicKey)>, + pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>, } impl WitnessSet { diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 4b8b25b..cffb4b8 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -145,12 +145,6 @@ impl V01State { *current_account = post; } - // // 5. Increment nonces - for address in tx.signer_addresses() { - let current_account = self.get_account_by_address_mut(address); - current_account.nonce += 1; - } - Ok(()) } @@ -219,6 +213,7 @@ pub mod tests { use crate::{ Address, PublicKey, PublicTransaction, V01State, error::NssaError, + execute_and_prove, privacy_preserving_transaction::{ PrivacyPreservingTransaction, circuit, message::Message, witness_set::WitnessSet, }, @@ -229,7 +224,7 @@ pub mod tests { use nssa_core::{ Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, - account::{Account, AccountWithMetadata, Nonce}, + account::{Account, AccountId, AccountWithMetadata, Nonce}, encryption::{EphemeralPublicKey, IncomingViewingPublicKey}, }; @@ -934,6 +929,13 @@ pub mod tests { &state, ); + let expected_sender_post = { + let mut this = state.get_account_by_address(&sender_keys.address()); + this.balance -= balance_to_move; + this.nonce += 1; + this + }; + let [expected_new_commitment] = tx.message().new_commitments.clone().try_into().unwrap(); assert!(!state.private_state.0.contains(&expected_new_commitment)); @@ -941,6 +943,8 @@ pub mod tests { .transition_from_privacy_preserving_transaction(&tx) .unwrap(); + let sender_post = state.get_account_by_address(&sender_keys.address()); + assert_eq!(sender_post, expected_sender_post); assert!(state.private_state.0.contains(&expected_new_commitment)); assert_eq!( @@ -1033,6 +1037,12 @@ pub mod tests { let balance_to_move = 37; + let expected_recipient_post = { + let mut this = state.get_account_by_address(&recipient_keys.address()); + this.balance += balance_to_move; + this + }; + let tx = deshielded_balance_transfer_for_tests( &sender_keys, &sender_private_account, @@ -1063,6 +1073,8 @@ pub mod tests { .transition_from_privacy_preserving_transaction(&tx) .unwrap(); + let recipient_post = state.get_account_by_address(&recipient_keys.address()); + assert_eq!(recipient_post, expected_recipient_post); assert!(state.private_state.0.contains(&sender_pre_commitment)); assert!(state.private_state.0.contains(&expected_new_commitment)); assert!(state.private_state.1.contains(&expected_new_nullifier)); @@ -1073,4 +1085,823 @@ pub mod tests { recipient_initial_balance + balance_to_move ); } + + #[test] + fn test_burner_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::burner(); + let public_account = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(10u128).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_minter_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::minter(); + let public_account = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(10u128).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_nonce_changer_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::nonce_changer_program(); + let public_account = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(()).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_data_changer_program_should_fail_for_non_owned_account_in_privacy_preserving_circuit() { + let program = Program::data_changer(); + let public_account = AccountWithMetadata::new( + Account { + program_owner: [0, 1, 2, 3, 4, 5, 6, 7], + balance: 0, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(()).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_extra_output_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::extra_output_program(); + let public_account = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(()).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_missing_output_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::missing_output_program(); + let public_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let public_account_2 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([1; 32]), + ); + + let result = execute_and_prove( + &[public_account_1, public_account_2], + &Program::serialize_instruction(()).unwrap(), + &[0, 0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_program_owner_changer_should_fail_in_privacy_preserving_circuit() { + let program = Program::program_owner_changer(); + let public_account = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(()).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_transfer_from_non_owned_account_should_fail_in_privacy_preserving_circuit() { + let program = Program::simple_balance_transfer(); + let public_account_1 = AccountWithMetadata::new( + Account { + program_owner: [0, 1, 2, 3, 4, 5, 6, 7], + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let public_account_2 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([1; 32]), + ); + + let result = execute_and_prove( + &[public_account_1, public_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[0, 0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_fails_if_visibility_masks_have_incorrect_lenght() { + let program = Program::simple_balance_transfer(); + let public_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let public_account_2 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([1; 32]), + ); + + // Setting only one visibility mask for a circuit execution with two pre_state accounts. + let visibility_mask = [0]; + let result = execute_and_prove( + &[public_account_1, public_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &visibility_mask, + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_fails_if_insufficient_nonces_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); + + // Setting only one nonce for an execution with two private accounts. + let private_account_nonces = [0xdeadbeef1]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &private_account_nonces, + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_fails_if_insufficient_keys_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); + + // Setting only one key for an execution with two private accounts. + let private_account_keys = [( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + )]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &private_account_keys, + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_fails_if_insufficient_auth_keys_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); + + // Setting no auth key for an execution with one non default private accounts. + let private_account_auth = []; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &private_account_auth, + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_fails_if_invalid_auth_keys_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); + + let private_account_keys = [ + // First private account is the sender + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + // Second private account is the recipient + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ]; + let private_account_auth = [ + // Setting the recipient key to authorize the sender. + // This should be set to the sender private account in + // a normal circumstance. The recipient can't authorize this. + (recipient_keys.nsk, (0, vec![])), + ]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &private_account_keys, + &private_account_auth, + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_if_new_private_account_with_non_default_balance_is_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let private_account_2 = AccountWithMetadata::new( + Account { + // Non default balance + balance: 1, + ..Account::default() + }, + false, + AccountId::new([1; 32]), + ); + + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_provided() + { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let private_account_2 = AccountWithMetadata::new( + Account { + // Non default program_owner + program_owner: [0, 1, 2, 3, 4, 5, 6, 7], + ..Account::default() + }, + false, + AccountId::new([1; 32]), + ); + + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_if_new_private_account_with_non_default_data_is_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let private_account_2 = AccountWithMetadata::new( + Account { + // Non default data + data: b"hola mundo".to_vec(), + ..Account::default() + }, + false, + AccountId::new([1; 32]), + ); + + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let private_account_2 = AccountWithMetadata::new( + Account { + // Non default nonce + nonce: 0xdeadbeef, + ..Account::default() + }, + false, + AccountId::new([1; 32]), + ); + + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_if_new_private_account_is_provided_with_default_values_but_marked_as_authorized() + { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let private_account_2 = AccountWithMetadata::new( + Account::default(), + // This should be set to false in normal circumstances + true, + AccountId::new([1; 32]), + ); + + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_with_invalid_visibility_mask_value() { + let program = Program::simple_balance_transfer(); + let public_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let public_account_2 = + AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); + + let visibility_mask = [0, 3]; + let result = execute_and_prove( + &[public_account_1, public_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &visibility_mask, + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_with_too_many_nonces() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); + + // Setting three new private account nonces for a circuit execution with only two private + // accounts. + let private_account_nonces = [0xdeadbeef1, 0xdeadbeef2, 0xdeadbeef3]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &private_account_nonces, + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_with_too_many_private_account_keys() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); + + // Setting three private account keys for a circuit execution with only two private + // accounts. + let private_account_keys = [ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ( + sender_keys.npk(), + SharedSecretKey::new(&[57; 32], &sender_keys.ivk()), + ), + ]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &private_account_keys, + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_with_too_many_private_account_auth_keys() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); + + // Setting two private account keys for a circuit execution with only one non default + // private account (visibility mask equal to 1 means that auth keys are expected). + let visibility_mask = [1, 2]; + let private_account_auth = [ + (sender_keys.nsk, (0, vec![])), + (recipient_keys.nsk, (1, vec![])), + ]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &visibility_mask, + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &private_account_auth, + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } } diff --git a/nssa/test_program_methods/guest/src/bin/missing_output.rs b/nssa/test_program_methods/guest/src/bin/missing_output.rs index 2174266..7b6016c 100644 --- a/nssa/test_program_methods/guest/src/bin/missing_output.rs +++ b/nssa/test_program_methods/guest/src/bin/missing_output.rs @@ -5,12 +5,12 @@ type Instruction = (); fn main() { let ProgramInput { pre_states, .. } = read_nssa_inputs::(); - let [pre1, _] = match pre_states.try_into() { + let [pre1, pre2] = match pre_states.try_into() { Ok(array) => array, Err(_) => return, }; let account_pre1 = pre1.account.clone(); - write_nssa_outputs(vec![pre1], vec![account_pre1]); + write_nssa_outputs(vec![pre1, pre2], vec![account_pre1]); } diff --git a/sequencer_core/Cargo.toml b/sequencer_core/Cargo.toml index 1c56f11..40e420a 100644 --- a/sequencer_core/Cargo.toml +++ b/sequencer_core/Cargo.toml @@ -9,6 +9,7 @@ anyhow.workspace = true serde.workspace = true rand.workspace = true tempfile.workspace = true +chrono.workspace = true log.workspace = true [dependencies.storage] diff --git a/sequencer_core/src/config.rs b/sequencer_core/src/config.rs index 4a99f0b..cdf79a3 100644 --- a/sequencer_core/src/config.rs +++ b/sequencer_core/src/config.rs @@ -27,4 +27,6 @@ pub struct SequencerConfig { pub port: u16, ///List of initial accounts data pub initial_accounts: Vec, + ///Sequencer own signing key + pub signing_key: [u8; 32], } diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 2137c8b..59b787e 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -1,7 +1,11 @@ use std::fmt::Display; use anyhow::Result; -use common::{block::HashableBlockData, merkle_tree_public::TreeHashType}; +use common::{ + TreeHashType, + block::HashableBlockData, + transaction::{EncodedTransaction, NSSATransaction}, +}; use config::SequencerConfig; use log::warn; use mempool::MemPool; @@ -13,7 +17,7 @@ pub mod sequencer_store; pub struct SequencerCore { pub store: SequecerChainStore, - pub mempool: MemPool, + pub mempool: MemPool, pub sequencer_config: SequencerConfig, pub chain_height: u64, } @@ -51,6 +55,7 @@ impl SequencerCore { config.genesis_id, config.is_genesis_random, &config.initial_accounts, + nssa::PrivateKey::try_new(config.signing_key).unwrap(), ), mempool: MemPool::default(), chain_height: config.genesis_id, @@ -60,20 +65,37 @@ impl SequencerCore { pub fn transaction_pre_check( &mut self, - tx: nssa::PublicTransaction, - ) -> Result { + tx: NSSATransaction, + ) -> Result { // Stateless checks here - if tx.witness_set().is_valid_for(tx.message()) { - Ok(tx) - } else { - Err(TransactionMalformationErrorKind::InvalidSignature) + match tx { + NSSATransaction::Public(tx) => { + if tx.witness_set().is_valid_for(tx.message()) { + Ok(NSSATransaction::Public(tx)) + } else { + Err(TransactionMalformationErrorKind::InvalidSignature) + } + } + NSSATransaction::PrivacyPreserving(tx) => { + if tx.witness_set().signatures_are_valid_for(tx.message()) { + Ok(NSSATransaction::PrivacyPreserving(tx)) + } else { + Err(TransactionMalformationErrorKind::InvalidSignature) + } + } } } pub fn push_tx_into_mempool_pre_check( &mut self, - transaction: nssa::PublicTransaction, + transaction: EncodedTransaction, ) -> Result<(), TransactionMalformationErrorKind> { + let transaction = NSSATransaction::try_from(&transaction).map_err(|_| { + TransactionMalformationErrorKind::FailedToDecode { + tx: transaction.hash(), + } + })?; + let mempool_size = self.mempool.len(); if mempool_size >= self.sequencer_config.max_num_tx_in_block { return Err(TransactionMalformationErrorKind::MempoolFullForRound); @@ -83,19 +105,29 @@ impl SequencerCore { .transaction_pre_check(transaction) .inspect_err(|err| warn!("Error at pre_check {err:#?}"))?; - self.mempool.push_item(authenticated_tx); + self.mempool.push_item(authenticated_tx.into()); Ok(()) } fn execute_check_transaction_on_state( &mut self, - tx: nssa::PublicTransaction, - ) -> Result { - self.store - .state - .transition_from_public_transaction(&tx) - .inspect_err(|err| warn!("Error at transition {err:#?}"))?; + tx: NSSATransaction, + ) -> Result { + match &tx { + NSSATransaction::Public(tx) => { + self.store + .state + .transition_from_public_transaction(tx) + .inspect_err(|err| warn!("Error at transition {err:#?}"))?; + } + NSSATransaction::PrivacyPreserving(tx) => { + self.store + .state + .transition_from_privacy_preserving_transaction(tx) + .inspect_err(|err| warn!("Error at transition {err:#?}"))?; + } + } Ok(tx) } @@ -104,29 +136,41 @@ impl SequencerCore { pub fn produce_new_block_with_mempool_transactions(&mut self) -> Result { let new_block_height = self.chain_height + 1; - let transactions = self - .mempool - .pop_size(self.sequencer_config.max_num_tx_in_block); + let mut num_valid_transactions_in_block = 0; + let mut valid_transactions = vec![]; - let valid_transactions: Vec<_> = transactions - .into_iter() - .filter_map(|tx| self.execute_check_transaction_on_state(tx).ok()) - .collect(); + while let Some(tx) = self.mempool.pop_last() { + let nssa_transaction = NSSATransaction::try_from(&tx) + .map_err(|_| TransactionMalformationErrorKind::FailedToDecode { tx: tx.hash() })?; + + if let Ok(valid_tx) = self.execute_check_transaction_on_state(nssa_transaction) { + valid_transactions.push(valid_tx.into()); + + num_valid_transactions_in_block += 1; + + if num_valid_transactions_in_block >= self.sequencer_config.max_num_tx_in_block { + break; + } + } + } let prev_block_hash = self .store .block_store .get_block_at_id(self.chain_height)? + .header .hash; + let curr_time = chrono::Utc::now().timestamp_millis() as u64; + let hashable_data = HashableBlockData { block_id: new_block_height, - prev_block_id: self.chain_height, transactions: valid_transactions, prev_block_hash, + timestamp: curr_time, }; - let block = hashable_data.into(); + let block = hashable_data.into_block(&self.store.block_store.signing_key); self.store.block_store.put_block_at_id(block)?; @@ -138,10 +182,18 @@ impl SequencerCore { #[cfg(test)] mod tests { + use common::test_utils::sequencer_sign_key_for_testing; + use crate::config::AccountInitialData; use super::*; + fn parse_unwrap_tx_body_into_nssa_tx(tx_body: EncodedTransaction) -> NSSATransaction { + NSSATransaction::try_from(&tx_body) + .map_err(|_| TransactionMalformationErrorKind::FailedToDecode { tx: tx_body.hash() }) + .unwrap() + } + fn setup_sequencer_config_variable_initial_accounts( initial_accounts: Vec, ) -> SequencerConfig { @@ -157,6 +209,7 @@ mod tests { block_create_timeout_millis: 1000, port: 8080, initial_accounts, + signing_key: *sequencer_sign_key_for_testing().value(), } } @@ -298,7 +351,7 @@ mod tests { common_setup(&mut sequencer); let tx = common::test_utils::produce_dummy_empty_transaction(); - let result = sequencer.transaction_pre_check(tx); + let result = sequencer.transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx)); assert!(result.is_ok()); } @@ -324,7 +377,7 @@ mod tests { let tx = common::test_utils::create_transaction_native_token_transfer( acc1, 0, acc2, 10, sign_key1, ); - let result = sequencer.transaction_pre_check(tx); + let result = sequencer.transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx)); assert!(result.is_ok()); } @@ -352,7 +405,9 @@ mod tests { ); // Signature is valid, stateless check pass - let tx = sequencer.transaction_pre_check(tx).unwrap(); + let tx = sequencer + .transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx)) + .unwrap(); // Signature is not from sender. Execution fails let result = sequencer.execute_check_transaction_on_state(tx); @@ -385,7 +440,7 @@ mod tests { acc1, 0, acc2, 10000000, sign_key1, ); - let result = sequencer.transaction_pre_check(tx); + let result = sequencer.transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx)); //Passed pre-check assert!(result.is_ok()); @@ -421,7 +476,9 @@ mod tests { acc1, 0, acc2, 100, sign_key1, ); - sequencer.execute_check_transaction_on_state(tx).unwrap(); + sequencer + .execute_check_transaction_on_state(parse_unwrap_tx_body_into_nssa_tx(tx)) + .unwrap(); let bal_from = sequencer .store @@ -528,7 +585,7 @@ mod tests { .unwrap(); // Only one should be included in the block - assert_eq!(block.transactions, vec![tx.clone()]); + assert_eq!(block.body.transactions, vec![tx.clone()]); } #[test] @@ -563,7 +620,7 @@ mod tests { .block_store .get_block_at_id(current_height) .unwrap(); - assert_eq!(block.transactions, vec![tx.clone()]); + assert_eq!(block.body.transactions, vec![tx.clone()]); // Add same transaction should fail sequencer.mempool.push_item(tx); @@ -575,6 +632,6 @@ mod tests { .block_store .get_block_at_id(current_height) .unwrap(); - assert!(block.transactions.is_empty()); + assert!(block.body.transactions.is_empty()); } } diff --git a/sequencer_core/src/sequencer_store/block_store.rs b/sequencer_core/src/sequencer_store/block_store.rs index 3bd68cb..42e4ec5 100644 --- a/sequencer_core/src/sequencer_store/block_store.rs +++ b/sequencer_core/src/sequencer_store/block_store.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, path::Path}; use anyhow::Result; -use common::{block::Block, merkle_tree_public::TreeHashType}; +use common::{TreeHashType, block::Block, transaction::EncodedTransaction}; use storage::RocksDBIO; pub struct SequecerBlockStore { @@ -9,6 +9,7 @@ pub struct SequecerBlockStore { // TODO: Consider adding the hashmap to the database for faster recovery. tx_hash_to_block_map: HashMap, pub genesis_id: u64, + pub signing_key: nssa::PrivateKey, } impl SequecerBlockStore { @@ -16,7 +17,11 @@ impl SequecerBlockStore { /// Creates files if necessary. /// /// ATTENTION: Will overwrite genesis block. - pub fn open_db_with_genesis(location: &Path, genesis_block: Option) -> Result { + pub fn open_db_with_genesis( + location: &Path, + genesis_block: Option, + signing_key: nssa::PrivateKey, + ) -> Result { let tx_hash_to_block_map = if let Some(block) = &genesis_block { block_to_transactions_map(block) } else { @@ -31,16 +36,17 @@ impl SequecerBlockStore { dbio, genesis_id, tx_hash_to_block_map, + signing_key, }) } ///Reopening existing database - pub fn open_db_restart(location: &Path) -> Result { - SequecerBlockStore::open_db_with_genesis(location, None) + pub fn open_db_restart(location: &Path, signing_key: nssa::PrivateKey) -> Result { + SequecerBlockStore::open_db_with_genesis(location, None, signing_key) } pub fn get_block_at_id(&self, id: u64) -> Result { - Ok(self.dbio.get_block(id)?) + Ok(self.dbio.get_block(id)?.into_block(&self.signing_key)) } pub fn put_block_at_id(&mut self, block: Block) -> Result<()> { @@ -51,11 +57,11 @@ impl SequecerBlockStore { } /// Returns the transaction corresponding to the given hash, if it exists in the blockchain. - pub fn get_transaction_by_hash(&self, hash: TreeHashType) -> Option { + pub fn get_transaction_by_hash(&self, hash: TreeHashType) -> Option { let block_id = self.tx_hash_to_block_map.get(&hash); let block = block_id.map(|&id| self.get_block_at_id(id)); if let Some(Ok(block)) = block { - for transaction in block.transactions.into_iter() { + for transaction in block.body.transactions.into_iter() { if transaction.hash() == hash { return Some(transaction); } @@ -67,9 +73,10 @@ impl SequecerBlockStore { fn block_to_transactions_map(block: &Block) -> HashMap { block + .body .transactions .iter() - .map(|transaction| (transaction.hash(), block.block_id)) + .map(|transaction| (transaction.hash(), block.header.block_id)) .collect() } @@ -77,22 +84,28 @@ fn block_to_transactions_map(block: &Block) -> HashMap { mod tests { use super::*; + use common::{block::HashableBlockData, test_utils::sequencer_sign_key_for_testing}; use tempfile::tempdir; #[test] fn test_get_transaction_by_hash() { let temp_dir = tempdir().unwrap(); let path = temp_dir.path(); - let genesis_block = Block { + + let signing_key = sequencer_sign_key_for_testing(); + + let genesis_block_hashable_data = HashableBlockData { block_id: 0, - prev_block_id: 0, prev_block_hash: [0; 32], - hash: [1; 32], + timestamp: 0, transactions: vec![], }; + + let genesis_block = genesis_block_hashable_data.into_block(&signing_key); // Start an empty node store let mut node_store = - SequecerBlockStore::open_db_with_genesis(path, Some(genesis_block)).unwrap(); + SequecerBlockStore::open_db_with_genesis(path, Some(genesis_block), signing_key) + .unwrap(); let tx = common::test_utils::produce_dummy_empty_transaction(); let block = common::test_utils::produce_dummy_block(1, None, vec![tx.clone()]); diff --git a/sequencer_core/src/sequencer_store/mod.rs b/sequencer_core/src/sequencer_store/mod.rs index bb0a7c1..366c9e2 100644 --- a/sequencer_core/src/sequencer_store/mod.rs +++ b/sequencer_core/src/sequencer_store/mod.rs @@ -20,6 +20,7 @@ impl SequecerChainStore { genesis_id: u64, is_genesis_random: bool, initial_accounts: &[AccountInitialData], + signing_key: nssa::PrivateKey, ) -> Self { let init_accs: Vec<(Address, u128)> = initial_accounts .iter() @@ -36,20 +37,23 @@ impl SequecerChainStore { OsRng.fill_bytes(&mut prev_block_hash); } + let curr_time = chrono::Utc::now().timestamp_millis() as u64; + let hashable_data = HashableBlockData { block_id: genesis_id, - prev_block_id: genesis_id.saturating_sub(1), transactions: vec![], prev_block_hash, + timestamp: curr_time, }; - let genesis_block = hashable_data.into(); + let genesis_block = hashable_data.into_block(&signing_key); //Sequencer should panic if unable to open db, //as fixing this issue may require actions non-native to program scope let block_store = SequecerBlockStore::open_db_with_genesis( &home_dir.join("rocksdb"), Some(genesis_block), + signing_key, ) .unwrap(); diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index 2f55dd2..00c5724 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -5,19 +5,20 @@ use sequencer_core::config::AccountInitialData; use serde_json::Value; use common::{ + TreeHashType, block::HashableBlockData, - merkle_tree_public::TreeHashType, rpc_primitives::{ errors::RpcError, message::{Message, Request}, parser::RpcRequest, requests::{ - GetAccountBalanceRequest, GetAccountBalanceResponse, GetAccountDataRequest, - GetAccountDataResponse, GetAccountsNoncesRequest, GetAccountsNoncesResponse, + GetAccountBalanceRequest, GetAccountBalanceResponse, GetAccountRequest, + GetAccountResponse, GetAccountsNoncesRequest, GetAccountsNoncesResponse, GetInitialTestnetAccountsRequest, GetTransactionByHashRequest, GetTransactionByHashResponse, }, }, + transaction::EncodedTransaction, }; use common::rpc_primitives::requests::{ @@ -36,7 +37,7 @@ pub const GET_LAST_BLOCK: &str = "get_last_block"; pub const GET_ACCOUNT_BALANCE: &str = "get_account_balance"; pub const GET_TRANSACTION_BY_HASH: &str = "get_transaction_by_hash"; pub const GET_ACCOUNTS_NONCES: &str = "get_accounts_nonces"; -pub const GET_ACCOUNT_DATA: &str = "get_account_data"; +pub const GET_ACCOUNT: &str = "get_account"; pub const HELLO_FROM_SEQUENCER: &str = "HELLO_FROM_SEQUENCER"; @@ -74,8 +75,7 @@ impl JsonHandler { async fn process_send_tx(&self, request: Request) -> Result { let send_tx_req = SendTxRequest::parse(Some(request.params))?; - let tx = nssa::PublicTransaction::from_bytes(&send_tx_req.transaction) - .map_err(|e| RpcError::serialization_error(&e.to_string()))?; + let tx = EncodedTransaction::from_bytes(send_tx_req.transaction); let tx_hash = hex::encode(tx.hash()); { @@ -204,10 +204,10 @@ impl JsonHandler { respond(helperstruct) } - ///Returns account struct for give address. + /// Returns account struct for given address. /// Address must be a valid hex string of the correct length. - async fn process_get_account_data(&self, request: Request) -> Result { - let get_account_nonces_req = GetAccountDataRequest::parse(Some(request.params))?; + async fn process_get_account(&self, request: Request) -> Result { + let get_account_nonces_req = GetAccountRequest::parse(Some(request.params))?; let address = get_account_nonces_req .address @@ -220,12 +220,7 @@ impl JsonHandler { state.store.state.get_account_by_address(&address) }; - let helperstruct = GetAccountDataResponse { - balance: account.balance, - nonce: account.nonce, - program_owner: account.program_owner, - data: account.data, - }; + let helperstruct = GetAccountResponse { account }; respond(helperstruct) } @@ -265,7 +260,7 @@ impl JsonHandler { GET_INITIAL_TESTNET_ACCOUNTS => self.get_initial_testnet_accounts(request).await, GET_ACCOUNT_BALANCE => self.process_get_account_balance(request).await, GET_ACCOUNTS_NONCES => self.process_get_accounts_nonces(request).await, - GET_ACCOUNT_DATA => self.process_get_account_data(request).await, + GET_ACCOUNT => self.process_get_account(request).await, GET_TRANSACTION_BY_HASH => self.process_get_transaction_by_hash(request).await, _ => Err(RpcErr(RpcError::method_not_found(request.method))), } @@ -278,7 +273,10 @@ mod tests { use crate::{JsonHandler, rpc_handler}; use base64::{Engine, engine::general_purpose}; - use common::rpc_primitives::RpcPollingConfig; + use common::{ + rpc_primitives::RpcPollingConfig, test_utils::sequencer_sign_key_for_testing, + transaction::EncodedTransaction, + }; use sequencer_core::{ SequencerCore, @@ -322,14 +320,11 @@ mod tests { block_create_timeout_millis: 1000, port: 8080, initial_accounts, + signing_key: *sequencer_sign_key_for_testing().value(), } } - fn components_for_tests() -> ( - JsonHandler, - Vec, - nssa::PublicTransaction, - ) { + fn components_for_tests() -> (JsonHandler, Vec, EncodedTransaction) { let config = sequencer_config_for_tests(); let mut sequencer_core = SequencerCore::start_from_config(config); let initial_accounts = sequencer_core.sequencer_config.initial_accounts.clone(); @@ -534,7 +529,7 @@ mod tests { let (json_handler, _, _) = components_for_tests(); let request = serde_json::json!({ "jsonrpc": "2.0", - "method": "get_account_data", + "method": "get_account", "params": { "address": "efac".repeat(16) }, "id": 1 }); @@ -542,10 +537,12 @@ mod tests { "id": 1, "jsonrpc": "2.0", "result": { - "balance": 0, - "nonce": 0, - "program_owner": [ 0, 0, 0, 0, 0, 0, 0, 0], - "data": [], + "account": { + "balance": 0, + "nonce": 0, + "program_owner": [ 0, 0, 0, 0, 0, 0, 0, 0], + "data": [], + } } }); diff --git a/sequencer_runner/configs/debug/sequencer_config.json b/sequencer_runner/configs/debug/sequencer_config.json index 010a9ba..f7c6369 100644 --- a/sequencer_runner/configs/debug/sequencer_config.json +++ b/sequencer_runner/configs/debug/sequencer_config.json @@ -7,6 +7,13 @@ "block_create_timeout_millis": 10000, "port": 3040, "initial_accounts": [ - + { + "addr": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "balance": 10000 + }, + { + "addr": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766", + "balance": 20000 + } ] -} \ No newline at end of file +} diff --git a/sequencer_runner/src/lib.rs b/sequencer_runner/src/lib.rs index b682a18..068d3d7 100644 --- a/sequencer_runner/src/lib.rs +++ b/sequencer_runner/src/lib.rs @@ -77,7 +77,9 @@ pub async fn main_runner() -> Result<()> { } //ToDo: Add restart on failures - let (_, _) = startup_sequencer(app_config).await?; + let (_, main_loop_handle) = startup_sequencer(app_config).await?; + + main_loop_handle.await??; Ok(()) } diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 9ead317..d06fc0c 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -92,7 +92,7 @@ impl RocksDBIO { if is_start_set { Ok(dbio) } else if let Some(block) = start_block { - let block_id = block.block_id; + let block_id = block.header.block_id; dbio.put_meta_first_block_in_db(block)?; dbio.put_meta_is_first_block_set()?; @@ -186,7 +186,7 @@ impl RocksDBIO { .put_cf( &cf_meta, DB_META_FIRST_BLOCK_IN_DB_KEY.as_bytes(), - block.block_id.to_be_bytes(), + block.header.block_id.to_be_bytes(), ) .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; @@ -233,22 +233,22 @@ impl RocksDBIO { if !first { let last_curr_block = self.get_meta_last_block_in_db()?; - if block.block_id > last_curr_block { - self.put_meta_last_block_in_db(block.block_id)?; + if block.header.block_id > last_curr_block { + self.put_meta_last_block_in_db(block.header.block_id)?; } } self.db .put_cf( &cf_block, - block.block_id.to_be_bytes(), + block.header.block_id.to_be_bytes(), HashableBlockData::from(block).to_bytes(), ) .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; Ok(()) } - pub fn get_block(&self, block_id: u64) -> DbResult { + pub fn get_block(&self, block_id: u64) -> DbResult { let cf_block = self.block_column(); let res = self .db @@ -256,7 +256,7 @@ impl RocksDBIO { .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; if let Some(data) = res { - Ok(HashableBlockData::from_bytes(&data).into()) + Ok(HashableBlockData::from_bytes(&data)) } else { Err(DbError::db_interaction_error( "Block on this id not found".to_string(), diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 2caa7ff..e37586b 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -14,6 +14,7 @@ tempfile.workspace = true clap.workspace = true nssa-core = { path = "../nssa/core" } base64.workspace = true +bytemuck = "1.23.2" [dependencies.key_protocol] path = "../key_protocol" diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index 6f4201c..a803cf8 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -1,14 +1,12 @@ use std::collections::HashMap; use anyhow::Result; -use common::merkle_tree_public::merkle_tree::UTXOCommitmentsMerkleTree; use key_protocol::key_protocol_core::NSSAUserData; use crate::config::{PersistentAccountData, WalletConfig}; pub struct WalletChainStore { pub user_data: NSSAUserData, - pub utxo_commitments_store: UTXOCommitmentsMerkleTree, pub wallet_config: WalletConfig, } @@ -21,11 +19,8 @@ impl WalletChainStore { .map(|init_acc_data| (init_acc_data.address, init_acc_data.pub_sign_key)) .collect(); - let utxo_commitments_store = UTXOCommitmentsMerkleTree::new(vec![]); - Ok(Self { user_data: NSSAUserData::new_with_accounts(accounts_keys)?, - utxo_commitments_store, wallet_config: config, }) } @@ -94,11 +89,6 @@ mod tests { let config = create_sample_wallet_config(path.to_path_buf()); - let store = WalletChainStore::new(config.clone()).unwrap(); - - assert_eq!( - store.utxo_commitments_store.get_root().unwrap_or([0; 32]), - [0; 32] - ); + let _ = WalletChainStore::new(config.clone()).unwrap(); } } diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 8a7b7b6..dc5b2f6 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -1,20 +1,22 @@ +use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr}; use anyhow::Result; use key_protocol::key_protocol_core::NSSAUserData; -use nssa::Address; +use nssa::{Account, Address}; +use serde::Serialize; use crate::{ HOME_DIR_ENV_VAR, config::{PersistentAccountData, WalletConfig}, }; -///Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed. +/// Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed. pub fn get_home() -> Result { Ok(PathBuf::from_str(&std::env::var(HOME_DIR_ENV_VAR)?)?) } -///Fetch config from `NSSA_WALLET_HOME_DIR` +/// Fetch config from `NSSA_WALLET_HOME_DIR` pub fn fetch_config() -> Result { let config_home = get_home()?; let file = File::open(config_home.join("wallet_config.json"))?; @@ -23,12 +25,12 @@ pub fn fetch_config() -> Result { Ok(serde_json::from_reader(reader)?) } -//ToDo: Replace with structures conversion in future +// ToDo: Replace with structures conversion in future pub fn produce_account_addr_from_hex(hex_str: String) -> Result
{ Ok(hex_str.parse()?) } -///Fetch list of accounts stored at `NSSA_WALLET_HOME_DIR/curr_accounts.json` +/// Fetch list of accounts stored at `NSSA_WALLET_HOME_DIR/curr_accounts.json` /// /// If file not present, it is considered as empty list of persistent accounts pub fn fetch_persistent_accounts() -> Result> { @@ -49,7 +51,7 @@ pub fn fetch_persistent_accounts() -> Result> { } } -///Produces a list of accounts for storage +/// Produces a list of accounts for storage pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec { let mut vec_for_storage = vec![]; @@ -63,6 +65,28 @@ pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec for HumanReadableAccount { + fn from(account: Account) -> Self { + let program_owner_b64 = BASE64.encode(bytemuck::cast_slice(&account.program_owner)); + let data_b64 = BASE64.encode(account.data); + Self { + balance: account.balance, + program_owner_b64, + data_b64, + nonce: account.nonce, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 3814925..e4c538a 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -1,9 +1,10 @@ use std::{fs::File, io::Write, path::PathBuf, str::FromStr, sync::Arc}; -use base64::Engine; +use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; use common::{ ExecutionFailureKind, sequencer_client::{SequencerClient, json::SendTxResponse}, + transaction::{EncodedTransaction, NSSATransaction}, }; use anyhow::Result; @@ -17,8 +18,8 @@ use nssa_core::account::Account; use crate::{ helperfunctions::{ - fetch_config, fetch_persistent_accounts, get_home, produce_account_addr_from_hex, - produce_data_for_storage, + HumanReadableAccount, fetch_config, fetch_persistent_accounts, get_home, + produce_account_addr_from_hex, produce_data_for_storage, }, poller::TxPoller, }; @@ -120,7 +121,7 @@ impl WalletCore { let tx = nssa::PublicTransaction::new(message, witness_set); - Ok(self.sequencer_client.send_tx(tx).await?) + Ok(self.sequencer_client.send_tx_public(tx).await?) } else { Err(ExecutionFailureKind::InsufficientFundsError) } @@ -144,17 +145,19 @@ impl WalletCore { .nonces) } - ///Poll transactions - pub async fn poll_public_native_token_transfer( - &self, - hash: String, - ) -> Result { - let transaction_encoded = self.poller.poll_tx(hash).await?; - let tx_base64_decode = - base64::engine::general_purpose::STANDARD.decode(transaction_encoded)?; - let pub_tx = nssa::PublicTransaction::from_bytes(&tx_base64_decode)?; + ///Get account + pub async fn get_account(&self, addr: Address) -> Result { + let response = self.sequencer_client.get_account(addr.to_string()).await?; + Ok(response.account) + } - Ok(pub_tx) + ///Poll transactions + pub async fn poll_public_native_token_transfer(&self, hash: String) -> Result { + let transaction_encoded = self.poller.poll_tx(hash).await?; + let tx_base64_decode = BASE64.decode(transaction_encoded)?; + let pub_tx = EncodedTransaction::from_bytes(tx_base64_decode); + + Ok(NSSATransaction::try_from(&pub_tx)?) } } @@ -191,6 +194,11 @@ pub enum Command { #[arg(short, long)] addr: String, }, + ///Get account at address `addr` + GetAccount { + #[arg(short, long)] + addr: String, + }, } ///To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config @@ -215,21 +223,19 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { .send_public_native_token_transfer(from, to, amount) .await?; - info!("Results of tx send is {res:#?}"); + println!("Results of tx send is {res:#?}"); let transfer_tx = wallet_core .poll_public_native_token_transfer(res.tx_hash) .await?; - info!("Transaction data is {transfer_tx:?}"); + println!("Transaction data is {transfer_tx:?}"); } Command::RegisterAccount {} => { let addr = wallet_core.create_new_account(); + wallet_core.store_persistent_accounts()?; - let key = wallet_core.storage.user_data.get_account_signing_key(&addr); - - info!("Generated new account with addr {addr:#?}"); - info!("With key {key:#?}"); + println!("Generated new account with addr {addr}"); } Command::FetchTx { tx_hash } => { let tx_obj = wallet_core @@ -237,23 +243,26 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { .get_transaction_by_hash(tx_hash) .await?; - info!("Transaction object {tx_obj:#?}"); + println!("Transaction object {tx_obj:#?}"); } Command::GetAccountBalance { addr } => { let addr = Address::from_str(&addr)?; let balance = wallet_core.get_account_balance(addr).await?; - info!("Accounts {addr:#?} balance is {balance}"); + println!("Accounts {addr} balance is {balance}"); } Command::GetAccountNonce { addr } => { let addr = Address::from_str(&addr)?; let nonce = wallet_core.get_accounts_nonces(vec![addr]).await?[0]; - info!("Accounts {addr:#?} nonce is {nonce}"); + println!("Accounts {addr} nonce is {nonce}"); + } + Command::GetAccount { addr } => { + let addr: Address = addr.parse()?; + let account: HumanReadableAccount = wallet_core.get_account(addr).await?.into(); + println!("{}", serde_json::to_string(&account).unwrap()); } } - wallet_core.store_persistent_accounts()?; - Ok(()) }