Merge branch 'Pravdyvy/key-protocol-update-public-part' into Pravdyvy/accounts-dump-fetch

This commit is contained in:
Oleksandr Pravdyvyi 2025-08-20 08:22:47 +03:00
commit 0abb23e26a
No known key found for this signature in database
GPG Key ID: 9F8955C63C443871
103 changed files with 3406 additions and 7752 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ data/
.idea/
.vscode/
rocksdb
Cargo.lock

View File

@ -1,22 +1,20 @@
[workspace]
resolver = "2"
members = [
"integration_tests",
"sequencer_runner",
"storage",
"accounts",
"utxo",
"key_protocol",
"sequencer_rpc",
"mempool",
"zkvm",
"wallet",
"sequencer_core",
"common",
"sc_core",
"integration_tests",
"nssa",
]
[workspace.dependencies]
anyhow = "1.0"
anyhow = "1.0.98"
num_cpus = "1.13.1"
openssl = { version = "0.10", features = ["vendored"] }
openssl-probe = { version = "0.1.2" }
@ -29,7 +27,7 @@ lazy_static = "1.5.0"
env_logger = "0.10"
log = "0.4"
lru = "0.7.8"
thiserror = "1.0"
thiserror = "2.0.12"
rs_merkle = "1.4"
sha2 = "0.10.8"
hex = "0.4.3"
@ -42,6 +40,7 @@ light-poseidon = "0.3.0"
ark-bn254 = "0.5.0"
ark-ff = "0.5.0"
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
base64 = "0.22.1"
rocksdb = { version = "0.21.0", default-features = false, features = [
"snappy",

View File

@ -1,36 +0,0 @@
use common::transaction::SignaturePublicKey;
use tiny_keccak::{Hasher, Keccak};
// TODO: Consider wrapping `AccountAddress` in a struct.
pub type AccountAddress = [u8; 32];
/// Returns the address associated with a public key
pub fn from_public_key(public_key: &SignaturePublicKey) -> AccountAddress {
let mut address = [0; 32];
let mut keccak_hasher = Keccak::v256();
keccak_hasher.update(&public_key.to_sec1_bytes());
keccak_hasher.finalize(&mut address);
address
}
#[cfg(test)]
mod tests {
use common::transaction::SignaturePrivateKey;
use super::*;
use crate::account_core::address;
#[test]
fn test_address_key_equal_keccak_pub_sign_key() {
let signing_key = SignaturePrivateKey::from_slice(&[1; 32]).unwrap();
let public_key = signing_key.verifying_key();
let mut expected_address = [0; 32];
let mut keccak_hasher = Keccak::v256();
keccak_hasher.update(&public_key.to_sec1_bytes());
keccak_hasher.finalize(&mut expected_address);
assert_eq!(expected_address, address::from_public_key(public_key));
}
}

View File

@ -1,279 +0,0 @@
use std::collections::HashMap;
use anyhow::Result;
use common::{merkle_tree_public::TreeHashType, transaction::Tag};
use k256::AffinePoint;
use log::info;
use serde::{Deserialize, Serialize};
use utxo::utxo_core::UTXO;
pub mod address;
use crate::{
account_core::address::AccountAddress,
key_management::{
constants_types::{CipherText, Nonce},
ephemeral_key_holder::EphemeralKeyHolder,
AddressKeyHolder,
},
};
pub type PublicKey = AffinePoint;
#[derive(Clone, Debug)]
pub struct Account {
pub key_holder: AddressKeyHolder,
pub address: AccountAddress,
pub balance: u64,
pub utxos: HashMap<TreeHashType, UTXO>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AccountForSerialization {
pub key_holder: AddressKeyHolder,
pub address: AccountAddress,
pub balance: u64,
pub utxos: HashMap<String, UTXO>,
}
impl From<Account> for AccountForSerialization {
fn from(value: Account) -> Self {
AccountForSerialization {
key_holder: value.key_holder,
address: value.address,
balance: value.balance,
utxos: value
.utxos
.into_iter()
.map(|(key, val)| (hex::encode(key), val))
.collect(),
}
}
}
impl From<AccountForSerialization> for Account {
fn from(value: AccountForSerialization) -> Self {
Account {
key_holder: value.key_holder,
address: value.address,
balance: value.balance,
utxos: value
.utxos
.into_iter()
.map(|(key, val)| (hex::decode(key).unwrap().try_into().unwrap(), val))
.collect(),
}
}
}
impl Serialize for Account {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let account_for_serialization: AccountForSerialization = From::from(self.clone());
account_for_serialization.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Account {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let account_for_serialization = <AccountForSerialization>::deserialize(deserializer)?;
Ok(account_for_serialization.into())
}
}
///A strucure, which represents all the visible(public) information
///
/// known to each node about account `address`
///
/// Main usage is to encode data for other account
#[derive(Serialize, Clone)]
pub struct AccountPublicMask {
pub nullifier_public_key: AffinePoint,
pub viewing_public_key: AffinePoint,
pub address: AccountAddress,
pub balance: u64,
}
impl AccountPublicMask {
pub fn encrypt_data(
ephemeral_key_holder: &EphemeralKeyHolder,
viewing_public_key_receiver: AffinePoint,
data: &[u8],
) -> (CipherText, Nonce) {
//Using of parent Account fuction
Account::encrypt_data(ephemeral_key_holder, viewing_public_key_receiver, data)
}
pub fn make_tag(&self) -> Tag {
self.address[0]
}
}
impl Account {
pub fn new() -> Self {
let key_holder = AddressKeyHolder::new_os_random();
let public_key = *key_holder.get_pub_account_signing_key().verifying_key();
let address = address::from_public_key(&public_key);
let balance = 0;
let utxos = HashMap::new();
Self {
key_holder,
address,
balance,
utxos,
}
}
pub fn new_with_balance(balance: u64) -> Self {
let key_holder = AddressKeyHolder::new_os_random();
let public_key = *key_holder.get_pub_account_signing_key().verifying_key();
let address = address::from_public_key(&public_key);
let utxos = HashMap::new();
Self {
key_holder,
address,
balance,
utxos,
}
}
pub fn encrypt_data(
ephemeral_key_holder: &EphemeralKeyHolder,
viewing_public_key_receiver: AffinePoint,
data: &[u8],
) -> (CipherText, Nonce) {
ephemeral_key_holder.encrypt_data(viewing_public_key_receiver, data)
}
pub fn decrypt_data(
&self,
ephemeral_public_key_sender: AffinePoint,
ciphertext: CipherText,
nonce: Nonce,
) -> Result<Vec<u8>, aes_gcm::Error> {
self.key_holder
.decrypt_data(ephemeral_public_key_sender, ciphertext, nonce)
}
pub fn add_new_utxo_outputs(&mut self, utxos: Vec<UTXO>) -> Result<()> {
for utxo in utxos {
if self.utxos.contains_key(&utxo.hash) {
return Err(anyhow::anyhow!("UTXO already exists"));
}
self.utxos.insert(utxo.hash, utxo);
}
Ok(())
}
pub fn update_public_balance(&mut self, new_balance: u64) {
self.balance = new_balance;
}
pub fn add_asset<Asset: Serialize>(
&mut self,
asset: Asset,
amount: u128,
privacy_flag: bool,
) -> Result<()> {
let asset_utxo = UTXO::new(
self.address,
serde_json::to_vec(&asset)?,
amount,
privacy_flag,
);
self.utxos.insert(asset_utxo.hash, asset_utxo);
Ok(())
}
pub fn log(&self) {
info!("Keys generated");
info!("Account address is {:?}", hex::encode(self.address));
info!("Account balance is {:?}", self.balance);
}
pub fn make_tag(&self) -> Tag {
self.address[0]
}
///Produce account public mask
pub fn make_account_public_mask(&self) -> AccountPublicMask {
AccountPublicMask {
nullifier_public_key: self.key_holder.nullifer_public_key,
viewing_public_key: self.key_holder.viewing_public_key,
address: self.address,
balance: self.balance,
}
}
}
impl Default for Account {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn generate_dummy_utxo(address: TreeHashType, amount: u128) -> UTXO {
UTXO::new(address, vec![], amount, false)
}
#[test]
fn test_new_account() {
let account = Account::new();
assert_eq!(account.balance, 0);
}
#[test]
fn test_add_new_utxo_outputs() {
let mut account = Account::new();
let utxo1 = generate_dummy_utxo(account.address, 100);
let utxo2 = generate_dummy_utxo(account.address, 200);
let result = account.add_new_utxo_outputs(vec![utxo1.clone(), utxo2.clone()]);
assert!(result.is_ok());
assert_eq!(account.utxos.len(), 2);
}
#[test]
fn test_update_public_balance() {
let mut account = Account::new();
account.update_public_balance(500);
assert_eq!(account.balance, 500);
}
#[test]
fn test_add_asset() {
let mut account = Account::new();
let asset = "dummy_asset";
let amount = 1000u128;
let result = account.add_asset(asset, amount, false);
assert!(result.is_ok());
assert_eq!(account.utxos.len(), 1);
}
#[test]
fn accounts_accounts_mask_tag_consistency() {
let account = Account::new();
let account_mask = account.make_account_public_mask();
assert_eq!(account.make_tag(), account_mask.make_tag());
}
}

View File

@ -1,2 +0,0 @@
pub mod account_core;
pub mod key_management;

0
ci_scripts/lint-ubuntu.sh Normal file → Executable file
View File

View File

@ -9,7 +9,6 @@ thiserror.workspace = true
serde_json.workspace = true
serde.workspace = true
reqwest.workspace = true
risc0-zkvm = { git = "https://github.com/risc0/risc0.git", branch = "release-2.3" }
k256.workspace = true
rand.workspace = true
@ -22,3 +21,6 @@ hex.workspace = true
[dependencies.secp256k1-zkp]
workspace = true
features = ["std", "rand-std", "rand", "serde", "global-context"]
[dependencies.nssa]
path = "../nssa"

View File

@ -1,35 +1,33 @@
use rs_merkle::Hasher;
use serde::{Deserialize, Serialize};
use std::io::{Cursor, Read};
use crate::{merkle_tree_public::hasher::OwnHasher, transaction::Transaction};
use rs_merkle::Hasher;
use crate::merkle_tree_public::hasher::OwnHasher;
use nssa;
pub type BlockHash = [u8; 32];
pub type Data = Vec<u8>;
pub type BlockId = u64;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[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<Transaction>,
pub data: Data,
pub transactions: Vec<nssa::PublicTransaction>,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Eq)]
pub struct HashableBlockData {
pub block_id: BlockId,
pub prev_block_id: BlockId,
pub prev_block_hash: BlockHash,
pub transactions: Vec<Transaction>,
pub data: Data,
pub transactions: Vec<nssa::PublicTransaction>,
}
impl From<HashableBlockData> for Block {
fn from(value: HashableBlockData) -> Self {
let data = serde_json::to_vec(&value).unwrap();
let data = value.to_bytes();
let hash = OwnHasher::hash(&data);
Self {
@ -37,8 +35,88 @@ impl From<HashableBlockData> for Block {
prev_block_id: value.prev_block_id,
hash,
transactions: value.transactions,
data: value.data,
prev_block_hash: value.prev_block_hash,
}
}
}
impl From<Block> 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,
}
}
}
impl HashableBlockData {
pub fn to_bytes(&self) -> Vec<u8> {
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);
let num_transactions: u32 = self.transactions.len() as u32;
bytes.extend_from_slice(&num_transactions.to_le_bytes());
for tx in &self.transactions {
bytes.extend_from_slice(&tx.to_bytes());
}
bytes
}
// TODO: Improve error handling. Remove unwraps.
pub fn from_bytes(data: &[u8]) -> Self {
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 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();
transactions.push(tx);
}
Self {
block_id,
prev_block_id,
prev_block_hash,
transactions,
}
}
}
// TODO: Improve error handling. Remove unwraps.
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 {
let mut word_buf = [0u8; 8];
cursor.read_exact(&mut word_buf).unwrap();
u64::from_le_bytes(word_buf)
}
#[cfg(test)]
mod tests {
use crate::{block::HashableBlockData, test_utils};
#[test]
fn test_encoding_roundtrip() {
let transactions = vec![test_utils::produce_dummy_empty_transaction()];
let block = test_utils::produce_dummy_block(1, Some([1; 32]), transactions);
let hashable = HashableBlockData::from(block);
let bytes = hashable.to_bytes();
let block_from_bytes = HashableBlockData::from_bytes(&bytes);
assert_eq!(hashable, block_from_bytes);
}
}

View File

