Merge branch 'schouhy/implement-nssa-v0.1-private-state' into Pravdyvy/sequencer-update

This commit is contained in:
Oleksandr Pravdyvyi 2025-08-26 14:06:49 +03:00
commit 562480225c
No known key found for this signature in database
GPG Key ID: 9F8955C63C443871
39 changed files with 3040 additions and 146 deletions

View File

@ -52,7 +52,7 @@ version = "0.8.5"
[workspace.dependencies.k256]
features = ["ecdsa-core", "arithmetic", "expose-field", "serde", "pem"]
version = "0.13.4"
version = "0.13.3"
[workspace.dependencies.elliptic-curve]
features = ["arithmetic"]

View File

@ -12,8 +12,10 @@ serde = "1.0.219"
sha2 = "0.10.9"
secp256k1 = "0.31.1"
rand = "0.8"
borsh = "1.5.7"
bytemuck = "1.13"
hex = "0.4.3"
anyhow.workspace = true
[dev-dependencies]
test-program-methods = { path = "test_program_methods" }
hex-literal = "1.0.0"

View File

@ -6,3 +6,11 @@ edition = "2024"
[dependencies]
risc0-zkvm = "3.0.1"
serde = { version = "1.0", default-features = false }
thiserror = { version = "2.0.12", optional = true }
bytemuck = { version = "1.13", optional = true }
chacha20 = { version = "0.9", default-features = false }
k256 = "0.13.3"
[features]
default = []
host = ["thiserror", "bytemuck"]

View File

@ -0,0 +1,34 @@
use risc0_zkvm::{
serde::to_vec,
sha::{Impl, Sha256},
};
use serde::{Deserialize, Serialize};
use crate::account::{Account, NullifierPublicKey};
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, PartialEq, Eq, Hash))]
pub struct Commitment(pub(super) [u8; 32]);
impl Commitment {
pub fn new(Npk: &NullifierPublicKey, account: &Account) -> Self {
let mut bytes = Vec::new();
bytes.extend_from_slice(&Npk.to_byte_array());
let account_bytes_with_hashed_data = {
let mut this = Vec::new();
for word in &account.program_owner {
this.extend_from_slice(&word.to_le_bytes());
}
this.extend_from_slice(&account.balance.to_le_bytes());
this.extend_from_slice(&account.nonce.to_le_bytes());
let hashed_data: [u8; 32] = Impl::hash_bytes(&account.data)
.as_bytes()
.try_into()
.unwrap();
this.extend_from_slice(&hashed_data);
this
};
bytes.extend_from_slice(&account_bytes_with_hashed_data);
Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap())
}
}

View File

@ -0,0 +1,173 @@
// TODO: Consider switching to deriving Borsh
use risc0_zkvm::sha::{Impl, Sha256};
#[cfg(feature = "host")]
use std::io::Cursor;
#[cfg(feature = "host")]
use std::io::Read;
use crate::account::{Account, Commitment, Nullifier, NullifierPublicKey};
#[cfg(feature = "host")]
use crate::error::NssaCoreError;
impl Account {
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
for word in &self.program_owner {
bytes.extend_from_slice(&word.to_le_bytes());
}
bytes.extend_from_slice(&self.balance.to_le_bytes());
bytes.extend_from_slice(&self.nonce.to_le_bytes());
let data_length: u32 = self.data.len() as u32;
bytes.extend_from_slice(&data_length.to_le_bytes());
bytes.extend_from_slice(self.data.as_slice());
bytes
}
#[cfg(feature = "host")]
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
let mut u32_bytes = [0u8; 4];
let mut u128_bytes = [0u8; 16];
// program owner
let mut program_owner = [0u32; 8];
for word in &mut program_owner {
cursor.read_exact(&mut u32_bytes)?;
*word = u32::from_le_bytes(u32_bytes);
}
// balance
cursor.read_exact(&mut u128_bytes)?;
let balance = u128::from_le_bytes(u128_bytes);
// nonce
cursor.read_exact(&mut u128_bytes)?;
let nonce = u128::from_le_bytes(u128_bytes);
//data
cursor.read_exact(&mut u32_bytes)?;
let data_length = u32::from_le_bytes(u32_bytes);
let mut data = vec![0; data_length as usize];
cursor.read_exact(&mut data)?;
Ok(Self {
program_owner,
balance,
data,
nonce,
})
}
}
impl Commitment {
pub fn to_byte_array(&self) -> [u8; 32] {
self.0
}
#[cfg(feature = "host")]
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
let mut bytes = [0u8; 32];
cursor.read_exact(&mut bytes)?;
Ok(Self(bytes))
}
}
impl NullifierPublicKey {
pub fn to_byte_array(&self) -> [u8; 32] {
self.0
}
}
#[cfg(feature = "host")]
impl Nullifier {
pub fn to_byte_array(&self) -> [u8; 32] {
self.0
}
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
let mut bytes = [0u8; 32];
cursor.read_exact(&mut bytes)?;
Ok(Self(bytes))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_enconding() {
let account = Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 123456789012345678901234567890123456,
nonce: 42,
data: b"hola mundo".to_vec(),
};
// program owner || balance || nonce || hash(data)
let expected_bytes = [
1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8,
0, 0, 0, 192, 186, 220, 114, 113, 65, 236, 234, 222, 15, 215, 191, 227, 198, 23, 0, 42,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 104, 111, 108, 97, 32, 109,
117, 110, 100, 111,
];
let bytes = account.to_bytes();
assert_eq!(bytes, expected_bytes);
}
#[test]
fn test_commitment_to_bytes() {
let commitment = Commitment((0..32).collect::<Vec<u8>>().try_into().unwrap());
let expected_bytes: [u8; 32] = (0..32).collect::<Vec<u8>>().try_into().unwrap();
let bytes = commitment.to_byte_array();
assert_eq!(expected_bytes, bytes);
}
#[test]
fn test_nullifier_to_bytes() {
let nullifier = Nullifier((0..32).collect::<Vec<u8>>().try_into().unwrap());
let expected_bytes: [u8; 32] = (0..32).collect::<Vec<u8>>().try_into().unwrap();
let bytes = nullifier.to_byte_array();
assert_eq!(expected_bytes, bytes);
}
#[cfg(feature = "host")]
#[test]
fn test_commitment_to_bytes_roundtrip() {
let commitment = Commitment((0..32).collect::<Vec<u8>>().try_into().unwrap());
let bytes = commitment.to_byte_array();
let mut cursor = Cursor::new(bytes.as_ref());
let commitment_from_cursor = Commitment::from_cursor(&mut cursor).unwrap();
assert_eq!(commitment, commitment_from_cursor);
}
#[cfg(feature = "host")]
#[test]
fn test_nullifier_to_bytes_roundtrip() {
let nullifier = Nullifier((0..32).collect::<Vec<u8>>().try_into().unwrap());
let bytes = nullifier.to_byte_array();
let mut cursor = Cursor::new(bytes.as_ref());
let nullifier_from_cursor = Nullifier::from_cursor(&mut cursor).unwrap();
assert_eq!(nullifier, nullifier_from_cursor);
}
#[cfg(feature = "host")]
#[test]
fn test_account_to_bytes_roundtrip() {
let account = Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 123456789012345678901234567890123456,
nonce: 42,
data: b"hola mundo".to_vec(),
};
let bytes = account.to_bytes();
let mut cursor = Cursor::new(bytes.as_ref());
let account_from_cursor = Account::from_cursor(&mut cursor).unwrap();
assert_eq!(account, account_from_cursor);
}
}

View File

