add encodings

This commit is contained in:
Sergio Chouhy 2025-08-18 14:28:26 -03:00
parent 1e1ab787bc
commit a185e52203
15 changed files with 366 additions and 29 deletions

View File

@ -6,7 +6,7 @@ edition = "2024"
[dependencies]
thiserror = "2.0.12"
risc0-zkvm = "2.3.1"
nssa-core = { path = "core" }
nssa-core = { path = "core", features=["host"]}
program-methods = { path = "program_methods" }
serde = "1.0.219"
sha2 = "0.10.9"

View File

@ -6,3 +6,8 @@ edition = "2024"
[dependencies]
risc0-zkvm = "2.3.1"
serde = { version = "1.0", default-features = false }
thiserror = { version = "2.0.12", optional = true }
[features]
default = []
host = ["thiserror"]

View File

@ -13,7 +13,21 @@ impl Commitment {
pub fn new(Npk: &NullifierPublicKey, account: &Account) -> Self {
let mut bytes = Vec::new();
bytes.extend_from_slice(&Npk.to_byte_array());
bytes.extend_from_slice(&account.to_bytes());
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

@ -1,9 +1,14 @@
use risc0_zkvm::{
serde::to_vec,
sha::{Impl, Sha256},
};
use risc0_zkvm::sha::{Impl, Sha256};
use crate::account::{Account, Commitment, NullifierPublicKey};
#[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> {
@ -13,28 +18,82 @@ impl Account {
}
bytes.extend_from_slice(&self.balance.to_le_bytes());
bytes.extend_from_slice(&self.nonce.to_le_bytes());
let hashed_data: [u8; 32] = Impl::hash_bytes(&self.data).as_bytes().try_into().unwrap();
bytes.extend_from_slice(&hashed_data);
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(crate) fn to_byte_array(&self) -> [u8; 32] {
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
}
}
impl NullifierPublicKey {
pub(crate) fn to_byte_array(&self) -> [u8; 32] {
#[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 crate::account::Account;
use super::*;
#[test]
fn test_enconding() {
@ -49,12 +108,64 @@ mod tests {
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, 11, 137, 65, 102, 211, 51, 100, 53, 200,
0, 190, 163, 111, 242, 27, 41, 234, 168, 1, 165, 47, 88, 76, 0, 108, 73, 40, 154, 13,
207, 110, 47,
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

@ -23,7 +23,7 @@ impl From<&NullifierSecretKey> for NullifierPublicKey {
pub type NullifierSecretKey = [u8; 32];
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub struct Nullifier([u8; 32]);
pub struct Nullifier(pub(super) [u8; 32]);
impl Nullifier {
pub fn new(commitment: &Commitment, nsk: &NullifierSecretKey) -> Self {
@ -53,12 +53,12 @@ mod tests {
#[test]
fn test_from_secret_key() {
let nsk = [
50, 139, 109, 225, 82, 86, 80, 108, 140, 248, 232, 229, 96, 80, 148, 250, 15, 9, 155,
44, 196, 224, 115, 180, 160, 44, 113, 133, 15, 196, 253, 42,
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([
38, 90, 215, 216, 195, 66, 157, 77, 161, 59, 121, 18, 118, 37, 57, 199, 189, 251, 95,
130, 12, 9, 171, 169, 140, 221, 87, 242, 46, 243, 111, 85,
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("Invalid transaction: {0}")]
DeserializationError(String),
#[error("IO error: {0}")]
Io(#[from] io::Error),
}

View File

@ -1,5 +1,7 @@
use serde::{Deserialize, Serialize};
#[cfg(feature = "host")]
use crate::error::NssaCoreError;
use crate::{
account::{
Account, AccountWithMetadata, Commitment, Nonce, Nullifier, NullifierPublicKey,
@ -8,9 +10,15 @@ use crate::{
program::{ProgramId, ProgramOutput},
};
#[cfg(feature = "host")]
use std::io::Cursor;
pub mod account;
pub mod program;
#[cfg(feature = "host")]
pub mod error;
pub type CommitmentSetDigest = [u32; 8];
pub type MembershipProof = Vec<[u8; 32]>;
pub fn verify_membership_proof(
@ -51,6 +59,18 @@ impl EncryptedAccountData {
// TODO: implement
Self
}
#[cfg(feature = "host")]
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
todo!()
}
}
impl EncryptedAccountData {
pub fn to_bytes(&self) -> Vec<u8> {
// TODO: implement
vec![0]
}
}
#[derive(Serialize, Deserialize)]

View File

@ -31,4 +31,9 @@ pub enum NssaError {
#[error("Risc0 error: {0}")]
ProgramProveFailed(String),
#[error("Invalid transaction: {0}")]
TransactionDeserializationError(String),
#[error("Core error")]
Core(#[from] nssa_core::error::NssaCoreError),
}

View File

@ -0,0 +1,140 @@
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 = 37;
const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] =
b"NSSA/v0.1/TxMessage/PrivacyPreserving";
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;
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;
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;
for nullifier in &self.new_nullifiers {
bytes.extend_from_slice(&nullifier.to_byte_array());
}
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
};
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 {
new_nullifiers.push(Nullifier::from_cursor(cursor)?);
}
Ok(Self {
public_addresses,
nonces,
public_post_states,
encrypted_private_post_states,
new_commitments,
new_nullifiers,
})
}
}

View File

@ -36,7 +36,7 @@ impl Message {
}
#[cfg(test)]
mod tests {
pub mod tests {
use nssa_core::account::{
Account, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey,
};

View File

@ -1,5 +1,6 @@
mod transaction;
mod message;
mod witness_set;
mod encoding;
pub use transaction::PrivacyPreservingTransaction;

View File

@ -146,3 +146,8 @@ fn n_unique<T: Eq + Hash>(data: &[T]) -> usize {
let set: HashSet<&T> = data.iter().collect();
set.len()
}
#[cfg(test)]
mod tests {
}

View File

@ -9,7 +9,7 @@ pub struct WitnessSet {
}
impl WitnessSet {
pub fn for_message(message: &Message, private_keys: &[&PrivateKey]) -> Self {
pub fn for_message(message: &Message, proof: Proof, private_keys: &[&PrivateKey]) -> Self {
todo!()
}

View File

@ -8,8 +8,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 = 37;
const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] =
b"NSSA/v0.1/TxMessage/Public\0\0\0\0\0\0\0\0\0\0\0";
impl Message {
/// Serializes a `Message` into bytes in the following layout:
@ -52,7 +53,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

@ -21,6 +21,10 @@ impl CommitmentSet {
// TODO: implement
[0; 8]
}
fn contains(&self, commitment: &Commitment) -> bool {
self.0.contains(commitment)
}
}
type NullifierSet = HashSet<Nullifier>;
@ -139,14 +143,28 @@ impl V01State {
&self,
new_commitments: &[Commitment],
) -> Result<(), NssaError> {
todo!()
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_new(
&self,
new_nullifiers: &[Nullifier],
) -> Result<(), NssaError> {
todo!()
for nullifier in new_nullifiers.iter() {
if self.private_state.1.contains(nullifier) {
return Err(NssaError::InvalidInput(
"Nullifier already seen".to_string(),
));
}
}
Ok(())
}
}