@ -65,9 +65,11 @@ pub enum ExecutionFailureKind {
#[error("Failed prove execution err: {0:?}")]
ProveError(anyhow::Error),
#[error("Failed to decode data from VM: {0:?}")]
DecodeError(#[from] risc0_zkvm::serde::Error),
DecodeError(String),
#[error("Inputs amounts does not match outputs")]
AmountMismatchError,
#[error("Accounts key not found")]
KeyNotFoundError,
#[error("Sequencer client error: {0:?}")]
SequencerClientError(#[from] SequencerClientError),
#[error("Insufficient gas for operation")]

View File

@ -1,6 +1,4 @@
use crate::block::Block;
use crate::parse_request;
use crate::transaction::Transaction;
use super::errors::RpcParseError;
use super::parser::parse_params;
@ -18,7 +16,7 @@ pub struct RegisterAccountRequest {
#[derive(Serialize, Deserialize, Debug)]
pub struct SendTxRequest {
pub transaction: Transaction,
pub transaction: Vec<u8>,
}
#[derive(Serialize, Deserialize, Debug)]
@ -72,7 +70,7 @@ pub struct SendTxResponse {
#[derive(Serialize, Deserialize, Debug)]
pub struct GetBlockDataResponse {
pub block: Block,
pub block: Vec<u8>,
}
#[derive(Serialize, Deserialize, Debug)]
@ -87,10 +85,10 @@ pub struct GetLastBlockResponse {
#[derive(Serialize, Deserialize, Debug)]
pub struct GetAccountBalanceResponse {
pub balance: u64,
pub balance: u128,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GetTransactionByHashResponse {
pub transaction: Option<Transaction>,
pub transaction: Option<String>,
}

View File

@ -1,12 +1,10 @@
use serde::{Deserialize, Serialize};
use crate::transaction::Transaction;
//Requests
#[derive(Serialize, Deserialize, Debug)]
pub struct SendTxRequest {
pub transaction: Transaction,
pub transaction: Vec<u8>,
}
//Responses

View File

@ -8,7 +8,6 @@ use reqwest::Client;
use serde_json::Value;
use crate::sequencer_client::json::AccountInitialData;
use crate::transaction::Transaction;
use crate::{SequencerClientError, SequencerRpcError};
pub mod json;
@ -90,9 +89,11 @@ impl SequencerClient {
///Send transaction to sequencer
pub async fn send_tx(
&self,
transaction: Transaction,
transaction: nssa::PublicTransaction,
) -> Result<SendTxResponse, SequencerClientError> {
let tx_req = SendTxRequest { transaction };
let tx_req = SendTxRequest {
transaction: transaction.to_bytes(),
};
let req = serde_json::to_value(tx_req)?;

View File

@ -1,11 +1,6 @@
use k256::ecdsa::SigningKey;
use secp256k1_zkp::Tweak;
use nssa;
use crate::{
block::{Block, HashableBlockData},
execution_input::PublicNativeTokenSend,
transaction::{SignaturePrivateKey, Transaction, TransactionBody, TxKind},
};
use crate::block::{Block, HashableBlockData};
//Dummy producers
@ -16,100 +11,47 @@ use crate::{
/// `prev_hash` - hash of previous block, provide None for genesis
///
/// `transactions` - vector of `Transaction` objects
///
/// `additional_data` - vector with additional data
pub fn produce_dummy_block(
id: u64,
prev_hash: Option<[u8; 32]>,
transactions: Vec<Transaction>,
additional_data: Vec<u8>,
transactions: Vec<nssa::PublicTransaction>,
) -> Block {
let block_data = HashableBlockData {
block_id: id,
prev_block_id: id.saturating_sub(1),
prev_block_hash: prev_hash.unwrap_or_default(),
transactions,
data: additional_data,
};
block_data.into()
}
pub fn produce_dummy_empty_transaction() -> Transaction {
let body = TransactionBody {
tx_kind: TxKind::Public,
execution_input: Default::default(),
execution_output: Default::default(),
utxo_commitments_spent_hashes: Default::default(),
utxo_commitments_created_hashes: Default::default(),
nullifier_created_hashes: Default::default(),
execution_proof_private: Default::default(),
encoded_data: Default::default(),
ephemeral_pub_key: Default::default(),
commitment: Default::default(),
tweak: Default::default(),
secret_r: Default::default(),
sc_addr: Default::default(),
};
Transaction::new(body, SignaturePrivateKey::from_slice(&[1; 32]).unwrap())
pub fn produce_dummy_empty_transaction() -> nssa::PublicTransaction {
let program_id = nssa::program::Program::authenticated_transfer_program().id();
let addresses = vec![];
let nonces = vec![];
let instruction_data: u128 = 0;
let message =
nssa::public_transaction::Message::try_new(program_id, addresses, nonces, instruction_data)
.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)
}
pub fn create_dummy_private_transaction_random_signer(
nullifier_created_hashes: Vec<[u8; 32]>,
utxo_commitments_spent_hashes: Vec<[u8; 32]>,
utxo_commitments_created_hashes: Vec<[u8; 32]>,
) -> Transaction {
let mut rng = rand::thread_rng();
let body = TransactionBody {
tx_kind: TxKind::Private,
execution_input: vec![],
execution_output: vec![],
utxo_commitments_spent_hashes,
utxo_commitments_created_hashes,
nullifier_created_hashes,
execution_proof_private: "dummy_proof".to_string(),
encoded_data: vec![],
ephemeral_pub_key: vec![10, 11, 12],
commitment: vec![],
tweak: Tweak::new(&mut rng),
secret_r: [0; 32],
sc_addr: "sc_addr".to_string(),
};
Transaction::new(body, SignaturePrivateKey::random(&mut rng))
}
pub fn create_dummy_transaction_native_token_transfer(
pub fn create_transaction_native_token_transfer(
from: [u8; 32],
nonce: u64,
nonce: u128,
to: [u8; 32],
balance_to_move: u64,
signing_key: SigningKey,
) -> Transaction {
let mut rng = rand::thread_rng();
let native_token_transfer = PublicNativeTokenSend {
from,
nonce,
to,
balance_to_move,
};
let body = TransactionBody {
tx_kind: TxKind::Public,
execution_input: serde_json::to_vec(&native_token_transfer).unwrap(),
execution_output: vec![],
utxo_commitments_spent_hashes: vec![],
utxo_commitments_created_hashes: vec![],
nullifier_created_hashes: vec![],
execution_proof_private: "".to_string(),
encoded_data: vec![],
ephemeral_pub_key: vec![10, 11, 12],
commitment: vec![],
tweak: Tweak::new(&mut rng),
secret_r: [0; 32],
sc_addr: "sc_addr".to_string(),
};
Transaction::new(body, signing_key)
balance_to_move: u128,
signing_key: nssa::PrivateKey,
) -> nssa::PublicTransaction {
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();
let message =
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)
}

View File

@ -36,5 +36,5 @@ path = "../wallet"
[dependencies.common]
path = "../common"
[dependencies.accounts]
path = "../accounts"
[dependencies.key_protocol]
path = "../key_protocol"

View File

@ -8,12 +8,12 @@
"port": 3040,
"initial_accounts": [
{
"addr": "0d96dfcc414019380c9dde0cd3dce5aac90fb5443bf871108741aeafde552ad7",
"addr": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
"balance": 10000
},
{
"addr": "974870e9be8d0ac08aa83b3fc7a7a686291d8732508aba98b36080f39c2cf364",
"addr": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
"balance": 20000
}
]
}
}

View File

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

View File

@ -20,8 +20,8 @@ struct Args {
test_name: String,
}
pub const ACC_SENDER: &str = "0d96dfcc414019380c9dde0cd3dce5aac90fb5443bf871108741aeafde552ad7";
pub const ACC_RECEIVER: &str = "974870e9be8d0ac08aa83b3fc7a7a686291d8732508aba98b36080f39c2cf364";
pub const ACC_SENDER: &str = "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f";
pub const ACC_RECEIVER: &str = "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766";
pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12;
@ -156,7 +156,9 @@ pub async fn test_failure() {
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
wallet::execute_subcommand(command).await.unwrap();
let failed_send = wallet::execute_subcommand(command).await;
assert!(failed_send.is_err());
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;

View File

@ -1,5 +1,5 @@
[package]
name = "accounts"
name = "key_protocol"
version = "0.1.0"
edition = "2021"
@ -17,9 +17,10 @@ hex.workspace = true
aes-gcm.workspace = true
lazy_static.workspace = true
tiny-keccak.workspace = true
[dependencies.utxo]
path = "../utxo"
nssa-core = { path = "../nssa/core" }
[dependencies.common]
path = "../common"
[dependencies.nssa]
path = "../nssa"

View File

@ -1,13 +1,14 @@
use std::collections::HashMap;
use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit};
use constants_types::{CipherText, Nonce};
use elliptic_curve::point::AffineCoordinates;
use k256::{ecdsa::SigningKey, AffinePoint, FieldBytes};
use k256::AffinePoint;
use log::info;
use rand::{rngs::OsRng, RngCore};
use secret_holders::{SeedHolder, TopSecretKeyHolder, UTXOSecretKeyHolder};
use serde::{Deserialize, Serialize};
use crate::account_core::PublicKey;
use crate::key_protocol_core::PublicKey;
pub type PublicAccountSigningKey = [u8; 32];
pub mod constants_types;
@ -16,15 +17,16 @@ pub mod secret_holders;
#[derive(Serialize, Deserialize, Clone, Debug)]
///Entrypoint to key management
pub struct AddressKeyHolder {
pub struct KeyChain {
top_secret_key_holder: TopSecretKeyHolder,
pub utxo_secret_key_holder: UTXOSecretKeyHolder,
pub_account_signing_key: PublicAccountSigningKey,
///Map for all users accounts
pub_account_signing_keys: HashMap<nssa::Address, nssa::PrivateKey>,
pub nullifer_public_key: PublicKey,
pub viewing_public_key: PublicKey,
}
impl AddressKeyHolder {
impl KeyChain {
pub fn new_os_random() -> Self {
//Currently dropping SeedHolder at the end of initialization.
//Now entirely sure if we need it in the future.
@ -36,26 +38,50 @@ impl AddressKeyHolder {
let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key();
let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key();
let pub_account_signing_key = {
let mut bytes = [0; 32];
OsRng.fill_bytes(&mut bytes);
bytes
};
Self {
top_secret_key_holder,
utxo_secret_key_holder,
nullifer_public_key,
viewing_public_key,
pub_account_signing_keys: HashMap::new(),
}
}
pub fn new_os_random_with_accounts(accounts: HashMap<nssa::Address, nssa::PrivateKey>) -> Self {
//Currently dropping SeedHolder at the end of initialization.
//Now entirely sure if we need it in the future.
let seed_holder = SeedHolder::new_os_random();
let top_secret_key_holder = seed_holder.produce_top_secret_key_holder();
let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder();
let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key();
let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key();
Self {
top_secret_key_holder,
utxo_secret_key_holder,
nullifer_public_key,
viewing_public_key,
pub_account_signing_key,
pub_account_signing_keys: accounts,
}
}
pub fn generate_new_private_key(&mut self) -> nssa::Address {
let private_key = nssa::PrivateKey::new_os_random();
let address = nssa::Address::from(&nssa::PublicKey::new_from_private_key(&private_key));
self.pub_account_signing_keys.insert(address, private_key);
address
}
/// Returns the signing key for public transaction signatures
pub fn get_pub_account_signing_key(&self) -> SigningKey {
let field_bytes = FieldBytes::from_slice(&self.pub_account_signing_key);
// TODO: remove unwrap
SigningKey::from_bytes(field_bytes).unwrap()
pub fn get_pub_account_signing_key(
&self,
address: &nssa::Address,
) -> Option<&nssa::PrivateKey> {
self.pub_account_signing_keys.get(address)
}
pub fn calculate_shared_secret_receiver(
@ -120,14 +146,14 @@ mod tests {
use elliptic_curve::point::AffineCoordinates;
use k256::{AffinePoint, ProjectivePoint, Scalar};
use crate::{account_core::address, key_management::ephemeral_key_holder::EphemeralKeyHolder};
use crate::key_management::ephemeral_key_holder::EphemeralKeyHolder;
use super::*;
#[test]
fn test_new_os_random() {
// Ensure that a new AddressKeyHolder instance can be created without errors.
let address_key_holder = AddressKeyHolder::new_os_random();
// Ensure that a new KeyChain instance can be created without errors.
let address_key_holder = KeyChain::new_os_random();
// Check that key holder fields are initialized with expected types
assert!(!Into::<bool>::into(
@ -140,7 +166,7 @@ mod tests {
#[test]
fn test_calculate_shared_secret_receiver() {
let address_key_holder = AddressKeyHolder::new_os_random();
let address_key_holder = KeyChain::new_os_random();
// Generate a random ephemeral public key sender
let scalar = Scalar::random(&mut OsRng);
@ -156,7 +182,7 @@ mod tests {
#[test]
fn test_decrypt_data() {
let address_key_holder = AddressKeyHolder::new_os_random();
let address_key_holder = KeyChain::new_os_random();
// Generate an ephemeral key and shared secret
let ephemeral_public_key_sender =
@ -187,8 +213,8 @@ mod tests {
#[test]
fn test_new_os_random_initialization() {
// Ensure that AddressKeyHolder is initialized correctly
let address_key_holder = AddressKeyHolder::new_os_random();
// Ensure that KeyChain is initialized correctly
let address_key_holder = KeyChain::new_os_random();
// Check that key holder fields are initialized with expected types and values
assert!(!Into::<bool>::into(
@ -201,7 +227,7 @@ mod tests {
#[test]
fn test_calculate_shared_secret_with_identity_point() {
let address_key_holder = AddressKeyHolder::new_os_random();
let address_key_holder = KeyChain::new_os_random();
// Use identity point as ephemeral public key
let identity_point = AffinePoint::identity();
@ -216,7 +242,7 @@ mod tests {
#[test]
#[should_panic]
fn test_decrypt_data_with_incorrect_nonce() {
let address_key_holder = AddressKeyHolder::new_os_random();
let address_key_holder = KeyChain::new_os_random();
// Generate ephemeral public key and shared secret
let scalar = Scalar::random(OsRng);
@ -249,7 +275,7 @@ mod tests {
#[test]
#[should_panic]
fn test_decrypt_data_with_incorrect_ciphertext() {
let address_key_holder = AddressKeyHolder::new_os_random();
let address_key_holder = KeyChain::new_os_random();
// Generate ephemeral public key and shared secret
let scalar = Scalar::random(OsRng);
@ -284,7 +310,7 @@ mod tests {
#[test]
fn test_encryption_decryption_round_trip() {
let address_key_holder = AddressKeyHolder::new_os_random();
let address_key_holder = KeyChain::new_os_random();
// Generate ephemeral key and shared secret
let scalar = Scalar::random(OsRng);
@ -302,7 +328,7 @@ mod tests {
.encrypt(nonce, plaintext.as_ref())
.expect("encryption failure");
// Decrypt the data using the `AddressKeyHolder` instance
// Decrypt the data using the `KeyChain` instance
let decrypted_data = address_key_holder
.decrypt_data(
ephemeral_public_key_sender,
@ -317,12 +343,15 @@ mod tests {
#[test]
fn test_get_public_account_signing_key() {
let address_key_holder = AddressKeyHolder::new_os_random();
let signing_key = address_key_holder.get_pub_account_signing_key();
assert_eq!(
signing_key.to_bytes().as_slice(),
address_key_holder.pub_account_signing_key
);
let mut address_key_holder = KeyChain::new_os_random();
let address = address_key_holder.generate_new_private_key();
let is_private_key_generated = address_key_holder
.get_pub_account_signing_key(&address)
.is_some();
assert!(is_private_key_generated);
}
#[test]
@ -335,19 +364,11 @@ mod tests {
let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key();
let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key();
let pub_account_signing_key = {
let mut bytes = [0; 32];
OsRng.fill_bytes(&mut bytes);
bytes
};
let pub_account_signing_key = nssa::PrivateKey::new_os_random();
//Address is a Keccak(verification_key)
let field_bytes = FieldBytes::from_slice(&pub_account_signing_key);
let signing_key = SigningKey::from_bytes(field_bytes).unwrap();
let public_key = nssa::PublicKey::new_from_private_key(&pub_account_signing_key);
let verifying_key = signing_key.verifying_key();
let address = address::from_public_key(verifying_key);
let address = nssa::Address::from(&public_key);
println!("======Prerequisites======");
println!();
@ -373,7 +394,7 @@ mod tests {
println!("======Public data======");
println!();
println!("Address{:?}", hex::encode(address));
println!("Address{:?}", hex::encode(address.value()));
println!(
"Nulifier public key {:?}",
hex::encode(serde_json::to_vec(&nullifer_public_key).unwrap())

View File

@ -0,0 +1,171 @@
use std::collections::HashMap;
use anyhow::Result;
use k256::AffinePoint;
use serde::{Deserialize, Serialize};
use crate::key_management::{
constants_types::{CipherText, Nonce},
ephemeral_key_holder::EphemeralKeyHolder,
KeyChain,
};
pub type PublicKey = AffinePoint;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NSSAUserData {
pub key_holder: KeyChain,
pub accounts: HashMap<nssa::Address, nssa_core::account::Account>,
}
///A strucure, which represents all the visible(public) information
///
/// known to each node about account `address`
///
/// Main usage is to encode data for other account
#[derive(Serialize, Clone)]
pub struct NSSAUserDataPublicMask {
pub nullifier_public_key: AffinePoint,
pub viewing_public_key: AffinePoint,
}
impl NSSAUserDataPublicMask {
pub fn encrypt_data(
ephemeral_key_holder: &EphemeralKeyHolder,
viewing_public_key_receiver: AffinePoint,
data: &[u8],
) -> (CipherText, Nonce) {
//Using of parent NSSAUserData fuction
NSSAUserData::encrypt_data(ephemeral_key_holder, viewing_public_key_receiver, data)
}
//ToDo: Part of a private keys update
// pub fn make_tag(&self) -> Tag {
// self.address.value()[0]
// }
}
impl NSSAUserData {
pub fn new() -> Self {
let key_holder = KeyChain::new_os_random();
Self {
key_holder,
accounts: HashMap::new(),
}
}
pub fn new_with_accounts(
accounts_keys: HashMap<nssa::Address, nssa::PrivateKey>,
accounts: HashMap<nssa::Address, nssa_core::account::Account>,
) -> Self {
let key_holder = KeyChain::new_os_random_with_accounts(accounts_keys);
Self {
key_holder,
accounts,
}
}
pub fn generate_new_account(&mut self) -> nssa::Address {
let address = self.key_holder.generate_new_private_key();
self.accounts
.insert(address, nssa_core::account::Account::default());
address
}
pub fn get_account_balance(&self, address: &nssa::Address) -> u128 {
self.accounts
.get(address)
.map(|acc| acc.balance)
.unwrap_or(0)
}
pub fn get_account(&self, address: &nssa::Address) -> Option<&nssa_core::account::Account> {
self.accounts.get(address)
}
pub fn get_account_signing_key(&self, address: &nssa::Address) -> Option<&nssa::PrivateKey> {
self.key_holder.get_pub_account_signing_key(address)
}
pub fn encrypt_data(
ephemeral_key_holder: &EphemeralKeyHolder,
viewing_public_key_receiver: AffinePoint,
data: &[u8],
) -> (CipherText, Nonce) {
ephemeral_key_holder.encrypt_data(viewing_public_key_receiver, data)
}
pub fn decrypt_data(
&self,
ephemeral_public_key_sender: AffinePoint,
ciphertext: CipherText,
nonce: Nonce,
) -> Result<Vec<u8>, aes_gcm::Error> {
self.key_holder
.decrypt_data(ephemeral_public_key_sender, ciphertext, nonce)
}
pub fn update_account_balance(&mut self, address: nssa::Address, new_balance: u128) {
self.accounts
.entry(address)
.and_modify(|acc| acc.balance = new_balance)
.or_default();
}
//ToDo: Part of a private keys update
// pub fn make_tag(&self) -> Tag {
// self.address.value()[0]
// }
///Produce account public mask
pub fn make_account_public_mask(&self) -> NSSAUserDataPublicMask {
NSSAUserDataPublicMask {
nullifier_public_key: self.key_holder.nullifer_public_key,
viewing_public_key: self.key_holder.viewing_public_key,
}
}
}
impl Default for NSSAUserData {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_account() {
let mut user_data = NSSAUserData::new();
let addr = user_data.generate_new_account();
assert_eq!(user_data.get_account_balance(&addr), 0);
}
#[test]
fn test_update_balance() {
let mut user_data = NSSAUserData::new();
let address = user_data.generate_new_account();
user_data.update_account_balance(address, 500);
assert_eq!(user_data.get_account_balance(&address), 500);
}
//ToDo: Part of a private keys update
// #[test]
// fn accounts_accounts_mask_tag_consistency() {
// let account = NSSAUserData::new();
// let account_mask = account.make_account_public_mask();
// assert_eq!(account.make_tag(), account_mask.make_tag());
// }
}

2
key_protocol/src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod key_management;
pub mod key_protocol_core;

View File

@ -1,14 +1,10 @@
use std::collections::VecDeque;
use mempoolitem::MemPoolItem;
pub mod mempoolitem;
pub struct MemPool<Item: MemPoolItem> {
pub struct MemPool<Item> {
items: VecDeque<Item>,
}
impl<Item: MemPoolItem> MemPool<Item> {
impl<Item> MemPool<Item> {
pub fn new() -> Self {
Self {
items: VecDeque::new(),
@ -55,7 +51,7 @@ impl<Item: MemPoolItem> MemPool<Item> {
}
}
impl<Item: MemPoolItem> Default for MemPool<Item> {
impl<Item> Default for MemPool<Item> {
fn default() -> Self {
Self::new()
}
@ -74,14 +70,6 @@ mod tests {
id: ItemId,
}
impl MemPoolItem for TestItem {
type Identifier = ItemId;
fn identifier(&self) -> Self::Identifier {
self.id
}
}
fn test_item_with_id(id: u64) -> TestItem {
TestItem { id }
}

View File

@ -1,4 +0,0 @@
pub trait MemPoolItem {
type Identifier;
fn identifier(&self) -> Self::Identifier;
}

19
nssa/Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "nssa"
version = "0.1.0"
edition = "2024"
[dependencies]
thiserror = "2.0.12"
risc0-zkvm = "2.3.1"
nssa-core = { path = "core" }
program-methods = { path = "program_methods" }
serde = "1.0.219"
sha2 = "0.10.9"
secp256k1 = "0.31.1"
rand = "0.8"
hex = "0.4.3"
anyhow.workspace = true
[dev-dependencies]
test-program-methods = { path = "test_program_methods" }

8
nssa/core/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "nssa-core"
version = "0.1.0"
edition = "2024"
[dependencies]
risc0-zkvm = "2.3.1"
serde = { version = "1.0", default-features = false }

View File

@ -0,0 +1,56 @@
use serde::{Deserialize, Serialize};
use crate::program::ProgramId;
pub type Nonce = u128;
type Data = Vec<u8>;
/// Account to be used both in public and private contexts
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Account {
pub program_owner: ProgramId,
pub balance: u128,
pub data: Data,
pub nonce: Nonce,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct AccountWithMetadata {
pub account: Account,
pub is_authorized: bool,
}
#[cfg(test)]
mod tests {
use crate::program::DEFAULT_PROGRAM_ID;
use super::*;
#[test]
fn test_zero_balance_account_data_creation() {
let new_acc = Account::default();
assert_eq!(new_acc.balance, 0);
}
#[test]
fn test_zero_nonce_account_data_creation() {
let new_acc = Account::default();
assert_eq!(new_acc.nonce, 0);
}
#[test]
fn test_empty_data_account_data_creation() {
let new_acc = Account::default();
assert!(new_acc.data.is_empty());
}
#[test]
fn test_default_program_owner_account_data_creation() {
let new_acc = Account::default();
assert_eq!(new_acc.program_owner, DEFAULT_PROGRAM_ID);
}
}

2
nssa/core/src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod account;
pub mod program;

64
nssa/core/src/program.rs Normal file
View File

@ -0,0 +1,64 @@
use crate::account::{Account, AccountWithMetadata};
use risc0_zkvm::serde::Deserializer;
use risc0_zkvm::{DeserializeOwned, guest::env};
pub type ProgramId = [u32; 8];
pub type InstructionData = Vec<u32>;
pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8];
pub fn read_nssa_inputs<T: DeserializeOwned>() -> (Vec<AccountWithMetadata>, T) {
let pre_states: Vec<AccountWithMetadata> = env::read();
let words: InstructionData = env::read();
let instruction_data = T::deserialize(&mut Deserializer::new(words.as_ref())).unwrap();
(pre_states, instruction_data)
}
/// Validates well-behaved program execution
///
/// # Parameters
/// - `pre_states`: The list of input accounts, each annotated with authorization metadata.
/// - `post_states`: The list of resulting accounts after executing the program logic.
/// - `executing_program_id`: The identifier of the program that was executed.
pub fn validate_execution(
pre_states: &[AccountWithMetadata],
post_states: &[Account],
executing_program_id: ProgramId,
) -> bool {
// 1. Lengths must match
if pre_states.len() != post_states.len() {
return false;
}
for (pre, post) in pre_states.iter().zip(post_states) {
// 2. Nonce must remain unchanged
if pre.account.nonce != post.nonce {
return false;
}
// 3. Ownership change only allowed from default accounts
if pre.account.program_owner != post.program_owner && pre.account != Account::default() {
return false;
}
// 4. Decreasing balance only allowed if owned by executing program
if post.balance < pre.account.balance && pre.account.program_owner != executing_program_id {
return false;
}
// 5. Data changes only allowed if owned by executing program
if pre.account.data != post.data
&& (executing_program_id != pre.account.program_owner
|| executing_program_id != post.program_owner)
{
return false;
}
}
// 6. Total balance is preserved
let total_balance_pre_states: u128 = pre_states.iter().map(|pre| pre.account.balance).sum();
let total_balance_post_states: u128 = post_states.iter().map(|post| post.balance).sum();
if total_balance_pre_states != total_balance_post_states {
return false;
}
true
}

View File

@ -1,10 +1,10 @@
[package]
name = "test-methods"
name = "program-methods"
version = "0.1.0"
edition = "2021"
[build-dependencies]
risc0-build = { git = "https://github.com/risc0/risc0.git", branch = "release-2.3" }
risc0-build = { version = "2.3.1" }
[package.metadata.risc0]
methods = ["guest"]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
[package]
name = "programs"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
risc0-zkvm = { version = "2.3.1", default-features = false, features = ['std'] }
nssa-core = { path = "../../core" }

View File

@ -0,0 +1,36 @@
use nssa_core::program::read_nssa_inputs;
use risc0_zkvm::guest::env;
type Instruction = u128;
/// A transfer of balance program.
/// To be used both in public and private contexts.
fn main() {
// Read input accounts.
// It is expected to receive only two accounts: [sender_account, receiver_account]
let (input_accounts, balance_to_move) = read_nssa_inputs::<Instruction>();
// Continue only if input_accounts is an array of two elements
let [sender, receiver] = match input_accounts.try_into() {
Ok(array) => array,
Err(_) => return,
};
// Continue only if the sender has authorized this operation
if !sender.is_authorized {
return;
}
// Continue only if the sender has enough balance
if sender.account.balance < balance_to_move {
return;
}
// Create accounts post states, with updated balances
let mut sender_post = sender.account.clone();
let mut receiver_post = receiver.account.clone();
sender_post.balance -= balance_to_move;
receiver_post.balance += balance_to_move;
env::commit(&vec![sender_post, receiver_post]);
}

4
nssa/rust-toolchain.toml Normal file
View File

@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rustfmt", "rust-src"]
profile = "minimal"

130
nssa/src/address.rs Normal file
View File

@ -0,0 +1,130 @@
use std::{fmt::Display, str::FromStr};
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use crate::signature::PublicKey;
pub const LENGTH_MISMATCH_ERROR_MESSAGE: &str = "Slice length != 32 ";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Address {
value: [u8; 32],
}
impl Address {
pub fn new(value: [u8; 32]) -> Self {
Self { value }
}
pub fn value(&self) -> &[u8; 32] {
&self.value
}
}
impl AsRef<[u8]> for Address {
fn as_ref(&self) -> &[u8] {
&self.value
}
}
impl TryFrom<Vec<u8>> for Address {
type Error = anyhow::Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
let addr_val: [u8; 32] = value
.try_into()
.map_err(|_| anyhow!(LENGTH_MISMATCH_ERROR_MESSAGE))?;
Ok(Address::new(addr_val))
}
}
impl From<&PublicKey> for Address {
fn from(value: &PublicKey) -> Self {
// TODO: Check specs
Self::new(*value.value())
}
}
#[derive(Debug, thiserror::Error)]
pub enum AddressError {
#[error("invalid hex")]
InvalidHex(#[from] hex::FromHexError),
#[error("invalid length: expected 32 bytes, got {0}")]
InvalidLength(usize),
}
impl FromStr for Address {
type Err = AddressError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = hex::decode(s)?;
if bytes.len() != 32 {
return Err(AddressError::InvalidLength(bytes.len()));
}
let mut value = [0u8; 32];
value.copy_from_slice(&bytes);
Ok(Address { value })
}
}
impl Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", hex::encode(self.value))
}
}
impl Serialize for Address {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let hex_string = self.to_string();
hex_string.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Address {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let hex_string = String::deserialize(deserializer)?;
Address::from_str(&hex_string).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use crate::{Address, address::AddressError};
#[test]
fn parse_valid_address() {
let hex_str = "00".repeat(32); // 64 hex chars = 32 bytes
let addr: Address = hex_str.parse().unwrap();
assert_eq!(addr.value, [0u8; 32]);
}
#[test]
fn parse_invalid_hex() {
let hex_str = "zz".repeat(32); // invalid hex chars
let result = hex_str.parse::<Address>().unwrap_err();
assert!(matches!(result, AddressError::InvalidHex(_)));
}
#[test]
fn parse_wrong_length_short() {
let hex_str = "00".repeat(31); // 62 chars = 31 bytes
let result = hex_str.parse::<Address>().unwrap_err();
assert!(matches!(result, AddressError::InvalidLength(_)));
}
#[test]
fn parse_wrong_length_long() {
let hex_str = "00".repeat(33); // 66 chars = 33 bytes
let result = hex_str.parse::<Address>().unwrap_err();
assert!(matches!(result, AddressError::InvalidLength(_)));
}
}

27
nssa/src/error.rs Normal file
View File

@ -0,0 +1,27 @@
use std::io;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum NssaError {
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Risc0 error: {0}")]
ProgramExecutionFailed(String),
#[error("Program violated execution rules")]
InvalidProgramBehavior,
#[error("Serialization error: {0}")]
InstructionSerializationError(String),
#[error("Invalid private key")]
InvalidPrivateKey,
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Invalid Public Key")]
InvalidPublicKey,
}

13
nssa/src/lib.rs Normal file
View File

@ -0,0 +1,13 @@
pub mod address;
pub mod error;
pub mod program;
pub mod public_transaction;
mod signature;
mod state;
pub use address::Address;
pub use public_transaction::PublicTransaction;
pub use signature::PrivateKey;
pub use signature::PublicKey;
pub use signature::Signature;
pub use state::V01State;

206
nssa/src/program.rs Normal file
View File

@ -0,0 +1,206 @@
use nssa_core::{
account::{Account, AccountWithMetadata},
program::{DEFAULT_PROGRAM_ID, InstructionData, ProgramId},
};
use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID};
use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec};
use serde::Serialize;
use crate::error::NssaError;
#[derive(Debug, PartialEq, Eq)]
pub struct Program {
id: ProgramId,
elf: &'static [u8],
}
impl Program {
pub fn id(&self) -> ProgramId {
self.id
}
pub fn serialize_instruction<T: Serialize>(
instruction: T,
) -> Result<InstructionData, NssaError> {
to_vec(&instruction).map_err(|e| NssaError::InstructionSerializationError(e.to_string()))
}
pub(crate) fn execute(
&self,
pre_states: &[AccountWithMetadata],
instruction_data: &InstructionData,
) -> Result<Vec<Account>, NssaError> {
// Write inputs to the program
let mut env_builder = ExecutorEnv::builder();
Self::write_inputs(pre_states, instruction_data, &mut env_builder)?;
let env = env_builder.build().unwrap();
// Execute the program (without proving)
let executor = default_executor();
let session_info = executor
.execute(env, self.elf)
.map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?;
// Get outputs
let mut post_states: Vec<Account> = session_info
.journal
.decode()
.map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?;
// Claim any output account with default program owner field
for account in post_states.iter_mut() {
if account.program_owner == DEFAULT_PROGRAM_ID {
account.program_owner = self.id;
}
}
Ok(post_states)
}
/// Writes inputs to `env_builder` in the order expected by the programs
fn write_inputs(
pre_states: &[AccountWithMetadata],
instruction_data: &[u32],
env_builder: &mut ExecutorEnvBuilder,
) -> Result<(), NssaError> {
let pre_states = pre_states.to_vec();
env_builder
.write(&(pre_states, instruction_data))
.map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?;
Ok(())
}
pub fn authenticated_transfer_program() -> Self {
Self {
id: AUTHENTICATED_TRANSFER_ID,
elf: AUTHENTICATED_TRANSFER_ELF,
}
}
}
#[cfg(test)]
mod tests {
use nssa_core::account::{Account, AccountWithMetadata};
use crate::program::Program;
impl Program {
/// A program that changes the nonce of an account
pub fn nonce_changer_program() -> Self {
use test_program_methods::{NONCE_CHANGER_ELF, NONCE_CHANGER_ID};
Program {
id: NONCE_CHANGER_ID,
elf: NONCE_CHANGER_ELF,
}
}
/// A program that produces more output accounts than the inputs it received
pub fn extra_output_program() -> Self {
use test_program_methods::{EXTRA_OUTPUT_ELF, EXTRA_OUTPUT_ID};
Program {
id: EXTRA_OUTPUT_ID,
elf: EXTRA_OUTPUT_ELF,
}
}
/// A program that produces less output accounts than the inputs it received
pub fn missing_output_program() -> Self {
use test_program_methods::{MISSING_OUTPUT_ELF, MISSING_OUTPUT_ID};
Program {
id: MISSING_OUTPUT_ID,
elf: MISSING_OUTPUT_ELF,
}
}
/// A program that changes the program owner of an account to [0, 1, 2, 3, 4, 5, 6, 7]
pub fn program_owner_changer() -> Self {
use test_program_methods::{PROGRAM_OWNER_CHANGER_ELF, PROGRAM_OWNER_CHANGER_ID};
Program {
id: PROGRAM_OWNER_CHANGER_ID,
elf: PROGRAM_OWNER_CHANGER_ELF,
}
}
/// A program that transfers balance without caring about authorizations
pub fn simple_balance_transfer() -> Self {
use test_program_methods::{SIMPLE_BALANCE_TRANSFER_ELF, SIMPLE_BALANCE_TRANSFER_ID};
Program {
id: SIMPLE_BALANCE_TRANSFER_ID,
elf: SIMPLE_BALANCE_TRANSFER_ELF,
}
}
/// A program that modifies the data of an account
pub fn data_changer() -> Self {
use test_program_methods::{DATA_CHANGER_ELF, DATA_CHANGER_ID};
Program {
id: DATA_CHANGER_ID,
elf: DATA_CHANGER_ELF,
}
}
/// A program that mints balance
pub fn minter() -> Self {
use test_program_methods::{MINTER_ELF, MINTER_ID};
Program {
id: MINTER_ID,
elf: MINTER_ELF,
}
}
/// A program that burns balance
pub fn burner() -> Self {
use test_program_methods::{BURNER_ELF, BURNER_ID};
Program {
id: BURNER_ID,
elf: BURNER_ELF,
}
}
}
#[test]
fn test_program_execution() {
let program = Program::simple_balance_transfer();
let balance_to_move: u128 = 11223344556677;
let instruction_data = Program::serialize_instruction(balance_to_move).unwrap();
let sender = AccountWithMetadata {
account: Account {
balance: 77665544332211,
..Account::default()
},
is_authorized: false,
};
let recipient = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
let expected_sender_post = Account {
balance: 77665544332211 - balance_to_move,
program_owner: program.id(),
..Account::default()
};
let expected_recipient_post = Account {
balance: balance_to_move,
// Program claims the account since the pre_state has default prorgam owner
program_owner: program.id(),
..Account::default()
};
let [sender_post, recipient_post] = program
.execute(&[sender, recipient], &instruction_data)
.unwrap()
.try_into()
.unwrap();
assert_eq!(sender_post, expected_sender_post);
assert_eq!(recipient_post, expected_recipient_post);
}
}

View File

@ -0,0 +1,145 @@
use std::io::{Cursor, Read};
use nssa_core::program::ProgramId;
use crate::{
Address, PublicKey, PublicTransaction, Signature,
error::NssaError,
public_transaction::{Message, WitnessSet},
};
const MESSAGE_ENCODING_PREFIX_LEN: usize = 19;
const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"NSSA/v0.1/TxMessage";
impl Message {
/// Serializes a `Message` into bytes in the following layout:
/// PREFIX || <program_id> (4 bytes LE) * 8 || addresses_len (4 bytes LE) || addresses (32 bytes * N) || nonces_len (4 bytes LE) || nonces (16 bytes LE * M) || instruction_data_len || instruction_data (4 bytes LE * K)
/// Integers and words are encoded in little-endian byte order, and fields appear in the above order.
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut bytes = MESSAGE_ENCODING_PREFIX.to_vec();
// program_id: [u32; 8]
for word in &self.program_id {
bytes.extend_from_slice(&word.to_le_bytes());
}
// addresses: Vec<[u8;32]>
// serialize length as u32 little endian, then all addresses concatenated
let addresses_len = self.addresses.len() as u32;
bytes.extend(&addresses_len.to_le_bytes());
for addr in &self.addresses {
bytes.extend_from_slice(addr.value());
}
// nonces: Vec<u128>
// serialize length as u32 little endian, then all nonces concatenated in LE
let nonces_len = self.nonces.len() as u32;
bytes.extend(&nonces_len.to_le_bytes());
for nonce in &self.nonces {
bytes.extend(&nonce.to_le_bytes());
}
// instruction_data: Vec<u32>
// serialize length as u32 little endian, then all addresses concatenated
let instr_len = self.instruction_data.len() as u32;
bytes.extend(&instr_len.to_le_bytes());
for word in &self.instruction_data {
bytes.extend(&word.to_le_bytes());
}
bytes
}
pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let prefix = {
let mut this = [0u8; MESSAGE_ENCODING_PREFIX_LEN];
cursor.read_exact(&mut this)?;
this
};
assert_eq!(&prefix, MESSAGE_ENCODING_PREFIX);
let program_id: ProgramId = {
let mut this = [0u32; 8];
for item in &mut this {
*item = u32_from_cursor(cursor)?;
}
this
};
let addresses_len = u32_from_cursor(cursor)?;
let mut addresses = Vec::with_capacity(addresses_len as usize);
for _ in 0..addresses_len {
let mut value = [0u8; 32];
cursor.read_exact(&mut value)?;
addresses.push(Address::new(value))
}
let nonces_len = u32_from_cursor(cursor)?;
let mut nonces = Vec::with_capacity(nonces_len as usize);
for _ in 0..nonces_len {
let mut buf = [0u8; 16];
cursor.read_exact(&mut buf)?;
nonces.push(u128::from_le_bytes(buf))
}
let instruction_data_len = u32_from_cursor(cursor)?;
let mut instruction_data = Vec::with_capacity(instruction_data_len as usize);
for _ in 0..instruction_data_len {
let word = u32_from_cursor(cursor)?;
instruction_data.push(word)
}
Ok(Self {
program_id,
addresses,
nonces,
instruction_data,
})
}
}
impl WitnessSet {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
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
}
pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
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))
}
Ok(Self {
signatures_and_public_keys,
})
}
}
impl PublicTransaction {
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = self.message().to_bytes();
bytes.extend_from_slice(&self.witness_set().to_bytes());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, NssaError> {
let mut cursor = Cursor::new(bytes);
Self::from_cursor(&mut cursor)
}
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let message = Message::from_cursor(cursor)?;
let witness_set = WitnessSet::from_cursor(cursor)?;
Ok(PublicTransaction::new(message, witness_set))
}
}
fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<u32, NssaError> {
let mut word_buf = [0u8; 4];
cursor.read_exact(&mut word_buf)?;
Ok(u32::from_le_bytes(word_buf))
}

View File

@ -0,0 +1,32 @@
use nssa_core::{
account::Nonce,
program::{InstructionData, ProgramId},
};
use serde::Serialize;
use crate::{Address, error::NssaError, program::Program};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Message {
pub(crate) program_id: ProgramId,
pub(crate) addresses: Vec<Address>,
pub(crate) nonces: Vec<Nonce>,
pub(crate) instruction_data: InstructionData,
}
impl Message {
pub fn try_new<T: Serialize>(
program_id: ProgramId,
addresses: Vec<Address>,
nonces: Vec<Nonce>,
instruction: T,
) -> Result<Self, NssaError> {
let instruction_data = Program::serialize_instruction(instruction)?;
Ok(Self {
program_id,
addresses,
nonces,
instruction_data,
})
}
}

View File

@ -0,0 +1,8 @@
mod encoding;
mod message;
mod transaction;
mod witness_set;
pub use message::Message;
pub use transaction::PublicTransaction;
pub use witness_set::WitnessSet;

View File

@ -0,0 +1,315 @@
use std::collections::{HashMap, HashSet};
use nssa_core::{
account::{Account, AccountWithMetadata},
program::validate_execution,
};
use sha2::{Digest, digest::FixedOutput};
use crate::{
V01State,
address::Address,
error::NssaError,
public_transaction::{Message, WitnessSet},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublicTransaction {
message: Message,
witness_set: WitnessSet,
}
impl PublicTransaction {
pub fn new(message: Message, witness_set: WitnessSet) -> Self {
Self {
message,
witness_set,
}
}
pub fn message(&self) -> &Message {
&self.message
}
pub fn witness_set(&self) -> &WitnessSet {
&self.witness_set
}
pub(crate) fn signer_addresses(&self) -> Vec<Address> {
self.witness_set
.signatures_and_public_keys()
.iter()
.map(|(_, public_key)| Address::from(public_key))
.collect()
}
pub fn hash(&self) -> [u8; 32] {
let bytes = self.to_bytes();
let mut hasher = sha2::Sha256::new();
hasher.update(&bytes);
hasher.finalize_fixed().into()
}
pub(crate) fn validate_and_compute_post_states(
&self,
state: &V01State,
) -> Result<HashMap<Address, Account>, NssaError> {
let message = self.message();
let witness_set = self.witness_set();
// All addresses must be different
if message.addresses.iter().collect::<HashSet<_>>().len() != message.addresses.len() {
return Err(NssaError::InvalidInput(
"Duplicate addresses found in message".into(),
));
}
if message.nonces.len() != witness_set.signatures_and_public_keys.len() {
return Err(NssaError::InvalidInput(
"Mismatch between number of nonces and signatures/public keys".into(),
));
}
// Check the signatures are valid
if !witness_set.is_valid_for(message) {
return Err(NssaError::InvalidInput(
"Invalid signature for given message and public key".into(),
));
}
let signer_addresses = self.signer_addresses();
// Check nonces corresponds to the current nonces on the public state.
for (address, nonce) in signer_addresses.iter().zip(&message.nonces) {
let current_nonce = state.get_account_by_address(address).nonce;
if current_nonce != *nonce {
return Err(NssaError::InvalidInput("Nonce mismatch".into()));
}
}
// Build pre_states for execution
let pre_states: Vec<_> = message
.addresses
.iter()
.map(|address| AccountWithMetadata {
account: state.get_account_by_address(address),
is_authorized: signer_addresses.contains(address),
})
.collect();
// Check the `program_id` corresponds to a built-in program
// Only allowed program so far is the authenticated transfer program
let Some(program) = state.builtin_programs().get(&message.program_id) else {
return Err(NssaError::InvalidInput("Unknown program".into()));
};
// // Execute program
let post_states = program.execute(&pre_states, &message.instruction_data)?;
// Verify execution corresponds to a well-behaved program.
// See the # Programs section for the definition of the `validate_execution` method.
if !validate_execution(&pre_states, &post_states, message.program_id) {
return Err(NssaError::InvalidProgramBehavior);
}
Ok(message.addresses.iter().cloned().zip(post_states).collect())
}
}
#[cfg(test)]
pub mod tests {
use sha2::{Digest, digest::FixedOutput};
use crate::{
Address, PrivateKey, PublicKey, PublicTransaction, Signature, V01State,
error::NssaError,
program::Program,
public_transaction::{Message, 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 state_for_tests() -> V01State {
let (_, _, addr1, addr2) = keys_for_tests();
let initial_data = [(addr1, 10000), (addr2, 20000)];
V01State::new_with_genesis_accounts(&initial_data)
}
fn transaction_for_tests() -> PublicTransaction {
let (key1, key2, addr1, addr2) = keys_for_tests();
let nonces = vec![0, 0];
let instruction = 1337;
let message = Message::try_new(
Program::authenticated_transfer_program().id(),
vec![addr1, addr2],
nonces,
instruction,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
PublicTransaction::new(message, witness_set)
}
#[test]
fn test_new_constructor() {
let tx = transaction_for_tests();
let message = tx.message().clone();
let witness_set = tx.witness_set().clone();
let tx_from_constructor = PublicTransaction::new(message.clone(), witness_set.clone());
assert_eq!(tx_from_constructor.message, message);
assert_eq!(tx_from_constructor.witness_set, witness_set);
}
#[test]
fn test_message_getter() {
let tx = transaction_for_tests();
assert_eq!(&tx.message, tx.message());
}
#[test]
fn test_witness_set_getter() {
let tx = transaction_for_tests();
assert_eq!(&tx.witness_set, tx.witness_set());
}
#[test]
fn test_signer_addresses() {
let tx = transaction_for_tests();
let expected_signer_addresses = vec![
Address::new([
27, 132, 197, 86, 123, 18, 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30,
24, 52, 96, 72, 25, 255, 156, 23, 245, 233, 213, 221, 7, 143,
]),
Address::new([
77, 75, 108, 209, 54, 16, 50, 202, 155, 210, 174, 185, 217, 0, 170, 77, 69, 217,
234, 216, 10, 201, 66, 51, 116, 196, 81, 167, 37, 77, 7, 102,
]),
];
let signer_addresses = tx.signer_addresses();
assert_eq!(signer_addresses, expected_signer_addresses);
}
#[test]
fn test_public_transaction_encoding_bytes_roundtrip() {
let tx = transaction_for_tests();
let bytes = tx.to_bytes();
let tx_from_bytes = PublicTransaction::from_bytes(&bytes).unwrap();
assert_eq!(tx, tx_from_bytes);
}
#[test]
fn test_hash_is_sha256_of_transaction_bytes() {
let tx = transaction_for_tests();
let hash = tx.hash();
let expected_hash: [u8; 32] = {
let bytes = tx.to_bytes();
let mut hasher = sha2::Sha256::new();
hasher.update(&bytes);
hasher.finalize_fixed().into()
};
assert_eq!(hash, expected_hash);
}
#[test]
fn test_address_list_cant_have_duplicates() {
let (key1, _, addr1, _) = keys_for_tests();
let state = state_for_tests();
let nonces = vec![0, 0];
let instruction = 1337;
let message = Message::try_new(
Program::authenticated_transfer_program().id(),
vec![addr1, addr1],
nonces,
instruction,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, &[&key1, &key1]);
let tx = PublicTransaction::new(message, witness_set);
let result = tx.validate_and_compute_post_states(&state);
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
}
#[test]
fn test_number_of_nonces_must_match_number_of_signatures() {
let (key1, key2, addr1, addr2) = keys_for_tests();
let state = state_for_tests();
let nonces = vec![0];
let instruction = 1337;
let message = Message::try_new(
Program::authenticated_transfer_program().id(),
vec![addr1, addr2],
nonces,
instruction,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
let tx = PublicTransaction::new(message, witness_set);
let result = tx.validate_and_compute_post_states(&state);
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
}
#[test]
fn test_all_signatures_must_be_valid() {
let (key1, key2, addr1, addr2) = keys_for_tests();
let state = state_for_tests();
let nonces = vec![0, 0];
let instruction = 1337;
let message = Message::try_new(
Program::authenticated_transfer_program().id(),
vec![addr1, addr2],
nonces,
instruction,
)
.unwrap();
let mut witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
witness_set.signatures_and_public_keys[0].0 = Signature::new_for_tests([1; 64]);
let tx = PublicTransaction::new(message, witness_set);
let result = tx.validate_and_compute_post_states(&state);
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
}
#[test]
fn test_nonces_must_match_the_state_current_nonces() {
let (key1, key2, addr1, addr2) = keys_for_tests();
let state = state_for_tests();
let nonces = vec![0, 1];
let instruction = 1337;
let message = Message::try_new(
Program::authenticated_transfer_program().id(),
vec![addr1, addr2],
nonces,
instruction,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
let tx = PublicTransaction::new(message, witness_set);
let result = tx.validate_and_compute_post_states(&state);
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
}
#[test]
fn test_program_id_must_belong_to_bulitin_program_ids() {
let (key1, key2, addr1, addr2) = keys_for_tests();
let state = state_for_tests();
let nonces = vec![0, 0];
let instruction = 1337;
let unknown_program_id = [0xdeadbeef; 8];
let message =
Message::try_new(unknown_program_id, vec![addr1, addr2], nonces, instruction).unwrap();
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
let tx = PublicTransaction::new(message, witness_set);
let result = tx.validate_and_compute_post_states(&state);
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
}
}

View File

@ -0,0 +1,72 @@
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)>,
}
impl WitnessSet {
pub fn for_message(message: &Message, private_keys: &[&PrivateKey]) -> Self {
let message_bytes = message.to_bytes();
let signatures_and_public_keys = private_keys
.iter()
.map(|&key| {
(
Signature::new(key, &message_bytes),
PublicKey::new_from_private_key(key),
)
})
.collect();
Self {
signatures_and_public_keys,
}
}
pub fn is_valid_for(&self, message: &Message) -> bool {
let message_bytes = message.to_bytes();
for (signature, public_key) in self.signatures_and_public_keys() {
if !signature.is_valid_for(&message_bytes, public_key) {
return false;
}
}
true
}
pub fn signatures_and_public_keys(&self) -> &[(Signature, PublicKey)] {
&self.signatures_and_public_keys
}
}
#[cfg(test)]
mod tests {
use crate::Address;
use super::*;
#[test]
fn test_for_message_constructor() {
let key1 = PrivateKey::try_new([1; 32]).unwrap();
let key2 = PrivateKey::try_new([2; 32]).unwrap();
let pubkey1 = PublicKey::new_from_private_key(&key1);
let pubkey2 = PublicKey::new_from_private_key(&key2);
let addr1 = Address::from(&pubkey1);
let addr2 = Address::from(&pubkey2);
let nonces = vec![1, 2];
let instruction = vec![1, 2, 3, 4];
let message = Message::try_new([0; 8], vec![addr1, addr2], nonces, instruction).unwrap();
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
assert_eq!(witness_set.signatures_and_public_keys.len(), 2);
let message_bytes = message.to_bytes();
for ((signature, public_key), expected_public_key) in witness_set
.signatures_and_public_keys
.into_iter()
.zip([pubkey1, pubkey2])
{
assert_eq!(public_key, expected_public_key);
assert!(signature.is_valid_for(&message_bytes, &expected_public_key));
}
}
}

View File

@ -0,0 +1,367 @@
use crate::{PrivateKey, PublicKey, Signature};
fn hex_to_bytes<const N: usize>(hex: &str) -> [u8; N] {
hex::decode(hex).unwrap().try_into().unwrap()
}
pub struct TestVector {
pub seckey: Option<PrivateKey>,
pub pubkey: PublicKey,
pub aux_rand: Option<[u8; 32]>,
pub message: Option<Vec<u8>>,
pub signature: Signature,
pub verification_result: bool,
}
/// Test vectors from
/// https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv
//
pub fn test_vectors() -> Vec<TestVector> {
vec![
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"0000000000000000000000000000000000000000000000000000000000000003",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"0000000000000000000000000000000000000000000000000000000000000000",
)),
message: Some(
hex::decode("0000000000000000000000000000000000000000000000000000000000000000")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0",
),
},
verification_result: true,
},
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"0000000000000000000000000000000000000000000000000000000000000001",
)),
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A",
),
},
verification_result: true,
},
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906",
)),
message: Some(
hex::decode("7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7",
),
},
verification_result: true,
},
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
)),
message: Some(
hex::decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3",
),
},
verification_result: true,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4",
),
},
verification_result: true,
},
// Test with invalid public key
// TestVector {
// seckey: None,
// pubkey: PublicKey::new(hex_to_bytes(
// "EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34",
// )).unwrap(),
// aux_rand: None,
// message: Some(
// hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89").unwrap(),
// ),
// signature: Signature {
// value: hex_to_bytes(
// "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B",
// ),
// },
// verification_result: false,
// },
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2",
),
},
verification_result: false,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD",
),
},
verification_result: false,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6",
),
},
verification_result: false,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051",
),
},
verification_result: false,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197",
),
},
verification_result: false,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B",
),
},
verification_result: false,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B",
),
},
verification_result: false,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
),
},
verification_result: false,
},
// Test with invalid public key
// TestVector {
// seckey: None,
// pubkey: PublicKey::new(hex_to_bytes(
// "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30",
// )).unwrap(),
// aux_rand: None,
// message: Some(
// hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89").unwrap(),
// ),
// signature: Signature {
// value: hex_to_bytes(
// "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B",
// ),
// },
// verification_result: false,
// },
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"0340034003400340034003400340034003400340034003400340034003400340",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"0000000000000000000000000000000000000000000000000000000000000000",
)),
message: None,
signature: Signature {
value: hex_to_bytes(
"71535DB165ECD9FBBC046E5FFAEA61186BB6AD436732FCCC25291A55895464CF6069CE26BF03466228F19A3A62DB8A649F2D560FAC652827D1AF0574E427AB63",
),
},
verification_result: true,
},
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"0340034003400340034003400340034003400340034003400340034003400340",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"0000000000000000000000000000000000000000000000000000000000000000",
)),
message: Some(hex::decode("11").unwrap()),
signature: Signature {
value: hex_to_bytes(
"08A20A0AFEF64124649232E0693C583AB1B9934AE63B4C3511F3AE1134C6A303EA3173BFEA6683BD101FA5AA5DBC1996FE7CACFC5A577D33EC14564CEC2BACBF",
),
},
verification_result: true,
},
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"0340034003400340034003400340034003400340034003400340034003400340",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"0000000000000000000000000000000000000000000000000000000000000000",
)),
message: Some(hex::decode("0102030405060708090A0B0C0D0E0F1011").unwrap()),
signature: Signature {
value: hex_to_bytes(
"5130F39A4059B43BC7CAC09A19ECE52B5D8699D1A71E3C52DA9AFDB6B50AC370C4A482B77BF960F8681540E25B6771ECE1E5A37FD80E5A51897C5566A97EA5A5",
),
},
verification_result: true,
},
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"0340034003400340034003400340034003400340034003400340034003400340",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"0000000000000000000000000000000000000000000000000000000000000000",
)),
message: Some(
hex::decode("99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999").unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"403B12B0D8555A344175EA7EC746566303321E5DBFA8BE6F091635163ECA79A8585ED3E3170807E7C03B720FC54C7B23897FCBA0E9D0B4A06894CFD249F22367",
),
},
verification_result: true,
},
]
}