@ -2,11 +2,20 @@ use serde::{Deserialize, Serialize};
use crate::program::ProgramId;
mod commitment;
mod encoding;
mod nullifier;
pub use commitment::Commitment;
pub use nullifier::{Nullifier, NullifierPublicKey, NullifierSecretKey};
pub type Nonce = u128;
// TODO: Consider changing `Data` to `Vec<u32>` for r0 friendlinenss
type Data = Vec<u8>;
/// Account to be used both in public and private contexts
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
#[cfg_attr(any(feature = "host", test), derive(Debug))]
pub struct Account {
pub program_owner: ProgramId,
pub balance: u128,
@ -14,7 +23,8 @@ pub struct Account {
pub nonce: Nonce,
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
pub struct AccountWithMetadata {
pub account: Account,
pub is_authorized: bool,

View File

@ -0,0 +1,68 @@
use risc0_zkvm::sha::{Impl, Sha256};
use serde::{Deserialize, Serialize};
use crate::account::Commitment;
#[derive(Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, Hash))]
pub struct NullifierPublicKey(pub(super) [u8; 32]);
impl From<&NullifierSecretKey> for NullifierPublicKey {
fn from(value: &NullifierSecretKey) -> Self {
let mut bytes = Vec::new();
const PREFIX: &[u8; 9] = b"NSSA_keys";
const SUFFIX_1: &[u8; 1] = &[7];
const SUFFIX_2: &[u8; 22] = &[0; 22];
bytes.extend_from_slice(PREFIX);
bytes.extend_from_slice(value);
bytes.extend_from_slice(SUFFIX_1);
bytes.extend_from_slice(SUFFIX_2);
Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap())
}
}
pub type NullifierSecretKey = [u8; 32];
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, PartialEq, Eq, Hash))]
pub struct Nullifier(pub(super) [u8; 32]);
impl Nullifier {
pub fn new(commitment: &Commitment, nsk: &NullifierSecretKey) -> Self {
let mut bytes = Vec::new();
bytes.extend_from_slice(&commitment.to_byte_array());
bytes.extend_from_slice(nsk);
Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constructor() {
let commitment = Commitment((0..32u8).collect::<Vec<_>>().try_into().unwrap());
let nsk = [0x42; 32];
let expected_nullifier = Nullifier([
97, 87, 111, 191, 0, 44, 125, 145, 237, 104, 31, 230, 203, 254, 68, 176, 126, 17, 240,
205, 249, 143, 11, 43, 15, 198, 189, 219, 191, 49, 36, 61,
]);
let nullifier = Nullifier::new(&commitment, &nsk);
assert_eq!(nullifier, expected_nullifier);
}
#[test]
fn test_from_secret_key() {
let nsk = [
57, 5, 64, 115, 153, 56, 184, 51, 207, 238, 99, 165, 147, 214, 213, 151, 30, 251, 30,
196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28,
];
let expected_Npk = NullifierPublicKey([
202, 120, 42, 189, 194, 218, 78, 244, 31, 6, 108, 169, 29, 61, 22, 221, 69, 138, 197,
161, 241, 39, 142, 242, 242, 50, 188, 201, 99, 28, 176, 238,
]);
let Npk = NullifierPublicKey::from(&nsk);
assert_eq!(Npk, expected_Npk);
}
}

12
nssa/core/src/error.rs Normal file
View File

@ -0,0 +1,12 @@
use std::io;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum NssaCoreError {
#[error("Deserialization error: {0}")]
DeserializationError(String),
#[error("IO error: {0}")]
Io(#[from] io::Error),
}

View File

@ -1,2 +1,338 @@
use chacha20::{
ChaCha20,
cipher::{KeyIvInit, StreamCipher},
};
use risc0_zkvm::{
serde::to_vec,
sha::{Impl, Sha256},
};
use serde::{Deserialize, Serialize};
#[cfg(feature = "host")]
use crate::error::NssaCoreError;
use crate::{
account::{
Account, AccountWithMetadata, Commitment, Nonce, Nullifier, NullifierPublicKey,
NullifierSecretKey,
},
program::{ProgramId, ProgramOutput},
};
#[cfg(feature = "host")]
use std::io::{Cursor, Read};
pub mod account;
pub mod program;
use k256::{
AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint, PublicKey, Scalar,
elliptic_curve::{
PrimeField,
sec1::{FromEncodedPoint, ToEncodedPoint},
},
};
#[cfg(feature = "host")]
pub mod error;
pub type CommitmentSetDigest = [u8; 32];
pub type MembershipProof = (usize, Vec<[u8; 32]>);
pub fn compute_root_associated_to_path(
commitment: &Commitment,
proof: &MembershipProof,
) -> CommitmentSetDigest {
let value_bytes = commitment.to_byte_array();
let mut result: [u8; 32] = Impl::hash_bytes(&value_bytes)
.as_bytes()
.try_into()
.unwrap();
let mut level_index = proof.0;
for node in &proof.1 {
let is_left_child = level_index & 1 == 0;
if is_left_child {
let mut bytes = [0u8; 64];
bytes[..32].copy_from_slice(&result);
bytes[32..].copy_from_slice(node);
result = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap();
} else {
let mut bytes = [0u8; 64];
bytes[..32].copy_from_slice(node);
bytes[32..].copy_from_slice(&result);
result = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap();
}
level_index >>= 1;
}
result
}
pub type EphemeralPublicKey = Secp256k1Point;
pub type IncomingViewingPublicKey = Secp256k1Point;
pub type EphemeralSecretKey = [u8; 32];
impl From<&EphemeralSecretKey> for EphemeralPublicKey {
fn from(value: &EphemeralSecretKey) -> Self {
Secp256k1Point::from_scalar(*value)
}
}
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
pub struct Secp256k1Point(pub Vec<u8>);
impl Secp256k1Point {
pub fn from_scalar(value: [u8; 32]) -> Secp256k1Point {
let x_bytes: FieldBytes = value.into();
let x = Scalar::from_repr(x_bytes).unwrap();
let p = ProjectivePoint::GENERATOR * x;
let q = AffinePoint::from(p);
let enc = q.to_encoded_point(true);
Self(enc.as_bytes().to_vec())
}
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, PartialEq, Eq))]
pub struct EncryptedAccountData {
ciphertext: Vec<u8>,
epk: EphemeralPublicKey,
view_tag: u8,
}
impl EncryptedAccountData {
#[cfg(feature = "host")]
pub fn decrypt(self, isk: &[u8; 32], output_index: u32) -> Option<Account> {
let ss_bytes = Self::ecdh(isk, &self.epk.0.clone().try_into().unwrap());
let ipk = IncomingViewingPublicKey::from_scalar(*isk);
let key = Self::kdf(
ss_bytes,
&self.epk,
&ipk,
// &commitment.to_byte_array(),
output_index,
);
let mut cipher = ChaCha20::new(&key.into(), &[0; 12].into());
let mut buffer = self.ciphertext;
cipher.apply_keystream(&mut buffer);
let mut cursor = Cursor::new(buffer.as_slice());
Account::from_cursor(&mut cursor).ok()
}
pub fn new(
account: &Account,
// commitment: &Commitment,
esk: &EphemeralSecretKey,
npk: &NullifierPublicKey,
ipk: &IncomingViewingPublicKey,
output_index: u32,
) -> Self {
let mut buffer = account.to_bytes().to_vec();
let ss_bytes = Self::ecdh(esk, &ipk.0.clone().try_into().unwrap());
let epk = EphemeralPublicKey::from(esk);
let key = Self::kdf(
ss_bytes,
&epk,
ipk,
// &commitment.to_byte_array(),
output_index,
);
let mut cipher = ChaCha20::new(&key.into(), &[0; 12].into());
cipher.apply_keystream(&mut buffer);
let view_tag = Self::view_tag(&npk, &ipk);
Self {
ciphertext: buffer,
epk,
view_tag,
}
}
pub fn kdf(
ss_bytes: [u8; 32],
epk: &EphemeralPublicKey,
ipk: &IncomingViewingPublicKey,
// commitment: &[u8; 32],
output_index: u32,
) -> [u8; 32] {
let mut bytes = Vec::new();
bytes.extend_from_slice(b"NSSA/v0.1/KDF-SHA256");
bytes.extend_from_slice(&ss_bytes);
bytes.extend_from_slice(&epk.0[..]);
bytes.extend_from_slice(&ipk.0[..]);
// bytes.extend_from_slice(&commitment[..]);
bytes.extend_from_slice(&output_index.to_le_bytes());
Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap()
}
pub fn ecdh(scalar: &[u8; 32], point: &[u8; 33]) -> [u8; 32] {
let scalar = Scalar::from_repr((*scalar).into()).unwrap();
let encoded = EncodedPoint::from_bytes(point).unwrap();
let pubkey_affine = AffinePoint::from_encoded_point(&encoded).unwrap();
let shared = ProjectivePoint::from(pubkey_affine) * scalar;
let shared_affine = shared.to_affine();
let encoded = shared_affine.to_encoded_point(false);
let x_bytes_slice = encoded.x().unwrap();
let mut x_bytes = [0u8; 32];
x_bytes.copy_from_slice(x_bytes_slice);
x_bytes
}
#[cfg(feature = "host")]
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
let mut u32_bytes = [0; 4];
cursor.read_exact(&mut u32_bytes)?;
let ciphertext_lenght = u32::from_le_bytes(u32_bytes);
let mut ciphertext = vec![0; ciphertext_lenght as usize];
cursor.read_exact(&mut ciphertext)?;
let mut epk_bytes = vec![0; 33];
cursor.read_exact(&mut epk_bytes)?;
let mut tag_bytes = [0; 1];
cursor.read_exact(&mut tag_bytes)?;
Ok(Self {
ciphertext,
epk: Secp256k1Point(epk_bytes),
view_tag: tag_bytes[0],
})
}
fn view_tag(npk: &NullifierPublicKey, ipk: &&IncomingViewingPublicKey) -> u8 {
// TODO: implement
0
}
}
impl EncryptedAccountData {
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
let ciphertext_length: u32 = self.ciphertext.len() as u32;
bytes.extend_from_slice(&ciphertext_length.to_le_bytes());
bytes.extend_from_slice(&self.ciphertext);
bytes.extend_from_slice(&self.epk.0);
bytes.push(self.view_tag);
bytes
}
}
#[derive(Serialize, Deserialize)]
pub struct PrivacyPreservingCircuitInput {
pub program_output: ProgramOutput,
pub visibility_mask: Vec<u8>,
pub private_account_nonces: Vec<Nonce>,
pub private_account_keys: Vec<(
NullifierPublicKey,
IncomingViewingPublicKey,
EphemeralSecretKey,
)>,
pub private_account_auth: Vec<(NullifierSecretKey, MembershipProof)>,
pub program_id: ProgramId,
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
pub struct PrivacyPreservingCircuitOutput {
pub public_pre_states: Vec<AccountWithMetadata>,
pub public_post_states: Vec<Account>,
pub encrypted_private_post_states: Vec<EncryptedAccountData>,
pub new_commitments: Vec<Commitment>,
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
}
#[cfg(feature = "host")]
impl PrivacyPreservingCircuitOutput {
pub fn to_bytes(&self) -> Vec<u8> {
bytemuck::cast_slice(&to_vec(&self).unwrap()).to_vec()
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use risc0_zkvm::serde::from_slice;
use crate::{
EncryptedAccountData, EphemeralPublicKey, PrivacyPreservingCircuitOutput, Secp256k1Point,
account::{Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey},
};
#[test]
fn test_privacy_preserving_circuit_output_to_bytes_is_compatible_with_from_slice() {
let output = PrivacyPreservingCircuitOutput {
public_pre_states: vec![
AccountWithMetadata {
account: Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 12345678901234567890,
data: b"test data".to_vec(),
nonce: 18446744073709551614,
},
is_authorized: true,
},
AccountWithMetadata {
account: Account {
program_owner: [9, 9, 9, 8, 8, 8, 7, 7],
balance: 123123123456456567112,
data: b"test data".to_vec(),
nonce: 9999999999999999999999,
},
is_authorized: false,
},
],
public_post_states: vec![Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 100,
data: b"post state data".to_vec(),
nonce: 18446744073709551615,
}],
encrypted_private_post_states: vec![EncryptedAccountData {
ciphertext: vec![255, 255, 1, 1, 2, 2],
epk: EphemeralPublicKey::from_scalar([123; 32]),
view_tag: 1,
}],
new_commitments: vec![Commitment::new(
&NullifierPublicKey::from(&[1; 32]),
&Account::default(),
)],
new_nullifiers: vec![(
Nullifier::new(
&Commitment::new(&NullifierPublicKey::from(&[2; 32]), &Account::default()),
&[1; 32],
),
[0xab; 32],
)],
};
let bytes = output.to_bytes();
let output_from_slice: PrivacyPreservingCircuitOutput = from_slice(&bytes).unwrap();
assert_eq!(output, output_from_slice);
}
#[test]
fn test_encrypted_account_data_to_bytes_roundtrip() {
let data = EncryptedAccountData {
ciphertext: vec![255, 255, 1, 1, 2, 2],
epk: EphemeralPublicKey::from_scalar([123; 32]),
view_tag: 95,
};
let bytes = data.to_bytes();
let mut cursor = Cursor::new(bytes.as_slice());
let data_from_cursor = EncryptedAccountData::from_cursor(&mut cursor).unwrap();
assert_eq!(data, data_from_cursor);
}
}

View File

@ -1,17 +1,45 @@
use crate::account::{Account, AccountWithMetadata};
use risc0_zkvm::serde::Deserializer;
use risc0_zkvm::{DeserializeOwned, guest::env};
use serde::{Deserialize, Serialize};
#[cfg(feature = "host")]
use crate::error::NssaCoreError;
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) {
pub struct ProgramInput<T> {
pub pre_states: Vec<AccountWithMetadata>,
pub instruction: T,
}
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
pub struct ProgramOutput {
pub pre_states: Vec<AccountWithMetadata>,
pub post_states: Vec<Account>,
}
pub fn read_nssa_inputs<T: DeserializeOwned>() -> ProgramInput<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)
let instruction = T::deserialize(&mut Deserializer::new(words.as_ref())).unwrap();
ProgramInput {
pre_states,
instruction,
}
}
pub fn write_nssa_outputs(pre_states: Vec<AccountWithMetadata>, post_states: Vec<Account>) {
let output = ProgramOutput {
pre_states,
post_states,
};
env::commit(&output);
}
/// Validates well-behaved program execution
///
/// # Parameters

View File

@ -8,3 +8,4 @@ risc0-build = { version = "3.0.1" }
[package.metadata.risc0]
methods = ["guest"]

View File

@ -312,6 +312,12 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "base16ct"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]]
name = "base64"
version = "0.22.1"
@ -516,6 +522,24 @@ dependencies = [
"num-traits",
"serde",
"windows-link",
name = "chacha20"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
@ -569,6 +593,18 @@ dependencies = [
"libc",
]
[[package]]
name = "crypto-bigint"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
dependencies = [
"generic-array",
"rand_core 0.6.4",
"subtle",
"zeroize",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -770,6 +806,18 @@ name = "dyn-clone"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
name = "ecdsa"
version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
"der",
"digest",
"elliptic-curve",
"rfc6979",
"signature",
"spki",
]
[[package]]
name = "educe"
@ -795,6 +843,25 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b"
[[package]]
name = "elliptic-curve"
version = "0.13.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
"base16ct",
"crypto-bigint",
"digest",
"ff",
"generic-array",
"group",
"pkcs8",
"rand_core 0.6.4",
"sec1",
"subtle",
"zeroize",
]
[[package]]
name = "embedded-io"
version = "0.4.0"
@ -858,6 +925,16 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "ff"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
dependencies = [
"rand_core 0.6.4",
"subtle",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -976,6 +1053,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
"zeroize",
]
[[package]]
@ -1011,6 +1089,17 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
dependencies = [
"ff",
"rand_core 0.6.4",
"subtle",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@ -1054,6 +1143,15 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]]
name = "http"
version = "1.3.1"
@ -1319,6 +1417,15 @@ dependencies = [
"serde",
]
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"generic-array",
]
[[package]]
name = "io-uring"
version = "0.7.9"
@ -1371,6 +1478,20 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "k256"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
dependencies = [
"cfg-if",
"ecdsa",
"elliptic-curve",
"once_cell",
"sha2",
"signature",
]
[[package]]
name = "keccak"
version = "0.1.5"
@ -1541,6 +1662,8 @@ checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e"
name = "nssa-core"
version = "0.1.0"
dependencies = [
"chacha20",
"k256",
"risc0-zkvm",
"serde",
]
@ -2043,6 +2166,16 @@ dependencies = [
"webpki-roots",
]
[[package]]
name = "rfc6979"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
dependencies = [
"hmac",
"subtle",
]
[[package]]
name = "ring"
version = "0.17.14"
@ -2433,6 +2566,17 @@ dependencies = [
"ref-cast",
"serde",
"serde_json",
name = "sec1"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
"base16ct",
"der",
"generic-array",
"pkcs8",
"subtle",
"zeroize",
]
[[package]]
@ -3527,3 +3671,8 @@ dependencies = [
"quote",
"syn 2.0.104",
]
[[patch.unused]]
name = "k256"
version = "0.13.3"
source = "git+https://github.com/risc0/RustCrypto-elliptic-curves?tag=k256%2Fv0.13.3-risczero.1#ff5d67b095cfcc2569b7789f2079ed87ef2c7756"

View File

@ -8,3 +8,7 @@ edition = "2021"
[dependencies]
risc0-zkvm = { version = "3.0.1", default-features = false, features = ['std'] }
nssa-core = { path = "../../core" }
[patch.crates-io]
k256 = { git = "https://github.com/risc0/RustCrypto-elliptic-curves", tag = "k256/v0.13.3-risczero.1" }

View File

