From 66b5efaacb0061ed7f232d27f089f1fa98c7108d Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 26 Aug 2025 14:53:02 -0300 Subject: [PATCH] file refactor --- nssa/core/Cargo.toml | 3 +- nssa/core/src/{account/mod.rs => account.rs} | 9 - nssa/core/src/account/commitment.rs | 34 --- nssa/core/src/circuit_io.rs | 93 +++++++ nssa/core/src/commitment.rs | 63 +++++ nssa/core/src/{account => }/encoding.rs | 52 +++- nssa/core/src/encryption/mod.rs | 77 ++++++ .../src/encryption/shared_key_derivation.rs | 56 ++++ nssa/core/src/lib.rs | 242 +----------------- nssa/core/src/{account => }/nullifier.rs | 6 +- nssa/core/src/program.rs | 3 - .../src/bin/privacy_preserving_circuit.rs | 14 +- nssa/src/merkle_tree/mod.rs | 1 - .../privacy_preserving_transaction/circuit.rs | 79 +++--- .../encoding.rs | 20 +- .../privacy_preserving_transaction/message.rs | 72 +----- .../src/privacy_preserving_transaction/mod.rs | 2 +- .../transaction.rs | 7 +- nssa/src/state.rs | 34 ++- 19 files changed, 424 insertions(+), 443 deletions(-) rename nssa/core/src/{account/mod.rs => account.rs} (85%) delete mode 100644 nssa/core/src/account/commitment.rs create mode 100644 nssa/core/src/circuit_io.rs create mode 100644 nssa/core/src/commitment.rs rename nssa/core/src/{account => }/encoding.rs (80%) create mode 100644 nssa/core/src/encryption/mod.rs create mode 100644 nssa/core/src/encryption/shared_key_derivation.rs rename nssa/core/src/{account => }/nullifier.rs (95%) diff --git a/nssa/core/Cargo.toml b/nssa/core/Cargo.toml index a68a3f2..97d7558 100644 --- a/nssa/core/Cargo.toml +++ b/nssa/core/Cargo.toml @@ -9,7 +9,8 @@ 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 = { version = "0.13.3", optional = true} [features] default = [] -host = ["thiserror", "bytemuck"] +host = ["thiserror", "bytemuck", "k256"] diff --git a/nssa/core/src/account/mod.rs b/nssa/core/src/account.rs similarity index 85% rename from nssa/core/src/account/mod.rs rename to nssa/core/src/account.rs index 6f5e017..4b2f51d 100644 --- a/nssa/core/src/account/mod.rs +++ b/nssa/core/src/account.rs @@ -1,16 +1,7 @@ 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` for r0 friendlinenss type Data = Vec; /// Account to be used both in public and private contexts diff --git a/nssa/core/src/account/commitment.rs b/nssa/core/src/account/commitment.rs deleted file mode 100644 index 487789f..0000000 --- a/nssa/core/src/account/commitment.rs +++ /dev/null @@ -1,34 +0,0 @@ -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()) - } -} diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs new file mode 100644 index 0000000..f16d010 --- /dev/null +++ b/nssa/core/src/circuit_io.rs @@ -0,0 +1,93 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + Commitment, CommitmentSetDigest, MembershipProof, Nullifier, NullifierPublicKey, + NullifierSecretKey, SharedSecretKey, + account::{Account, AccountWithMetadata, Nonce}, + encryption::Ciphertext, + program::{ProgramId, ProgramOutput}, +}; + +#[derive(Serialize, Deserialize)] +pub struct PrivacyPreservingCircuitInput { + pub program_output: ProgramOutput, + pub visibility_mask: Vec, + pub private_account_nonces: Vec, + pub private_account_keys: Vec<(NullifierPublicKey, SharedSecretKey)>, + 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, + pub public_post_states: Vec, + pub ciphertexts: Vec, + pub new_commitments: Vec, + pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, +} + +#[cfg(feature = "host")] +impl PrivacyPreservingCircuitOutput { + pub fn to_bytes(&self) -> Vec { + bytemuck::cast_slice(&risc0_zkvm::serde::to_vec(&self).unwrap()).to_vec() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + Commitment, Nullifier, NullifierPublicKey, + account::{Account, AccountWithMetadata}, + }; + use risc0_zkvm::serde::from_slice; + + #[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, + }], + ciphertexts: vec![Ciphertext(vec![255, 255, 1, 1, 2, 2])], + 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); + } +} diff --git a/nssa/core/src/commitment.rs b/nssa/core/src/commitment.rs new file mode 100644 index 0000000..a4a4efb --- /dev/null +++ b/nssa/core/src/commitment.rs @@ -0,0 +1,63 @@ +use risc0_zkvm::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()) + } +} + +pub type CommitmentSetDigest = [u8; 32]; + +pub type MembershipProof = (usize, Vec<[u8; 32]>); + +pub fn compute_digest_for_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 +} diff --git a/nssa/core/src/account/encoding.rs b/nssa/core/src/encoding.rs similarity index 80% rename from nssa/core/src/account/encoding.rs rename to nssa/core/src/encoding.rs index 5b1dd0e..0bf2383 100644 --- a/nssa/core/src/account/encoding.rs +++ b/nssa/core/src/encoding.rs @@ -1,17 +1,25 @@ // 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}; +use crate::account::Account; + +#[cfg(feature = "host")] +use crate::encryption::shared_key_derivation::Secp256k1Point; + +use crate::encryption::Ciphertext; + #[cfg(feature = "host")] use crate::error::NssaCoreError; +use crate::Commitment; +#[cfg(feature = "host")] +use crate::Nullifier; +use crate::NullifierPublicKey; + impl Account { pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); @@ -93,6 +101,42 @@ impl Nullifier { } } +impl Ciphertext { + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + let ciphertext_length: u32 = self.0.len() as u32; + bytes.extend_from_slice(&ciphertext_length.to_le_bytes()); + bytes.extend_from_slice(&self.0); + + bytes + } + + #[cfg(feature = "host")] + pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + 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)?; + + Ok(Self(ciphertext)) + } +} + +#[cfg(feature = "host")] +impl Secp256k1Point { + pub fn to_bytes(&self) -> [u8; 33] { + self.0.clone().try_into().unwrap() + } + + pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + let mut value = vec![0; 33]; + cursor.read_exact(&mut value)?; + Ok(Self(value)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/nssa/core/src/encryption/mod.rs b/nssa/core/src/encryption/mod.rs new file mode 100644 index 0000000..8fc45bd --- /dev/null +++ b/nssa/core/src/encryption/mod.rs @@ -0,0 +1,77 @@ +use chacha20::{ + ChaCha20, + cipher::{KeyIvInit, StreamCipher}, +}; +use risc0_zkvm::sha::{Impl, Sha256}; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "host")] +pub(crate) mod shared_key_derivation; + +#[cfg(feature = "host")] +pub use shared_key_derivation::{EphemeralPublicKey, EphemeralSecretKey, IncomingViewingPublicKey}; + +use crate::{Commitment, NullifierPublicKey, account::Account}; + +#[derive(Serialize, Deserialize, Clone)] +pub struct SharedSecretKey([u8; 32]); + +pub struct EncryptionScheme; + +#[derive(Serialize, Deserialize)] +#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, PartialEq, Eq))] +pub struct Ciphertext(pub(crate) Vec); + +impl EncryptionScheme { + pub fn encrypt( + account: &Account, + shared_secret: &SharedSecretKey, + commitment: &Commitment, + output_index: u32, + ) -> Ciphertext { + let mut buffer = account.to_bytes().to_vec(); + Self::symmetric_transform(&mut buffer, shared_secret, commitment, output_index); + Ciphertext(buffer) + } + + fn symmetric_transform( + buffer: &mut [u8], + shared_secret: &SharedSecretKey, + commitment: &Commitment, + output_index: u32, + ) { + let key = Self::kdf(shared_secret, commitment, output_index); + let mut cipher = ChaCha20::new(&key.into(), &[0; 12].into()); + cipher.apply_keystream(buffer); + } + + fn kdf( + shared_secret: &SharedSecretKey, + commitment: &Commitment, + output_index: u32, + ) -> [u8; 32] { + let mut bytes = Vec::new(); + + bytes.extend_from_slice(b"NSSA/v0.1/KDF-SHA256"); + bytes.extend_from_slice(&shared_secret.0); + bytes.extend_from_slice(&commitment.to_byte_array()); + bytes.extend_from_slice(&output_index.to_le_bytes()); + + Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap() + } + + #[cfg(feature = "host")] + pub fn decrypt( + ciphertext: &Ciphertext, + shared_secret: &SharedSecretKey, + commitment: &Commitment, + output_index: u32, + ) -> Option { + use std::io::Cursor; + let mut buffer = ciphertext.0.to_owned(); + Self::symmetric_transform(&mut buffer, shared_secret, commitment, output_index); + + let mut cursor = Cursor::new(buffer.as_slice()); + Account::from_cursor(&mut cursor).ok() + } +} diff --git a/nssa/core/src/encryption/shared_key_derivation.rs b/nssa/core/src/encryption/shared_key_derivation.rs new file mode 100644 index 0000000..c735105 --- /dev/null +++ b/nssa/core/src/encryption/shared_key_derivation.rs @@ -0,0 +1,56 @@ +use serde::{Deserialize, Serialize}; + +use k256::{ + AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint, Scalar, + elliptic_curve::{ + PrimeField, + sec1::{FromEncodedPoint, ToEncodedPoint}, + }, +}; + +use crate::SharedSecretKey; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct Secp256k1Point(pub(crate) Vec); + +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()) + } +} + +pub type EphemeralSecretKey = [u8; 32]; +pub type EphemeralPublicKey = Secp256k1Point; +pub type IncomingViewingPublicKey = Secp256k1Point; +impl From<&EphemeralSecretKey> for EphemeralPublicKey { + fn from(value: &EphemeralSecretKey) -> Self { + Secp256k1Point::from_scalar(*value) + } +} + +impl SharedSecretKey { + pub fn new(scalar: &[u8; 32], point: &Secp256k1Point) -> Self { + let scalar = Scalar::from_repr((*scalar).into()).unwrap(); + let point: [u8; 33] = point.0.clone().try_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); + + Self(x_bytes) + } +} diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index 89dbeac..2275e31 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -1,237 +1,15 @@ -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; +mod circuit_io; +mod commitment; +mod encoding; +pub mod encryption; +mod nullifier; pub mod program; +pub use circuit_io::{PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput}; +pub use commitment::{Commitment, CommitmentSetDigest, MembershipProof, compute_digest_for_path}; +pub use encryption::{EncryptionScheme, SharedSecretKey}; +pub use nullifier::{Nullifier, NullifierPublicKey, NullifierSecretKey}; + #[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 SharedSecretKey = [u8; 32]; - -#[derive(Serialize, Deserialize)] -#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, PartialEq, Eq))] -pub struct Ciphertext(Vec); - -impl Ciphertext { - #[cfg(feature = "host")] - pub fn decrypt( - self, - shared_secret: &[u8; 32], - npk: &NullifierPublicKey, - commitment: &Commitment, - output_index: u32, - ) -> Option { - let key = Self::kdf(&shared_secret, npk, commitment, output_index); - let mut cipher = ChaCha20::new(&key.into(), &[0; 12].into()); - let mut buffer = self.0; - - cipher.apply_keystream(&mut buffer); - let mut cursor = Cursor::new(buffer.as_slice()); - Account::from_cursor(&mut cursor).ok() - } - - pub fn new( - account: &Account, - shared_secret: &[u8; 32], - npk: &NullifierPublicKey, - commitment: &Commitment, - output_index: u32, - ) -> Self { - let mut buffer = account.to_bytes().to_vec(); - - let key = Self::kdf(shared_secret, npk, commitment, output_index); - let mut cipher = ChaCha20::new(&key.into(), &[0; 12].into()); - cipher.apply_keystream(&mut buffer); - - Self(buffer) - } - - pub fn kdf( - ss_bytes: &[u8; 32], - npk: &NullifierPublicKey, - commitment: &Commitment, - 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(&npk.to_byte_array()); - bytes.extend_from_slice(&commitment.to_byte_array()); - bytes.extend_from_slice(&output_index.to_le_bytes()); - - Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap() - } - - #[cfg(feature = "host")] - pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { - 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)?; - - Ok(Self(ciphertext)) - } -} - -impl Ciphertext { - pub fn to_bytes(&self) -> Vec { - let mut bytes = Vec::new(); - let ciphertext_length: u32 = self.0.len() as u32; - bytes.extend_from_slice(&ciphertext_length.to_le_bytes()); - bytes.extend_from_slice(&self.0); - - bytes - } -} - -#[derive(Serialize, Deserialize)] -pub struct PrivacyPreservingCircuitInput { - pub program_output: ProgramOutput, - pub visibility_mask: Vec, - pub private_account_nonces: Vec, - pub private_account_keys: Vec<(NullifierPublicKey, SharedSecretKey)>, - 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, - pub public_post_states: Vec, - pub ciphertexts: Vec, - pub new_commitments: Vec, - pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, -} - -#[cfg(feature = "host")] -impl PrivacyPreservingCircuitOutput { - pub fn to_bytes(&self) -> Vec { - 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::{ - Ciphertext, PrivacyPreservingCircuitOutput, - 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, - }], - ciphertexts: vec![Ciphertext(vec![255, 255, 1, 1, 2, 2])], - 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_ciphertext_to_bytes_roundtrip() { - let data = Ciphertext(vec![255, 255, 1, 1, 2, 2]); - - let bytes = data.to_bytes(); - let mut cursor = Cursor::new(bytes.as_slice()); - let data_from_cursor = Ciphertext::from_cursor(&mut cursor).unwrap(); - assert_eq!(data, data_from_cursor); - } -} diff --git a/nssa/core/src/account/nullifier.rs b/nssa/core/src/nullifier.rs similarity index 95% rename from nssa/core/src/account/nullifier.rs rename to nssa/core/src/nullifier.rs index 8faef0b..346a815 100644 --- a/nssa/core/src/account/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -1,7 +1,7 @@ use risc0_zkvm::sha::{Impl, Sha256}; use serde::{Deserialize, Serialize}; -use crate::account::Commitment; +use crate::Commitment; #[derive(Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr(any(feature = "host", test), derive(Debug, Clone, Hash))] @@ -58,11 +58,11 @@ mod tests { 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([ + 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); + assert_eq!(Npk, expected_npk); } } diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index f84bd61..d284bbc 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -3,9 +3,6 @@ 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; pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8]; diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index fb4a41b..d43f4d3 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -1,10 +1,12 @@ use risc0_zkvm::{guest::env, serde::to_vec}; use nssa_core::{ - account::{Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey}, - compute_root_associated_to_path, + account::{Account, AccountWithMetadata}, + compute_digest_for_path, + encryption::Ciphertext, program::{validate_execution, ProgramOutput, DEFAULT_PROGRAM_ID}, - Ciphertext, CommitmentSetDigest, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, + Commitment, CommitmentSetDigest, EncryptionScheme, Nullifier, NullifierPublicKey, + PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, }; fn main() { @@ -82,8 +84,7 @@ fn main() { // 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); + let set_digest = compute_digest_for_path(&commitment_pre, membership_proof); // Check pre_state authorization if !pre_states[i].is_authorized { @@ -116,10 +117,9 @@ fn main() { let commitment_post = Commitment::new(Npk, &post_with_updated_values); // Encrypt and push post state - let encrypted_account = Ciphertext::new( + let encrypted_account = EncryptionScheme::encrypt( &post_with_updated_values, shared_secret, - Npk, &commitment_post, output_index, ); diff --git a/nssa/src/merkle_tree/mod.rs b/nssa/src/merkle_tree/mod.rs index 4b0b06f..a242379 100644 --- a/nssa/src/merkle_tree/mod.rs +++ b/nssa/src/merkle_tree/mod.rs @@ -181,7 +181,6 @@ fn prev_power_of_two(x: usize) -> usize { #[cfg(test)] mod tests { use hex_literal::hex; - use nssa_core::account::{Account, NullifierPublicKey}; use super::*; diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 49dd8f3..ad4b03c 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -1,7 +1,7 @@ use nssa_core::{ - CommitmentSetDigest, MembershipProof, PrivacyPreservingCircuitInput, - PrivacyPreservingCircuitOutput, SharedSecretKey, - account::{Account, AccountWithMetadata, Nonce, NullifierPublicKey, NullifierSecretKey}, + CommitmentSetDigest, MembershipProof, NullifierPublicKey, NullifierSecretKey, + PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey, + account::{Account, AccountWithMetadata, Nonce}, program::{InstructionData, ProgramId, ProgramOutput}, }; use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover}; @@ -91,11 +91,9 @@ fn execute_and_prove_program( #[cfg(test)] mod tests { use nssa_core::{ - Ciphertext, - account::{ - Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey, - NullifierSecretKey, - }, + Commitment, EncryptionScheme, Nullifier, NullifierPublicKey, NullifierSecretKey, + account::{Account, AccountWithMetadata}, + encryption::EphemeralPublicKey, }; use risc0_zkvm::{InnerReceipt, Journal, Receipt}; @@ -104,7 +102,7 @@ mod tests { merkle_tree::MerkleTree, privacy_preserving_transaction::{ circuit::{Proof, execute_and_prove}, - message::{EncryptedAccountData, EphemeralPublicKey}, + message::EncryptedAccountData, }, program::Program, state::{ @@ -153,8 +151,7 @@ mod tests { let recipient_keys = test_private_account_keys_1(); let esk = [3; 32]; - let shared_secret = - EncryptedAccountData::compute_shared_secret(&esk, &recipient_keys.ivk()); + let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk()); let epk = EphemeralPublicKey::from_scalar(esk); let (output, proof) = execute_and_prove( @@ -162,7 +159,7 @@ mod tests { &Program::serialize_instruction(balance_to_move).unwrap(), &[0, 2], &[0xdeadbeef], - &[(recipient_keys.npk(), shared_secret)], + &[(recipient_keys.npk(), shared_secret.clone())], &[], &Program::authenticated_transfer_program(), ) @@ -178,15 +175,13 @@ mod tests { assert_eq!(output.new_nullifiers.len(), 0); assert_eq!(output.ciphertexts.len(), 1); - let recipient_post = output.ciphertexts[0] - .clone() - .decrypt( - &shared_secret, - &recipient_keys.npk(), - &output.new_commitments[0], - 0, - ) - .unwrap(); + let recipient_post = EncryptionScheme::decrypt( + &output.ciphertexts[0], + &shared_secret, + &output.new_commitments[0], + 0, + ) + .unwrap(); assert_eq!(recipient_post, expected_recipient_post); } @@ -238,13 +233,11 @@ mod tests { ]; let esk_1 = [3; 32]; - let shared_secret_1 = - EncryptedAccountData::compute_shared_secret(&esk_1, &sender_keys.ivk()); + let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk()); let epk_1 = EphemeralPublicKey::from_scalar(esk_1); let esk_2 = [5; 32]; - let shared_secret_2 = - EncryptedAccountData::compute_shared_secret(&esk_2, &recipient_keys.ivk()); + let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk()); let epk_2 = EphemeralPublicKey::from_scalar(esk_2); let (output, proof) = execute_and_prove( @@ -253,8 +246,8 @@ mod tests { &[1, 2], &[0xdeadbeef1, 0xdeadbeef2], &[ - (sender_keys.npk(), shared_secret_1), - (recipient_keys.npk(), shared_secret_2), + (sender_keys.npk(), shared_secret_1.clone()), + (recipient_keys.npk(), shared_secret_2.clone()), ], &[( sender_keys.nsk, @@ -271,26 +264,22 @@ mod tests { assert_eq!(output.new_nullifiers, expected_new_nullifiers); assert_eq!(output.ciphertexts.len(), 2); - let sender_post = output.ciphertexts[0] - .clone() - .decrypt( - &shared_secret_1, - &sender_keys.npk(), - &expected_new_commitments[0], - 0, - ) - .unwrap(); + let sender_post = EncryptionScheme::decrypt( + &output.ciphertexts[0], + &shared_secret_1, + &expected_new_commitments[0], + 0, + ) + .unwrap(); assert_eq!(sender_post, expected_private_account_1); - let recipient_post = output.ciphertexts[1] - .clone() - .decrypt( - &shared_secret_2, - &recipient_keys.npk(), - &expected_new_commitments[1], - 1, - ) - .unwrap(); + let recipient_post = EncryptionScheme::decrypt( + &output.ciphertexts[1], + &shared_secret_2, + &expected_new_commitments[1], + 1, + ) + .unwrap(); assert_eq!(recipient_post, expected_private_account_2); } } diff --git a/nssa/src/privacy_preserving_transaction/encoding.rs b/nssa/src/privacy_preserving_transaction/encoding.rs index fd818e2..bfaf4ff 100644 --- a/nssa/src/privacy_preserving_transaction/encoding.rs +++ b/nssa/src/privacy_preserving_transaction/encoding.rs @@ -1,18 +1,13 @@ -// TODO: Consider switching to deriving Borsh - use std::io::{Cursor, Read}; use nssa_core::{ - Ciphertext, - account::{Account, Commitment, Nullifier}, + Commitment, Nullifier, + account::Account, + encryption::{Ciphertext, EphemeralPublicKey}, }; use crate::{ - Address, - error::NssaError, - privacy_preserving_transaction::message::{ - EncryptedAccountData, EphemeralPublicKey, Secp256k1Point, - }, + Address, error::NssaError, privacy_preserving_transaction::message::EncryptedAccountData, }; use super::message::Message; @@ -23,17 +18,14 @@ const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"\x01/NSSA/ impl EncryptedAccountData { pub(crate) fn to_bytes(&self) -> Vec { let mut bytes = self.ciphertext.to_bytes(); - bytes.extend_from_slice(&self.epk.0); + bytes.extend_from_slice(&self.epk.to_bytes()); bytes.push(self.view_tag); bytes } pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { let ciphertext = Ciphertext::from_cursor(cursor)?; - - let mut epk_bytes = vec![0; 33]; - cursor.read_exact(&mut epk_bytes)?; - let epk = Secp256k1Point(epk_bytes); + let epk = EphemeralPublicKey::from_cursor(cursor)?; let mut tag_bytes = [0; 1]; cursor.read_exact(&mut tag_bytes)?; diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index a56ed7d..b2f8247 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -1,44 +1,14 @@ use std::io::Cursor; -use k256::{ - AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint, PublicKey, Scalar, - elliptic_curve::{ - PrimeField, - sec1::{FromEncodedPoint, ToEncodedPoint}, - }, -}; use nssa_core::{ - Ciphertext, CommitmentSetDigest, PrivacyPreservingCircuitOutput, SharedSecretKey, - account::{Account, Commitment, Nonce, Nullifier, NullifierPublicKey}, + Commitment, CommitmentSetDigest, Nullifier, PrivacyPreservingCircuitOutput, + account::{Account, Nonce}, + encryption::{Ciphertext, EphemeralPublicKey}, }; use serde::{Deserialize, Serialize}; use crate::{Address, error::NssaError}; -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -pub struct Secp256k1Point(pub(crate) Vec); -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()) - } -} - -pub type EphemeralSecretKey = [u8; 32]; -pub type EphemeralPublicKey = Secp256k1Point; -pub type IncomingViewingPublicKey = Secp256k1Point; -impl From<&EphemeralSecretKey> for EphemeralPublicKey { - fn from(value: &EphemeralSecretKey) -> Self { - Secp256k1Point::from_scalar(*value) - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct EncryptedAccountData { pub(crate) ciphertext: Ciphertext, @@ -46,38 +16,6 @@ pub struct EncryptedAccountData { pub(crate) view_tag: u8, } -impl EncryptedAccountData { - pub fn decrypt( - self, - isk: &[u8; 32], - epk: &EphemeralPublicKey, - npk: &NullifierPublicKey, - commitment: &Commitment, - output_index: u32, - ) -> Option { - let shared_secret = Self::compute_shared_secret(isk, &epk); - self.ciphertext.decrypt(&shared_secret, npk, commitment, output_index) - } - - pub fn compute_shared_secret(scalar: &[u8; 32], point: &Secp256k1Point) -> SharedSecretKey { - let scalar = Scalar::from_repr((*scalar).into()).unwrap(); - let point: [u8; 33] = point.0.clone().try_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 - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct Message { pub(crate) public_addresses: Vec
, @@ -126,9 +64,7 @@ impl Message { pub mod tests { use std::io::Cursor; - use nssa_core::account::{ - Account, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, - }; + use nssa_core::{account::Account, Commitment, Nullifier, NullifierPublicKey}; use crate::{Address, privacy_preserving_transaction::message::Message}; diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index d773395..448a488 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -5,6 +5,6 @@ mod witness_set; pub mod circuit; -pub use message::{Message, IncomingViewingPublicKey, EphemeralPublicKey, EphemeralSecretKey, EncryptedAccountData}; +pub use message::{Message, EncryptedAccountData}; pub use transaction::PrivacyPreservingTransaction; pub use witness_set::WitnessSet; diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index adb0c66..632aec9 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -1,7 +1,10 @@ use std::collections::{HashMap, HashSet}; -use nssa_core::account::{Account, AccountWithMetadata, Commitment, Nullifier}; -use nssa_core::{Ciphertext, CommitmentSetDigest, PrivacyPreservingCircuitOutput}; +use nssa_core::{ + Commitment, CommitmentSetDigest, Nullifier, PrivacyPreservingCircuitOutput, + account::{Account, AccountWithMetadata}, + encryption::Ciphertext, +}; use crate::error::NssaError; use crate::privacy_preserving_transaction::circuit::Proof; diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 35f9719..81d8746 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -4,9 +4,7 @@ use crate::{ public_transaction::PublicTransaction, }; use nssa_core::{ - CommitmentSetDigest, MembershipProof, - account::{Account, Commitment, Nullifier}, - program::{DEFAULT_PROGRAM_ID, ProgramId}, + account::Account, program::{ProgramId, DEFAULT_PROGRAM_ID}, Commitment, CommitmentSetDigest, MembershipProof, Nullifier }; use std::collections::{HashMap, HashSet}; @@ -222,19 +220,17 @@ pub mod tests { Address, PublicKey, PublicTransaction, V01State, error::NssaError, privacy_preserving_transaction::{ - EncryptedAccountData, EphemeralPublicKey, IncomingViewingPublicKey, Message, - PrivacyPreservingTransaction, WitnessSet, circuit, + EncryptedAccountData, Message, PrivacyPreservingTransaction, WitnessSet, circuit, }, program::Program, public_transaction, signature::PrivateKey, }; + use nssa_core::{ - Ciphertext, account::{ - Account, AccountWithMetadata, Commitment, Nonce, Nullifier, NullifierPublicKey, - NullifierSecretKey, - }, + Account, AccountWithMetadata, Nonce, + }, encryption::{EphemeralPublicKey, IncomingViewingPublicKey}, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey }; use program_methods::AUTHENTICATED_TRANSFER_ID; @@ -794,8 +790,7 @@ pub mod tests { }; let esk = [3; 32]; - let shared_secret = - EncryptedAccountData::compute_shared_secret(&esk, &recipient_keys.ivk()); + let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk()); let epk = EphemeralPublicKey::from_scalar(esk); let (output, proof) = circuit::execute_and_prove( @@ -814,7 +809,8 @@ pub mod tests { vec![sender_nonce], vec![epk], output, - ).unwrap(); + ) + .unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[&sender_keys.signing_key]); PrivacyPreservingTransaction::new(message, witness_set) @@ -840,13 +836,11 @@ pub mod tests { }; let esk_1 = [3; 32]; - let shared_secret_1 = - EncryptedAccountData::compute_shared_secret(&esk_1, &sender_keys.ivk()); + let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk()); let epk_1 = EphemeralPublicKey::from_scalar(esk_1); let esk_2 = [3; 32]; - let shared_secret_2 = - EncryptedAccountData::compute_shared_secret(&esk_2, &recipient_keys.ivk()); + let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk()); let epk_2 = EphemeralPublicKey::from_scalar(esk_2); let (output, proof) = circuit::execute_and_prove( @@ -870,7 +864,8 @@ pub mod tests { ) .unwrap(); - let message = Message::try_from_circuit_output(vec![], vec![], vec![epk_1, epk_2], output).unwrap(); + let message = + Message::try_from_circuit_output(vec![], vec![], vec![epk_1, epk_2], output).unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[]); @@ -897,7 +892,7 @@ pub mod tests { }; let esk = [3; 32]; - let shared_secret = EncryptedAccountData::compute_shared_secret(&esk, &sender_keys.ivk()); + let shared_secret = SharedSecretKey::new(&esk, &sender_keys.ivk()); let epk = EphemeralPublicKey::from_scalar(esk); let (output, proof) = circuit::execute_and_prove( @@ -923,7 +918,8 @@ pub mod tests { vec![], vec![epk], output, - ).unwrap(); + ) + .unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[]);