View File

@ -0,0 +1,27 @@
use std::io::{Cursor, Read};
use crate::{PublicKey, Signature, error::NssaError};
impl PublicKey {
pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let mut value = [0u8; 32];
cursor.read_exact(&mut value)?;
Self::try_new(value)
}
pub(crate) fn to_bytes(&self) -> &[u8] {
self.value()
}
}
impl Signature {
pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let mut value = [0u8; 64];
cursor.read_exact(&mut value)?;
Ok(Self { value })
}
pub(crate) fn to_bytes(&self) -> &[u8] {
&self.value
}
}

95
nssa/src/signature/mod.rs Normal file
View File

@ -0,0 +1,95 @@
mod encoding;
mod private_key;
mod public_key;
pub use private_key::PrivateKey;
pub use public_key::PublicKey;
use rand::{RngCore, rngs::OsRng};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signature {
value: [u8; 64],
}
impl Signature {
pub fn new(key: &PrivateKey, message: &[u8]) -> Self {
let mut aux_random = [0u8; 32];
OsRng.fill_bytes(&mut aux_random);
Self::new_with_aux_random(key, message, aux_random)
}
pub(crate) fn new_with_aux_random(
key: &PrivateKey,
message: &[u8],
aux_random: [u8; 32],
) -> Self {
let value = {
let secp = secp256k1::Secp256k1::new();
let secret_key = secp256k1::SecretKey::from_byte_array(*key.value()).unwrap();
let keypair = secp256k1::Keypair::from_secret_key(&secp, &secret_key);
let signature = secp.sign_schnorr_with_aux_rand(message, &keypair, &aux_random);
signature.to_byte_array()
};
Self { value }
}
pub fn is_valid_for(&self, bytes: &[u8], public_key: &PublicKey) -> bool {
let pk = secp256k1::XOnlyPublicKey::from_byte_array(*public_key.value()).unwrap();
let secp = secp256k1::Secp256k1::new();
let sig = secp256k1::schnorr::Signature::from_byte_array(self.value);
secp.verify_schnorr(&sig, bytes, &pk).is_ok()
}
}
#[cfg(test)]
mod bip340_test_vectors;
#[cfg(test)]
mod tests {
use crate::{Signature, signature::bip340_test_vectors};
impl Signature {
pub(crate) fn new_for_tests(value: [u8; 64]) -> Self {
Self { value }
}
}
#[test]
fn test_signature_generation_from_bip340_test_vectors() {
for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() {
let Some(private_key) = test_vector.seckey else {
continue;
};
let Some(aux_random) = test_vector.aux_rand else {
continue;
};
let Some(message) = test_vector.message else {
continue;
};
if !test_vector.verification_result {
continue;
}
let expected_signature = &test_vector.signature;
let signature = Signature::new_with_aux_random(&private_key, &message, aux_random);
assert_eq!(&signature, expected_signature, "Failed test vector {i}");
}
}
#[test]
fn test_signature_verification_from_bip340_test_vectors() {
for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() {
let message = test_vector.message.unwrap_or(vec![]);
let expected_result = test_vector.verification_result;
let result = test_vector
.signature
.is_valid_for(&message, &test_vector.pubkey);
assert_eq!(result, expected_result, "Failed test vector {i}");
}
}
}

View File