@ -1,17 +1,17 @@
use nssa_core::program::read_nssa_inputs;
use risc0_zkvm::guest::env;
type Instruction = u128;
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
/// 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>();
let ProgramInput {
pre_states,
instruction: balance_to_move,
} = read_nssa_inputs();
// Continue only if input_accounts is an array of two elements
let [sender, receiver] = match input_accounts.try_into() {
let [sender, receiver] = match pre_states.try_into() {
Ok(array) => array,
Err(_) => return,
};
@ -32,5 +32,5 @@ fn main() {
sender_post.balance -= balance_to_move;
receiver_post.balance += balance_to_move;
env::commit(&vec![sender_post, receiver_post]);
write_nssa_outputs(vec![sender, receiver], vec![sender_post, receiver_post]);
}

View File

@ -0,0 +1,157 @@
use risc0_zkvm::{guest::env, serde::to_vec};
use nssa_core::{
account::{Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey},
compute_root_associated_to_path,
program::{validate_execution, ProgramOutput, DEFAULT_PROGRAM_ID},
CommitmentSetDigest, EncryptedAccountData, EphemeralPublicKey, EphemeralSecretKey,
IncomingViewingPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput,
};
fn main() {
let PrivacyPreservingCircuitInput {
program_output,
visibility_mask,
private_account_nonces,
private_account_keys,
private_account_auth,
program_id,
} = env::read();
// TODO: Check that `program_execution_proof` is one of the allowed built-in programs
// assert!(BUILTIN_PROGRAM_IDS.contains(executing_program_id));
// Check that `program_output` is consistent with the execution of the corresponding program.
env::verify(program_id, &to_vec(&program_output).unwrap()).unwrap();
let ProgramOutput {
pre_states,
post_states,
} = program_output;
// Check that the program is well behaved.
// See the # Programs section for the definition of the `validate_execution` method.
validate_execution(&pre_states, &post_states, program_id);
let n_accounts = pre_states.len();
if visibility_mask.len() != n_accounts {
panic!();
}
// These lists will be the public outputs of this circuit
// and will be populated next.
let mut public_pre_states: Vec<AccountWithMetadata> = Vec::new();
let mut public_post_states: Vec<Account> = Vec::new();
let mut encrypted_private_post_states: Vec<EncryptedAccountData> = Vec::new();
let mut new_commitments: Vec<Commitment> = Vec::new();
let mut new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)> = Vec::new();
let mut private_nonces_iter = private_account_nonces.iter();
let mut private_keys_iter = private_account_keys.iter();
let mut private_auth_iter = private_account_auth.iter();
let mut output_index = 0;
for i in 0..n_accounts {
match visibility_mask[i] {
0 => {
// Public account
public_pre_states.push(pre_states[i].clone());
let mut post = post_states[i].clone();
post.nonce += 1;
if post.program_owner == DEFAULT_PROGRAM_ID {
// Claim account
post.program_owner = program_id;
}
public_post_states.push(post);
}
1 | 2 => {
let new_nonce = private_nonces_iter.next().expect("Missing private nonce");
let (Npk, Ipk, esk) = private_keys_iter.next().expect("Missing private keys");
if visibility_mask[i] == 1 {
// Private account with authentication
let (nsk, membership_proof) =
private_auth_iter.next().expect("Missing private auth");
// Verify Npk
let expected_Npk = NullifierPublicKey::from(nsk);
if &expected_Npk != Npk {
panic!("Npk mismatch");
}
// Compute commitment set digest associated with provided auth path
let commitment_pre = Commitment::new(Npk, &pre_states[i].account);
let set_digest =
compute_root_associated_to_path(&commitment_pre, membership_proof);
// Check pre_state authorization
if !pre_states[i].is_authorized {
panic!("Pre-state not authorized");
}
// Compute nullifier
let nullifier = Nullifier::new(&commitment_pre, nsk);
new_nullifiers.push((nullifier, set_digest));
} else {
if pre_states[i].account != Account::default() {
panic!("Found new private account with non default values.");
}
if pre_states[i].is_authorized {
panic!("Found new private account marked as authorized.");
}
}
// Update post-state with new nonce
let mut post_with_updated_values = post_states[i].clone();
post_with_updated_values.nonce = *new_nonce;
if post_with_updated_values.program_owner == DEFAULT_PROGRAM_ID {
// Claim account
post_with_updated_values.program_owner = program_id;
}
// Compute commitment
let commitment_post = Commitment::new(Npk, &post_with_updated_values);
// Encrypt and push post state
let encrypted_account = EncryptedAccountData::new(
&post_with_updated_values,
// &commitment_post,
esk,
Npk,
Ipk,
output_index,
);
new_commitments.push(commitment_post);
encrypted_private_post_states.push(encrypted_account);
output_index += 1;
}
_ => panic!("Invalid visibility mask value"),
}
}
if private_nonces_iter.next().is_some() {
panic!("Too many nonces.");
}
if private_keys_iter.next().is_some() {
panic!("Too many private accounts keys.");
}
if private_auth_iter.next().is_some() {
panic!("Too many private account authentication keys.");
}
let output = PrivacyPreservingCircuitOutput {
public_pre_states,
public_post_states,
encrypted_private_post_states,
new_commitments,
new_nullifiers,
};
env::commit(&output);
}

View File

@ -1,12 +1,7 @@
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],
@ -28,18 +23,6 @@ impl AsRef<[u8]> for Address {
}
}
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
@ -74,28 +57,6 @@ impl Display for Address {
}
}
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};

View File