@ -0,0 +1,54 @@
use rand::{Rng, rngs::OsRng};
use serde::{Deserialize, Serialize};
use crate::error::NssaError;
// TODO: Remove Debug, Clone, Serialize, Deserialize, PartialEq and Eq for security reasons
// TODO: Implement Zeroize
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PrivateKey([u8; 32]);
impl PrivateKey {
pub fn new_os_random() -> Self {
let mut rng = OsRng;
loop {
match Self::try_new(rng.r#gen()) {
Ok(key) => break key,
Err(_) => continue,
};
}
}
fn is_valid_key(value: [u8; 32]) -> bool {
secp256k1::SecretKey::from_byte_array(value).is_ok()
}
pub fn try_new(value: [u8; 32]) -> Result<Self, NssaError> {
if Self::is_valid_key(value) {
Ok(Self(value))
} else {
Err(NssaError::InvalidPrivateKey)
}
}
pub fn value(&self) -> &[u8; 32] {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_value_getter() {
let key = PrivateKey::try_new([1; 32]).unwrap();
assert_eq!(key.value(), &key.0);
}
#[test]
fn test_produce_key() {
let key = PrivateKey::new_os_random();
println!("{:?}", key.0);
}
}

View File

@ -0,0 +1,81 @@
use crate::{PrivateKey, error::NssaError};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublicKey([u8; 32]);
impl PublicKey {
pub fn new_from_private_key(key: &PrivateKey) -> Self {
let value = {
let secret_key = secp256k1::SecretKey::from_byte_array(*key.value()).unwrap();
let public_key =
secp256k1::PublicKey::from_secret_key(&secp256k1::Secp256k1::new(), &secret_key);
let (x_only, _) = public_key.x_only_public_key();
x_only.serialize()
};
Self(value)
}
pub(super) fn try_new(value: [u8; 32]) -> Result<Self, NssaError> {
// Check point is valid
let _ = secp256k1::XOnlyPublicKey::from_byte_array(value)
.map_err(|_| NssaError::InvalidPublicKey)?;
Ok(Self(value))
}
pub fn value(&self) -> &[u8; 32] {
&self.0
}
}
#[cfg(test)]
mod test {
use crate::{PublicKey, error::NssaError, signature::bip340_test_vectors};
#[test]
fn test_try_new_invalid_public_key_from_bip340_test_vectors_5() {
let value_invalid_key = [
238, 253, 234, 76, 219, 103, 119, 80, 164, 32, 254, 232, 7, 234, 207, 33, 235, 152,
152, 174, 121, 185, 118, 135, 102, 228, 250, 160, 74, 45, 74, 52,
];
let result = PublicKey::try_new(value_invalid_key);
assert!(matches!(result, Err(NssaError::InvalidPublicKey)));
}
#[test]
fn test_try_new_invalid_public_key_from_bip340_test_vector_14() {
let value_invalid_key = [
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 255, 255, 252, 48,
];
let result = PublicKey::try_new(value_invalid_key);
assert!(matches!(result, Err(NssaError::InvalidPublicKey)));
}
#[test]
fn test_try_new_valid_public_keys() {
for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() {
let expected_public_key = test_vector.pubkey;
let public_key = PublicKey::try_new(*expected_public_key.value()).unwrap();
assert_eq!(public_key, expected_public_key, "Failed on test vector {i}");
}
}
#[test]
fn test_public_key_generation_from_bip340_test_vectors() {
for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() {
let Some(private_key) = &test_vector.seckey else {
continue;
};
let public_key = PublicKey::new_from_private_key(private_key);
let expected_public_key = &test_vector.pubkey;
assert_eq!(
&public_key, expected_public_key,
"Failed test vector at index {i}"
);
}
}
}

573
nssa/src/state.rs Normal file
View File

@ -0,0 +1,573 @@
use crate::{
address::Address, error::NssaError, program::Program, public_transaction::PublicTransaction,
};
use nssa_core::{account::Account, program::ProgramId};
use std::collections::HashMap;
pub struct V01State {
public_state: HashMap<Address, Account>,
builtin_programs: HashMap<ProgramId, Program>,
}
impl V01State {
pub fn new_with_genesis_accounts(initial_data: &[(Address, u128)]) -> Self {
let authenticated_transfer_program = Program::authenticated_transfer_program();
let public_state = initial_data
.iter()
.copied()
.map(|(address, balance)| {
let account = Account {
balance,
program_owner: authenticated_transfer_program.id(),
..Account::default()
};
(address, account)
})
.collect();
let mut this = Self {
public_state,
builtin_programs: HashMap::new(),
};
this.insert_program(Program::authenticated_transfer_program());
this
}
pub(crate) fn insert_program(&mut self, program: Program) {
self.builtin_programs.insert(program.id(), program);
}
pub fn transition_from_public_transaction(
&mut self,
tx: &PublicTransaction,
) -> Result<(), NssaError> {
let state_diff = tx.validate_and_compute_post_states(self)?;
for (address, post) in state_diff.into_iter() {
let current_account = self.get_account_by_address_mut(address);
*current_account = post;
}
for address in tx.signer_addresses() {
let current_account = self.get_account_by_address_mut(address);
current_account.nonce += 1;
}
Ok(())
}
fn get_account_by_address_mut(&mut self, address: Address) -> &mut Account {
self.public_state.entry(address).or_default()
}
pub fn get_account_by_address(&self, address: &Address) -> Account {
self.public_state
.get(address)
.cloned()
.unwrap_or(Account::default())
}
pub(crate) fn builtin_programs(&self) -> &HashMap<ProgramId, Program> {
&self.builtin_programs
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use crate::{
Address, PublicKey, PublicTransaction, V01State, error::NssaError, program::Program,
public_transaction, signature::PrivateKey,
};
use nssa_core::account::Account;
fn transfer_transaction(
from: Address,
from_key: PrivateKey,
nonce: u128,
to: Address,
balance: u128,
) -> PublicTransaction {
let addresses = vec![from, to];
let nonces = vec![nonce];
let program_id = Program::authenticated_transfer_program().id();
let message =
public_transaction::Message::try_new(program_id, addresses, nonces, balance).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
PublicTransaction::new(message, witness_set)
}
#[test]
fn test_new_with_genesis() {
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));
let initial_data = [(addr1, 100u128), (addr2, 151u128)];
let program = Program::authenticated_transfer_program();
let expected_public_state = {
let mut this = HashMap::new();
this.insert(
addr1,
Account {
balance: 100,
program_owner: program.id(),
..Account::default()
},
);
this.insert(
addr2,
Account {
balance: 151,
program_owner: program.id(),
..Account::default()
},
);
this
};
let expected_builtin_programs = {
let mut this = HashMap::new();
this.insert(program.id(), program);
this
};
let state = V01State::new_with_genesis_accounts(&initial_data);
assert_eq!(state.public_state, expected_public_state);
assert_eq!(state.builtin_programs, expected_builtin_programs);
}
#[test]
fn test_insert_program() {
let mut state = V01State::new_with_genesis_accounts(&[]);
let program_to_insert = Program::simple_balance_transfer();
let program_id = program_to_insert.id();
assert!(!state.builtin_programs.contains_key(&program_id));
state.insert_program(program_to_insert);
assert!(state.builtin_programs.contains_key(&program_id));
}
#[test]
fn test_get_account_by_address_non_default_account() {
let key = PrivateKey::try_new([1; 32]).unwrap();
let addr = Address::from(&PublicKey::new_from_private_key(&key));
let initial_data = [(addr, 100u128)];
let state = V01State::new_with_genesis_accounts(&initial_data);
let expected_account = state.public_state.get(&addr).unwrap();
let account = state.get_account_by_address(&addr);
assert_eq!(&account, expected_account);
}
#[test]
fn test_get_account_by_address_default_account() {
let addr2 = Address::new([0; 32]);
let state = V01State::new_with_genesis_accounts(&[]);
let expected_account = Account::default();
let account = state.get_account_by_address(&addr2);
assert_eq!(account, expected_account);
}
#[test]
fn test_builtin_programs_getter() {
let state = V01State::new_with_genesis_accounts(&[]);
let builtin_programs = state.builtin_programs();
assert_eq!(builtin_programs, &state.builtin_programs);
}
#[test]
fn transition_from_authenticated_transfer_program_invocation_default_account_destination() {
let key = PrivateKey::try_new([1; 32]).unwrap();
let address = Address::from(&PublicKey::new_from_private_key(&key));
let initial_data = [(address, 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let from = address;
let to = Address::new([2; 32]);
assert_eq!(state.get_account_by_address(&to), Account::default());
let balance_to_move = 5;
let tx = transfer_transaction(from, key, 0, to, balance_to_move);
state.transition_from_public_transaction(&tx).unwrap();
assert_eq!(state.get_account_by_address(&from).balance, 95);
assert_eq!(state.get_account_by_address(&to).balance, 5);
assert_eq!(state.get_account_by_address(&from).nonce, 1);
assert_eq!(state.get_account_by_address(&to).nonce, 0);
}
#[test]
fn transition_from_authenticated_transfer_program_invocation_insuficient_balance() {
let key = PrivateKey::try_new([1; 32]).unwrap();
let address = Address::from(&PublicKey::new_from_private_key(&key));
let initial_data = [(address, 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let from = address;
let from_key = key;
let to = Address::new([2; 32]);
let balance_to_move = 101;
assert!(state.get_account_by_address(&from).balance < balance_to_move);
let tx = transfer_transaction(from, from_key, 0, to, balance_to_move);
let result = state.transition_from_public_transaction(&tx);
assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_))));
assert_eq!(state.get_account_by_address(&from).balance, 100);
assert_eq!(state.get_account_by_address(&to).balance, 0);
assert_eq!(state.get_account_by_address(&from).nonce, 0);
assert_eq!(state.get_account_by_address(&to).nonce, 0);
}
#[test]
fn transition_from_authenticated_transfer_program_invocation_non_default_account_destination() {
let key1 = PrivateKey::try_new([1; 32]).unwrap();
let key2 = PrivateKey::try_new([2; 32]).unwrap();
let address1 = Address::from(&PublicKey::new_from_private_key(&key1));
let address2 = Address::from(&PublicKey::new_from_private_key(&key2));
let initial_data = [(address1, 100), (address2, 200)];
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let from = address2;
let from_key = key2;
let to = address1;
assert_ne!(state.get_account_by_address(&to), Account::default());
let balance_to_move = 8;
let tx = transfer_transaction(from, from_key, 0, to, balance_to_move);
state.transition_from_public_transaction(&tx).unwrap();
assert_eq!(state.get_account_by_address(&from).balance, 192);
assert_eq!(state.get_account_by_address(&to).balance, 108);
assert_eq!(state.get_account_by_address(&from).nonce, 1);
assert_eq!(state.get_account_by_address(&to).nonce, 0);
}
#[test]
fn transition_from_chained_authenticated_transfer_program_invocations() {
let key1 = PrivateKey::try_new([8; 32]).unwrap();
let address1 = Address::from(&PublicKey::new_from_private_key(&key1));
let key2 = PrivateKey::try_new([2; 32]).unwrap();
let address2 = Address::from(&PublicKey::new_from_private_key(&key2));
let initial_data = [(address1, 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let address3 = Address::new([3; 32]);
let balance_to_move = 5;
let tx = transfer_transaction(address1, key1, 0, address2, balance_to_move);
state.transition_from_public_transaction(&tx).unwrap();
let balance_to_move = 3;
let tx = transfer_transaction(address2, key2, 0, address3, balance_to_move);
state.transition_from_public_transaction(&tx).unwrap();
assert_eq!(state.get_account_by_address(&address1).balance, 95);
assert_eq!(state.get_account_by_address(&address2).balance, 2);
assert_eq!(state.get_account_by_address(&address3).balance, 3);
assert_eq!(state.get_account_by_address(&address1).nonce, 1);
assert_eq!(state.get_account_by_address(&address2).nonce, 1);
assert_eq!(state.get_account_by_address(&address3).nonce, 0);
}
impl V01State {
pub fn force_insert_account(&mut self, address: Address, account: Account) {
self.public_state.insert(address, account);
}
/// Include test programs in the builtin programs map
pub fn with_test_programs(mut self) -> Self {
self.insert_program(Program::nonce_changer_program());
self.insert_program(Program::extra_output_program());
self.insert_program(Program::missing_output_program());
self.insert_program(Program::program_owner_changer());
self.insert_program(Program::simple_balance_transfer());
self.insert_program(Program::data_changer());
self.insert_program(Program::minter());
self.insert_program(Program::burner());
self
}
pub fn with_non_default_accounts_but_default_program_owners(mut self) -> Self {
let account_with_default_values_except_balance = Account {
balance: 100,
..Account::default()
};
let account_with_default_values_except_nonce = Account {
nonce: 37,
..Account::default()
};
let account_with_default_values_except_data = Account {
data: vec![0xca, 0xfe],
..Account::default()
};
self.force_insert_account(
Address::new([255; 32]),
account_with_default_values_except_balance,
);
self.force_insert_account(
Address::new([254; 32]),
account_with_default_values_except_nonce,
);
self.force_insert_account(
Address::new([253; 32]),
account_with_default_values_except_data,
);
self
}
pub fn with_account_owned_by_burner_program(mut self) -> Self {
let account = Account {
program_owner: Program::burner().id(),
balance: 100,
..Default::default()
};
self.force_insert_account(Address::new([252; 32]), account);
self
}
}
#[test]
fn test_program_should_fail_if_modifies_nonces() {
let initial_data = [(Address::new([1; 32]), 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let addresses = vec![Address::new([1; 32])];
let program_id = Program::nonce_changer_program().id();
let message =
public_transaction::Message::try_new(program_id, addresses, vec![], ()).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
#[test]
fn test_program_should_fail_if_output_accounts_exceed_inputs() {
let initial_data = [(Address::new([1; 32]), 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let addresses = vec![Address::new([1; 32])];
let program_id = Program::extra_output_program().id();
let message =
public_transaction::Message::try_new(program_id, addresses, vec![], ()).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
#[test]
fn test_program_should_fail_with_missing_output_accounts() {
let initial_data = [(Address::new([1; 32]), 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let addresses = vec![Address::new([1; 32]), Address::new([2; 32])];
let program_id = Program::missing_output_program().id();
let message =
public_transaction::Message::try_new(program_id, addresses, vec![], ()).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
#[test]
fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_program_owner() {
let initial_data = [(Address::new([1; 32]), 0)];
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let address = Address::new([1; 32]);
let account = state.get_account_by_address(&address);
// Assert the target account only differs from the default account in the program owner field
assert_ne!(account.program_owner, Account::default().program_owner);
assert_eq!(account.balance, Account::default().balance);
assert_eq!(account.nonce, Account::default().nonce);
assert_eq!(account.data, Account::default().data);
let program_id = Program::program_owner_changer().id();
let message =
public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
#[test]
fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_balance() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data)
.with_test_programs()
.with_non_default_accounts_but_default_program_owners();
let address = Address::new([255; 32]);
let account = state.get_account_by_address(&address);
// Assert the target account only differs from the default account in balance field
assert_eq!(account.program_owner, Account::default().program_owner);
assert_ne!(account.balance, Account::default().balance);
assert_eq!(account.nonce, Account::default().nonce);
assert_eq!(account.data, Account::default().data);
let program_id = Program::program_owner_changer().id();
let message =
public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
#[test]
fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_nonce() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data)
.with_test_programs()
.with_non_default_accounts_but_default_program_owners();
let address = Address::new([254; 32]);
let account = state.get_account_by_address(&address);
// Assert the target account only differs from the default account in nonce field
assert_eq!(account.program_owner, Account::default().program_owner);
assert_eq!(account.balance, Account::default().balance);
assert_ne!(account.nonce, Account::default().nonce);
assert_eq!(account.data, Account::default().data);
let program_id = Program::program_owner_changer().id();
let message =
public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
#[test]
fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_data() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data)
.with_test_programs()
.with_non_default_accounts_but_default_program_owners();
let address = Address::new([253; 32]);
let account = state.get_account_by_address(&address);
// Assert the target account only differs from the default account in data field
assert_eq!(account.program_owner, Account::default().program_owner);
assert_eq!(account.balance, Account::default().balance);
assert_eq!(account.nonce, Account::default().nonce);
assert_ne!(account.data, Account::default().data);
let program_id = Program::program_owner_changer().id();
let message =
public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
#[test]
fn test_program_should_fail_if_transfers_balance_from_non_owned_account() {
let initial_data = [(Address::new([1; 32]), 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let sender_address = Address::new([1; 32]);
let receiver_address = Address::new([2; 32]);
let balance_to_move: u128 = 1;
let program_id = Program::simple_balance_transfer().id();
assert_ne!(
state.get_account_by_address(&sender_address).program_owner,
program_id
);
let message = public_transaction::Message::try_new(
program_id,
vec![sender_address, receiver_address],
vec![],
balance_to_move,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
#[test]
fn test_program_should_fail_if_modifies_data_of_non_owned_account() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let address = Address::new([1; 32]);
let program_id = Program::data_changer().id();
// Consider the extreme case where the target account is the default account
assert_eq!(state.get_account_by_address(&address), Account::default());
assert_ne!(
state.get_account_by_address(&address).program_owner,
program_id
);
let message =
public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
#[test]
fn test_program_should_fail_if_does_not_preserve_total_balance_by_minting() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let address = Address::new([1; 32]);
let program_id = Program::minter().id();
let message =
public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
#[test]
fn test_program_should_fail_if_does_not_preserve_total_balance_by_burning() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data)
.with_test_programs()
.with_account_owned_by_burner_program();
let program_id = Program::burner().id();
let address = Address::new([252; 32]);
assert_eq!(
state.get_account_by_address(&address).program_owner,
program_id
);
let balance_to_burn: u128 = 1;
assert!(state.get_account_by_address(&address).balance > balance_to_burn);
let message = public_transaction::Message::try_new(
program_id,
vec![address],
vec![],
balance_to_burn,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
}

View File

@ -0,0 +1,10 @@
[package]
name = "test-program-methods"
version = "0.1.0"
edition = "2021"
[build-dependencies]
risc0-build = { version = "2.3.1" }
[package.metadata.risc0]
methods = ["guest"]

View File

@ -0,0 +1,3 @@
fn main() {
risc0_build::embed_methods();
}

View File

@ -0,0 +1,10 @@
[package]
name = "programs"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
risc0-zkvm = { version = "2.3.1", default-features = false, features = ['std'] }
nssa-core = { path = "../../core" }

View File

@ -0,0 +1,19 @@
use nssa_core::program::read_nssa_inputs;
use risc0_zkvm::guest::env;
type Instruction = u128;
fn main() {
let (input_accounts, balance_to_burn) = read_nssa_inputs::<Instruction>();
let [pre] = match input_accounts.try_into() {
Ok(array) => array,
Err(_) => return,
};
let account_pre = pre.account;
let mut account_post = account_pre.clone();
account_post.balance -= balance_to_burn;
env::commit(&vec![account_post]);
}

View File

@ -0,0 +1,20 @@
use nssa_core::program::read_nssa_inputs;
use risc0_zkvm::guest::env;
type Instruction = ();
fn main() {
let (input_accounts, _) = read_nssa_inputs::<Instruction>();
let [pre] = match input_accounts.try_into() {
Ok(array) => array,
Err(_) => return,
};
let account_pre = pre.account;
let mut account_post = account_pre.clone();
account_post.data.push(0);
env::commit(&vec![account_post]);
}

View File

@ -0,0 +1,17 @@
use nssa_core::{account::Account, program::read_nssa_inputs};
use risc0_zkvm::guest::env;
type Instruction = ();
fn main() {
let (input_accounts, _) = read_nssa_inputs::<Instruction>();
let [pre] = match input_accounts.try_into() {
Ok(array) => array,
Err(_) => return,
};
let account_pre = pre.account;
env::commit(&vec![account_pre, Account::default()]);
}

View File

@ -0,0 +1,19 @@
use nssa_core::program::read_nssa_inputs;
use risc0_zkvm::guest::env;
type Instruction = ();
fn main() {
let (input_accounts, _) = read_nssa_inputs::<Instruction>();
let [pre] = match input_accounts.try_into() {
Ok(array) => array,
Err(_) => return,
};
let account_pre = pre.account;
let mut account_post = account_pre.clone();
account_post.balance += 1;
env::commit(&vec![account_post]);
}

View File

@ -0,0 +1,17 @@
use nssa_core::program::read_nssa_inputs;
use risc0_zkvm::guest::env;
type Instruction = ();
fn main() {
let (input_accounts, _) = read_nssa_inputs::<Instruction>();
let [pre1, _] = match input_accounts.try_into() {
Ok(array) => array,
Err(_) => return,
};
let account_pre1 = pre1.account;
env::commit(&vec![account_pre1]);
}

View File

@ -0,0 +1,19 @@
use nssa_core::program::read_nssa_inputs;
use risc0_zkvm::guest::env;
type Instruction = ();
fn main() {
let (input_accounts, _) = read_nssa_inputs::<Instruction>();
let [pre] = match input_accounts.try_into() {
Ok(array) => array,
Err(_) => return,
};
let account_pre = pre.account;
let mut account_post = account_pre.clone();
account_post.nonce += 1;
env::commit(&vec![account_post]);
}

View File

@ -0,0 +1,19 @@
use nssa_core::program::read_nssa_inputs;
use risc0_zkvm::guest::env;
type Instruction = ();
fn main() {
let (input_accounts, _) = read_nssa_inputs::<Instruction>();
let [pre] = match input_accounts.try_into() {
Ok(array) => array,
Err(_) => return,
};
let account_pre = pre.account;
let mut account_post = account_pre.clone();
account_post.program_owner = [0, 1, 2, 3, 4, 5, 6, 7];
env::commit(&vec![account_post]);
}

View File

@ -0,0 +1,20 @@
use nssa_core::program::read_nssa_inputs;
use risc0_zkvm::guest::env;
type Instruction = u128;
fn main() {
let (input_accounts, balance) = read_nssa_inputs::<Instruction>();
let [sender_pre, receiver_pre] = match input_accounts.try_into() {
Ok(array) => array,
Err(_) => return,
};
let mut sender_post = sender_pre.account.clone();
let mut receiver_post = receiver_pre.account.clone();
sender_post.balance -= balance;
receiver_post.balance += balance;
env::commit(&vec![sender_post, receiver_post]);
}

View File

@ -0,0 +1 @@
include!(concat!(env!("OUT_DIR"), "/methods.rs"));

View File

@ -1,38 +0,0 @@
[package]
name = "sc_core"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
serde_json.workspace = true
env_logger.workspace = true
log.workspace = true
serde.workspace = true
rand.workspace = true
k256.workspace = true
sha2.workspace = true
bincode.workspace = true
elliptic-curve.workspace = true
hex.workspace = true
light-poseidon.workspace = true
ark-bn254.workspace = true
ark-ff.workspace = true
risc0-zkvm = { git = "https://github.com/risc0/risc0.git", branch = "release-2.3" }
[dependencies.accounts]
path = "../accounts"
[dependencies.storage]
path = "../storage"
[dependencies.utxo]
path = "../utxo"
[dependencies.common]
path = "../common"
[dependencies.secp256k1-zkp]
workspace = true
features = ["std", "rand-std", "rand", "serde", "global-context"]

View File

@ -1,125 +0,0 @@
use serde::Serialize;
use storage::{
sc_db_utils::{produce_blob_from_fit_vec, DataBlob, DataBlobChangeVariant},
SC_DATA_BLOB_SIZE,
};
///Creates blob list from generic serializable state
///
///`ToDo`: Find a way to align data in a way, to minimize read and write operations in db
pub fn produce_blob_list_from_sc_public_state<S: Serialize>(
state: &S,
) -> Result<Vec<DataBlob>, serde_json::Error> {
let mut blob_list = vec![];
let ser_data = serde_json::to_vec(state)?;
//`ToDo` Replace with `next_chunk` usage, when feature stabilizes in Rust
for i in 0..=(ser_data.len() / SC_DATA_BLOB_SIZE) {
let next_chunk: Vec<u8> = if (i + 1) * SC_DATA_BLOB_SIZE < ser_data.len() {
ser_data[(i * SC_DATA_BLOB_SIZE)..((i + 1) * SC_DATA_BLOB_SIZE)].to_vec()
} else {
ser_data[(i * SC_DATA_BLOB_SIZE)..(ser_data.len())].to_vec()
};
blob_list.push(produce_blob_from_fit_vec(next_chunk));
}
Ok(blob_list)
}
///Compare two consecutive in time blob lists to produce list of modified ids
pub fn compare_blob_lists(
blob_list_old: &[DataBlob],
blob_list_new: &[DataBlob],
) -> Vec<DataBlobChangeVariant> {
let mut changed_ids = vec![];
let mut id_end = 0;
let old_len = blob_list_old.len();
let new_len = blob_list_new.len();
if old_len > new_len {
for id in new_len..old_len {
changed_ids.push(DataBlobChangeVariant::Deleted { id });
}
} else if new_len > old_len {
for (id, blob) in blob_list_new.iter().enumerate().take(new_len).skip(old_len) {
changed_ids.push(DataBlobChangeVariant::Created { id, blob: *blob });
}
}
loop {
let old_blob = blob_list_old.get(id_end);
let new_blob = blob_list_new.get(id_end);
match (old_blob, new_blob) {
(Some(old), Some(new)) => {
if old != new {
changed_ids.push(DataBlobChangeVariant::Modified {
id: id_end,
blob_old: *old,
blob_new: *new,
});
}
}
_ => break,
}
id_end += 1;
}
changed_ids
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Serialize;
const TEST_BLOB_SIZE: usize = 256; // Define a test blob size for simplicity
static SC_DATA_BLOB_SIZE: usize = TEST_BLOB_SIZE;
#[derive(Serialize)]
struct TestState {
a: u32,
b: u32,
}
#[test]
fn test_produce_blob_list_from_sc_public_state() {
let state = TestState { a: 42, b: 99 };
let result = produce_blob_list_from_sc_public_state(&state).unwrap();
assert!(!result.is_empty());
}
#[test]
fn test_compare_blob_lists_created() {
let old_list: Vec<DataBlob> = vec![];
let new_list: Vec<DataBlob> = vec![[1; SC_DATA_BLOB_SIZE].into()];
let changes = compare_blob_lists(&old_list, &new_list);
assert_eq!(changes.len(), 1);
assert!(matches!(changes[0], DataBlobChangeVariant::Created { .. }));
}
#[test]
fn test_compare_blob_lists_deleted() {
let old_list: Vec<DataBlob> = vec![[1; SC_DATA_BLOB_SIZE].into()];
let new_list: Vec<DataBlob> = vec![];
let changes = compare_blob_lists(&old_list, &new_list);
assert_eq!(changes.len(), 1);
assert!(matches!(changes[0], DataBlobChangeVariant::Deleted { .. }));
}
#[test]
fn test_compare_blob_lists_modified() {
let old_list: Vec<DataBlob> = vec![[1; SC_DATA_BLOB_SIZE].into()];
let new_list: Vec<DataBlob> = vec![[2; SC_DATA_BLOB_SIZE].into()];
let changes = compare_blob_lists(&old_list, &new_list);
assert_eq!(changes.len(), 1);
assert!(matches!(changes[0], DataBlobChangeVariant::Modified { .. }));
}
}

View File

@ -1,10 +0,0 @@
use ark_bn254::Fr;
use light_poseidon::{Poseidon, PoseidonBytesHasher};
pub fn poseidon_hash(inputs: &[&[u8]]) -> anyhow::Result<[u8; 32]> {
let mut poseidon = Poseidon::<Fr>::new_circom(2).unwrap();
let hash = poseidon.hash_bytes_be(inputs)?;
Ok(hash)
}

View File

@ -1,5 +0,0 @@
pub mod blob_utils;
pub mod cryptography;
pub mod proofs_circuits;
pub mod public_context;
pub mod transaction_payloads_tools;

View File

@ -1,285 +0,0 @@
use bincode;
use common::merkle_tree_public::merkle_tree::UTXOCommitmentsMerkleTree;
use rand::{thread_rng, RngCore};
use secp256k1_zkp::{CommitmentSecrets, Generator, PedersenCommitment, Tag, Tweak, SECP256K1};
use sha2::{Digest, Sha256};
use utxo::utxo_core::UTXO;
//
use crate::{cryptography::poseidon_hash, public_context::PublicSCContext};
fn hash(input: &[u8]) -> Vec<u8> {
Sha256::digest(input).to_vec()
}
/// Generate nullifiers
///
/// takes the input_utxo and npk
///
/// returns the nullifiers[i], where the nullifiers[i] = poseidon_hash(in_commitments[i] || npk)
pub fn generate_nullifiers(input_utxo: &UTXO, npk: &[u8]) -> Vec<u8> {
let commitment = generate_commitment(input_utxo);
poseidon_hash(&[commitment.as_ref(), npk]).unwrap().to_vec()
}
/// Generate commitment for UTXO
///
/// uses the input_utxo
///
/// returns commitment here commitment is a hash(bincode(input_utxo))
pub fn generate_commitment(input_utxo: &UTXO) -> Vec<u8> {
let serialized = bincode::serialize(input_utxo).unwrap(); // Serialize UTXO.
hash(&serialized)
}
/// Generate commitments for UTXO
///
/// uses the input_utxos
///
/// returns commitments
pub fn generate_commitments(input_utxos: &[UTXO]) -> Vec<Vec<u8>> {
input_utxos
.iter()
.map(|utxo| {
let serialized = bincode::serialize(utxo).unwrap(); // Serialize UTXO.
hash(&serialized)
})
.collect()
}
/// Validate inclusion proof for in_commitments
///
/// ToDo: Solve it in more scalable way
pub fn validate_in_commitments_tree(
in_commitment: &[u8],
commitment_tree: &UTXOCommitmentsMerkleTree,
) -> bool {
let alighned_hash: [u8; 32] = in_commitment.try_into().unwrap();
commitment_tree.get_proof(alighned_hash).is_some()
}
/// Check, that input utxos balances is equal to out utxo balances
pub fn check_balances_private(in_utxos: &[UTXO], out_utxos: &[UTXO]) -> bool {
let in_sum = in_utxos.iter().fold(0, |prev, utxo| prev + utxo.amount);
let out_sum = out_utxos.iter().fold(0, |prev, utxo| prev + utxo.amount);
in_sum == out_sum
}
pub fn private_circuit(
input_utxos: &[UTXO],
output_utxos: &[UTXO],
public_context: &PublicSCContext,
) -> (Vec<Vec<u8>>, Vec<Vec<u8>>) {
assert!(check_balances_private(input_utxos, output_utxos));
let in_commitments = generate_commitments(input_utxos);
let mut in_nullifiers = vec![];
for in_utxo in input_utxos {
let nullifier_public_key = public_context
.account_masks
.get(&in_utxo.owner)
.unwrap()
.nullifier_public_key;
let key_ser = serde_json::to_vec(&nullifier_public_key).unwrap();
in_nullifiers.push(generate_nullifiers(in_utxo, &key_ser));
}
for in_commitment in in_commitments {
assert!(validate_in_commitments_tree(
&in_commitment,
&public_context.commitments_tree,
));
}
(in_nullifiers, generate_commitments(output_utxos))
}
/// Check balances DE
///
/// takes the input_utxos[] and output_balance,
///
/// returns the True if the token amount in output_balance matches the sum of all input_utxos[], otherwise return False.
pub fn check_balances_de(input_utxos: &[UTXO], output_balance: u128) -> bool {
let total_input: u128 = input_utxos.iter().map(|utxo| utxo.amount).sum();
total_input == output_balance
}
pub fn deshielded_circuit(
input_utxos: &[UTXO],
output_balance: u128,
public_context: &PublicSCContext,
) -> Vec<Vec<u8>> {
assert!(check_balances_de(input_utxos, output_balance));
let in_commitments = generate_commitments(input_utxos);
let mut in_nullifiers = vec![];
for in_utxo in input_utxos {
let nullifier_public_key = public_context
.account_masks
.get(&in_utxo.owner)
.unwrap()
.nullifier_public_key;
let key_ser = serde_json::to_vec(&nullifier_public_key).unwrap();
in_nullifiers.push(generate_nullifiers(in_utxo, &key_ser));
}
for in_commitment in in_commitments {
assert!(validate_in_commitments_tree(
&in_commitment,
&public_context.commitments_tree,
));
}
in_nullifiers
}
#[allow(unused)]
fn commitment_secrets_random(value: u64) -> CommitmentSecrets {
CommitmentSecrets {
value,
value_blinding_factor: Tweak::new(&mut thread_rng()),
generator_blinding_factor: Tweak::new(&mut thread_rng()),
}
}
pub fn tag_random() -> Tag {
use rand::thread_rng;
use rand::RngCore;
let mut bytes = [0u8; 32];
thread_rng().fill_bytes(&mut bytes);
Tag::from(bytes)
}
pub fn commit(comm: &CommitmentSecrets, tag: Tag) -> PedersenCommitment {
let generator = Generator::new_blinded(SECP256K1, tag, comm.generator_blinding_factor);
PedersenCommitment::new(SECP256K1, comm.value, comm.value_blinding_factor, generator)
}
/// new_commitment for a Vec of values
pub fn pedersen_commitment_vec(
public_info_vec: Vec<u64>,
) -> (Tweak, [u8; 32], Vec<PedersenCommitment>) {
let mut random_val: [u8; 32] = [0; 32];
thread_rng().fill_bytes(&mut random_val);
let generator_blinding_factor = Tweak::new(&mut thread_rng());
let tag = tag_random();
let vec_commitments = public_info_vec
.into_iter()
.map(|public_info| {
let commitment_secrets = CommitmentSecrets {
value: public_info,
value_blinding_factor: Tweak::from_slice(&random_val).unwrap(),
generator_blinding_factor,
};
commit(&commitment_secrets, tag)
})
.collect();
(generator_blinding_factor, random_val, vec_commitments)
}
/// Verify Pedersen commitment
///
/// takes the public_info, secret_r and pedersen_commitment and
///
/// checks that commitment(public_info,secret_r) is equal pedersen_commitment where the commitment is pedersen commitment.
pub fn verify_commitment(
public_info: u64,
secret_r: &[u8],
pedersen_commitment: &PedersenCommitment,
) -> bool {
let commitment_secrets = CommitmentSecrets {
value: public_info,
value_blinding_factor: Tweak::from_slice(secret_r).unwrap(),
generator_blinding_factor: Tweak::new(&mut thread_rng()),
};
let tag = tag_random();
let commitment = commit(&commitment_secrets, tag);
commitment == *pedersen_commitment
}
/// Validate inclusion proof for pedersen_commitment
///
/// ToDo: Solve it in more scalable way
pub fn validate_in_commitments_tree_se(
pedersen_commitment: &PedersenCommitment,
commitment_tree: &UTXOCommitmentsMerkleTree,
) -> bool {
let alighned_hash: [u8; 32] = pedersen_commitment.serialize()[0..32].try_into().unwrap();
commitment_tree.get_proof(alighned_hash).is_some()
}
/// Generate nullifier SE
///
/// takes the pedersen_commitment and npk then
/// returns a nullifier, where the nullifier = poseidon_hash(pedersen_commitment || npk)
pub fn generate_nullifiers_se(pedersen_commitment: &PedersenCommitment, npk: &[u8]) -> Vec<u8> {
let commitment_ser = pedersen_commitment.serialize().to_vec();
poseidon_hash(&[&commitment_ser, npk]).unwrap().to_vec()
}
/// Check balances SE
///
/// takes the input_balance and output_utxos[],
///
/// returns the True if the token amount in input_balance matches the sum of all output_utxos[], otherwise return False.
pub fn check_balances_se(input_balance: u128, output_utxos: &[UTXO]) -> bool {
let total_output: u128 = output_utxos.iter().map(|utxo| utxo.amount).sum();
total_output == input_balance
}
pub fn shielded_circuit(
public_info: u64,
output_utxos: &[UTXO],
pedersen_commitment: PedersenCommitment,
secret_r: &[u8],
public_context: &PublicSCContext,
) -> (Vec<Vec<u8>>, Vec<u8>) {
assert!(check_balances_se(public_info as u128, output_utxos));
let out_commitments = generate_commitments(output_utxos);
let nullifier_public_key = public_context
.account_masks
.get(&public_context.caller_address)
.unwrap()
.nullifier_public_key;
let key_ser = serde_json::to_vec(&nullifier_public_key).unwrap();
let nullifier = generate_nullifiers_se(&pedersen_commitment, &key_ser);
assert!(validate_in_commitments_tree_se(
&pedersen_commitment,
&public_context.commitments_tree,
));
assert!(verify_commitment(
public_info,
secret_r,
&pedersen_commitment
));
(out_commitments, nullifier)
}

View File

@ -1,180 +0,0 @@
use std::collections::BTreeMap;
use accounts::account_core::{address::AccountAddress, AccountPublicMask};
use common::merkle_tree_public::{merkle_tree::UTXOCommitmentsMerkleTree, TreeHashType};
use serde::{ser::SerializeStruct, Serialize};
pub const PUBLIC_SC_CONTEXT: &str = "PublicSCContext";
pub const CALLER_ADDRESS: &str = "caller_address";
pub const CALLER_BALANCE: &str = "caller_balance";
pub const ACCOUNT_MASKS_KEYS_SORTED: &str = "account_masks_keys_sorted";
pub const ACCOUNT_MASKS_VALUES_SORTED: &str = "account_masks_values_sorted";
pub const COMMITMENT_STORE_ROOT: &str = "commitment_store_root";
pub const PUT_TX_STORE_ROOT: &str = "put_tx_store_root";
pub const COMMITMENT_TREE: &str = "commitments_tree";
pub const NULLIFIERS_SET: &str = "nullifiers_set";
///Strucutre, representing context, given to a smart contract on a call
pub struct PublicSCContext {
pub caller_address: AccountAddress,
pub caller_balance: u64,
pub account_masks: BTreeMap<AccountAddress, AccountPublicMask>,
pub comitment_store_root: TreeHashType,
pub commitments_tree: UTXOCommitmentsMerkleTree,
}
impl Serialize for PublicSCContext {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut account_masks_keys: Vec<[u8; 32]> = self.account_masks.keys().cloned().collect();
account_masks_keys.sort();
let mut account_mask_values: Vec<AccountPublicMask> =
self.account_masks.values().cloned().collect();
account_mask_values.sort_by(|left, right| left.address.cmp(&right.address));
let mut s = serializer.serialize_struct(PUBLIC_SC_CONTEXT, 7)?;
s.serialize_field(CALLER_ADDRESS, &self.caller_address)?;
s.serialize_field(CALLER_BALANCE, &self.caller_balance)?;
s.serialize_field(ACCOUNT_MASKS_KEYS_SORTED, &account_masks_keys)?;
s.serialize_field(ACCOUNT_MASKS_VALUES_SORTED, &account_mask_values)?;
s.serialize_field(COMMITMENT_STORE_ROOT, &self.comitment_store_root)?;
s.serialize_field(COMMITMENT_TREE, &self.commitments_tree)?;
s.end()
}
}
impl PublicSCContext {
///Produces `u64` from bytes in a vector
///
/// Assumes, that vector of le_bytes
pub fn produce_u64_from_fit_vec(data: Vec<u8>) -> u64 {
let data_len = data.len();
assert!(data_len <= 8);
let mut le_bytes: [u8; 8] = [0; 8];
for (idx, item) in data.into_iter().enumerate() {
le_bytes[idx] = item
}
u64::from_le_bytes(le_bytes)
}
///Produces vector of `u64` from context
pub fn produce_u64_list_from_context(&self) -> Result<Vec<u64>, serde_json::Error> {
let mut u64_list = vec![];
let ser_data = serde_json::to_vec(self)?;
//`ToDo` Replace with `next_chunk` usage, when feature stabilizes in Rust
for i in 0..=(ser_data.len() / 8) {
let next_chunk: Vec<u8> = if (i + 1) * 8 < ser_data.len() {
ser_data[(i * 8)..((i + 1) * 8)].to_vec()
} else {
ser_data[(i * 8)..(ser_data.len())].to_vec()
};
u64_list.push(PublicSCContext::produce_u64_from_fit_vec(next_chunk));
}
Ok(u64_list)
}
}
#[cfg(test)]
mod tests {
use accounts::account_core::Account;
use common::utxo_commitment::UTXOCommitment;
use super::*;
fn create_test_context() -> PublicSCContext {
let caller_address = [1; 32];
let comitment_store_root = [3; 32];
let commitments_tree =
UTXOCommitmentsMerkleTree::new(vec![UTXOCommitment { hash: [5; 32] }]);
let mut account_masks = BTreeMap::new();
let acc_1 = Account::new();
let acc_2 = Account::new();
let acc_3 = Account::new();
account_masks.insert(acc_1.address, acc_1.make_account_public_mask());
account_masks.insert(acc_2.address, acc_2.make_account_public_mask());
account_masks.insert(acc_3.address, acc_3.make_account_public_mask());
PublicSCContext {
caller_address,
caller_balance: 100,
account_masks,
comitment_store_root,
commitments_tree,
}
}
#[test]
fn bin_ser_stability_test() {
let test_context = create_test_context();
let serialization_1 = serde_json::to_vec(&test_context).unwrap();
let serialization_2 = serde_json::to_vec(&test_context).unwrap();
assert_eq!(serialization_1, serialization_2);
}
#[test]
fn correct_u64_production_from_fit_vec() {
let le_vec = vec![1, 1, 1, 1, 2, 1, 1, 1];
let num = PublicSCContext::produce_u64_from_fit_vec(le_vec);
assert_eq!(num, 72340177133043969);
}
#[test]
fn correct_u64_production_from_small_vec() {
//7 items instead of 8
let le_vec = vec![1, 1, 1, 1, 2, 1, 1];
let num = PublicSCContext::produce_u64_from_fit_vec(le_vec);
assert_eq!(num, 282583095116033);
}
#[test]
fn correct_u64_production_from_small_vec_le_bytes() {
//7 items instead of 8
let le_vec = vec![1, 1, 1, 1, 2, 1, 1];
let le_vec_res = [1, 1, 1, 1, 2, 1, 1, 0];
let num = PublicSCContext::produce_u64_from_fit_vec(le_vec);
assert_eq!(num.to_le_bytes(), le_vec_res);
}
#[test]
#[should_panic]
fn correct_u64_production_from_unfit_vec_should_panic() {
//9 items instead of 8
let le_vec = vec![1, 1, 1, 1, 2, 1, 1, 1, 1];
PublicSCContext::produce_u64_from_fit_vec(le_vec);
}
#[test]
fn consistent_len_of_context_commitments() {
let test_context = create_test_context();
let context_num_vec1 = test_context.produce_u64_list_from_context().unwrap();
let context_num_vec2 = test_context.produce_u64_list_from_context().unwrap();
assert_eq!(context_num_vec1.len(), context_num_vec2.len());
}
}

View File

@ -1,100 +0,0 @@
use accounts::{account_core::Account, key_management::ephemeral_key_holder::EphemeralKeyHolder};
use anyhow::Result;
use common::transaction::{TransactionBody, TxKind};
use rand::thread_rng;
use risc0_zkvm::Receipt;
use secp256k1_zkp::{CommitmentSecrets, PedersenCommitment, Tweak};
use utxo::utxo_core::UTXO;
use crate::proofs_circuits::{commit, generate_nullifiers, tag_random};
pub fn create_public_transaction_payload(
execution_input: Vec<u8>,
commitment: Vec<PedersenCommitment>,
tweak: Tweak,
secret_r: [u8; 32],
sc_addr: String,
) -> TransactionBody {
TransactionBody {
tx_kind: TxKind::Public,
execution_input,
execution_output: vec![],
utxo_commitments_spent_hashes: vec![],
utxo_commitments_created_hashes: vec![],
nullifier_created_hashes: vec![],
execution_proof_private: "".to_string(),
encoded_data: vec![],
ephemeral_pub_key: vec![],
commitment,
tweak,
secret_r,
sc_addr,
}
}
pub fn encode_utxos_to_receivers(
utxos_receivers: Vec<(UTXO, &Account)>,
) -> Vec<(Vec<u8>, Vec<u8>)> {
let mut all_encoded_data = vec![];
for (utxo, receiver) in utxos_receivers {
let ephm_key_holder = EphemeralKeyHolder::new_os_random();
let encoded_data = Account::encrypt_data(
&ephm_key_holder,
receiver.key_holder.viewing_public_key,
&serde_json::to_vec(&utxo).unwrap(),
);
let encoded_data_vec = (encoded_data.0, encoded_data.1.to_vec());
all_encoded_data.push(encoded_data_vec);
}
all_encoded_data
}
pub fn generate_nullifiers_spent_utxos(utxos_spent: Vec<(UTXO, &Account)>) -> Vec<Vec<u8>> {
let mut all_nullifiers = vec![];
for (utxo, spender) in utxos_spent {
let nullifier = generate_nullifiers(
&utxo,
&spender
.key_holder
.utxo_secret_key_holder
.nullifier_secret_key
.to_bytes(),
);
all_nullifiers.push(nullifier);
}
all_nullifiers
}
pub fn encode_receipt(receipt: Receipt) -> Result<String> {
Ok(hex::encode(serde_json::to_vec(&receipt)?))
}
pub fn generate_secret_random_commitment(
value: u64,
account: &Account,
) -> Result<PedersenCommitment> {
let commitment_secrets = CommitmentSecrets {
value,
value_blinding_factor: Tweak::from_slice(
&account
.key_holder
.utxo_secret_key_holder
.viewing_secret_key
.to_bytes(),
)?,
generator_blinding_factor: Tweak::new(&mut thread_rng()),
};
let tag = tag_random();
let commitment = commit(&commitment_secrets, tag);
Ok(commitment)
}

View File

@ -22,12 +22,15 @@ path = "../storage"
[dependencies.mempool]
path = "../mempool"
[dependencies.accounts]
path = "../accounts"
[dependencies.key_protocol]
path = "../key_protocol"
[dependencies.common]
path = "../common"
[dependencies.nssa]
path = "../nssa"
[dependencies.secp256k1-zkp]
workspace = true
features = ["std", "rand-std", "rand", "serde", "global-context"]

View File

@ -6,7 +6,7 @@ use std::path::PathBuf;
pub struct AccountInitialData {
///Hex encoded `AccountAddress`
pub addr: String,
pub balance: u64,
pub balance: u128,
}
#[derive(Clone, Serialize, Deserialize)]

View File

@ -1,28 +1,18 @@
use std::fmt::Display;
use accounts::account_core::address::{self, AccountAddress};
use anyhow::Result;
use common::{
block::HashableBlockData,
execution_input::PublicNativeTokenSend,
merkle_tree_public::TreeHashType,
nullifier::UTXONullifier,
transaction::{AuthenticatedTransaction, Transaction, TransactionBody, TxKind},
utxo_commitment::UTXOCommitment,
};
use common::{block::HashableBlockData, merkle_tree_public::TreeHashType};
use config::SequencerConfig;
use mempool::MemPool;
use mempool_transaction::MempoolTransaction;
use sequencer_store::SequecerChainStore;
use serde::{Deserialize, Serialize};
pub mod config;
pub mod mempool_transaction;
pub mod sequencer_store;
pub struct SequencerCore {
pub store: SequecerChainStore,
pub mempool: MemPool<MempoolTransaction>,
pub mempool: MemPool<nssa::PublicTransaction>,
pub sequencer_config: SequencerConfig,
pub chain_height: u64,
}
@ -34,7 +24,7 @@ pub enum TransactionMalformationErrorKind {
TxHashAlreadyPresentInTree { tx: TreeHashType },
NullifierAlreadyPresentInTree { tx: TreeHashType },
UTXOCommitmentAlreadyPresentInTree { tx: TreeHashType },
MempoolFullForRound { tx: TreeHashType },
MempoolFullForRound,
ChainStateFurtherThanTransactionState { tx: TreeHashType },
FailedToInsert { tx: TreeHashType, details: String },
InvalidSignature,
@ -61,219 +51,47 @@ impl SequencerCore {
config.is_genesis_random,
&config.initial_accounts,
),
mempool: MemPool::<MempoolTransaction>::default(),
mempool: MemPool::default(),
chain_height: config.genesis_id,
sequencer_config: config,
}
}
pub fn get_tree_roots(&self) -> [[u8; 32]; 2] {
[
self.store
.utxo_commitments_store
.get_root()
.unwrap_or([0; 32]),
self.store.pub_tx_store.get_root().unwrap_or([0; 32]),
]
}
pub fn transaction_pre_check(
&mut self,
tx: Transaction,
) -> Result<AuthenticatedTransaction, TransactionMalformationErrorKind> {
let tx = tx
.into_authenticated()
.map_err(|_| TransactionMalformationErrorKind::InvalidSignature)?;
let TransactionBody {
tx_kind,
ref execution_input,
ref execution_output,
ref utxo_commitments_created_hashes,
ref nullifier_created_hashes,
..
} = tx.transaction().body();
let tx_hash = *tx.hash();
let mempool_size = self.mempool.len();
if mempool_size >= self.sequencer_config.max_num_tx_in_block {
return Err(TransactionMalformationErrorKind::MempoolFullForRound { tx: tx_hash });
tx: nssa::PublicTransaction,
) -> Result<nssa::PublicTransaction, TransactionMalformationErrorKind> {
// Stateless checks here
if tx.witness_set().is_valid_for(tx.message()) {
Ok(tx)
} else {
Err(TransactionMalformationErrorKind::InvalidSignature)
}
//Sanity check
match tx_kind {
TxKind::Public => {
if !utxo_commitments_created_hashes.is_empty()
|| !nullifier_created_hashes.is_empty()
{
//Public transactions can not make private operations.
return Err(
TransactionMalformationErrorKind::PublicTransactionChangedPrivateData {
tx: tx_hash,
},
);
}
}
TxKind::Private => {
if !execution_input.is_empty() || !execution_output.is_empty() {
//Not entirely necessary, but useful simplification for a future.
//This way only shielded and deshielded transactions can be used for interaction
//between public and private state.
return Err(
TransactionMalformationErrorKind::PrivateTransactionChangedPublicData {
tx: tx_hash,
},
);
}
}
_ => {}
};
//Native transfers checks
if let Ok(native_transfer_action) =
serde_json::from_slice::<PublicNativeTokenSend>(execution_input)
{
let signer_address = address::from_public_key(&tx.transaction().public_key);
//Correct sender check
if native_transfer_action.from != signer_address {
return Err(TransactionMalformationErrorKind::IncorrectSender);
}
}
//Tree checks
let tx_tree_check = self.store.pub_tx_store.get_tx(tx_hash).is_some();
let nullifier_tree_check = nullifier_created_hashes.iter().any(|nullifier_hash| {
self.store.nullifier_store.contains(&UTXONullifier {
utxo_hash: *nullifier_hash,
})
});
let utxo_commitments_check =
utxo_commitments_created_hashes
.iter()
.any(|utxo_commitment_hash| {
self.store
.utxo_commitments_store
.get_tx(*utxo_commitment_hash)
.is_some()
});
if tx_tree_check {
return Err(
TransactionMalformationErrorKind::TxHashAlreadyPresentInTree { tx: *tx.hash() },
);
}
if nullifier_tree_check {
return Err(
TransactionMalformationErrorKind::NullifierAlreadyPresentInTree { tx: *tx.hash() },
);
}
if utxo_commitments_check {
return Err(
TransactionMalformationErrorKind::UTXOCommitmentAlreadyPresentInTree {
tx: *tx.hash(),
},
);
}
Ok(tx)
}
pub fn push_tx_into_mempool_pre_check(
&mut self,
transaction: Transaction,
transaction: nssa::PublicTransaction,
) -> Result<(), TransactionMalformationErrorKind> {
let mempool_size = self.mempool.len();
if mempool_size >= self.sequencer_config.max_num_tx_in_block {
return Err(TransactionMalformationErrorKind::MempoolFullForRound {
tx: transaction.body().hash(),
});
return Err(TransactionMalformationErrorKind::MempoolFullForRound);
}
let authenticated_tx = self.transaction_pre_check(transaction)?;
self.mempool.push_item(authenticated_tx.into());
self.mempool.push_item(authenticated_tx);
Ok(())
}
fn execute_check_transaction_on_state(
&mut self,
mempool_tx: &MempoolTransaction,
) -> Result<(), TransactionMalformationErrorKind> {
let TransactionBody {
ref utxo_commitments_created_hashes,
ref nullifier_created_hashes,
execution_input,
..
} = mempool_tx.auth_tx.transaction().body();
tx: nssa::PublicTransaction,
) -> Result<nssa::PublicTransaction, nssa::error::NssaError> {
self.store.state.transition_from_public_transaction(&tx)?;
let tx_hash = *mempool_tx.auth_tx.hash();
//Balance move
if let Ok(native_transfer_action) =
serde_json::from_slice::<PublicNativeTokenSend>(execution_input)
{
// Nonce check
let signer_addres =
address::from_public_key(&mempool_tx.auth_tx.transaction().public_key);
if self.store.acc_store.get_account_nonce(&signer_addres)
!= native_transfer_action.nonce
{
return Err(TransactionMalformationErrorKind::NonceMismatch { tx: tx_hash });
}
let from_balance = self
.store
.acc_store
.get_account_balance(&native_transfer_action.from);
let to_balance = self
.store
.acc_store
.get_account_balance(&native_transfer_action.to);
//Balance check
if from_balance < native_transfer_action.balance_to_move {
return Err(TransactionMalformationErrorKind::BalanceMismatch { tx: tx_hash });
}
self.store.acc_store.set_account_balance(
&native_transfer_action.from,
from_balance - native_transfer_action.balance_to_move,
);
self.store.acc_store.set_account_balance(
&native_transfer_action.to,
to_balance + native_transfer_action.balance_to_move,
);
self.store.acc_store.increase_nonce(&signer_addres);
}
for utxo_comm in utxo_commitments_created_hashes {
self.store
.utxo_commitments_store
.add_tx(&UTXOCommitment { hash: *utxo_comm });
}
for nullifier in nullifier_created_hashes.iter() {
self.store.nullifier_store.insert(UTXONullifier {
utxo_hash: *nullifier,
});
}
self.store
.pub_tx_store
.add_tx(mempool_tx.auth_tx.transaction());
Ok(())
}
pub fn register_account(&mut self, account_addr: AccountAddress) {
self.store.acc_store.register_account(account_addr);
Ok(tx)
}
///Produces new block from transactions in mempool
@ -284,15 +102,9 @@ impl SequencerCore {
.mempool
.pop_size(self.sequencer_config.max_num_tx_in_block);
let valid_transactions = transactions
let valid_transactions: Vec<_> = transactions
.into_iter()
.filter_map(|mempool_tx| {
if self.execute_check_transaction_on_state(&mempool_tx).is_ok() {
Some(mempool_tx.auth_tx.into_transaction())
} else {
None
}
})
.filter_map(|tx| self.execute_check_transaction_on_state(tx).ok())
.collect();
let prev_block_hash = self
@ -305,7 +117,6 @@ impl SequencerCore {
block_id: new_block_height,
prev_block_id: self.chain_height,
transactions: valid_transactions,
data: vec![],
prev_block_hash,
};
@ -324,8 +135,6 @@ mod tests {
use crate::config::AccountInitialData;
use super::*;
use k256::{ecdsa::SigningKey, FieldBytes};
use mempool_transaction::MempoolTransaction;
fn setup_sequencer_config_variable_initial_accounts(
initial_accounts: Vec<AccountInitialData>,
@ -347,13 +156,13 @@ mod tests {
fn setup_sequencer_config() -> SequencerConfig {
let acc1_addr = vec![
13, 150, 223, 204, 65, 64, 25, 56, 12, 157, 222, 12, 211, 220, 229, 170, 201, 15, 181,
68, 59, 248, 113, 16, 135, 65, 174, 175, 222, 85, 42, 215,
27, 132, 197, 86, 123, 18, 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30, 24,
52, 96, 72, 25, 255, 156, 23, 245, 233, 213, 221, 7, 143,
];
let acc2_addr = vec![
151, 72, 112, 233, 190, 141, 10, 192, 138, 168, 59, 63, 199, 167, 166, 134, 41, 29,
135, 50, 80, 138, 186, 152, 179, 96, 128, 243, 156, 44, 243, 100,
77, 75, 108, 209, 54, 16, 50, 202, 155, 210, 174, 185, 217, 0, 170, 77, 69, 217, 234,
216, 10, 201, 66, 51, 116, 196, 81, 167, 37, 77, 7, 102,
];
let initial_acc1 = AccountInitialData {
@ -371,36 +180,17 @@ mod tests {
setup_sequencer_config_variable_initial_accounts(initial_accounts)
}
fn create_signing_key_for_account1() -> SigningKey {
let pub_sign_key_acc1 = [
133, 143, 177, 187, 252, 66, 237, 236, 234, 252, 244, 138, 5, 151, 3, 99, 217, 231,
112, 217, 77, 211, 58, 218, 176, 68, 99, 53, 152, 228, 198, 190,
];
let field_bytes = FieldBytes::from_slice(&pub_sign_key_acc1);
SigningKey::from_bytes(field_bytes).unwrap()
fn create_signing_key_for_account1() -> nssa::PrivateKey {
nssa::PrivateKey::try_new([1; 32]).unwrap()
}
fn create_signing_key_for_account2() -> SigningKey {
let pub_sign_key_acc2 = [
54, 90, 62, 225, 71, 225, 228, 148, 143, 53, 210, 23, 137, 158, 171, 156, 48, 7, 139,
52, 117, 242, 214, 7, 99, 29, 122, 184, 59, 116, 144, 107,
];
let field_bytes = FieldBytes::from_slice(&pub_sign_key_acc2);
SigningKey::from_bytes(field_bytes).unwrap()
fn create_signing_key_for_account2() -> nssa::PrivateKey {
nssa::PrivateKey::try_new([2; 32]).unwrap()
}
fn common_setup(sequencer: &mut SequencerCore) {
let tx = common::test_utils::create_dummy_private_transaction_random_signer(
vec![[9; 32]],
vec![[7; 32]],
vec![[8; 32]],
);
let mempool_tx = MempoolTransaction {
auth_tx: tx.into_authenticated().unwrap(),
};
sequencer.mempool.push_item(mempool_tx);
let tx = common::test_utils::produce_dummy_empty_transaction();
sequencer.mempool.push_item(tx);
sequencer
.produce_new_block_with_mempool_transactions()
@ -425,29 +215,31 @@ mod tests {
.try_into()
.unwrap();
assert!(sequencer.store.acc_store.contains_account(&acc1_addr));
assert!(sequencer.store.acc_store.contains_account(&acc2_addr));
let balance_acc_1 = sequencer
.store
.state
.get_account_by_address(&nssa::Address::new(acc1_addr))
.balance;
let balance_acc_2 = sequencer
.store
.state
.get_account_by_address(&nssa::Address::new(acc2_addr))
.balance;
assert_eq!(
10000,
sequencer.store.acc_store.get_account_balance(&acc1_addr)
);
assert_eq!(
20000,
sequencer.store.acc_store.get_account_balance(&acc2_addr)
);
assert_eq!(10000, balance_acc_1);
assert_eq!(20000, balance_acc_2);
}
#[test]
fn test_start_different_intial_accounts_balances() {
let acc1_addr = vec![
13, 150, 223, 204, 65, 64, 25, 56, 12, 157, 222, 12, 211, 220, 229, 170, 201, 15, 181,
68, 59, 248, 113, 16, 135, 65, 174, 175, 222, 42, 42, 42,
27, 132, 197, 86, 123, 18, 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30, 24,
52, 96, 72, 25, 255, 156, 23, 245, 233, 213, 221, 7, 143,
];
let acc2_addr = vec![
151, 72, 112, 233, 190, 141, 10, 192, 138, 168, 59, 63, 199, 167, 166, 134, 41, 29,
135, 50, 80, 138, 186, 152, 179, 96, 128, 243, 156, 42, 42, 42,
77, 75, 108, 209, 54, 16, 50, 202, 155, 210, 174, 185, 217, 0, 170, 77, 69, 217, 234,
216, 10, 201, 66, 51, 116, 196, 81, 167, 37, 77, 7, 102,
];
let initial_acc1 = AccountInitialData {
@ -462,8 +254,6 @@ mod tests {
let initial_accounts = vec![initial_acc1, initial_acc2];
let intial_accounts_len = initial_accounts.len();
let config = setup_sequencer_config_variable_initial_accounts(initial_accounts);
let sequencer = SequencerCore::start_from_config(config.clone());
@ -476,32 +266,24 @@ mod tests {
.try_into()
.unwrap();
assert!(sequencer.store.acc_store.contains_account(&acc1_addr));
assert!(sequencer.store.acc_store.contains_account(&acc2_addr));
assert_eq!(sequencer.store.acc_store.len(), intial_accounts_len);
assert_eq!(
10000,
sequencer.store.acc_store.get_account_balance(&acc1_addr)
sequencer
.store
.state
.get_account_by_address(&nssa::Address::new(acc1_addr))
.balance
);
assert_eq!(
20000,
sequencer.store.acc_store.get_account_balance(&acc2_addr)
sequencer
.store
.state
.get_account_by_address(&nssa::Address::new(acc2_addr))
.balance
);
}
#[test]
fn test_get_tree_roots() {
let config = setup_sequencer_config();
let mut sequencer = SequencerCore::start_from_config(config);
common_setup(&mut sequencer);
let roots = sequencer.get_tree_roots();
assert_eq!(roots.len(), 2); // Should return two roots
}
#[test]
fn test_transaction_pre_check_pass() {
let config = setup_sequencer_config();
@ -509,12 +291,7 @@ mod tests {
common_setup(&mut sequencer);
let tx = common::test_utils::create_dummy_private_transaction_random_signer(
vec![[91; 32]],
vec![[71; 32]],
vec![[81; 32]],
);
let tx = common::test_utils::produce_dummy_empty_transaction();
let result = sequencer.transaction_pre_check(tx);
assert!(result.is_ok());
@ -538,10 +315,9 @@ mod tests {
let sign_key1 = create_signing_key_for_account1();
let tx = common::test_utils::create_dummy_transaction_native_token_transfer(
let tx = common::test_utils::create_transaction_native_token_transfer(
acc1, 0, acc2, 10, sign_key1,
);
let result = sequencer.transaction_pre_check(tx);
assert!(result.is_ok());
@ -565,16 +341,20 @@ mod tests {
let sign_key2 = create_signing_key_for_account2();
let tx = common::test_utils::create_dummy_transaction_native_token_transfer(
let tx = common::test_utils::create_transaction_native_token_transfer(
acc1, 0, acc2, 10, sign_key2,
);
let result = sequencer.transaction_pre_check(tx);
// Signature is valid, stateless check pass
let tx = sequencer.transaction_pre_check(tx).unwrap();
assert_eq!(
result.err().unwrap(),
TransactionMalformationErrorKind::IncorrectSender
);
// Signature is not from sender. Execution fails
let result = sequencer.execute_check_transaction_on_state(tx);
assert!(matches!(
result,
Err(nssa::error::NssaError::ProgramExecutionFailed(_))
));
}
#[test]
@ -595,7 +375,7 @@ mod tests {
let sign_key1 = create_signing_key_for_account1();
let tx = common::test_utils::create_dummy_transaction_native_token_transfer(
let tx = common::test_utils::create_transaction_native_token_transfer(
acc1, 0, acc2, 10000000, sign_key1,
);
@ -604,10 +384,10 @@ mod tests {
//Passed pre-check
assert!(result.is_ok());
let result = sequencer.execute_check_transaction_on_state(&result.unwrap().into());
let result = sequencer.execute_check_transaction_on_state(result.unwrap());
let is_failed_at_balance_mismatch = matches!(
result.err().unwrap(),
TransactionMalformationErrorKind::BalanceMismatch { tx: _ }
nssa::error::NssaError::ProgramExecutionFailed(_)
);
assert!(is_failed_at_balance_mismatch);
@ -631,16 +411,22 @@ mod tests {
let sign_key1 = create_signing_key_for_account1();
let tx = common::test_utils::create_dummy_transaction_native_token_transfer(
let tx = common::test_utils::create_transaction_native_token_transfer(
acc1, 0, acc2, 100, sign_key1,
);
sequencer
.execute_check_transaction_on_state(&tx.into_authenticated().unwrap().into())
.unwrap();
sequencer.execute_check_transaction_on_state(tx).unwrap();
let bal_from = sequencer.store.acc_store.get_account_balance(&acc1);
let bal_to = sequencer.store.acc_store.get_account_balance(&acc2);
let bal_from = sequencer
.store
.state
.get_account_by_address(&nssa::Address::new(acc1))
.balance;
let bal_to = sequencer
.store
.state
.get_account_by_address(&nssa::Address::new(acc2))
.balance;
assert_eq!(bal_from, 9900);
assert_eq!(bal_to, 20100);
@ -656,23 +442,16 @@ mod tests {
common_setup(&mut sequencer);
let tx = common::test_utils::create_dummy_private_transaction_random_signer(
vec![[92; 32]],
vec![[72; 32]],
vec![[82; 32]],
);
let tx = common::test_utils::produce_dummy_empty_transaction();
// Fill the mempool
let dummy_tx = MempoolTransaction {
auth_tx: tx.clone().into_authenticated().unwrap(),
};
sequencer.mempool.push_item(dummy_tx);
sequencer.mempool.push_item(tx.clone());
let result = sequencer.push_tx_into_mempool_pre_check(tx);
assert!(matches!(
result,
Err(TransactionMalformationErrorKind::MempoolFullForRound { .. })
Err(TransactionMalformationErrorKind::MempoolFullForRound)
));
}
@ -683,11 +462,7 @@ mod tests {
common_setup(&mut sequencer);
let tx = common::test_utils::create_dummy_private_transaction_random_signer(
vec![[93; 32]],
vec![[73; 32]],
vec![[83; 32]],
);
let tx = common::test_utils::produce_dummy_empty_transaction();
let result = sequencer.push_tx_into_mempool_pre_check(tx);
assert!(result.is_ok());
@ -700,15 +475,8 @@ mod tests {
let mut sequencer = SequencerCore::start_from_config(config);
let genesis_height = sequencer.chain_height;
let tx = common::test_utils::create_dummy_private_transaction_random_signer(
vec![[94; 32]],
vec![[7; 32]],
vec![[8; 32]],
);
let tx_mempool = MempoolTransaction {
auth_tx: tx.into_authenticated().unwrap(),
};
sequencer.mempool.push_item(tx_mempool);
let tx = common::test_utils::produce_dummy_empty_transaction();
sequencer.mempool.push_item(tx);
let block_id = sequencer.produce_new_block_with_mempool_transactions();
assert!(block_id.is_ok());
@ -733,20 +501,15 @@ mod tests {
let sign_key1 = create_signing_key_for_account1();
let tx = common::test_utils::create_dummy_transaction_native_token_transfer(
let tx = common::test_utils::create_transaction_native_token_transfer(
acc1, 0, acc2, 100, sign_key1,
);
let tx_mempool_original = MempoolTransaction {
auth_tx: tx.clone().into_authenticated().unwrap(),
};
let tx_mempool_replay = MempoolTransaction {
auth_tx: tx.clone().into_authenticated().unwrap(),
};
let tx_original = tx.clone();
let tx_replay = tx.clone();
// Pushing two copies of the same tx to the mempool
sequencer.mempool.push_item(tx_mempool_original);
sequencer.mempool.push_item(tx_mempool_replay);
sequencer.mempool.push_item(tx_original);
sequencer.mempool.push_item(tx_replay);
// Create block
let current_height = sequencer
@ -780,15 +543,12 @@ mod tests {
let sign_key1 = create_signing_key_for_account1();
let tx = common::test_utils::create_dummy_transaction_native_token_transfer(
let tx = common::test_utils::create_transaction_native_token_transfer(
acc1, 0, acc2, 100, sign_key1,
);
// The transaction should be included the first time
let tx_mempool_original = MempoolTransaction {
auth_tx: tx.clone().into_authenticated().unwrap(),
};
sequencer.mempool.push_item(tx_mempool_original);
sequencer.mempool.push_item(tx.clone());
let current_height = sequencer
.produce_new_block_with_mempool_transactions()
.unwrap();
@ -800,10 +560,7 @@ mod tests {
assert_eq!(block.transactions, vec![tx.clone()]);
// Add same transaction should fail
let tx_mempool_replay = MempoolTransaction {
auth_tx: tx.into_authenticated().unwrap(),
};
sequencer.mempool.push_item(tx_mempool_replay);
sequencer.mempool.push_item(tx);
let current_height = sequencer
.produce_new_block_with_mempool_transactions()
.unwrap();

View File

@ -1,20 +0,0 @@
use common::{merkle_tree_public::TreeHashType, transaction::AuthenticatedTransaction};
use mempool::mempoolitem::MemPoolItem;
pub struct MempoolTransaction {
pub auth_tx: AuthenticatedTransaction,
}
impl From<AuthenticatedTransaction> for MempoolTransaction {
fn from(auth_tx: AuthenticatedTransaction) -> Self {
Self { auth_tx }
}
}
impl MemPoolItem for MempoolTransaction {
type Identifier = TreeHashType;
fn identifier(&self) -> Self::Identifier {
*self.auth_tx.hash()
}
}

View File

@ -1,314 +0,0 @@
use accounts::account_core::address::AccountAddress;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct AccountPublicData {
pub balance: u64,
pub address: AccountAddress,
nonce: u64,
}
impl AccountPublicData {
pub fn new(address: AccountAddress) -> Self {
Self {
balance: 0,
nonce: 0,
address,
}
}
fn new_with_balance(address: AccountAddress, balance: u64) -> Self {
Self {
balance,
address,
nonce: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct SequencerAccountsStore {
accounts: HashMap<AccountAddress, AccountPublicData>,
}
impl SequencerAccountsStore {
pub fn new(initial_accounts: &[(AccountAddress, u64)]) -> Self {
let mut accounts = HashMap::new();
for (account_addr, balance) in initial_accounts {
accounts.insert(
*account_addr,
AccountPublicData::new_with_balance(*account_addr, *balance),
);
}
Self { accounts }
}
///Register new account in accounts store
///
///Starts with zero public balance
pub fn register_account(&mut self, account_addr: AccountAddress) {
self.accounts
.insert(account_addr, AccountPublicData::new(account_addr));
}
///Check, if `account_addr` present in account store
pub fn contains_account(&self, account_addr: &AccountAddress) -> bool {
self.accounts.contains_key(account_addr)
}
///Check `account_addr` balance,
///
///returns 0, if account address not found
pub fn get_account_balance(&self, account_addr: &AccountAddress) -> u64 {
self.accounts
.get(account_addr)
.map(|acc| acc.balance)
.unwrap_or(0)
}
pub fn get_account_nonce(&self, account_addr: &AccountAddress) -> u64 {
self.accounts
.get(account_addr)
.map(|acc| acc.nonce)
.unwrap_or(0)
}
///Update `account_addr` balance,
///
/// returns 0, if account address not found, otherwise returns previous balance
///
/// Also, if account was not previously found, sets it with zero balance
pub fn set_account_balance(&mut self, account_addr: &AccountAddress, new_balance: u64) -> u64 {
let acc_data = self.accounts.get_mut(account_addr);
if let Some(acc_data) = acc_data {
let old_balance = acc_data.balance;
acc_data.balance = new_balance;
old_balance
} else {
self.register_account(*account_addr);
let acc = self.accounts.get_mut(account_addr).unwrap();
acc.balance = new_balance;
0
}
}
///Update `account_addr` nonce,
///
/// Returns previous nonce
pub fn increase_nonce(&mut self, account_addr: &AccountAddress) -> u64 {
if let Some(acc_data) = self.accounts.get_mut(account_addr) {
let old_nonce = acc_data.nonce;
acc_data.nonce += 1;
old_nonce
} else {
self.register_account(*account_addr);
self.increase_nonce(account_addr)
}
}
///Remove account from storage
///
/// Fails, if `balance` is != 0
///
/// Returns `Option<AccountAddress>` which is `None` if `account_addr` vere not present in store
pub fn unregister_account(
&mut self,
account_addr: AccountAddress,
) -> Result<Option<AccountAddress>> {
if self.get_account_balance(&account_addr) == 0 {
Ok(self.accounts.remove(&account_addr).map(|data| data.address))
} else {
anyhow::bail!("Chain consistency violation: It is forbidden to remove account with nonzero balance");
}
}
///Number of accounts present in store
pub fn len(&self) -> usize {
self.accounts.len()
}
///Is accounts store empty
pub fn is_empty(&self) -> bool {
self.accounts.is_empty()
}
}
impl Default for SequencerAccountsStore {
fn default() -> Self {
Self::new(&[])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zero_balance_account_data_creation() {
let new_acc = AccountPublicData::new([1; 32]);
assert_eq!(new_acc.balance, 0);
assert_eq!(new_acc.address, [1; 32]);
}
#[test]
fn test_zero_nonce_account_data_creation() {
let new_acc = AccountPublicData::new([1; 32]);
assert_eq!(new_acc.nonce, 0);
}
#[test]
fn test_non_zero_balance_account_data_creation() {
let new_acc = AccountPublicData::new_with_balance([1; 32], 10);
assert_eq!(new_acc.balance, 10);
assert_eq!(new_acc.address, [1; 32]);
}
#[test]
fn test_zero_nonce_account_data_creation_with_balance() {
let new_acc = AccountPublicData::new_with_balance([1; 32], 10);
assert_eq!(new_acc.nonce, 0);
}
#[test]
fn default_account_sequencer_store() {
let seq_acc_store = SequencerAccountsStore::default();
assert!(seq_acc_store.accounts.is_empty());
}
#[test]
fn account_sequencer_store_register_acc() {
let mut seq_acc_store = SequencerAccountsStore::default();
seq_acc_store.register_account([1; 32]);
assert!(seq_acc_store.contains_account(&[1; 32]));
let acc_balance = seq_acc_store.get_account_balance(&[1; 32]);
assert_eq!(acc_balance, 0);
}
#[test]
fn account_sequencer_store_unregister_acc_not_present() {
let mut seq_acc_store = SequencerAccountsStore::default();
seq_acc_store.register_account([1; 32]);
let rem_res = seq_acc_store.unregister_account([2; 32]).unwrap();
assert!(rem_res.is_none());
}
#[test]
fn account_sequencer_store_unregister_acc_not_zero_balance() {
let mut seq_acc_store = SequencerAccountsStore::new(&[([1; 32], 12), ([2; 32], 100)]);
let rem_res = seq_acc_store.unregister_account([1; 32]);
assert!(rem_res.is_err());
}
#[test]
fn account_sequencer_store_unregister_acc() {
let mut seq_acc_store = SequencerAccountsStore::default();
seq_acc_store.register_account([1; 32]);
assert!(seq_acc_store.contains_account(&[1; 32]));
seq_acc_store.unregister_account([1; 32]).unwrap().unwrap();
assert!(!seq_acc_store.contains_account(&[1; 32]));
}
#[test]
fn account_sequencer_store_with_preset_accounts_1() {
let seq_acc_store = SequencerAccountsStore::new(&[([1; 32], 12), ([2; 32], 100)]);
assert!(seq_acc_store.contains_account(&[1; 32]));
assert!(seq_acc_store.contains_account(&[2; 32]));
let acc_balance = seq_acc_store.get_account_balance(&[1; 32]);
assert_eq!(acc_balance, 12);
let acc_balance = seq_acc_store.get_account_balance(&[2; 32]);
assert_eq!(acc_balance, 100);
}
#[test]
fn account_sequencer_store_with_preset_accounts_2() {
let seq_acc_store =
SequencerAccountsStore::new(&[([6; 32], 120), ([7; 32], 15), ([8; 32], 10)]);
assert!(seq_acc_store.contains_account(&[6; 32]));
assert!(seq_acc_store.contains_account(&[7; 32]));
assert!(seq_acc_store.contains_account(&[8; 32]));
let acc_balance = seq_acc_store.get_account_balance(&[6; 32]);
assert_eq!(acc_balance, 120);
let acc_balance = seq_acc_store.get_account_balance(&[7; 32]);
assert_eq!(acc_balance, 15);
let acc_balance = seq_acc_store.get_account_balance(&[8; 32]);
assert_eq!(acc_balance, 10);
}
#[test]
fn account_sequencer_store_fetch_unknown_account() {
let seq_acc_store =
SequencerAccountsStore::new(&[([6; 32], 120), ([7; 32], 15), ([8; 32], 10)]);
let acc_balance = seq_acc_store.get_account_balance(&[9; 32]);
assert_eq!(acc_balance, 0);
}
#[test]
fn account_sequencer_store_is_empty_test() {
let seq_acc_store = SequencerAccountsStore::default();
assert!(seq_acc_store.is_empty());
}
#[test]
fn account_sequencer_store_set_balance_to_unknown_account() {
let mut seq_acc_store = SequencerAccountsStore::default();
let ret = seq_acc_store.set_account_balance(&[1; 32], 100);
assert_eq!(ret, 0);
assert!(seq_acc_store.contains_account(&[1; 32]));
assert_eq!(seq_acc_store.get_account_balance(&[1; 32]), 100);
}
#[test]
fn test_increase_nonce() {
let mut account_store = SequencerAccountsStore::default();
let address = [1; 32];
let first_nonce = account_store.increase_nonce(&address);
assert_eq!(first_nonce, 0);
let second_nonce = account_store.increase_nonce(&address);
assert_eq!(second_nonce, 1);
}
}

View File

@ -1,7 +1,7 @@
use std::{collections::HashMap, path::Path};
use anyhow::Result;
use common::{block::Block, merkle_tree_public::TreeHashType, transaction::Transaction};
use common::{block::Block, merkle_tree_public::TreeHashType};
use storage::RocksDBIO;
pub struct SequecerBlockStore {
@ -51,12 +51,12 @@ 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<Transaction> {
pub fn get_transaction_by_hash(&self, hash: TreeHashType) -> Option<nssa::PublicTransaction> {
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() {
if transaction.body().hash() == hash {
if transaction.hash() == hash {
return Some(transaction);
}
}
@ -69,13 +69,14 @@ fn block_to_transactions_map(block: &Block) -> HashMap<TreeHashType, u64> {
block
.transactions
.iter()
.map(|transaction| (transaction.body().hash(), block.block_id))
.map(|transaction| (transaction.hash(), block.block_id))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
@ -88,22 +89,21 @@ mod tests {
prev_block_hash: [0; 32],
hash: [1; 32],
transactions: vec![],
data: vec![],
};
// Start an empty node store
let mut node_store =
SequecerBlockStore::open_db_with_genesis(path, Some(genesis_block)).unwrap();
let tx = common::test_utils::produce_dummy_empty_transaction();
let block = common::test_utils::produce_dummy_block(1, None, vec![tx.clone()], vec![]);
let block = common::test_utils::produce_dummy_block(1, None, vec![tx.clone()]);
// Try retrieve a tx that's not in the chain yet.
let retrieved_tx = node_store.get_transaction_by_hash(tx.body().hash());
let retrieved_tx = node_store.get_transaction_by_hash(tx.hash());
assert_eq!(None, retrieved_tx);
// Add the block with the transaction
node_store.put_block_at_id(block).unwrap();
// Try again
let retrieved_tx = node_store.get_transaction_by_hash(tx.body().hash());
let retrieved_tx = node_store.get_transaction_by_hash(tx.hash());
assert_eq!(Some(tx), retrieved_tx);
}
}

View File

@ -1,25 +1,17 @@
use std::{collections::HashSet, path::Path};
use std::path::Path;
use accounts_store::SequencerAccountsStore;
use block_store::SequecerBlockStore;
use common::{
block::HashableBlockData,
merkle_tree_public::merkle_tree::{PublicTransactionMerkleTree, UTXOCommitmentsMerkleTree},
nullifier::UTXONullifier,
};
use common::block::HashableBlockData;
use nssa::{self, Address};
use rand::{rngs::OsRng, RngCore};
use crate::config::AccountInitialData;
pub mod accounts_store;
pub mod block_store;
pub struct SequecerChainStore {
pub acc_store: SequencerAccountsStore,
pub state: nssa::V01State,
pub block_store: SequecerBlockStore,
pub nullifier_store: HashSet<UTXONullifier>,
pub utxo_commitments_store: UTXOCommitmentsMerkleTree,
pub pub_tx_store: PublicTransactionMerkleTree,
}
impl SequecerChainStore {
@ -29,7 +21,7 @@ impl SequecerChainStore {
is_genesis_random: bool,
initial_accounts: &[AccountInitialData],
) -> Self {
let init_accs: Vec<_> = initial_accounts
let init_accs: Vec<(Address, u128)> = initial_accounts
.iter()
.map(|acc_data| {
(
@ -42,10 +34,7 @@ impl SequecerChainStore {
})
.collect();
let acc_store = SequencerAccountsStore::new(&init_accs);
let nullifier_store = HashSet::new();
let utxo_commitments_store = UTXOCommitmentsMerkleTree::new(vec![]);
let pub_tx_store = PublicTransactionMerkleTree::new(vec![]);
let state = nssa::V01State::new_with_genesis_accounts(&init_accs);
let mut data = [0; 32];
let mut prev_block_hash = [0; 32];
@ -59,7 +48,6 @@ impl SequecerChainStore {
block_id: genesis_id,
prev_block_id: genesis_id.saturating_sub(1),
transactions: vec![],
data: data.to_vec(),
prev_block_hash,
};
@ -73,12 +61,6 @@ impl SequecerChainStore {
)
.unwrap();
Self {
acc_store,
block_store,
nullifier_store,
utxo_commitments_store,
pub_tx_store,
}
Self { state, block_store }
}
}

View File

@ -14,6 +14,7 @@ actix-cors.workspace = true
futures.workspace = true
hex.workspace = true
tempfile.workspace = true
base64.workspace = true
actix-web.workspace = true
tokio.workspace = true
@ -21,8 +22,8 @@ tokio.workspace = true
[dependencies.mempool]
path = "../mempool"
[dependencies.accounts]
path = "../accounts"
[dependencies.key_protocol]
path = "../key_protocol"
[dependencies.sequencer_core]
path = "../sequencer_core"
@ -32,3 +33,6 @@ path = "../storage"
[dependencies.common]
path = "../common"
[dependencies.nssa]
path = "../nssa"

View File

@ -1,8 +1,11 @@
use actix_web::Error as HttpError;
use base64::{engine::general_purpose, Engine};
use nssa;
use sequencer_core::config::AccountInitialData;
use serde_json::Value;
use common::{
block::HashableBlockData,
merkle_tree_public::TreeHashType,
rpc_primitives::{
errors::RpcError,
@ -17,14 +20,13 @@ use common::{
use common::rpc_primitives::requests::{
GetBlockDataRequest, GetBlockDataResponse, GetGenesisIdRequest, GetGenesisIdResponse,
GetLastBlockRequest, GetLastBlockResponse, HelloRequest, HelloResponse, RegisterAccountRequest,
RegisterAccountResponse, SendTxRequest, SendTxResponse,
GetLastBlockRequest, GetLastBlockResponse, HelloRequest, HelloResponse, SendTxRequest,
SendTxResponse,
};
use super::{respond, types::err_rpc::RpcErr, JsonHandler};
pub const HELLO: &str = "hello";
pub const REGISTER_ACCOUNT: &str = "register_account";
pub const SEND_TX: &str = "send_tx";
pub const GET_BLOCK: &str = "get_block";
pub const GET_GENESIS: &str = "get_genesis";
@ -66,29 +68,15 @@ impl JsonHandler {
respond(helperstruct)
}
async fn process_register_account_request(&self, request: Request) -> Result<Value, RpcErr> {
let acc_req = RegisterAccountRequest::parse(Some(request.params))?;
{
let mut acc_store = self.sequencer_state.lock().await;
acc_store.register_account(acc_req.address);
}
let helperstruct = RegisterAccountResponse {
status: SUCCESS.to_string(),
};
respond(helperstruct)
}
async fn process_send_tx(&self, request: Request) -> Result<Value, RpcErr> {
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 mut state = self.sequencer_state.lock().await;
state.push_tx_into_mempool_pre_check(send_tx_req.transaction)?;
state.push_tx_into_mempool_pre_check(tx)?;
}
let helperstruct = SendTxResponse {
@ -110,7 +98,9 @@ impl JsonHandler {
.get_block_at_id(get_block_req.block_id)?
};
let helperstruct = GetBlockDataResponse { block };
let helperstruct = GetBlockDataResponse {
block: HashableBlockData::from(block).to_bytes(),
};
respond(helperstruct)
}
@ -164,13 +154,16 @@ impl JsonHandler {
let get_account_req = GetAccountBalanceRequest::parse(Some(request.params))?;
let address_bytes = hex::decode(get_account_req.address)
.map_err(|_| RpcError::invalid_params("invalid hex".to_string()))?;
let address = address_bytes
.try_into()
.map_err(|_| RpcError::invalid_params("invalid length".to_string()))?;
let address = nssa::Address::new(
address_bytes
.try_into()
.map_err(|_| RpcError::invalid_params("invalid length".to_string()))?,
);
let balance = {
let state = self.sequencer_state.lock().await;
state.store.acc_store.get_account_balance(&address)
let account = state.store.state.get_account_by_address(&address);
account.balance
};
let helperstruct = GetAccountBalanceResponse { balance };
@ -190,16 +183,22 @@ impl JsonHandler {
let transaction = {
let state = self.sequencer_state.lock().await;
state.store.block_store.get_transaction_by_hash(hash)
state
.store
.block_store
.get_transaction_by_hash(hash)
.map(|tx| tx.to_bytes())
};
let base64_encoded = transaction.map(|tx| general_purpose::STANDARD.encode(tx));
let helperstruct = GetTransactionByHashResponse {
transaction: base64_encoded,
};
let helperstruct = GetTransactionByHashResponse { transaction };
respond(helperstruct)
}
pub async fn process_request_internal(&self, request: Request) -> Result<Value, RpcErr> {
match request.method.as_ref() {
HELLO => self.process_temp_hello(request).await,
REGISTER_ACCOUNT => self.process_register_account_request(request).await,
SEND_TX => self.process_send_tx(request).await,
GET_BLOCK => self.process_get_block_data(request).await,
GET_GENESIS => self.process_get_genesis(request).await,
@ -217,10 +216,9 @@ mod tests {
use std::sync::Arc;
use crate::{rpc_handler, JsonHandler};
use common::{
rpc_primitives::RpcPollingConfig,
transaction::{SignaturePrivateKey, Transaction, TransactionBody},
};
use base64::{engine::general_purpose, Engine};
use common::rpc_primitives::RpcPollingConfig;
use sequencer_core::{
config::{AccountInitialData, SequencerConfig},
SequencerCore,
@ -233,13 +231,13 @@ mod tests {
let tempdir = tempdir().unwrap();
let home = tempdir.path().to_path_buf();
let acc1_addr = vec![
13, 150, 223, 204, 65, 64, 25, 56, 12, 157, 222, 12, 211, 220, 229, 170, 201, 15, 181,
68, 59, 248, 113, 16, 135, 65, 174, 175, 222, 85, 42, 215,
27, 132, 197, 86, 123, 18, 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30, 24,
52, 96, 72, 25, 255, 156, 23, 245, 233, 213, 221, 7, 143,
];
let acc2_addr = vec![
151, 72, 112, 233, 190, 141, 10, 192, 138, 168, 59, 63, 199, 167, 166, 134, 41, 29,
135, 50, 80, 138, 186, 152, 179, 96, 128, 243, 156, 44, 243, 100,
77, 75, 108, 209, 54, 16, 50, 202, 155, 210, 174, 185, 217, 0, 170, 77, 69, 217, 234,
216, 10, 201, 66, 51, 116, 196, 81, 167, 37, 77, 7, 102,
];
let initial_acc1 = AccountInitialData {
@ -266,31 +264,32 @@ mod tests {
}
}
fn json_handler_for_tests() -> (JsonHandler, Vec<AccountInitialData>) {
fn components_for_tests() -> (
JsonHandler,
Vec<AccountInitialData>,
nssa::PublicTransaction,
) {
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();
let tx_body = TransactionBody {
tx_kind: common::transaction::TxKind::Shielded,
execution_input: Default::default(),
execution_output: Default::default(),
utxo_commitments_spent_hashes: Default::default(),
utxo_commitments_created_hashes: Default::default(),
nullifier_created_hashes: Default::default(),
execution_proof_private: Default::default(),
encoded_data: Default::default(),
ephemeral_pub_key: Default::default(),
commitment: Default::default(),
tweak: Default::default(),
secret_r: Default::default(),
sc_addr: Default::default(),
};
let tx = Transaction::new(tx_body, SignaturePrivateKey::from_slice(&[1; 32]).unwrap());
let signing_key = nssa::PrivateKey::try_new([1; 32]).unwrap();
let balance_to_move = 10;
let tx = common::test_utils::create_transaction_native_token_transfer(
[
27, 132, 197, 86, 123, 18, 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30,
24, 52, 96, 72, 25, 255, 156, 23, 245, 233, 213, 221, 7, 143,
],
0,
[2; 32],
balance_to_move,
signing_key,
);
sequencer_core
.push_tx_into_mempool_pre_check(tx.clone())
.unwrap();
sequencer_core.push_tx_into_mempool_pre_check(tx).unwrap();
sequencer_core
.produce_new_block_with_mempool_transactions()
.unwrap();
@ -303,6 +302,7 @@ mod tests {
sequencer_state: sequencer_core,
},
initial_accounts,
tx,
)
}
@ -329,7 +329,7 @@ mod tests {
#[actix_web::test]
async fn test_get_account_balance_for_non_existent_account() {
let (json_handler, _) = json_handler_for_tests();
let (json_handler, _, _) = components_for_tests();
let request = serde_json::json!({
"jsonrpc": "2.0",
"method": "get_account_balance",
@ -351,7 +351,7 @@ mod tests {
#[actix_web::test]
async fn test_get_account_balance_for_invalid_hex() {
let (json_handler, _) = json_handler_for_tests();
let (json_handler, _, _) = components_for_tests();
let request = serde_json::json!({
"jsonrpc": "2.0",
"method": "get_account_balance",
@ -374,7 +374,7 @@ mod tests {
#[actix_web::test]
async fn test_get_account_balance_for_invalid_length() {
let (json_handler, _) = json_handler_for_tests();
let (json_handler, _, _) = components_for_tests();
let request = serde_json::json!({
"jsonrpc": "2.0",
"method": "get_account_balance",
@ -397,7 +397,7 @@ mod tests {
#[actix_web::test]
async fn test_get_account_balance_for_existing_account() {
let (json_handler, initial_accounts) = json_handler_for_tests();
let (json_handler, initial_accounts, _) = components_for_tests();
let acc1_addr = initial_accounts[0].addr.clone();
@ -411,7 +411,7 @@ mod tests {
"id": 1,
"jsonrpc": "2.0",
"result": {
"balance": 10000
"balance": 10000 - 10
}
});
@ -422,7 +422,7 @@ mod tests {
#[actix_web::test]
async fn test_get_transaction_by_hash_for_non_existent_hash() {
let (json_handler, _) = json_handler_for_tests();
let (json_handler, _, _) = components_for_tests();
let request = serde_json::json!({
"jsonrpc": "2.0",
"method": "get_transaction_by_hash",
@ -444,7 +444,7 @@ mod tests {
#[actix_web::test]
async fn test_get_transaction_by_hash_for_invalid_hex() {
let (json_handler, _) = json_handler_for_tests();
let (json_handler, _, _) = components_for_tests();
let request = serde_json::json!({
"jsonrpc": "2.0",
"method": "get_transaction_by_hash",
@ -468,7 +468,7 @@ mod tests {
#[actix_web::test]
async fn test_get_transaction_by_hash_for_invalid_length() {
let (json_handler, _) = json_handler_for_tests();
let (json_handler, _, _) = components_for_tests();
let request = serde_json::json!({
"jsonrpc": "2.0",
"method": "get_transaction_by_hash",
@ -492,11 +492,14 @@ mod tests {
#[actix_web::test]
async fn test_get_transaction_by_hash_for_existing_transaction() {
let (json_handler, _) = json_handler_for_tests();
let (json_handler, _, tx) = components_for_tests();
let tx_hash_hex = hex::encode(tx.hash());
let expected_base64_encoded = general_purpose::STANDARD.encode(tx.to_bytes());
let request = serde_json::json!({
"jsonrpc": "2.0",
"method": "get_transaction_by_hash",
"params": { "hash": "2c69b9639bcf8d58509204e18f1d5962029bf26840915f2bf2bb434501ad3c38"},
"params": { "hash": tx_hash_hex},
"id": 1
});
@ -504,28 +507,9 @@ mod tests {
"id": 1,
"jsonrpc": "2.0",
"result": {
"transaction": {
"body": {
"commitment": [],
"encoded_data": [],
"ephemeral_pub_key": [],
"execution_input": [],
"execution_output": [],
"execution_proof_private": "",
"nullifier_created_hashes": [],
"sc_addr": "",
"secret_r": vec![0; 32],
"tweak": "0".repeat(64),
"tx_kind": "Shielded",
"utxo_commitments_created_hashes": [],
"utxo_commitments_spent_hashes": [],
},
"public_key": "3056301006072A8648CE3D020106052B8104000A034200041B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F70BEAF8F588B541507FED6A642C5AB42DFDF8120A7F639DE5122D47A69A8E8D1",
"signature": "D75783642EA6E7D5E13AE8CCD3C2D3F82728C0A778A80C9F2976C739CD9F7F3F240B0532954D87761DE299A6CB9E6606295786BA5D0F5CACAB3F3626724528B1"
}
"transaction": expected_base64_encoded,
}
});
let response = call_rpc_handler_with_json(json_handler, request).await;
assert_eq!(response, expected_response);

View File

@ -1,6 +1,6 @@
use std::{path::Path, sync::Arc};
use common::block::Block;
use common::block::{Block, HashableBlockData};
use error::DbError;
use rocksdb::{
BoundColumnFamily, ColumnFamilyDescriptor, DBWithThreadMode, MultiThreaded, Options,
@ -242,12 +242,7 @@ impl RocksDBIO {
.put_cf(
&cf_block,
block.block_id.to_be_bytes(),
serde_json::to_vec(&block).map_err(|serr| {
DbError::serde_cast_message(
serr,
Some("Block Serialization failed".to_string()),
)
})?,
HashableBlockData::from(block).to_bytes(),
)
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
Ok(())
@ -261,9 +256,7 @@ impl RocksDBIO {
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
if let Some(data) = res {
Ok(serde_json::from_slice::<Block>(&data).map_err(|serr| {
DbError::serde_cast_message(serr, Some("Block Deserialization failed".to_string()))
})?)
Ok(HashableBlockData::from_bytes(&data).into())
} else {
Err(DbError::db_interaction_error(
"Block on this id not found".to_string(),

View File

@ -1,17 +0,0 @@
[package]
name = "utxo"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
serde_json.workspace = true
env_logger.workspace = true
log.workspace = true
serde.workspace = true
sha2.workspace = true
hex.workspace = true
rand.workspace = true
[dependencies.common]
path = "../common"

View File

@ -1 +0,0 @@
pub mod utxo_core;

View File

@ -1,162 +0,0 @@
use anyhow::Result;
use common::{merkle_tree_public::TreeHashType, AccountId};
use log::info;
use rand::{rngs::OsRng, RngCore};
use serde::{Deserialize, Serialize};
use sha2::{digest::FixedOutput, Digest};
///Raw asset data
pub type Asset = Vec<u8>;
pub type Randomness = [u8; 32];
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
///Container for raw utxo payload
pub struct UTXO {
pub hash: TreeHashType,
pub owner: AccountId,
pub asset: Asset,
// TODO: change to u256
pub amount: u128,
pub privacy_flag: bool,
pub randomness: Randomness,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UTXOPayload {
pub owner: AccountId,
pub asset: Asset,
// TODO: change to u256
pub amount: u128,
pub privacy_flag: bool,
pub randomness: Randomness,
}
impl UTXOPayload {
fn to_bytes(&self) -> Vec<u8> {
let mut result = Vec::new();
result.extend_from_slice(&self.owner);
result.extend_from_slice(&self.asset);
result.extend_from_slice(&self.amount.to_be_bytes());
result.push(self.privacy_flag as u8);
result.extend_from_slice(&self.randomness);
result
}
}
impl UTXO {
pub fn new(owner: AccountId, asset: Asset, amount: u128, privacy_flag: bool) -> Self {
let mut randomness = Randomness::default();
OsRng.fill_bytes(&mut randomness);
let payload = UTXOPayload {
owner,
asset,
amount,
privacy_flag,
randomness,
};
Self::create_utxo_from_payload(payload)
}
pub fn create_utxo_from_payload(payload_with_asset: UTXOPayload) -> Self {
let mut hasher = sha2::Sha256::new();
hasher.update(payload_with_asset.to_bytes());
let hash = <TreeHashType>::from(hasher.finalize_fixed());
Self {
hash,
owner: payload_with_asset.owner,
asset: payload_with_asset.asset,
amount: payload_with_asset.amount,
privacy_flag: payload_with_asset.privacy_flag,
randomness: payload_with_asset.randomness,
}
}
pub fn interpret_asset<'de, ToInterpret: Deserialize<'de>>(&'de self) -> Result<ToInterpret> {
Ok(serde_json::from_slice(&self.asset)?)
}
pub fn into_payload(&self) -> UTXOPayload {
UTXOPayload {
owner: self.owner,
asset: self.asset.clone(),
amount: self.amount,
privacy_flag: self.privacy_flag,
randomness: self.randomness,
}
}
pub fn log(&self) {
info!("UTXO hash is {:?}", hex::encode(self.hash));
info!("UTXO owner is {:?}", hex::encode(self.owner));
info!("UTXO asset is {:?}", hex::encode(self.asset.clone()));
info!("UTXO amount is {:?}", self.amount);
info!("UTXO privacy_flag is {:?}", self.privacy_flag);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct TestAsset {
id: u32,
name: String,
}
fn sample_account() -> AccountId {
AccountId::default()
}
fn sample_payload() -> UTXOPayload {
UTXOPayload {
owner: sample_account(),
asset: serde_json::to_vec(&TestAsset {
id: 1,
name: "Test".to_string(),
})
.unwrap(),
amount: 10,
privacy_flag: false,
randomness: Randomness::default(),
}
}
#[test]
fn test_create_utxo_from_payload() {
let payload = sample_payload();
let utxo = UTXO::create_utxo_from_payload(payload.clone());
// Ensure hash is created and the UTXO fields are correctly assigned
assert_eq!(utxo.owner, payload.owner);
assert_eq!(utxo.asset, payload.asset);
}
#[test]
fn test_interpret_asset() {
let payload = sample_payload();
let utxo = UTXO::create_utxo_from_payload(payload);
// Interpret asset as TestAsset
let interpreted: TestAsset = utxo.interpret_asset().unwrap();
assert_eq!(
interpreted,
TestAsset {
id: 1,
name: "Test".to_string()
}
);
}
#[test]
fn test_interpret_invalid_asset() {
let mut payload = sample_payload();
payload.asset = vec![0, 1, 2, 3]; // Invalid data for deserialization
let utxo = UTXO::create_utxo_from_payload(payload);
// This should fail because the asset is not valid JSON for TestAsset
let result: Result<TestAsset> = utxo.interpret_asset();
assert!(result.is_err());
}
}

View File

@ -18,22 +18,17 @@ reqwest.workspace = true
thiserror.workspace = true
tokio.workspace = true
tempfile.workspace = true
risc0-zkvm = { git = "https://github.com/risc0/risc0.git", branch = "release-2.3" }
risc0-zkvm = "2.3.1"
hex.workspace = true
actix-rt.workspace = true
clap.workspace = true
nssa-core = { path = "../nssa/core" }
[dependencies.sc_core]
path = "../sc_core"
[dependencies.key_protocol]
path = "../key_protocol"
[dependencies.accounts]
path = "../accounts"
[dependencies.utxo]
path = "../utxo"
[dependencies.zkvm]
path = "../zkvm"
[dependencies.nssa]
path = "../nssa"
[dependencies.common]
path = "../common"

View File

@ -1,111 +0,0 @@
use accounts::account_core::{address::AccountAddress, Account};
use std::collections::HashMap;
pub struct WalletAccountsStore {
pub accounts: HashMap<AccountAddress, Account>,
}
impl WalletAccountsStore {
pub fn new() -> Self {
Self {
accounts: HashMap::new(),
}
}
pub fn register_account(&mut self, account: Account) {
self.accounts.insert(account.address, account);
}
pub fn unregister_account(&mut self, account_addr: AccountAddress) {
self.accounts.remove(&account_addr);
}
}
impl Default for WalletAccountsStore {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use accounts::account_core::Account;
/// Helper function to create a sample account
fn create_sample_account(balance: u64) -> Account {
Account::new_with_balance(balance)
}
fn pad_to_32(slice: &[u8]) -> [u8; 32] {
let mut padded = [0u8; 32];
let len = slice.len().min(32);
padded[..len].copy_from_slice(&slice[..len]);
padded
}
#[test]
fn test_create_empty_store() {
let store = WalletAccountsStore::new();
assert!(store.accounts.is_empty());
}
#[test]
fn test_register_account() {
let mut store = WalletAccountsStore::new();
let account = create_sample_account(100);
let account_addr = account.address;
store.register_account(account);
assert_eq!(store.accounts.len(), 1);
let stored_account = store.accounts.get(&account_addr).unwrap();
assert_eq!(stored_account.balance, 100);
}
#[test]
fn test_unregister_account() {
let mut store = WalletAccountsStore::new();
let account = create_sample_account(100);
let account_addr = account.address;
store.register_account(account);
assert_eq!(store.accounts.len(), 1);
store.unregister_account(account_addr);
assert!(store.accounts.is_empty());
}
#[test]
fn test_unregister_nonexistent_account() {
let mut store = WalletAccountsStore::new();
let account_addr: [u8; 32] = pad_to_32("nonexistent".to_string().as_bytes());
store.unregister_account(account_addr);
assert!(store.accounts.is_empty());
}
#[test]
fn test_register_multiple_accounts() {
let mut store = WalletAccountsStore::new();
let account1 = create_sample_account(100);
let account2 = create_sample_account(200);
let address_1 = account1.address;
let address_2 = account2.address;
store.register_account(account1);
store.register_account(account2);
assert_eq!(store.accounts.len(), 2);
let stored_account1 = store.accounts.get(&address_1).unwrap();
let stored_account2 = store.accounts.get(&address_2).unwrap();
assert_eq!(stored_account1.balance, 100);
assert_eq!(stored_account2.balance, 200);
}
}

View File

@ -1,252 +1,72 @@
use std::collections::{BTreeMap, HashMap};
use std::collections::HashMap;
use accounts::account_core::{address::AccountAddress, Account};
use anyhow::Result;
use common::merkle_tree_public::merkle_tree::UTXOCommitmentsMerkleTree;
use sc_core::public_context::PublicSCContext;
use serde::{Deserialize, Serialize};
use key_protocol::key_protocol_core::NSSAUserData;
use crate::config::WalletConfig;
pub mod accounts_store;
#[derive(Deserialize, Serialize)]
pub struct AccMap {
pub acc_map: HashMap<String, Account>,
}
impl From<HashMap<[u8; 32], Account>> for AccMap {
fn from(value: HashMap<[u8; 32], Account>) -> Self {
AccMap {
acc_map: value
.into_iter()
.map(|(key, val)| (hex::encode(key), val))
.collect(),
}
}
}
impl From<AccMap> for HashMap<[u8; 32], Account> {
fn from(value: AccMap) -> Self {
value
.acc_map
.into_iter()
.map(|(key, val)| (hex::decode(key).unwrap().try_into().unwrap(), val))
.collect()
}
}
pub struct WalletChainStore {
pub acc_map: HashMap<AccountAddress, Account>,
pub user_data: NSSAUserData,
pub utxo_commitments_store: UTXOCommitmentsMerkleTree,
pub wallet_config: WalletConfig,
}
impl WalletChainStore {
pub fn new(config: WalletConfig) -> Result<Self> {
let acc_map = HashMap::new();
let accounts: HashMap<nssa::Address, nssa_core::account::Account> = config
.initial_accounts
.clone()
.into_iter()
.map(|init_acc_data| (init_acc_data.address, init_acc_data.account))
.collect();
let accounts_keys: HashMap<nssa::Address, nssa::PrivateKey> = config
.initial_accounts
.clone()
.into_iter()
.map(|init_acc_data| (init_acc_data.address, init_acc_data.pub_sign_key))
.collect();
let utxo_commitments_store = UTXOCommitmentsMerkleTree::new(vec![]);
Ok(Self {
acc_map,
user_data: NSSAUserData::new_with_accounts(accounts_keys, accounts),
utxo_commitments_store,
wallet_config: config,
})
}
pub fn produce_context(&self, caller: AccountAddress) -> PublicSCContext {
let mut account_masks = BTreeMap::new();
for (acc_addr, acc) in &self.acc_map {
account_masks.insert(*acc_addr, acc.make_account_public_mask());
}
PublicSCContext {
caller_address: caller,
caller_balance: self.acc_map.get(&caller).unwrap().balance,
account_masks,
comitment_store_root: self.utxo_commitments_store.get_root().unwrap_or([0; 32]),
commitments_tree: self.utxo_commitments_store.clone(),
}
}
}
#[cfg(test)]
mod tests {
use crate::config::InitialAccountData;
use super::*;
use accounts::account_core::Account;
use std::path::PathBuf;
use tempfile::tempdir;
fn create_initial_accounts() -> Vec<Account> {
fn create_initial_accounts() -> Vec<InitialAccountData> {
let initial_acc1 = serde_json::from_str(r#"{
"address": [
244,
55,
238,
205,
74,
115,
179,
192,
65,
186,
166,
169,
221,
45,
6,
57,
200,
65,
195,
70,
118,
252,
206,
100,
215,
250,
72,
230,
19,
71,
217,
249
],
"balance": 100,
"nonce": 0,
"key_holder": {
"nullifer_public_key": "03A340BECA9FAAB444CED0140681D72EA1318B5C611704FEE017DA9836B17DB718",
"pub_account_signing_key": [
244,
88,
134,
61,
35,
209,
229,
101,
85,
35,
140,
140,
192,
226,
83,
83,
190,
189,
110,
8,
89,
127,
147,
142,
157,
204,
51,
109,
189,
92,
144,
68
],
"top_secret_key_holder": {
"secret_spending_key": "7BC46784DB1BC67825D8F029436846712BFDF9B5D79EA3AB11D39A52B9B229D4"
},
"utxo_secret_key_holder": {
"nullifier_secret_key": "BB54A8D3C9C51B82C431082D1845A74677B0EF829A11B517E1D9885DE3139506",
"viewing_secret_key": "AD923E92F6A5683E30140CEAB2702AFB665330C1EE4EFA70FAF29767B6B52BAF"
},
"viewing_public_key": "0361220C5D277E7A1709340FD31A52600C1432B9C45B9BCF88A43581D58824A8B6"
},
"utxos": {}
"address": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
"pub_sign_key": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
"account": {
"program_owner": [0,0,0,0,0,0,0,0],
"balance": 100,
"nonce": 0,
"data": []
}
}"#).unwrap();
let initial_acc2 = serde_json::from_str(r#"{
"address": [
72,
169,
70,
237,
1,
96,
35,
157,
25,
15,
83,
18,
52,
206,
202,
63,
48,
59,
173,
76,
78,
7,
254,
229,
28,
45,
194,
79,
6,
89,
58,
85
],
"balance": 200,
"nonce": 0,
"key_holder": {
"nullifer_public_key": "02172F50274DE67C4087C344F5D58E11DF761D90285B095060E0994FAA6BCDE271",
"pub_account_signing_key": [
136,
105,
9,
53,
180,
145,
64,
5,
235,
174,
62,
211,
206,
116,
185,
24,
214,
62,
244,
64,
224,
59,
120,
150,
30,
249,
160,
46,
189,
254,
47,
244
],
"top_secret_key_holder": {
"secret_spending_key": "80A186737C8D38B4288A03F0F589957D9C040D79C19F3E0CC4BA80F8494E5179"
},
"utxo_secret_key_holder": {
"nullifier_secret_key": "746928E63F0984F6F4818933493CE9C067562D9CB932FDC06D82C86CDF6D7122",
"viewing_secret_key": "89176CF4BC9E673807643FD52110EF99D4894335AFB10D881AC0B5041FE1FCB7"
},
"viewing_public_key": "026072A8F83FEC3472E30CDD4767683F30B91661D25B1040AD9A5FC2E01D659F99"
},
"utxos": {}
"address": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
"pub_sign_key": [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
"account": {
"program_owner": [0,0,0,0,0,0,0,0],
"balance": 100,
"nonce": 0,
"data": []
}
}"#).unwrap();
let initial_accounts = vec![initial_acc1, initial_acc2];
@ -273,7 +93,7 @@ mod tests {
let store = WalletChainStore::new(config.clone()).unwrap();
assert!(store.acc_map.is_empty());
assert_eq!(store.user_data.accounts.len(), 2);
assert_eq!(
store.utxo_commitments_store.get_root().unwrap_or([0; 32]),
[0; 32]

View File

@ -1,8 +1,12 @@
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use accounts::account_core::Account;
use serde::{Deserialize, Serialize};
use zkvm::gas_calculator::GasCalculator;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitialAccountData {
pub address: nssa::Address,
pub account: nssa_core::account::Account,
pub pub_sign_key: nssa::PrivateKey,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GasConfig {
@ -22,20 +26,6 @@ pub struct GasConfig {
pub gas_limit_runtime: u64,
}
impl From<GasConfig> for zkvm::gas_calculator::GasCalculator {
fn from(value: GasConfig) -> Self {
GasCalculator::new(
value.gas_fee_per_byte_deploy,
value.gas_fee_per_input_buffer_runtime,
value.gas_fee_per_byte_runtime,
value.gas_cost_runtime,
value.gas_cost_deploy,
value.gas_limit_deploy,
value.gas_limit_runtime,
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WalletConfig {
///Home dir of sequencer storage
@ -47,5 +37,5 @@ pub struct WalletConfig {
///Sequencer polling duration for new blocks in seconds
pub seq_poll_timeout_secs: u64,
///Initial accounts for wallet
pub initial_accounts: Vec<Account>,
pub initial_accounts: Vec<InitialAccountData>,
}

View File

@ -1,9 +1,12 @@
use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr};
use accounts::account_core::Account;
use anyhow::{anyhow, Result};
use anyhow::Result;
use nssa::Address;
use crate::{config::WalletConfig, HOME_DIR_ENV_VAR};
use crate::{
config::{InitialAccountData, WalletConfig},
HOME_DIR_ENV_VAR,
};
///Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed.
pub fn get_home() -> Result<PathBuf> {
@ -20,16 +23,14 @@ pub fn fetch_config() -> Result<WalletConfig> {
}
//ToDo: Replace with structures conversion in future
pub fn produce_account_addr_from_hex(hex_str: String) -> Result<[u8; 32]> {
hex::decode(hex_str)?
.try_into()
.map_err(|_| anyhow!("Failed conversion to 32 bytes"))
pub fn produce_account_addr_from_hex(hex_str: String) -> Result<Address> {
Ok(hex_str.parse()?)
}
///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<Vec<Account>> {
pub fn fetch_persistent_accounts() -> Result<Vec<InitialAccountData>> {
let home = get_home()?;
let accs_path = home.join("curr_accounts.json");

View File

@ -1,24 +1,20 @@
use std::{fs::File, io::Write, path::PathBuf, sync::Arc};
use std::sync::Arc;
use common::{
execution_input::PublicNativeTokenSend,
sequencer_client::{json::SendTxResponse, SequencerClient},
transaction::Transaction,
ExecutionFailureKind,
};
use accounts::account_core::{address::AccountAddress, Account};
use anyhow::Result;
use chain_storage::WalletChainStore;
use common::transaction::TransactionBody;
use config::WalletConfig;
use log::info;
use sc_core::proofs_circuits::pedersen_commitment_vec;
use nssa::Address;
use clap::{Parser, Subcommand};
use crate::helperfunctions::{
fetch_config, fetch_persistent_accounts, get_home, produce_account_addr_from_hex,
fetch_config, fetch_persistent_accounts, produce_account_addr_from_hex,
};
pub const HOME_DIR_ENV_VAR: &str = "NSSA_WALLET_HOME_DIR";
@ -30,7 +26,6 @@ pub mod helperfunctions;
pub struct WalletCore {
pub storage: WalletChainStore,
pub wallet_config: WalletConfig,
pub sequencer_client: Arc<SequencerClient>,
}
@ -38,106 +33,66 @@ impl WalletCore {
pub async fn start_from_config_update_chain(config: WalletConfig) -> Result<Self> {
let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?);
let mut storage = WalletChainStore::new(config.clone())?;
for acc in config.clone().initial_accounts {
storage.acc_map.insert(acc.address, acc);
}
let mut storage = WalletChainStore::new(config)?;
//Persistent accounts take precedence for initial accounts
let persistent_accounts = fetch_persistent_accounts()?;
for acc in persistent_accounts {
storage.acc_map.insert(acc.address, acc);
storage
.user_data
.update_account_balance(acc.address, acc.account.balance);
}
Ok(Self {
storage,
wallet_config: config.clone(),
sequencer_client: client.clone(),
})
}
pub async fn create_new_account(&mut self) -> AccountAddress {
let account = Account::new();
account.log();
let addr = account.address;
self.storage.acc_map.insert(account.address, account);
addr
pub async fn create_new_account(&mut self) -> Address {
self.storage.user_data.generate_new_account()
}
pub async fn send_public_native_token_transfer(
&self,
from: AccountAddress,
nonce: u64,
to: AccountAddress,
balance_to_move: u64,
from: Address,
nonce: u128,
to: Address,
balance_to_move: u128,
) -> Result<SendTxResponse, ExecutionFailureKind> {
let public_context = self.storage.produce_context(from);
let (tweak, secret_r, commitment) = pedersen_commitment_vec(
//Will not panic, as public context is serializable
public_context.produce_u64_list_from_context().unwrap(),
);
let sc_addr = hex::encode([0; 32]);
let tx: TransactionBody =
sc_core::transaction_payloads_tools::create_public_transaction_payload(
serde_json::to_vec(&PublicNativeTokenSend {
from,
nonce,
to,
balance_to_move,
})
.unwrap(),
commitment,
tweak,
secret_r,
sc_addr,
);
tx.log();
let account = self.storage.acc_map.get(&from);
let account = self.storage.user_data.get_account(&from);
if let Some(account) = account {
let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key();
if account.balance >= balance_to_move {
let addresses = vec![from, to];
let nonces = vec![nonce];
let program_id = nssa::program::Program::authenticated_transfer_program().id();
let message = nssa::public_transaction::Message::try_new(
program_id,
addresses,
nonces,
balance_to_move,
)
.unwrap();
let signed_transaction = Transaction::new(tx, key_to_sign_transaction);
let signing_key = self.storage.user_data.get_account_signing_key(&from);
Ok(self.sequencer_client.send_tx(signed_transaction).await?)
if let Some(signing_key) = signing_key {
let witness_set =
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
let tx = nssa::PublicTransaction::new(message, witness_set);
Ok(self.sequencer_client.send_tx(tx).await?)
} else {
Err(ExecutionFailureKind::KeyNotFoundError)
}
} else {
Err(ExecutionFailureKind::InsufficientFundsError)
}
} else {
Err(ExecutionFailureKind::AmountMismatchError)
}
}
///Dumps all accounts from acc_map at `path`
///
///Currently storing everything in one file
///
///ToDo: extend storage
pub fn store_present_accounts_at_path(&self, path: PathBuf) -> Result<PathBuf> {
let dump_path = path.join("curr_accounts.json");
let curr_accs: Vec<Account> = self.storage.acc_map.values().cloned().collect();
let accs_serialized = serde_json::to_vec_pretty(&curr_accs)?;
let mut acc_file = File::create(&dump_path).unwrap();
acc_file.write_all(&accs_serialized).unwrap();
Ok(dump_path)
}
///Dumps all accounts from acc_map at `NSSA_WALLET_HOME_DIR`
///
///Currently storing everything in one file
///
///ToDo: extend storage
pub fn store_present_accounts_at_home(&self) -> Result<PathBuf> {
let home = get_home()?;
self.store_present_accounts_at_path(home)
}
}
///Represents CLI command for a wallet
@ -149,21 +104,15 @@ pub enum Command {
///from - valid 32 byte hex string
#[arg(long)]
from: String,
///nonce - u64 integer
///nonce - u128 integer
#[arg(long)]
nonce: u64,
nonce: u128,
///to - valid 32 byte hex string
#[arg(long)]
to: String,
///amount - amount of balance to move
#[arg(long)]
amount: u64,
},
///Dump accounts at destination
DumpAccountsOnDisc {
///Dump path for accounts
#[arg(short, long)]
dump_path: PathBuf,
amount: u128,
},
}
@ -198,19 +147,6 @@ pub async fn execute_subcommand(command: Command) -> Result<()> {
info!("Results of tx send is {res:#?}");
//ToDo: Insert transaction polling logic here
let acc_storage_path = wallet_core.store_present_accounts_at_home()?;
info!("Accounts stored at {acc_storage_path:#?}");
}
Command::DumpAccountsOnDisc { dump_path } => {
let node_config = fetch_config()?;
let wallet_core = WalletCore::start_from_config_update_chain(node_config).await?;
wallet_core.store_present_accounts_at_path(dump_path.clone())?;
info!("Accounts stored at path {dump_path:#?}");
}
}

View File

@ -1,30 +0,0 @@
[package]
name = "zkvm"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
serde_json.workspace = true
env_logger.workspace = true
log.workspace = true
serde.workspace = true
thiserror.workspace = true
rand.workspace = true
risc0-zkvm = { git = "https://github.com/risc0/risc0.git", branch = "release-2.3" }
test-methods = { path = "test_methods" }
[dependencies.accounts]
path = "../accounts"
[dependencies.utxo]
path = "../utxo"
[dependencies.common]
path = "../common"
[features]
cuda = ["risc0-zkvm/cuda"]
default = []
prove = ["risc0-zkvm/prove"]

View File

@ -1,113 +0,0 @@
#[derive(Debug, Clone)]
pub struct GasCalculator {
/// Gas spent per deploying one byte of data
gas_fee_per_byte_deploy: u64,
/// Gas spent per reading one byte of data in VM
gas_fee_per_input_buffer_runtime: u64,
/// Gas spent per one byte of contract data in runtime
gas_fee_per_byte_runtime: u64,
/// Cost of one gas of runtime in public balance
gas_cost_runtime: u64,
/// Cost of one gas of deployment in public balance
gas_cost_deploy: u64,
/// Gas limit for deployment
gas_limit_deploy: u64,
/// Gas limit for runtime
gas_limit_runtime: u64,
}
impl GasCalculator {
pub fn new(
gas_fee_per_byte_deploy: u64,
gas_fee_per_input_buffer_runtime: u64,
gas_fee_per_byte_runtime: u64,
gas_cost_runtime: u64,
gas_cost_deploy: u64,
gas_limit_deploy: u64,
gas_limit_runtime: u64,
) -> Self {
Self {
gas_fee_per_byte_deploy,
gas_fee_per_input_buffer_runtime,
gas_fee_per_byte_runtime,
gas_cost_deploy,
gas_cost_runtime,
gas_limit_deploy,
gas_limit_runtime,
}
}
pub fn gas_fee_per_byte_deploy(&self) -> u64 {
self.gas_fee_per_byte_deploy
}
pub fn gas_fee_per_input_buffer_runtime(&self) -> u64 {
self.gas_fee_per_input_buffer_runtime
}
pub fn gas_fee_per_byte_runtime(&self) -> u64 {
self.gas_fee_per_byte_runtime
}
pub fn gas_cost_runtime(&self) -> u64 {
self.gas_cost_runtime
}
pub fn gas_cost_deploy(&self) -> u64 {
self.gas_cost_deploy
}
pub fn gas_limit_deploy(&self) -> u64 {
self.gas_limit_deploy
}
pub fn gas_limit_runtime(&self) -> u64 {
self.gas_limit_runtime
}
///Returns Option<u64>
///
/// Some(_) - in case if `gas` < `gas_limit_deploy`
///
/// None - else
pub fn gas_deploy(&self, elf: &[u8]) -> Option<u64> {
let gas = self.gas_fee_per_byte_deploy() * (elf.len() as u64);
if gas < self.gas_limit_deploy() {
Some(gas)
} else {
None
}
}
pub fn gas_runtime(&self, elf: &[u8]) -> u64 {
self.gas_fee_per_byte_runtime() * (elf.len() as u64)
}
pub fn gas_input_buffer(&self, input_length: usize) -> u64 {
self.gas_fee_per_input_buffer_runtime() * (input_length as u64)
}
///Returns Option<u64>
///
/// Some(_) - in case if `gas` < `gas_limit_runtime`
///
/// None - else
pub fn gas_runtime_full(&self, elf: &[u8], input_length: usize) -> Option<u64> {
let gas = self.gas_runtime(elf) + self.gas_input_buffer(input_length);
if gas < self.gas_limit_runtime() {
Some(gas)
} else {
None
}
}
pub fn deploy_cost(&self, deploy_gas: u64) -> u64 {
deploy_gas * self.gas_cost_deploy()
}
pub fn runtime_cost(&self, runtime_gas: u64) -> u64 {
runtime_gas * self.gas_cost_runtime()
}
}

View File

@ -1,635 +0,0 @@
use accounts::account_core::address::AccountAddress;
use common::ExecutionFailureKind;
use rand::{rngs::OsRng, RngCore};
use risc0_zkvm::{default_executor, default_prover, sha::Digest, ExecutorEnv, Receipt};
use serde::Serialize;
use utxo::utxo_core::{Randomness, UTXOPayload, UTXO};
pub mod gas_calculator;
pub use test_methods;
#[allow(clippy::result_large_err)]
pub fn gas_limits_check<INP: Serialize>(
input_buffer: INP,
elf: &[u8],
gas_calculator: &gas_calculator::GasCalculator,
attached_funds: u64,
) -> Result<(), ExecutionFailureKind> {
let mut input_buffer_len: usize = 0;
input_buffer_len += serde_json::to_vec(&input_buffer).unwrap().len();
let gas_limit = gas_calculator
.gas_runtime_full(elf, input_buffer_len)
.ok_or(ExecutionFailureKind::InsufficientGasError)?;
let cost = gas_calculator.runtime_cost(gas_limit);
if cost > attached_funds {
return Err(ExecutionFailureKind::InsufficientFundsError);
}
Ok(())
}
#[allow(clippy::result_large_err)]
pub fn prove_mint_utxo(
amount_to_mint: u128,
owner: AccountAddress,
) -> Result<(UTXO, Receipt), ExecutionFailureKind> {
let mut builder = ExecutorEnv::builder();
builder
.write(&amount_to_mint)
.map_err(ExecutionFailureKind::write_error)?;
builder
.write(&owner)
.map_err(ExecutionFailureKind::write_error)?;
let mut randomness = Randomness::default();
OsRng.fill_bytes(&mut randomness);
builder
.write(&randomness)
.map_err(ExecutionFailureKind::write_error)?;
let env = builder
.build()
.map_err(ExecutionFailureKind::builder_error)?;
let prover = default_prover();
let receipt = prover
.prove(env, test_methods::MINT_UTXO_ELF)
.map_err(ExecutionFailureKind::prove_error)?
.receipt;
let digest: UTXOPayload = receipt.journal.decode()?;
Ok((UTXO::create_utxo_from_payload(digest), receipt))
}
#[allow(clippy::result_large_err)]
pub fn prove_send_utxo(
spent_utxo: UTXO,
owners_parts: Vec<(u128, AccountAddress)>,
) -> Result<(Vec<(UTXO, AccountAddress)>, Receipt), ExecutionFailureKind> {
let cumulative_spent = owners_parts.iter().fold(0, |acc, item| acc + item.0);
if cumulative_spent != spent_utxo.amount {
return Err(ExecutionFailureKind::AmountMismatchError);
}
let mut builder = ExecutorEnv::builder();
let utxo_payload = spent_utxo.into_payload();
builder
.write(&utxo_payload)
.map_err(ExecutionFailureKind::write_error)?;
let owners_parts_with_randomness = owners_parts
.into_iter()
.map(|(amount, addr)| {
let mut randomness = Randomness::default();
OsRng.fill_bytes(&mut randomness);
(amount, addr, randomness)
})
.collect::<Vec<_>>();
builder
.write(&owners_parts_with_randomness)
.map_err(ExecutionFailureKind::write_error)?;
let env = builder
.build()
.map_err(ExecutionFailureKind::builder_error)?;
let prover = default_prover();
let receipt = prover
.prove(env, test_methods::SEND_UTXO_ELF)
.map_err(ExecutionFailureKind::prove_error)?
.receipt;
let digest: Vec<(UTXOPayload, AccountAddress)> = receipt.journal.decode()?;
Ok((
digest
.into_iter()
.map(|(payload, addr)| (UTXO::create_utxo_from_payload(payload), addr))
.collect(),
receipt,
))
}
#[allow(clippy::result_large_err)]
pub fn prove_send_utxo_multiple_assets_one_receiver(
spent_utxos: Vec<UTXO>,
number_to_send: usize,
receiver: AccountAddress,
) -> Result<(Vec<UTXO>, Vec<UTXO>, Receipt), ExecutionFailureKind> {
if number_to_send > spent_utxos.len() {
return Err(ExecutionFailureKind::AmountMismatchError);
}
let mut builder = ExecutorEnv::builder();
let utxo_payload: Vec<UTXOPayload> = spent_utxos
.into_iter()
.map(|spent_utxo| spent_utxo.into_payload())
.collect();
builder
.write(&utxo_payload)
.map_err(ExecutionFailureKind::write_error)?;
builder
.write(&number_to_send)
.map_err(ExecutionFailureKind::write_error)?;
builder
.write(&receiver)
.map_err(ExecutionFailureKind::write_error)?;
let env = builder
.build()
.map_err(ExecutionFailureKind::builder_error)?;
let prover = default_prover();
let receipt = prover
.prove(env, test_methods::SEND_UTXO_MULTIPLE_ASSETS_ELF)
.map_err(ExecutionFailureKind::prove_error)?
.receipt;
let digest: (Vec<UTXOPayload>, Vec<UTXOPayload>) = receipt.journal.decode()?;
Ok((
digest
.0
.into_iter()
.map(UTXO::create_utxo_from_payload)
.collect(),
digest
.1
.into_iter()
.map(UTXO::create_utxo_from_payload)
.collect(),
receipt,
))
}
#[allow(clippy::result_large_err)]
pub fn prove_send_utxo_shielded(
owner: AccountAddress,
amount: u128,
owners_parts: Vec<(u128, AccountAddress)>,
) -> Result<(Vec<(UTXO, AccountAddress)>, Receipt), ExecutionFailureKind> {
let cumulative_spent = owners_parts.iter().fold(0, |acc, item| acc + item.0);
if cumulative_spent != amount {
return Err(ExecutionFailureKind::AmountMismatchError);
}
let temp_utxo_to_spend = UTXO::new(owner, vec![], amount, true);
let utxo_payload = temp_utxo_to_spend.into_payload();
let mut builder = ExecutorEnv::builder();
builder
.write(&utxo_payload)
.map_err(ExecutionFailureKind::write_error)?;
let owners_parts_with_randomness = owners_parts
.into_iter()
.map(|(amount, addr)| {
let mut randomness = Randomness::default();
OsRng.fill_bytes(&mut randomness);
(amount, addr, randomness)
})
.collect::<Vec<_>>();
builder
.write(&owners_parts_with_randomness)
.map_err(ExecutionFailureKind::write_error)?;
let env = builder
.build()
.map_err(ExecutionFailureKind::builder_error)?;
let prover = default_prover();
let receipt = prover
.prove(env, test_methods::SEND_UTXO_ELF)
.map_err(ExecutionFailureKind::prove_error)?
.receipt;
let digest: Vec<(UTXOPayload, AccountAddress)> = receipt.journal.decode()?;
Ok((
digest
.into_iter()
.map(|(payload, addr)| (UTXO::create_utxo_from_payload(payload), addr))
.collect(),
receipt,
))
}
#[allow(clippy::result_large_err)]
pub fn prove_send_utxo_deshielded(
spent_utxo: UTXO,
owners_parts: Vec<(u128, AccountAddress)>,
) -> Result<(Vec<(u128, AccountAddress)>, Receipt), ExecutionFailureKind> {
let cumulative_spent = owners_parts.iter().fold(0, |acc, item| acc + item.0);
if cumulative_spent != spent_utxo.amount {
return Err(ExecutionFailureKind::AmountMismatchError);
}
let mut builder = ExecutorEnv::builder();
let utxo_payload = spent_utxo.into_payload();
builder
.write(&utxo_payload)
.map_err(ExecutionFailureKind::write_error)?;
let owners_parts_with_randomness = owners_parts
.into_iter()
.map(|(amount, addr)| {
let mut randomness = Randomness::default();
OsRng.fill_bytes(&mut randomness);
(amount, addr, randomness)
})
.collect::<Vec<_>>();
builder
.write(&owners_parts_with_randomness)
.map_err(ExecutionFailureKind::write_error)?;
let env = builder
.build()
.map_err(ExecutionFailureKind::builder_error)?;
let prover = default_prover();
let receipt = prover
.prove(env, test_methods::SEND_UTXO_ELF)
.map_err(ExecutionFailureKind::prove_error)?
.receipt;
let digest: Vec<(UTXOPayload, AccountAddress)> = receipt.journal.decode()?;
Ok((
digest
.into_iter()
.map(|(payload, addr)| (payload.amount, addr))
.collect(),
receipt,
))
}
#[allow(clippy::result_large_err)]
pub fn prove_mint_utxo_multiple_assets(
amount_to_mint: u128,
number_of_assets: usize,
owner: AccountAddress,
) -> Result<(Vec<UTXO>, Receipt), ExecutionFailureKind> {
let mut builder = ExecutorEnv::builder();
builder
.write(&amount_to_mint)
.map_err(ExecutionFailureKind::write_error)?;
builder
.write(&number_of_assets)
.map_err(ExecutionFailureKind::write_error)?;
builder
.write(&owner)
.map_err(ExecutionFailureKind::write_error)?;
let env = builder
.build()
.map_err(ExecutionFailureKind::builder_error)?;
let prover = default_prover();
let receipt = prover
.prove(env, test_methods::MINT_UTXO_MULTIPLE_ASSETS_ELF)
.map_err(ExecutionFailureKind::prove_error)?
.receipt;
let digest: Vec<UTXOPayload> = receipt.journal.decode()?;
Ok((
digest
.into_iter()
.map(UTXO::create_utxo_from_payload)
.collect(),
receipt,
))
}
pub fn execute_mint_utxo(
amount_to_mint: u128,
owner: AccountAddress,
randomness: [u8; 32],
) -> anyhow::Result<UTXO> {
let mut builder = ExecutorEnv::builder();
builder.write(&amount_to_mint)?;
builder.write(&owner)?;
builder.write(&randomness)?;
let env = builder.build()?;
let executor = default_executor();
let receipt = executor.execute(env, test_methods::MINT_UTXO_ELF)?;
let digest: UTXOPayload = receipt.journal.decode()?;
Ok(UTXO::create_utxo_from_payload(digest))
}
pub fn execute_send_utxo(
spent_utxo: UTXO,
owners_parts: Vec<(u128, AccountAddress)>,
) -> anyhow::Result<(UTXO, Vec<(UTXO, AccountAddress)>)> {
let mut builder = ExecutorEnv::builder();
let utxo_payload = spent_utxo.into_payload();
builder.write(&utxo_payload)?;
let owners_parts_with_randomness = owners_parts
.into_iter()
.map(|(amount, addr)| {
let mut randomness = Randomness::default();
OsRng.fill_bytes(&mut randomness);
(amount, addr, randomness)
})
.collect::<Vec<_>>();
builder.write(&owners_parts_with_randomness)?;
let env = builder.build()?;
let executor = default_executor();
let receipt = executor.execute(env, test_methods::SEND_UTXO_ELF)?;
let digest: (UTXOPayload, Vec<(UTXOPayload, AccountAddress)>) = receipt.journal.decode()?;
Ok((
UTXO::create_utxo_from_payload(digest.0),
digest
.1
.into_iter()
.map(|(payload, addr)| (UTXO::create_utxo_from_payload(payload), addr))
.collect(),
))
}
pub fn prove<T: serde::ser::Serialize>(
input_vec: Vec<T>,
elf: &[u8],
) -> anyhow::Result<(u64, Receipt)> {
let mut builder = ExecutorEnv::builder();
for input in input_vec {
builder.write(&input)?;
}
let env = builder.build()?;
let prover = default_prover();
let receipt = prover.prove(env, elf)?.receipt;
let digest = receipt.journal.decode()?;
Ok((digest, receipt))
}
// This only executes the program and does not generate a receipt.
pub fn execute<T: serde::ser::Serialize + for<'de> serde::Deserialize<'de>>(
input_vec: Vec<T>,
elf: &[u8],
) -> anyhow::Result<T> {
let mut builder = ExecutorEnv::builder();
for input in input_vec {
builder.write(&input)?;
}
let env = builder.build()?;
let exec = default_executor();
let session = exec.execute(env, elf)?;
// We read the result committed to the journal by the guest code.
let result: T = session.journal.decode()?;
Ok(result)
}
pub fn verify(receipt: Receipt, image_id: impl Into<Digest>) -> anyhow::Result<()> {
Ok(receipt.verify(image_id)?)
}
#[cfg(test)]
mod tests {
use crate::gas_calculator::GasCalculator;
use super::*;
use test_methods::BIG_CALCULATION_ELF;
use test_methods::{MULTIPLICATION_ELF, MULTIPLICATION_ID};
use test_methods::{SUMMATION_ELF, SUMMATION_ID};
#[test]
fn prove_simple_sum() {
let message = 1;
let message_2 = 2;
let (digest, receipt) = prove(vec![message, message_2], SUMMATION_ELF).unwrap();
verify(receipt, SUMMATION_ID).unwrap();
assert_eq!(digest, message + message_2);
}
#[test]
fn prove_bigger_sum() {
let message = 123476;
let message_2 = 2342384;
let (digest, receipt) = prove(vec![message, message_2], SUMMATION_ELF).unwrap();
verify(receipt, SUMMATION_ID).unwrap();
assert_eq!(digest, message + message_2);
}
#[test]
fn prove_simple_multiplication() {
let message = 1;
let message_2 = 2;
let (digest, receipt) = prove(vec![message, message_2], MULTIPLICATION_ELF).unwrap();
verify(receipt, MULTIPLICATION_ID).unwrap();
assert_eq!(digest, message * message_2);
}
#[test]
fn prove_bigger_multiplication() {
let message = 3498;
let message_2 = 438563;
let (digest, receipt) = prove(vec![message, message_2], MULTIPLICATION_ELF).unwrap();
verify(receipt, MULTIPLICATION_ID).unwrap();
assert_eq!(digest, message * message_2);
}
#[test]
fn execute_simple_sum() {
let message: u64 = 1;
let message_2: u64 = 2;
let result = execute(vec![message, message_2], SUMMATION_ELF).unwrap();
assert_eq!(result, message + message_2);
}
#[test]
fn execute_bigger_sum() {
let message: u64 = 123476;
let message_2: u64 = 2342384;
let result = execute(vec![message, message_2], SUMMATION_ELF).unwrap();
assert_eq!(result, message + message_2);
}
#[test]
fn execute_big_calculation() {
let message: u128 = 1;
let message_2: u128 = 2;
let result = execute(vec![message, message_2], BIG_CALCULATION_ELF).unwrap();
assert_eq!(result, big_calculation(message, message_2));
}
#[test]
fn execute_big_calculation_long() {
let message: u128 = 20;
let message_2: u128 = 10;
let result = execute(vec![message, message_2], BIG_CALCULATION_ELF).unwrap();
assert_eq!(result, big_calculation(message, message_2));
}
fn big_calculation(lhs: u128, rhs: u128) -> u128 {
let mut res = 1_u128;
for _ in 0..lhs {
res *= rhs;
res += lhs;
}
res
}
#[test]
fn test_gas_limits_check_sufficient_funds() {
let message = 1;
let message_2 = 2;
let gas_calc = GasCalculator::new(1, 1, 1, 1, 1, 1000000, 1000000);
let result = gas_limits_check(vec![message, message_2], SUMMATION_ELF, &gas_calc, 1000000);
assert!(result.is_ok());
}
#[test]
fn test_gas_limits_check_insufficient_funds() {
let message = 1;
let message_2 = 2;
let gas_calc = GasCalculator::new(1, 1, 1, 1, 1, 1000000, 1000000);
let result = gas_limits_check(vec![message, message_2], SUMMATION_ELF, &gas_calc, 1);
assert!(matches!(
result,
Err(ExecutionFailureKind::InsufficientFundsError)
));
}
#[test]
fn test_execute_mint_utxo() {
let owner = AccountAddress::default();
let amount = 123456789;
let mut randomness = [0u8; 32];
OsRng.fill_bytes(&mut randomness);
let utxo_exec = execute_mint_utxo(amount, owner, randomness).expect("execution failed");
assert_eq!(utxo_exec.amount, amount);
assert_eq!(utxo_exec.owner, owner);
}
#[test]
fn test_prove_mint_utxo() {
let owner = AccountAddress::default();
let amount = 123456789;
let (utxo, _) = prove_mint_utxo(amount, owner).expect("proof failed");
assert_eq!(utxo.amount, amount);
assert_eq!(utxo.owner, owner);
}
#[test]
fn test_prove_send_utxo() {
let owner = AccountAddress::default();
let amount = 100;
let (input_utxo, _) = prove_mint_utxo(amount, owner).expect("mint failed");
let parts = vec![(40, owner), (60, owner)];
let (outputs, _receipt) = prove_send_utxo(input_utxo, parts.clone()).expect("send failed");
let total: u128 = outputs.iter().map(|(utxo, _)| utxo.amount).sum();
assert_eq!(total, amount);
assert_eq!(outputs.len(), 2);
}
#[test]
fn test_prove_send_utxo_deshielded() {
let owner = AccountAddress::default();
let amount = 100;
let (utxo, _) = prove_mint_utxo(amount, owner).unwrap();
let parts = vec![(60, owner), (40, owner)];
let (outputs, _) = prove_send_utxo_deshielded(utxo, parts.clone()).unwrap();
let total: u128 = outputs.iter().map(|(amt, _)| amt).sum();
assert_eq!(total, amount);
assert_eq!(outputs.len(), 2);
}
#[test]
fn test_prove_send_utxo_shielded() {
let owner = AccountAddress::default();
let amount = 100;
let parts = vec![(60, owner), (40, owner)];
let (outputs, _) = prove_send_utxo_shielded(owner, amount, parts.clone()).unwrap();
let total: u128 = outputs.iter().map(|(utxo, _)| utxo.amount).sum();
assert_eq!(total, amount);
assert_eq!(outputs.len(), 2);
}
#[test]
fn test_prove_send_utxo_multiple_assets_one_receiver() {
let owner = AccountAddress::default();
let receiver = AccountAddress::default();
let utxos = vec![
prove_mint_utxo(100, owner).unwrap().0,
prove_mint_utxo(50, owner).unwrap().0,
];
let (to_receiver, to_change, _receipt) =
prove_send_utxo_multiple_assets_one_receiver(utxos, 1, receiver).unwrap();
let total_to_receiver: u128 = to_receiver.iter().map(|u| u.amount).sum();
assert!(total_to_receiver > 0);
assert_eq!(to_receiver.len() + to_change.len(), 2);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +0,0 @@
[package]
name = "test"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
serde_json = "1.0.81"
risc0-zkvm = { git = "https://github.com/risc0/risc0.git", default-features = false, features = [
"std",
] }
[dependencies.serde]
features = ["derive"]
version = "1.0.60"

View File

@ -1,14 +0,0 @@
use risc0_zkvm::{
guest::env,
};
fn main() {
let lhs: u128 = env::read();
let rhs: u128 = env::read();
let mut res = 1;
for i in 0..lhs {
res *= rhs;
res += lhs;
}
env::commit(&(res));
}

View File

@ -1,32 +0,0 @@
use risc0_zkvm::{
guest::env,
};
use serde::{Deserialize, Serialize};
type AccountAddr = [u8; 32];
#[derive(Serialize, Deserialize)]
pub struct UTXOPayload {
pub owner: AccountAddr,
pub asset: Vec<u8>,
// TODO: change to u256
pub amount: u128,
pub privacy_flag: bool,
pub randomness: [u8; 32],
}
fn main() {
let amount_to_mint: u128 = env::read();
let owner: AccountAddr = env::read();
let randomness: [u8; 32] = env::read();
let payload = UTXOPayload {
owner,
asset: vec![],
amount: amount_to_mint,
privacy_flag: true,
randomness,
};
env::commit(&(payload));
}

View File

@ -1,39 +0,0 @@
use risc0_zkvm::{
guest::env,
};
use serde::{Deserialize, Serialize};
type AccountAddr = [u8; 32];
#[derive(Serialize, Deserialize)]
pub struct UTXOPayload {
pub owner: AccountAddr,
pub asset: Vec<u8>,
// TODO: change to u256
pub amount: u128,
pub privacy_flag: bool,
pub randomness: [u8; 32],
}
fn main() {
let amount_to_mint: u128 = env::read();
let number_of_assets: usize = env::read();
let owner: AccountAddr = env::read();
let randomness: [u8; 32] = env::read();
let mut asseted_utxos = vec![];
for i in 0..number_of_assets {
let payload = UTXOPayload {
owner,
asset: vec![i as u8],
amount: amount_to_mint,
privacy_flag: true,
randomness
};
asseted_utxos.push(payload);
}
env::commit(&(asseted_utxos));
}

View File

@ -1,9 +0,0 @@
use risc0_zkvm::{
guest::env,
};
fn main() {
let lhs: u64 = env::read();
let rhs: u64 = env::read();
env::commit(&(lhs * rhs));
}

Some files were not shown because too many files have changed in this diff Show More