@ -7,9 +7,6 @@ pub enum NssaError {
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Risc0 error: {0}")]
ProgramExecutionFailed(String),
#[error("Program violated execution rules")]
InvalidProgramBehavior,
@ -24,4 +21,28 @@ pub enum NssaError {
#[error("Invalid Public Key")]
InvalidPublicKey,
#[error("Risc0 error: {0}")]
ProgramWriteInputFailed(String),
#[error("Risc0 error: {0}")]
ProgramExecutionFailed(String),
#[error("Risc0 error: {0}")]
ProgramProveFailed(String),
#[error("Invalid transaction: {0}")]
TransactionDeserializationError(String),
#[error("Core error")]
Core(#[from] nssa_core::error::NssaCoreError),
#[error("Program output deserialization error: {0}")]
ProgramOutputDeserializationError(String),
#[error("Circuit output deserialization error: {0}")]
CircuitOutputDeserializationError(String),
#[error("Invalid privacy preserving execution circuit proof")]
InvalidPrivacyPreservingProof,
}

View File

@ -1,9 +1,11 @@
pub mod address;
pub mod error;
mod privacy_preserving_transaction;
pub mod program;
pub mod public_transaction;
mod signature;
mod state;
mod merkle_tree;
pub use address::Address;
pub use public_transaction::PublicTransaction;
@ -11,3 +13,4 @@ pub use signature::PrivateKey;
pub use signature::PublicKey;
pub use signature::Signature;
pub use state::V01State;

View File

@ -0,0 +1,130 @@
pub(crate) const DEFAULT_VALUES: [[u8; 32]; 32] = [
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0,
],
[
245, 165, 253, 66, 209, 106, 32, 48, 39, 152, 239, 110, 211, 9, 151, 155, 67, 0, 61, 35,
32, 217, 240, 232, 234, 152, 49, 169, 39, 89, 251, 75,
],
[
219, 86, 17, 78, 0, 253, 212, 193, 248, 92, 137, 43, 243, 90, 201, 168, 146, 137, 170, 236,
177, 235, 208, 169, 108, 222, 96, 106, 116, 139, 93, 113,
],
[
199, 128, 9, 253, 240, 127, 197, 106, 17, 241, 34, 55, 6, 88, 163, 83, 170, 165, 66, 237,
99, 228, 76, 75, 193, 95, 244, 205, 16, 90, 179, 60,
],
[
83, 109, 152, 131, 127, 45, 209, 101, 165, 93, 94, 234, 233, 20, 133, 149, 68, 114, 213,
111, 36, 109, 242, 86, 191, 60, 174, 25, 53, 42, 18, 60,
],
[
158, 253, 224, 82, 170, 21, 66, 159, 174, 5, 186, 212, 208, 177, 215, 198, 77, 166, 77, 3,
215, 161, 133, 74, 88, 140, 44, 184, 67, 12, 13, 48,
],
[
216, 141, 223, 238, 212, 0, 168, 117, 85, 150, 178, 25, 66, 193, 73, 126, 17, 76, 48, 46,
97, 24, 41, 15, 145, 230, 119, 41, 118, 4, 31, 161,
],
[
135, 235, 13, 219, 165, 126, 53, 246, 210, 134, 103, 56, 2, 164, 175, 89, 117, 226, 37, 6,
199, 207, 76, 100, 187, 107, 229, 238, 17, 82, 127, 44,
],
[
38, 132, 100, 118, 253, 95, 197, 74, 93, 67, 56, 81, 103, 201, 81, 68, 242, 100, 63, 83,
60, 200, 91, 185, 209, 107, 120, 47, 141, 125, 177, 147,
],
[
80, 109, 134, 88, 45, 37, 36, 5, 184, 64, 1, 135, 146, 202, 210, 191, 18, 89, 241, 239, 90,
165, 248, 135, 225, 60, 178, 240, 9, 79, 81, 225,
],
[
255, 255, 10, 215, 230, 89, 119, 47, 149, 52, 193, 149, 200, 21, 239, 196, 1, 78, 241, 225,
218, 237, 68, 4, 192, 99, 133, 209, 17, 146, 233, 43,
],
[
108, 240, 65, 39, 219, 5, 68, 28, 216, 51, 16, 122, 82, 190, 133, 40, 104, 137, 14, 67, 23,
230, 160, 42, 180, 118, 131, 170, 117, 150, 66, 32,
],
[
183, 208, 95, 135, 95, 20, 0, 39, 239, 81, 24, 162, 36, 123, 187, 132, 206, 143, 47, 15,
17, 35, 98, 48, 133, 218, 247, 150, 12, 50, 159, 95,
],
[
223, 106, 245, 245, 187, 219, 107, 233, 239, 138, 166, 24, 228, 191, 128, 115, 150, 8, 103,
23, 30, 41, 103, 111, 139, 40, 77, 234, 106, 8, 168, 94,
],
[
181, 141, 144, 15, 94, 24, 46, 60, 80, 239, 116, 150, 158, 161, 108, 119, 38, 197, 73, 117,
124, 194, 53, 35, 195, 105, 88, 125, 167, 41, 55, 132,
],
[
212, 154, 117, 2, 255, 207, 176, 52, 11, 29, 120, 133, 104, 133, 0, 202, 48, 129, 97, 167,
249, 107, 98, 223, 157, 8, 59, 113, 252, 200, 242, 187,
],
[
143, 230, 177, 104, 146, 86, 192, 211, 133, 244, 47, 91, 190, 32, 39, 162, 44, 25, 150,
225, 16, 186, 151, 193, 113, 211, 229, 148, 141, 233, 43, 235,
],
[
141, 13, 99, 195, 158, 186, 222, 133, 9, 224, 174, 60, 156, 56, 118, 251, 95, 161, 18, 190,
24, 249, 5, 236, 172, 254, 203, 146, 5, 118, 3, 171,
],
[
149, 238, 200, 178, 229, 65, 202, 212, 233, 29, 227, 131, 133, 242, 224, 70, 97, 159, 84,
73, 108, 35, 130, 203, 108, 172, 213, 185, 140, 38, 245, 164,
],
[
248, 147, 233, 8, 145, 119, 117, 182, 43, 255, 35, 41, 77, 187, 227, 161, 205, 142, 108,
193, 195, 91, 72, 1, 136, 123, 100, 106, 111, 129, 241, 127,
],
[
205, 219, 167, 181, 146, 227, 19, 51, 147, 193, 97, 148, 250, 199, 67, 26, 191, 47, 84,
133, 237, 113, 29, 178, 130, 24, 60, 129, 158, 8, 235, 170,
],
[
138, 141, 127, 227, 175, 140, 170, 8, 90, 118, 57, 168, 50, 0, 20, 87, 223, 185, 18, 138,
128, 97, 20, 42, 208, 51, 86, 41, 255, 35, 255, 156,
],
[
254, 179, 195, 55, 215, 165, 26, 111, 191, 0, 185, 227, 76, 82, 225, 201, 25, 92, 150, 155,
212, 231, 160, 191, 213, 29, 92, 91, 237, 156, 17, 103,
],
[
231, 31, 10, 168, 60, 195, 46, 223, 190, 250, 159, 77, 62, 1, 116, 202, 133, 24, 46, 236,
159, 58, 9, 246, 166, 192, 223, 99, 119, 165, 16, 215,
],
[
49, 32, 111, 168, 10, 80, 187, 106, 190, 41, 8, 80, 88, 241, 98, 18, 33, 42, 96, 238, 200,
240, 73, 254, 203, 146, 216, 200, 224, 168, 75, 192,
],
[
33, 53, 43, 254, 203, 237, 221, 233, 147, 131, 159, 97, 76, 61, 172, 10, 62, 227, 117, 67,
249, 180, 18, 177, 97, 153, 220, 21, 142, 35, 181, 68,
],
[
97, 158, 49, 39, 36, 187, 109, 124, 49, 83, 237, 157, 231, 145, 215, 100, 163, 102, 179,
137, 175, 19, 197, 139, 248, 168, 217, 4, 129, 164, 103, 101,
],
[
124, 221, 41, 134, 38, 130, 80, 98, 141, 12, 16, 227, 133, 197, 140, 97, 145, 230, 251,
224, 81, 145, 188, 192, 79, 19, 63, 44, 234, 114, 193, 196,
],
[
132, 137, 48, 189, 123, 168, 202, 197, 70, 97, 7, 33, 19, 251, 39, 136, 105, 224, 123, 184,
88, 127, 145, 57, 41, 51, 55, 77, 1, 123, 203, 225,
],
[
136, 105, 255, 44, 34, 178, 140, 193, 5, 16, 217, 133, 50, 146, 128, 51, 40, 190, 79, 176,
232, 4, 149, 232, 187, 141, 39, 31, 91, 136, 150, 54,
],
[
181, 254, 40, 231, 159, 27, 133, 15, 134, 88, 36, 108, 233, 182, 161, 231, 180, 159, 192,
109, 183, 20, 62, 143, 224, 180, 242, 176, 197, 82, 58, 92,
],
[
152, 94, 146, 159, 112, 175, 40, 208, 189, 209, 169, 10, 128, 143, 151, 127, 89, 124, 124,
119, 140, 72, 158, 152, 211, 189, 137, 16, 211, 26, 192, 247,
],
];

551
nssa/src/merkle_tree/mod.rs Normal file
View File

@ -0,0 +1,551 @@
use std::collections::{HashMap, HashSet};
use sha2::{Digest, Sha256};
mod default_values;
type Value = [u8; 32];
type Node = [u8; 32];
/// Compute parent as the hash of two child nodes
fn hash_two(left: &Node, right: &Node) -> Node {
let mut hasher = Sha256::new();
hasher.update(left);
hasher.update(right);
hasher.finalize().into()
}
fn hash_value(value: &Value) -> Node {
let mut hasher = Sha256::new();
hasher.update(value);
hasher.finalize().into()
}
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
pub struct MerkleTree {
nodes: Vec<Node>,
capacity: usize,
length: usize,
}
impl MerkleTree {
pub fn root(&self) -> Node {
let root_index = self.root_index();
*self.get_node(root_index)
}
fn root_index(&self) -> usize {
let tree_depth = self.depth();
let capacity_depth = self.capacity.trailing_zeros() as usize;
if tree_depth == capacity_depth {
0
} else {
(1 << (capacity_depth - tree_depth)) - 1
}
}
/// Number of levels required to hold all values
fn depth(&self) -> usize {
let result = self.length.next_power_of_two().trailing_zeros() as usize;
result
}
fn get_node(&self, index: usize) -> &Node {
&self.nodes[index]
}
fn set_node(&mut self, index: usize, node: Node) {
self.nodes[index] = node;
}
pub fn with_capacity(capacity: usize) -> Self {
let capacity = capacity.next_power_of_two();
let total_depth = capacity.trailing_zeros() as usize;
let nodes = default_values::DEFAULT_VALUES[..(total_depth + 1)]
.iter()
.rev()
.enumerate()
.flat_map(|(level, default_value)| std::iter::repeat_n(default_value, 1 << level))
.cloned()
.collect();
Self {
nodes,
capacity,
length: 0,
}
}
fn reallocate_to_double_capacity(&mut self) {
let old_capacity = self.capacity;
let new_capacity = old_capacity << 1;
let mut this = Self::with_capacity(new_capacity);
for (index, value) in self.nodes.iter().enumerate() {
let offset = prev_power_of_two(index + 1);
let new_index = index + offset;
this.set_node(new_index, *value);
}
this.length = self.length;
*self = this;
}
pub fn insert(&mut self, value: Value) -> usize {
if self.length == self.capacity {
self.reallocate_to_double_capacity();
}
let new_index = self.length;
let mut node_index = new_index + self.capacity - 1;
let mut node_hash = hash_value(&value);
self.set_node(node_index, node_hash);
self.length += 1;
let root_index = self.root_index();
for _ in 0..self.depth() {
let parent_index = (node_index - 1) >> 1;
let left_child = self.get_node((parent_index << 1) + 1);
let right_child = self.get_node((parent_index << 1) + 2);
node_hash = hash_two(left_child, right_child);
self.set_node(parent_index, node_hash);
node_index = parent_index;
}
new_index
}
pub fn new(values: &[Value]) -> Self {
let mut this = Self::with_capacity(values.len());
for value in values.iter().cloned() {
this.insert(value);
}
this
}
pub fn get_authentication_path_for(&self, index: usize) -> Option<Vec<Node>> {
if index >= self.length {
return None;
}
let mut path = Vec::with_capacity(self.depth());
let mut node_index = self.capacity + index - 1;
let root_index = self.root_index();
while node_index != root_index {
let parent_index = (node_index - 1) >> 1;
let is_left_child = node_index & 1 == 1;
let sibling_index = if is_left_child {
node_index + 1
} else {
node_index - 1
};
path.push(*self.get_node(sibling_index));
node_index = parent_index;
}
Some(path)
}
}
// Reference implementation
fn verify_authentication_path(value: &Value, index: usize, path: &[Node], root: &Node) -> bool {
let mut result = hash_value(value);
let mut level_index = index;
for node in path {
let is_left_child = level_index & 1 == 0;
if is_left_child {
result = hash_two(&result, node);
} else {
result = hash_two(node, &result);
}
level_index >>= 1;
}
&result == root
}
fn prev_power_of_two(x: usize) -> usize {
if x == 0 {
return 0;
}
1 << (usize::BITS as usize - x.leading_zeros() as usize - 1)
}
#[cfg(test)]
mod tests {
use hex_literal::hex;
use nssa_core::account::{Account, NullifierPublicKey};
use super::*;
#[test]
fn test_empty_merkle_tree() {
let tree = MerkleTree::with_capacity(4);
let expected_root =
hex!("0000000000000000000000000000000000000000000000000000000000000000");
assert_eq!(tree.root(), expected_root);
assert_eq!(tree.capacity, 4);
assert_eq!(tree.length, 0);
}
#[test]
fn test_merkle_tree_0() {
let values = [[0; 32]];
let tree = MerkleTree::new(&values);
assert_eq!(tree.root(), hash_value(&[0; 32]));
assert_eq!(tree.capacity, 1);
assert_eq!(tree.length, 1);
}
#[test]
fn test_merkle_tree_1() {
let values = [[1; 32], [2; 32], [3; 32], [4; 32]];
let tree = MerkleTree::new(&values);
let expected_root =
hex!("48c73f7821a58a8d2a703e5b39c571c0aa20cf14abcd0af8f2b955bc202998de");
assert_eq!(tree.root(), expected_root);
assert_eq!(tree.capacity, 4);
assert_eq!(tree.length, 4)
}
#[test]
fn test_merkle_tree_2() {
let values = [[1; 32], [2; 32], [3; 32], [0; 32]];
let tree = MerkleTree::new(&values);
let expected_root =
hex!("c9bbb83096df85157a146e7d770455a98412dee0633187ee86fee6c8a45b831a");
assert_eq!(tree.root(), expected_root);
assert_eq!(tree.capacity, 4);
assert_eq!(tree.length, 4);
}
#[test]
fn test_merkle_tree_3() {
let values = [[1; 32], [2; 32], [3; 32]];
let tree = MerkleTree::new(&values);
let expected_root =
hex!("c8d3d8d2b13f27ceeccdc699119871f9f32ea7ed86ff45d0ad11f77b28cd7568");
assert_eq!(tree.root(), expected_root);
assert_eq!(tree.capacity, 4);
assert_eq!(tree.length, 3);
}
#[test]
fn test_merkle_tree_4() {
let values = [[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]];
let tree = MerkleTree::new(&values);
let expected_root =
hex!("ef418aed5aa20702d4d94c92da79a4012f2e36f1008bfdb3cd1e38749dca2499");
assert_eq!(tree.root(), expected_root);
assert_eq!(tree.capacity, 8);
assert_eq!(tree.length, 5);
}
#[test]
fn test_merkle_tree_5() {
let values = [
[11; 32], [12; 32], [12; 32], [13; 32], [14; 32], [15; 32], [15; 32], [13; 32],
[13; 32], [15; 32], [11; 32],
];
let tree = MerkleTree::new(&values);
let expected_root =
hex!("3f72d2ff55921a86c48e5988ec3e19ee9d0d5aa3e23197842970a903508ed767");
assert_eq!(tree.root(), expected_root);
assert_eq!(tree.capacity, 16);
assert_eq!(tree.length, 11);
}
#[test]
fn test_merkle_tree_6() {
let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]];
let tree = MerkleTree::new(&values);
let expected_root =
hex!("069cb8259a06fe6edb3fa7ff7933a6dd7dca6fca299314379794a688926c3792");
assert_eq!(tree.root(), expected_root);
}
#[test]
fn test_with_capacity_4() {
let tree = MerkleTree::with_capacity(4);
assert_eq!(tree.length, 0);
assert_eq!(tree.nodes.len(), 7);
for i in 3..7 {
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[0], "{i}");
}
for i in 1..3 {
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[1], "{i}");
}
assert_eq!(*tree.get_node(0), default_values::DEFAULT_VALUES[2]);
}
#[test]
fn test_with_capacity_5() {
let tree = MerkleTree::with_capacity(5);
assert_eq!(tree.length, 0);
assert_eq!(tree.nodes.len(), 15);
for i in 7..15 {
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[0])
}
for i in 3..7 {
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[1])
}
for i in 1..3 {
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[2])
}
assert_eq!(*tree.get_node(0), default_values::DEFAULT_VALUES[3])
}
#[test]
fn test_with_capacity_6() {
let mut tree = MerkleTree::with_capacity(100);
let values = [[1; 32], [2; 32], [3; 32], [4; 32]];
let expected_root =
hex!("48c73f7821a58a8d2a703e5b39c571c0aa20cf14abcd0af8f2b955bc202998de");
assert_eq!(0, tree.insert(values[0]));
assert_eq!(1, tree.insert(values[1]));
assert_eq!(2, tree.insert(values[2]));
assert_eq!(3, tree.insert(values[3]));
assert_eq!(tree.root(), expected_root);
}
#[test]
fn test_with_capacity_7() {
let mut tree = MerkleTree::with_capacity(599);
let values = [[1; 32], [2; 32], [3; 32]];
let expected_root =
hex!("c8d3d8d2b13f27ceeccdc699119871f9f32ea7ed86ff45d0ad11f77b28cd7568");
assert_eq!(0, tree.insert(values[0]));
assert_eq!(1, tree.insert(values[1]));
assert_eq!(2, tree.insert(values[2]));
assert_eq!(tree.root(), expected_root);
}
#[test]
fn test_with_capacity_8() {
let mut tree = MerkleTree::with_capacity(1);
let values = [[1; 32], [2; 32], [3; 32]];
let expected_root =
hex!("c8d3d8d2b13f27ceeccdc699119871f9f32ea7ed86ff45d0ad11f77b28cd7568");
assert_eq!(0, tree.insert(values[0]));
assert_eq!(1, tree.insert(values[1]));
assert_eq!(2, tree.insert(values[2]));
assert_eq!(tree.root(), expected_root);
}
#[test]
fn test_insert_value_1() {
let mut tree = MerkleTree::with_capacity(1);
let values = [[1; 32], [2; 32], [3; 32]];
let expected_tree = MerkleTree::new(&values);
assert_eq!(0, tree.insert(values[0]));
assert_eq!(1, tree.insert(values[1]));
assert_eq!(2, tree.insert(values[2]));
assert_eq!(expected_tree, tree);
}
#[test]
fn test_insert_value_2() {
let mut tree = MerkleTree::with_capacity(1);
let values = [[1; 32], [2; 32], [3; 32], [4; 32]];
let expected_tree = MerkleTree::new(&values);
assert_eq!(0, tree.insert(values[0]));
assert_eq!(1, tree.insert(values[1]));
assert_eq!(2, tree.insert(values[2]));
assert_eq!(3, tree.insert(values[3]));
assert_eq!(expected_tree, tree);
}
#[test]
fn test_insert_value_3() {
let mut tree = MerkleTree::with_capacity(1);
let values = [[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]];
let expected_tree = MerkleTree::new(&values);
tree.insert(values[0]);
tree.insert(values[1]);
tree.insert(values[2]);
tree.insert(values[3]);
tree.insert(values[4]);
assert_eq!(expected_tree, tree);
}
#[test]
fn test_authentication_path_1() {
let values = [[1; 32], [2; 32], [3; 32], [4; 32]];
let tree = MerkleTree::new(&values);
let expected_authentication_path = vec![
hex!("9f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed"),
hex!("50a27d4746f357cb700cbe9d4883b77fb64f0128828a3489dc6a6f21ddbf2414"),
];
let authentication_path = tree.get_authentication_path_for(2).unwrap();
assert_eq!(authentication_path, expected_authentication_path);
}
#[test]
fn test_authentication_path_2() {
let values = [[1; 32], [2; 32], [3; 32]];
let tree = MerkleTree::new(&values);
let expected_authentication_path = vec![
hex!("75877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a"),
hex!("a41b855d2db4de9052cd7be5ec67d6586629cb9f6e3246a4afa5ba313f07a9c5"),
];
let authentication_path = tree.get_authentication_path_for(0).unwrap();
assert_eq!(authentication_path, expected_authentication_path);
}
#[test]
fn test_authentication_path_3() {
let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]];
let tree = MerkleTree::new(&values);
let expected_authentication_path = vec![
hex!("0000000000000000000000000000000000000000000000000000000000000000"),
hex!("f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b"),
hex!("48c73f7821a58a8d2a703e5b39c571c0aa20cf14abcd0af8f2b955bc202998de"),
];
let authentication_path = tree.get_authentication_path_for(4).unwrap();
assert_eq!(authentication_path, expected_authentication_path);
}
#[test]
fn test_authentication_path_4() {
let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]];
let tree = MerkleTree::new(&values);
assert!(tree.get_authentication_path_for(5).is_none());
}
#[test]
fn test_authentication_path_5() {
let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]];
let tree = MerkleTree::new(&values);
let index = 4;
let value = values[index];
let path = tree.get_authentication_path_for(index).unwrap();
assert!(verify_authentication_path(
&value,
index,
&path,
&tree.root()
));
}
#[test]
fn test_tree_with_63_insertions() {
let values = [
hex!("cd00acab0f45736e6c6311f1953becc0b69a062e7c2a7310875d28bdf9ef9c5b"),
hex!("0df5a6afbcc7bf126caf7084acfc593593ab512e6ca433c61c1a922be40a04ea"),
hex!("23c1258620266c7bedb6d1ee32f6da9413e4010ace975239dccb34e727e07c40"),
hex!("f33ccc3a11476b0ef62326ca5ec292056759b05e6a28023d2d1ce66165611353"),
hex!("77f914ab016b8049f6bea7704000e413a393865918a3824f9285c3db0aacff23"),
hex!("910a1c23188e54d57fd167ddb0f8bf68c6b70ed9ec76ef56c4b7f2632f82ca7f"),
hex!("047ee85526197d1e7403a559cf6d2f22c1926c8ad59481a2e2f1b697af45e40b"),
hex!("9d355cf89fb382ae34bf80566b28489278d10f2cebb5b0ea42fab1bac5adae0c"),
hex!("604018b95232596b2685a9bc737b6cccb53b10e483d2d9a2f4a755410b02a188"),
hex!("a16708ef7b6bf1796063addaf57d6a566b6f87b0bbe42af43a4590d05f1684cb"),
hex!("820f2dfa271cd2fd41e1452406d5dad552c85c1223c45d45dbd7446759fdc6b8"),
hex!("680b6912d7e219f8805d4d28adb4428dd78fea0dc1b8cdb2412645c4b1962c88"),
hex!("14d5471ce6c45506753982b17cac5790ac7bc29e6f388f31052d7dfd62b294e5"),
hex!("8b364200172b777d4aa16d2098b5eb98ac3dd4a1b9597e5c2bf6f6930031f230"),
hex!("9bb45b910711874339dda8a21a9aad73822286f5e52d7d3de0ed78dfbba329a5"),
hex!("d6806d5df5cb25ce5d531042f09b3cb34fb9e47c61182b63cccd9d44392f6027"),
hex!("b8cfa90ebc8fd09c04682d93a08fddd3e8e57715174dcc92451edd191264a58b"),
hex!("3463c7f81d00f809b3dfa83195447c927fb4045b3913dac6f45bee6c4010d7ed"),
hex!("1d6ad7f7d677905feb506c58f4b404a79370ebc567296abea3a368b61d5a8239"),
hex!("a58085ecf00963cb22da23c901b9b3ddc56462bb96ff03c923d67708e10dd29c"),
hex!("c3319f4a65fb5bbb8447137b0972c03cbd84ebf7d9da194e0fcbd68c2d4d5bdb"),
hex!("4aa31e90e0090faf3648d05e5d5499df2c78ebed4d6e6c23d8147de5d67dae73"),
hex!("9f33b1d2c8bc7bd265336de1033ede6344bc41260313bdcb43f1108b83b9be92"),
hex!("6500d4ad93d41c16ec81eaa5e70f173194aabe5c1072ac263b5727296f5b7cac"),
hex!("3584f5d260003669fad98786e13171376b0f19410cb232ce65606cbff79e6768"),
hex!("c8410946ebf56f13141c894a34ced85a5230088af70dcea581e44f52847830ac"),
hex!("71dd90281cdebb70422f2d04ae446d5d2d5ea64b803c16128d37e3fcd5d1a4cc"),
hex!("c05acf8d77ab4d659a538bd35af590864a7ad9c055ff5d6cda9d5aecfccecba3"),
hex!("f1df98822ea084cce9021aa9cc81b1746cd1e84a75690da63e10fd877633ed77"),
hex!("2ca822bc8f67bceb0a71a0d06fea7349036ef3e5ec21795a851e4182bd35ce01"),
hex!("7fd2179abc3bcf89b4d8092988ba8c23952b3bbd3d7caea6b5ea0c13cf19f68b"),
hex!("91b6ad516e017f6aa5a2e95776538bd3a3e933c1b1d32bb5e0f00a9db63c9c24"),
hex!("cd31a8b5eef5ca0be5ef1cb261d0bf0a74d774a3152bb99739cfd296a1d0b85e"),
hex!("3fb16f48b2bf93f3815979e6638f975d7f935088ec37db0be0f07965fbc78339"),
hex!("c60c61b99bf486af5f4bf780a69860dafcd35c1474306a8575666fb5449bcec0"),
hex!("8048d0d7e14091251f3f6c6b10bf6b5880a014b513f9f8c2395501dbffa6192a"),
hex!("778b5af10b9dbe80b60a8e4f0bb91caf4476bcb812801099760754ae623fbd84"),
hex!("d3ac25467920a4e08998b7a3226b8b54bfe66ac58cfedc71f15b2402fee0054a"),
hex!("029aa94598fae2961a0d43937b8a9a3138bcfeae99a7cb15f77fac7c506f8432"),
hex!("2eee5ef52fe669cb6882a68c893abdc1262dcf4424e4ba7a479da7cf1c10171d"),
hex!("de3fb3d070e3a90f0eed8b5e65088a8dc0e4e3c342b9c0bf33bab714eae5dfec"),
hex!("14d40177e833ab45bbfdc5f2b11fba7efaebb3f69facc554f24b549a2efe8538"),
hex!("5734355069702448774fb2df95f1d562e1b9fe1514aeb6b922554ee9d2d01068"),
hex!("8a273d49ac110343cec2cf3359d16eb2906b446bd9ec9833e2a640cebc8d5155"),
hex!("e3fa984dd3cbeb9a7e827ed32d3d4e6a6ba643a55d82be97d9ddb06ee809fa3e"),
hex!("90b1d5a364e17c8b7965396b06ec6e13749b5fc16500731518ad8fc30ae33e77"),
hex!("7517376541b2e8ec83cbab04522b54a26610908a9872feb663451385aea58eb1"),
hex!("5cba2e4cf7448e526d161133c4b2ea7c919ac4813a7308612595f46f11dea6cd"),
hex!("c721911b300bec0691c8a2dfaabfef1d66b7b6258918914d3c3ad690729f05b7"),
hex!("d0d0a70d8ae0d27806fa0b711c507290c260a89cbca0436d339d1dccdd087d62"),
hex!("2a625c28ea763c5e82dd0a93ecfca7ec371ccbb363cd42be359c2c875f58009d"),
hex!("174ef0119932ed890397d9f3837dd85f9100558b6fc9085d4af947ae8cf74bbc"),
hex!("b497bc267151e8efa3c6daa461e6804b01a3f05f44f1f4d5b41d5f0d3f5219b1"),
hex!("e987e91f5734630ddd7e6b58733b4fcdbc316ee9e8cac0e94c36c91cf58e59cc"),
hex!("55019ad8bbe656c51eb042190c1c8da53f42baf43fd2350ebea38fc7cca2fae3"),
hex!("c45a638edd18a6d9f5ad20b870c81b8626459bcb22dae7d58add7a6b6c6a84a8"),
hex!("d42d3a5fb2ad50b2027fe5a36d59dd71e49a63e4b1b299073c96bbf7ba5d68a1"),
hex!("9599e561054bcd3f647eb018ab0b069d3176497d42be9c4466551cbb959be47c"),
hex!("42f33b23775327ff71aea6569548255f3cc9929da73373cc9bb1743d417f7cda"),
hex!("ab24294f44fc6fdbeb96e0f6e93c4f6d97d035b73b9a337c353e18c6d0603bdd"),
hex!("33954ec63520334f99b640a2982ac966b68c363fed383d621a1ab573934f1d33"),
hex!("5e2a1f7df963d1fd8f50a285387cfbb5df581426619b325563e20bf7886c62b7"),
hex!("13ffde471d4e27c473254e766fd1328ad80c42cab4d4955cffeae43d866f86e5"),
];
let expected_root =
hex!("1cf9b214217d7823f9de51b8f6cb34d0a99436a3a1bb762f90b815672a6afcc0");
let mut tree_less_capacity = MerkleTree::with_capacity(1);
let mut tree_exact_capacity = MerkleTree::with_capacity(64);
let mut tree_more_capacity = MerkleTree::with_capacity(128);
for value in &values {
tree_less_capacity.insert(*value);
tree_exact_capacity.insert(*value);
tree_more_capacity.insert(*value);
}
assert_eq!(tree_more_capacity.root(), expected_root);
assert_eq!(tree_less_capacity.root(), expected_root);
assert_eq!(tree_exact_capacity.root(), expected_root);
}
}
//

View File

@ -0,0 +1,261 @@
use nssa_core::{
CommitmentSetDigest, EphemeralSecretKey, IncomingViewingPublicKey, MembershipProof,
PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput,
account::{Account, AccountWithMetadata, Nonce, NullifierPublicKey, NullifierSecretKey},
program::{InstructionData, ProgramId, ProgramOutput},
};
use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover};
use crate::{error::NssaError, program::Program};
use program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT_ID};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Proof(Vec<u8>);
impl Proof {
pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool {
let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap();
let receipt = Receipt::new(inner, circuit_output.to_bytes());
receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok()
}
}
pub fn execute_and_prove(
pre_states: &[AccountWithMetadata],
instruction_data: &InstructionData,
visibility_mask: &[u8],
private_account_nonces: &[u128],
private_account_keys: &[(
NullifierPublicKey,
IncomingViewingPublicKey,
EphemeralSecretKey,
)],
private_account_auth: &[(NullifierSecretKey, MembershipProof)],
program: &Program,
) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> {
let inner_receipt = execute_and_prove_program(program, pre_states, instruction_data)?;
let program_output: ProgramOutput = inner_receipt
.journal
.decode()
.map_err(|e| NssaError::ProgramOutputDeserializationError(e.to_string()))?;
let circuit_input = PrivacyPreservingCircuitInput {
program_output,
visibility_mask: visibility_mask.to_vec(),
private_account_nonces: private_account_nonces.to_vec(),
private_account_keys: private_account_keys.to_vec(),
private_account_auth: private_account_auth.to_vec(),
program_id: program.id(),
};
// Prove circuit.
let mut env_builder = ExecutorEnv::builder();
env_builder.add_assumption(inner_receipt);
env_builder.write(&circuit_input).unwrap();
let env = env_builder.build().unwrap();
let prover = default_prover();
let prove_info = prover.prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF).unwrap();
let proof = Proof(borsh::to_vec(&prove_info.receipt.inner)?);
let circuit_output: PrivacyPreservingCircuitOutput = prove_info
.receipt
.journal
.decode()
.map_err(|e| NssaError::CircuitOutputDeserializationError(e.to_string()))?;
Ok((circuit_output, proof))
}
fn execute_and_prove_program(
program: &Program,
pre_states: &[AccountWithMetadata],
instruction_data: &InstructionData,
) -> Result<Receipt, NssaError> {
// Write inputs to the program
let mut env_builder = ExecutorEnv::builder();
Program::write_inputs(pre_states, instruction_data, &mut env_builder)?;
let env = env_builder.build().unwrap();
// Prove the program
let prover = default_prover();
Ok(prover
.prove(env, program.elf())
.map_err(|e| NssaError::ProgramProveFailed(e.to_string()))?
.receipt)
}
#[cfg(test)]
mod tests {
use nssa_core::{
EncryptedAccountData,
account::{
Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey,
NullifierSecretKey,
},
};
use risc0_zkvm::{InnerReceipt, Journal, Receipt};
use crate::{
Address, V01State,
merkle_tree::MerkleTree,
privacy_preserving_transaction::circuit::{Proof, execute_and_prove},
program::Program,
state::{
CommitmentSet,
tests::{test_private_account_keys_1, test_private_account_keys_2},
},
};
use rand::{Rng, RngCore, rngs::OsRng};
use super::*;
#[test]
fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() {
let program = Program::authenticated_transfer_program();
let sender = AccountWithMetadata {
account: Account {
balance: 100,
..Account::default()
},
is_authorized: true,
};
let recipient = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
let balance_to_move: u128 = 37;
let expected_sender_post = Account {
program_owner: program.id(),
balance: 100 - balance_to_move,
nonce: 1,
data: vec![],
};
let expected_recipient_post = Account {
program_owner: program.id(),
balance: balance_to_move,
nonce: 0xdeadbeef,
data: vec![],
};
let expected_sender_pre = sender.clone();
let recipient_keys = test_private_account_keys_1();
let (output, proof) = execute_and_prove(
&[sender, recipient],
&Program::serialize_instruction(balance_to_move).unwrap(),
&[0, 2],
&[0xdeadbeef],
&[(recipient_keys.npk(), recipient_keys.ivk(), [3; 32])],
&[],
&Program::authenticated_transfer_program(),
)
.unwrap();
assert!(proof.is_valid_for(&output));
let [sender_pre] = output.public_pre_states.try_into().unwrap();
let [sender_post] = output.public_post_states.try_into().unwrap();
assert_eq!(sender_pre, expected_sender_pre);
assert_eq!(sender_post, expected_sender_post);
assert_eq!(output.new_commitments.len(), 1);
assert_eq!(output.new_nullifiers.len(), 0);
assert_eq!(output.encrypted_private_post_states.len(), 1);
let recipient_post = output.encrypted_private_post_states[0]
.clone()
.decrypt(&recipient_keys.isk, 0)
.unwrap();
assert_eq!(recipient_post, expected_recipient_post);
}
#[test]
fn prove_privacy_preserving_execution_circuit_fully_private() {
let sender_pre = AccountWithMetadata {
account: Account {
balance: 100,
nonce: 0xdeadbeef,
..Account::default()
},
is_authorized: true,
};
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let commitment_sender = Commitment::new(&sender_keys.npk(), &sender_pre.account);
let recipient = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
let balance_to_move: u128 = 37;
let mut commitment_set = CommitmentSet::with_capacity(2);
commitment_set.extend(&[commitment_sender.clone()]);
let expected_new_nullifiers = vec![(
Nullifier::new(&commitment_sender, &sender_keys.nsk),
commitment_set.digest(),
)];
let program = Program::authenticated_transfer_program();
let expected_private_account_1 = Account {
program_owner: program.id(),
balance: 100 - balance_to_move,
nonce: 0xdeadbeef1,
..Default::default()
};
let expected_private_account_2 = Account {
program_owner: program.id(),
balance: balance_to_move,
nonce: 0xdeadbeef2,
..Default::default()
};
let expected_new_commitments = vec![
Commitment::new(&sender_keys.npk(), &expected_private_account_1),
Commitment::new(&recipient_keys.npk(), &expected_private_account_2),
];
let (output, proof) = execute_and_prove(
&[sender_pre.clone(), recipient],
&Program::serialize_instruction(balance_to_move).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&[
(sender_keys.npk(), sender_keys.ivk(), [3; 32]),
(recipient_keys.npk(), recipient_keys.ivk(), [5; 32]),
],
&[(
sender_keys.nsk,
commitment_set.get_proof_for(&commitment_sender).unwrap(),
)],
&program,
)
.unwrap();
assert!(proof.is_valid_for(&output));
assert!(output.public_pre_states.is_empty());
assert!(output.public_post_states.is_empty());
assert_eq!(output.new_commitments, expected_new_commitments);
assert_eq!(output.new_nullifiers, expected_new_nullifiers);
assert_eq!(output.encrypted_private_post_states.len(), 2);
let recipient_post_1 = output.encrypted_private_post_states[0]
.clone()
.decrypt(&sender_keys.isk, 0)
.unwrap();
assert_eq!(recipient_post_1, expected_private_account_1);
let recipient_post_2 = output.encrypted_private_post_states[1]
.clone()
.decrypt(&recipient_keys.isk, 1)
.unwrap();
assert_eq!(recipient_post_2, expected_private_account_2);
}
}

View File

@ -0,0 +1,144 @@
// TODO: Consider switching to deriving Borsh
use std::io::{Cursor, Read};
use nssa_core::{
EncryptedAccountData,
account::{Account, Commitment, Nullifier},
};
use crate::{Address, error::NssaError};
use super::message::Message;
const MESSAGE_ENCODING_PREFIX_LEN: usize = 22;
const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"\x01/NSSA/v0.1/TxMessage/";
impl Message {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut bytes = MESSAGE_ENCODING_PREFIX.to_vec();
// Public addresses
let public_addresses_len: u32 = self.public_addresses.len() as u32;
bytes.extend_from_slice(&public_addresses_len.to_le_bytes());
for address in &self.public_addresses {
bytes.extend_from_slice(address.value());
}
// Nonces
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());
}
// Public post states
let public_post_states_len: u32 = self.public_post_states.len() as u32;
bytes.extend_from_slice(&public_post_states_len.to_le_bytes());
for account in &self.public_post_states {
bytes.extend_from_slice(&account.to_bytes());
}
// Encrypted post states
let encrypted_accounts_post_states_len: u32 =
self.encrypted_private_post_states.len() as u32;
bytes.extend_from_slice(&encrypted_accounts_post_states_len.to_le_bytes());
for encrypted_account in &self.encrypted_private_post_states {
bytes.extend_from_slice(&encrypted_account.to_bytes());
}
// New commitments
let new_commitments_len: u32 = self.new_commitments.len() as u32;
bytes.extend_from_slice(&new_commitments_len.to_le_bytes());
for commitment in &self.new_commitments {
bytes.extend_from_slice(&commitment.to_byte_array());
}
// New nullifiers
let new_nullifiers_len: u32 = self.new_nullifiers.len() as u32;
bytes.extend_from_slice(&new_nullifiers_len.to_le_bytes());
for (nullifier, commitment_set_digest) in &self.new_nullifiers {
bytes.extend_from_slice(&nullifier.to_byte_array());
bytes.extend_from_slice(commitment_set_digest);
}
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
};
if &prefix != MESSAGE_ENCODING_PREFIX {
return Err(NssaError::TransactionDeserializationError(
"Invalid privacy preserving message prefix".to_string(),
));
}
let mut len_bytes = [0u8; 4];
// Public addresses
cursor.read_exact(&mut len_bytes)?;
let public_addresses_len = u32::from_le_bytes(len_bytes) as usize;
let mut public_addresses = Vec::with_capacity(public_addresses_len);
for _ in 0..public_addresses_len {
let mut value = [0u8; 32];
cursor.read_exact(&mut value)?;
public_addresses.push(Address::new(value))
}
// Nonces
cursor.read_exact(&mut len_bytes)?;
let nonces_len = u32::from_le_bytes(len_bytes) as usize;
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))
}
// Public post states
cursor.read_exact(&mut len_bytes)?;
let public_post_states_len = u32::from_le_bytes(len_bytes) as usize;
let mut public_post_states = Vec::with_capacity(public_post_states_len);
for _ in 0..public_post_states_len {
public_post_states.push(Account::from_cursor(cursor)?);
}
// Encrypted private post states
cursor.read_exact(&mut len_bytes)?;
let encrypted_len = u32::from_le_bytes(len_bytes) as usize;
let mut encrypted_private_post_states = Vec::with_capacity(encrypted_len);
for _ in 0..encrypted_len {
encrypted_private_post_states.push(EncryptedAccountData::from_cursor(cursor)?);
}
// New commitments
cursor.read_exact(&mut len_bytes)?;
let new_commitments_len = u32::from_le_bytes(len_bytes) as usize;
let mut new_commitments = Vec::with_capacity(new_commitments_len);
for _ in 0..new_commitments_len {
new_commitments.push(Commitment::from_cursor(cursor)?);
}
// New nullifiers
cursor.read_exact(&mut len_bytes)?;
let new_nullifiers_len = u32::from_le_bytes(len_bytes) as usize;
let mut new_nullifiers = Vec::with_capacity(new_nullifiers_len);
for _ in 0..new_nullifiers_len {
let nullifier = Nullifier::from_cursor(cursor)?;
let mut commitment_set_digest = [0; 32];
cursor.read_exact(&mut commitment_set_digest);
new_nullifiers.push((nullifier, commitment_set_digest));
}
Ok(Self {
public_addresses,
nonces,
public_post_states,
encrypted_private_post_states,
new_commitments,
new_nullifiers,
})
}
}

View File

@ -0,0 +1,108 @@
use nssa_core::{
CommitmentSetDigest, EncryptedAccountData,
account::{Account, Commitment, Nonce, Nullifier},
};
use crate::Address;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Message {
pub(crate) public_addresses: Vec<Address>,
pub(crate) nonces: Vec<Nonce>,
pub(crate) public_post_states: Vec<Account>,
pub(crate) encrypted_private_post_states: Vec<EncryptedAccountData>,
pub(crate) new_commitments: Vec<Commitment>,
pub(crate) new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
}
impl Message {
pub fn new(
public_addresses: Vec<Address>,
nonces: Vec<Nonce>,
public_post_states: Vec<Account>,
encrypted_private_post_states: Vec<EncryptedAccountData>,
new_commitments: Vec<Commitment>,
new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
) -> Self {
Self {
public_addresses,
nonces,
public_post_states,
encrypted_private_post_states,
new_commitments,
new_nullifiers,
}
}
}
#[cfg(test)]
pub mod tests {
use std::io::Cursor;
use nssa_core::account::{
Account, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey,
};
use crate::{Address, privacy_preserving_transaction::message::Message};
pub fn message_for_tests() -> Message {
let account1 = Account::default();
let account2 = Account::default();
let nsk1 = [11; 32];
let nsk2 = [12; 32];
let Npk1 = NullifierPublicKey::from(&nsk1);
let Npk2 = NullifierPublicKey::from(&nsk2);
let public_addresses = vec![Address::new([1; 32])];
let nonces = vec![1, 2, 3];
let public_post_states = vec![Account::default()];
let encrypted_private_post_states = Vec::new();
let new_commitments = vec![Commitment::new(&Npk2, &account2)];
let old_commitment = Commitment::new(&Npk1, &account1);
let new_nullifiers = vec![(Nullifier::new(&old_commitment, &nsk1), [0; 32])];
Message {
public_addresses: public_addresses.clone(),
nonces: nonces.clone(),
public_post_states: public_post_states.clone(),
encrypted_private_post_states: encrypted_private_post_states.clone(),
new_commitments: new_commitments.clone(),
new_nullifiers: new_nullifiers.clone(),
}
}
#[test]
fn test_constructor() {
let message = message_for_tests();
let expected_message = message.clone();
let message = Message::new(
message.public_addresses,
message.nonces,
message.public_post_states,
message.encrypted_private_post_states,
message.new_commitments,
message.new_nullifiers,
);
assert_eq!(message, expected_message);
}
#[test]
fn test_message_serialization_roundtrip() {
let message = message_for_tests();
let bytes = message.to_bytes();
let mut cursor = Cursor::new(bytes.as_ref());
let message_from_cursor = Message::from_cursor(&mut cursor).unwrap();
assert_eq!(message, message_from_cursor);
}
}

View File

@ -0,0 +1,10 @@
mod encoding;
mod message;
mod transaction;
mod witness_set;
pub mod circuit;
pub use message::Message;
pub use transaction::PrivacyPreservingTransaction;
pub use witness_set::WitnessSet;

View File

@ -0,0 +1,162 @@
use std::collections::{HashMap, HashSet};
use nssa_core::account::{Account, AccountWithMetadata, Commitment, Nullifier};
use nssa_core::{CommitmentSetDigest, EncryptedAccountData, PrivacyPreservingCircuitOutput};
use crate::error::NssaError;
use crate::privacy_preserving_transaction::circuit::Proof;
use crate::{Address, V01State};
use super::message::Message;
use super::witness_set::WitnessSet;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrivacyPreservingTransaction {
message: Message,
witness_set: WitnessSet,
}
impl PrivacyPreservingTransaction {
pub fn new(message: Message, witness_set: WitnessSet) -> Self {
Self {
message,
witness_set,
}
}
pub(crate) fn validate_and_produce_public_state_diff(
&self,
state: &mut V01State,
) -> Result<HashMap<Address, Account>, NssaError> {
let message = &self.message;
let witness_set = &self.witness_set;
// 1. Commitments or nullifiers are non empty
if message.new_commitments.is_empty() && message.new_nullifiers.is_empty() {
return Err(NssaError::InvalidInput(
"Empty commitments and empty nullifiers found in message".into(),
));
}
// 2. Check there are no duplicate addresses in the public_addresses list.
if n_unique(&message.public_addresses) != message.public_addresses.len() {
return Err(NssaError::InvalidInput(
"Duplicate addresses found in message".into(),
));
}
// Check there are no duplicate nullifiers in the new_nullifiers list
if n_unique(&message.new_nullifiers) != message.new_nullifiers.len() {
return Err(NssaError::InvalidInput(
"Duplicate nullifiers found in message".into(),
));
}
// Check there are no duplicate commitments in the new_commitments list
if n_unique(&message.new_commitments) != message.new_commitments.len() {
return Err(NssaError::InvalidInput(
"Duplicate commitments found in message".into(),
));
}
// 3. Nonce checks and Valid signatures
// Check exactly one nonce is provided for each signature
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.signatures_are_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 proof verification
let public_pre_states: Vec<_> = message
.public_addresses
.iter()
.map(|address| AccountWithMetadata {
account: state.get_account_by_address(address),
is_authorized: signer_addresses.contains(address),
})
.collect();
// 4. Proof verification
check_privacy_preserving_circuit_proof_is_valid(
&witness_set.proof,
&public_pre_states,
&message.public_post_states,
&message.encrypted_private_post_states,
&message.new_commitments,
&message.new_nullifiers,
)?;
// 5. Commitment freshness
state.check_commitments_are_new(&message.new_commitments)?;
// 6. Nullifier uniqueness
state.check_nullifiers_are_valid(&message.new_nullifiers)?;
Ok(message
.public_addresses
.iter()
.cloned()
.zip(message.public_post_states.clone())
.collect())
}
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()
}
}
fn check_privacy_preserving_circuit_proof_is_valid(
proof: &Proof,
public_pre_states: &[AccountWithMetadata],
public_post_states: &[Account],
encrypted_private_post_states: &[EncryptedAccountData],
new_commitments: &[Commitment],
new_nullifiers: &[(Nullifier, CommitmentSetDigest)],
) -> Result<(), NssaError> {
let output = PrivacyPreservingCircuitOutput {
public_pre_states: public_pre_states.to_vec(),
public_post_states: public_post_states.to_vec(),
encrypted_private_post_states: encrypted_private_post_states.to_vec(),
new_commitments: new_commitments.to_vec(),
new_nullifiers: new_nullifiers.to_vec(),
};
proof
.is_valid_for(&output)
.then_some(())
.ok_or(NssaError::InvalidPrivacyPreservingProof)
}
use std::hash::Hash;
fn n_unique<T: Eq + Hash>(data: &[T]) -> usize {
let set: HashSet<&T> = data.iter().collect();
set.len()
}

View File

@ -0,0 +1,47 @@
use crate::{
PrivateKey, PublicKey, Signature,
privacy_preserving_transaction::{circuit::Proof, message::Message},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WitnessSet {
pub(super) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
pub(super) proof: Proof,
}
impl WitnessSet {
pub fn for_message(message: &Message, proof: Proof, 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 {
proof,
signatures_and_public_keys,
}
}
pub fn signatures_are_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
}
pub fn proof(&self) -> &Proof {
&self.proof
}
}

View File

@ -1,9 +1,13 @@
use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::{
account::{Account, AccountWithMetadata},
program::{DEFAULT_PROGRAM_ID, InstructionData, ProgramId},
program::{DEFAULT_PROGRAM_ID, InstructionData, ProgramId, ProgramOutput},
};
use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID};
use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec};
use risc0_zkvm::{
ExecutorEnv, ExecutorEnvBuilder, Journal, Receipt, default_executor, default_prover,
serde::to_vec,
};
use serde::Serialize;
use crate::error::NssaError;
@ -19,6 +23,10 @@ impl Program {
self.id
}
pub(crate) fn elf(&self) -> &'static [u8] {
self.elf
}
pub fn serialize_instruction<T: Serialize>(
instruction: T,
) -> Result<InstructionData, NssaError> {
@ -42,23 +50,18 @@ impl Program {
.map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?;
// Get outputs
let mut post_states: Vec<Account> = session_info
let ProgramOutput {
mut post_states, ..
} = 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(
pub(crate) fn write_inputs(
pre_states: &[AccountWithMetadata],
instruction_data: &[u32],
env_builder: &mut ExecutorEnvBuilder,
@ -66,7 +69,7 @@ impl Program {
let pre_states = pre_states.to_vec();
env_builder
.write(&(pre_states, instruction_data))
.map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?;
.map_err(|e| NssaError::ProgramWriteInputFailed(e.to_string()))?;
Ok(())
}
@ -80,7 +83,11 @@ impl Program {
#[cfg(test)]
mod tests {
use nssa_core::account::{Account, AccountWithMetadata};
use nssa_core::{
account::{Account, AccountWithMetadata},
program::ProgramOutput,
};
use risc0_zkvm::{InnerReceipt, Receipt, serde::to_vec};
use crate::program::Program;
@ -185,13 +192,10 @@ mod tests {
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

View File

@ -1,3 +1,5 @@
// TODO: Consider switching to deriving Borsh
use std::io::{Cursor, Read};
use nssa_core::program::ProgramId;
@ -8,8 +10,9 @@ use crate::{
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";
const MESSAGE_ENCODING_PREFIX_LEN: usize = 22;
const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] =
b"\x00/NSSA/v0.1/TxMessage/";
impl Message {
/// Serializes a `Message` into bytes in the following layout:
@ -52,7 +55,12 @@ impl Message {
cursor.read_exact(&mut this)?;
this
};
assert_eq!(&prefix, MESSAGE_ENCODING_PREFIX);
if &prefix != MESSAGE_ENCODING_PREFIX {
return Err(NssaError::TransactionDeserializationError(
"Invalid public message prefix".to_string(),
));
}
let program_id: ProgramId = {
let mut this = [0u32; 8];
for item in &mut this {

View File

@ -50,7 +50,7 @@ impl PublicTransaction {
hasher.finalize_fixed().into()
}
pub(crate) fn validate_and_compute_post_states(
pub(crate) fn validate_and_produce_public_state_diff(
&self,
state: &V01State,
) -> Result<HashMap<Address, Account>, NssaError> {
@ -64,6 +64,7 @@ impl PublicTransaction {
));
}
// Check exactly one nonce is provided for each signature
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(),
@ -232,7 +233,7 @@ pub mod tests {
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);
let result = tx.validate_and_produce_public_state_diff(&state);
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
}
@ -252,7 +253,7 @@ pub mod tests {
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);
let result = tx.validate_and_produce_public_state_diff(&state);
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
}
@ -273,7 +274,7 @@ pub mod tests {
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);
let result = tx.validate_and_produce_public_state_diff(&state);
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
}
@ -293,7 +294,7 @@ pub mod tests {
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);
let result = tx.validate_and_produce_public_state_diff(&state);
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
}
@ -309,7 +310,7 @@ pub mod tests {
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);
let result = tx.validate_and_produce_public_state_diff(&state);
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
}
}

View File

@ -1,11 +1,61 @@
use crate::{
address::Address, error::NssaError, program::Program, public_transaction::PublicTransaction,
address::Address, error::NssaError, merkle_tree::MerkleTree,
privacy_preserving_transaction::PrivacyPreservingTransaction, program::Program,
public_transaction::PublicTransaction,
};
use nssa_core::{account::Account, program::ProgramId};
use std::collections::HashMap;
use nssa_core::{
CommitmentSetDigest, MembershipProof,
account::{Account, Commitment, Nullifier},
program::{DEFAULT_PROGRAM_ID, ProgramId},
};
use std::collections::{HashMap, HashSet};
pub(crate) struct CommitmentSet {
merkle_tree: MerkleTree,
commitments: HashMap<Commitment, usize>,
pub root_history: HashSet<CommitmentSetDigest>,
}
impl CommitmentSet {
pub(crate) fn digest(&self) -> CommitmentSetDigest {
self.merkle_tree.root()
}
pub(crate) fn get_proof_for(&self, commitment: &Commitment) -> Option<MembershipProof> {
let index = *self.commitments.get(commitment)?;
let proof = self
.merkle_tree
.get_authentication_path_for(index)
.map(|path| (index, path));
proof
}
pub(crate) fn extend(&mut self, commitments: &[Commitment]) {
for commitment in commitments.iter().cloned() {
let index = self.merkle_tree.insert(commitment.to_byte_array());
self.commitments.insert(commitment, index);
}
self.root_history.insert(self.digest());
}
fn contains(&self, commitment: &Commitment) -> bool {
self.commitments.contains_key(commitment)
}
pub(crate) fn with_capacity(capacity: usize) -> CommitmentSet {
Self {
merkle_tree: MerkleTree::with_capacity(capacity),
commitments: HashMap::new(),
root_history: HashSet::new(),
}
}
}
type NullifierSet = HashSet<Nullifier>;
pub struct V01State {
public_state: HashMap<Address, Account>,
pub private_state: (CommitmentSet, NullifierSet),
builtin_programs: HashMap<ProgramId, Program>,
}
@ -27,6 +77,7 @@ impl V01State {
let mut this = Self {
public_state,
private_state: (CommitmentSet::with_capacity(32), NullifierSet::new()),
builtin_programs: HashMap::new(),
};
@ -43,13 +94,54 @@ impl V01State {
&mut self,
tx: &PublicTransaction,
) -> Result<(), NssaError> {
let state_diff = tx.validate_and_compute_post_states(self)?;
let state_diff = tx.validate_and_produce_public_state_diff(self)?;
for (address, post) in state_diff.into_iter() {
let current_account = self.get_account_by_address_mut(address);
*current_account = post;
// The invoked program claims the accounts with default program id.
if current_account.program_owner == DEFAULT_PROGRAM_ID {
current_account.program_owner = tx.message().program_id;
}
}
for address in tx.signer_addresses() {
let current_account = self.get_account_by_address_mut(address);
current_account.nonce += 1;
}
Ok(())
}
pub fn transition_from_privacy_preserving_transaction(
&mut self,
tx: &PrivacyPreservingTransaction,
) -> Result<(), NssaError> {
// 1. Verify the transaction satisfies acceptance criteria
let public_state_diff = tx.validate_and_produce_public_state_diff(self)?;
let message = tx.message();
// 2. Add new commitments
self.private_state.0.extend(&message.new_commitments);
// 3. Add new nullifiers
let new_nullifiers = message
.new_nullifiers
.iter()
.cloned()
.map(|(nullifier, _)| nullifier)
.collect::<Vec<Nullifier>>();
self.private_state.1.extend(new_nullifiers);
// 4. Update public accounts
for (address, post) in public_state_diff.into_iter() {
let current_account = self.get_account_by_address_mut(address);
*current_account = post;
}
// // 5. Increment nonces
for address in tx.signer_addresses() {
let current_account = self.get_account_by_address_mut(address);
current_account.nonce += 1;
@ -72,18 +164,78 @@ impl V01State {
pub(crate) fn builtin_programs(&self) -> &HashMap<ProgramId, Program> {
&self.builtin_programs
}
pub fn commitment_set_digest(&self) -> CommitmentSetDigest {
self.private_state.0.digest()
}
pub(crate) fn check_commitments_are_new(
&self,
new_commitments: &[Commitment],
) -> Result<(), NssaError> {
for commitment in new_commitments.iter() {
if self.private_state.0.contains(commitment) {
return Err(NssaError::InvalidInput(
"Commitment already seen".to_string(),
));
}
}
Ok(())
}
pub(crate) fn check_nullifiers_are_valid(
&self,
new_nullifiers: &[(Nullifier, CommitmentSetDigest)],
) -> Result<(), NssaError> {
for (nullifier, digest) in new_nullifiers.iter() {
if self.private_state.1.contains(nullifier) {
return Err(NssaError::InvalidInput(
"Nullifier already seen".to_string(),
));
}
if !self.private_state.0.root_history.contains(digest) {
return Err(NssaError::InvalidInput(
"Unrecognized commitment set digest".to_string(),
));
}
}
Ok(())
}
pub(crate) fn check_commitment_set_digest_is_valid(
&self,
commitment_set_digest: &CommitmentSetDigest,
) -> bool {
self.private_state
.0
.root_history
.contains(commitment_set_digest)
}
}
#[cfg(test)]
mod tests {
pub mod tests {
use std::collections::HashMap;
use crate::{
Address, PublicKey, PublicTransaction, V01State, error::NssaError, program::Program,
public_transaction, signature::PrivateKey,
Address, PublicKey, PublicTransaction, V01State,
error::NssaError,
privacy_preserving_transaction::{
Message, PrivacyPreservingTransaction, WitnessSet, circuit,
},
program::Program,
public_transaction,
signature::PrivateKey,
};
use nssa_core::account::Account;
use nssa_core::{
IncomingViewingPublicKey,
account::{
Account, AccountWithMetadata, Commitment, Nonce, Nullifier, NullifierPublicKey,
NullifierSecretKey,
},
};
use program_methods::AUTHENTICATED_TRANSFER_ID;
fn transfer_transaction(
from: Address,
@ -331,6 +483,12 @@ mod tests {
self.force_insert_account(Address::new([252; 32]), account);
self
}
pub fn with_private_account(mut self, keys: &TestPrivateKeys, account: &Account) -> Self {
let commitment = Commitment::new(&keys.npk(), &account);
self.private_state.0.extend(&[commitment]);
self
}
}
#[test]
@ -570,4 +728,352 @@ mod tests {
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
pub struct TestPublicKeys {
pub signing_key: PrivateKey,
}
impl TestPublicKeys {
pub fn address(&self) -> Address {
Address::from(&PublicKey::new_from_private_key(&self.signing_key))
}
}
fn test_public_account_keys_1() -> TestPublicKeys {
TestPublicKeys {
signing_key: PrivateKey::try_new([37; 32]).unwrap(),
}
}
pub struct TestPrivateKeys {
pub nsk: NullifierSecretKey,
pub isk: [u8; 32],
}
impl TestPrivateKeys {
pub fn npk(&self) -> NullifierPublicKey {
NullifierPublicKey::from(&self.nsk)
}
pub fn ivk(&self) -> IncomingViewingPublicKey {
IncomingViewingPublicKey::from_scalar(self.isk)
}
}
pub fn test_private_account_keys_1() -> TestPrivateKeys {
TestPrivateKeys {
nsk: [13; 32],
isk: [31; 32],
}
}
pub fn test_private_account_keys_2() -> TestPrivateKeys {
TestPrivateKeys {
nsk: [38; 32],
isk: [83; 32],
}
}
fn shielded_balance_transfer_for_tests(
sender_keys: &TestPublicKeys,
recipient_keys: &TestPrivateKeys,
balance_to_move: u128,
state: &V01State,
) -> PrivacyPreservingTransaction {
let esk = [3; 32];
let sender = AccountWithMetadata {
account: state.get_account_by_address(&sender_keys.address()),
is_authorized: true,
};
let sender_nonce = sender.account.nonce;
let recipient = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
let (output, proof) = circuit::execute_and_prove(
&[sender, recipient],
&Program::serialize_instruction(balance_to_move).unwrap(),
&[0, 2],
&[0xdeadbeef],
&[(recipient_keys.npk(), recipient_keys.ivk(), esk)],
&[],
&Program::authenticated_transfer_program(),
)
.unwrap();
let message = Message::new(
vec![sender_keys.address()],
vec![sender_nonce],
output.public_post_states,
output.encrypted_private_post_states,
output.new_commitments.clone(),
output.new_nullifiers,
);
let witness_set = WitnessSet::for_message(&message, proof, &[&sender_keys.signing_key]);
PrivacyPreservingTransaction::new(message, witness_set)
}
fn private_balance_transfer_for_tests(
sender_keys: &TestPrivateKeys,
sender_private_account: &Account,
recipient_keys: &TestPrivateKeys,
balance_to_move: u128,
new_nonces: [Nonce; 2],
state: &V01State,
) -> PrivacyPreservingTransaction {
let program = Program::authenticated_transfer_program();
let sender_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account);
let sender_pre = AccountWithMetadata {
account: sender_private_account.clone(),
is_authorized: true,
};
let recipient_pre = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
let (output, proof) = circuit::execute_and_prove(
&[sender_pre, recipient_pre],
&Program::serialize_instruction(balance_to_move).unwrap(),
&[1, 2],
&new_nonces,
&[
(sender_keys.npk(), sender_keys.ivk(), [3; 32]),
(recipient_keys.npk(), recipient_keys.ivk(), [4; 32]),
],
&[(
sender_keys.nsk,
state
.private_state
.0
.get_proof_for(&sender_commitment)
.unwrap(),
)],
&program,
)
.unwrap();
let message = Message::new(
vec![],
vec![],
output.public_post_states,
output.encrypted_private_post_states,
output.new_commitments.clone(),
output.new_nullifiers,
);
let witness_set = WitnessSet::for_message(&message, proof, &[]);
PrivacyPreservingTransaction::new(message, witness_set)
}
fn deshielded_balance_transfer_for_tests(
sender_keys: &TestPrivateKeys,
sender_private_account: &Account,
recipient_address: &Address,
balance_to_move: u128,
new_nonce: Nonce,
state: &V01State,
) -> PrivacyPreservingTransaction {
let program = Program::authenticated_transfer_program();
let sender_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account);
let sender_pre = AccountWithMetadata {
account: sender_private_account.clone(),
is_authorized: true,
};
let recipient_pre = AccountWithMetadata {
account: state.get_account_by_address(recipient_address),
is_authorized: false,
};
let (output, proof) = circuit::execute_and_prove(
&[sender_pre, recipient_pre],
&Program::serialize_instruction(balance_to_move).unwrap(),
&[1, 0],
&[new_nonce],
&[(sender_keys.npk(), sender_keys.ivk(), [3; 32])],
&[(
sender_keys.nsk,
state
.private_state
.0
.get_proof_for(&sender_commitment)
.unwrap(),
)],
&program,
)
.unwrap();
let message = Message::new(
vec![recipient_address.clone()],
vec![],
output.public_post_states,
output.encrypted_private_post_states,
output.new_commitments.clone(),
output.new_nullifiers,
);
let witness_set = WitnessSet::for_message(&message, proof, &[]);
PrivacyPreservingTransaction::new(message, witness_set)
}
#[test]
fn test_transition_from_privacy_preserving_transaction_shielded() {
let sender_keys = test_public_account_keys_1();
let recipient_keys = test_private_account_keys_1();
let mut state = V01State::new_with_genesis_accounts(&[(sender_keys.address(), 200)]);
let balance_to_move = 37;
let tx = shielded_balance_transfer_for_tests(
&sender_keys,
&recipient_keys,
balance_to_move,
&state,
);
let [expected_new_commitment] = tx.message().new_commitments.clone().try_into().unwrap();
assert!(!state.private_state.0.contains(&expected_new_commitment));
state
.transition_from_privacy_preserving_transaction(&tx)
.unwrap();
assert!(state.private_state.0.contains(&expected_new_commitment));
assert_eq!(
state.get_account_by_address(&sender_keys.address()).balance,
200 - balance_to_move
);
}
#[test]
fn test_transition_from_privacy_preserving_transaction_private() {
let sender_keys = test_private_account_keys_1();
let sender_private_account = Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 100,
nonce: 0xdeadbeef,
data: vec![],
};
let recipient_keys = test_private_account_keys_2();
let mut state = V01State::new_with_genesis_accounts(&[])
.with_private_account(&sender_keys, &sender_private_account);
let balance_to_move = 37;
let tx = private_balance_transfer_for_tests(
&sender_keys,
&sender_private_account,
&recipient_keys,
balance_to_move,
[0xcafecafe, 0xfecafeca],
&state,
);
let expected_new_commitment_1 = Commitment::new(
&sender_keys.npk(),
&Account {
program_owner: Program::authenticated_transfer_program().id(),
nonce: 0xcafecafe,
balance: sender_private_account.balance - balance_to_move,
data: vec![],
},
);
let sender_pre_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account);
let expected_new_nullifier = Nullifier::new(&sender_pre_commitment, &sender_keys.nsk);
let expected_new_commitment_2 = Commitment::new(
&recipient_keys.npk(),
&Account {
program_owner: Program::authenticated_transfer_program().id(),
nonce: 0xfecafeca,
balance: balance_to_move,
..Account::default()
},
);
let previous_public_state = state.public_state.clone();
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(!state.private_state.0.contains(&expected_new_commitment_1));
assert!(!state.private_state.0.contains(&expected_new_commitment_2));
assert!(!state.private_state.1.contains(&expected_new_nullifier));
state
.transition_from_privacy_preserving_transaction(&tx)
.unwrap();
assert_eq!(state.public_state, previous_public_state);
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(state.private_state.0.contains(&expected_new_commitment_1));
assert!(state.private_state.0.contains(&expected_new_commitment_2));
assert!(state.private_state.1.contains(&expected_new_nullifier));
}
#[test]
fn test_transition_from_privacy_preserving_transaction_deshielded() {
let sender_keys = test_private_account_keys_1();
let sender_private_account = Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 100,
nonce: 0xdeadbeef,
data: vec![],
};
let recipient_keys = test_public_account_keys_1();
let recipient_initial_balance = 400;
let mut state = V01State::new_with_genesis_accounts(&[(
recipient_keys.address(),
recipient_initial_balance,
)])
.with_private_account(&sender_keys, &sender_private_account);
let balance_to_move = 37;
let tx = deshielded_balance_transfer_for_tests(
&sender_keys,
&sender_private_account,
&recipient_keys.address(),
balance_to_move,
0xcafecafe,
&state,
);
let expected_new_commitment = Commitment::new(
&sender_keys.npk(),
&Account {
program_owner: Program::authenticated_transfer_program().id(),
nonce: 0xcafecafe,
balance: sender_private_account.balance - balance_to_move,
data: vec![],
},
);
let sender_pre_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account);
let expected_new_nullifier = Nullifier::new(&sender_pre_commitment, &sender_keys.nsk);
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(!state.private_state.0.contains(&expected_new_commitment));
assert!(!state.private_state.1.contains(&expected_new_nullifier));
state
.transition_from_privacy_preserving_transaction(&tx)
.unwrap();
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(state.private_state.0.contains(&expected_new_commitment));
assert!(state.private_state.1.contains(&expected_new_nullifier));
assert_eq!(
state
.get_account_by_address(&recipient_keys.address())
.balance,
recipient_initial_balance + balance_to_move
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,19 +1,18 @@
use nssa_core::program::read_nssa_inputs;
use risc0_zkvm::guest::env;
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
type Instruction = ();
fn main() {
let (input_accounts, _) = read_nssa_inputs::<Instruction>();
let ProgramInput { pre_states, .. } = read_nssa_inputs::<Instruction>();
let [pre] = match input_accounts.try_into() {
let [pre] = match pre_states.try_into() {
Ok(array) => array,
Err(_) => return,
};
let account_pre = pre.account;
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]);
write_nssa_outputs(vec![pre], vec![account_post]);
}

View File

@ -1,12 +1,14 @@
use nssa_core::program::read_nssa_inputs;
use risc0_zkvm::guest::env;
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
type Instruction = u128;
fn main() {
let (input_accounts, balance) = read_nssa_inputs::<Instruction>();
let ProgramInput {
pre_states,
instruction: balance,
} = read_nssa_inputs::<Instruction>();
let [sender_pre, receiver_pre] = match input_accounts.try_into() {
let [sender_pre, receiver_pre] = match pre_states.try_into() {
Ok(array) => array,
Err(_) => return,
};
@ -16,5 +18,8 @@ fn main() {
sender_post.balance -= balance;
receiver_post.balance += balance;
env::commit(&vec![sender_post, receiver_post]);
write_nssa_outputs(
vec![sender_pre, receiver_pre],
vec![sender_post, receiver_post],
);
}

View File

@ -23,15 +23,7 @@ impl SequecerChainStore {
) -> Self {
let init_accs: Vec<(Address, u128)> = initial_accounts
.iter()
.map(|acc_data| {
(
hex::decode(acc_data.addr.clone())
.unwrap()
.try_into()
.unwrap(),
acc_data.balance,
)
})
.map(|acc_data| (acc_data.addr.parse().unwrap(), acc_data.balance))
.collect();
let state = nssa::V01State::new_with_genesis_accounts(&init_accs);