From d623812c2455508697b306ef985e43a1bae53af7 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 25 Aug 2025 14:51:46 -0300 Subject: [PATCH] remove shared secrete derivation from r0 --- nssa/Cargo.toml | 1 + nssa/core/Cargo.toml | 1 - nssa/core/src/lib.rs | 168 +++++------------ nssa/program_methods/guest/Cargo.lock | 175 ------------------ nssa/program_methods/guest/Cargo.toml | 4 - .../src/bin/privacy_preserving_circuit.rs | 21 ++- .../privacy_preserving_transaction/circuit.rs | 54 ++++-- .../encoding.rs | 37 +++- .../privacy_preserving_transaction/message.rs | 128 ++++++++++--- .../src/privacy_preserving_transaction/mod.rs | 2 +- .../transaction.rs | 9 +- nssa/src/state.rs | 63 ++++--- 12 files changed, 271 insertions(+), 392 deletions(-) diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index c050c65..aad78af 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -11,6 +11,7 @@ program-methods = { path = "program_methods" } serde = "1.0.219" sha2 = "0.10.9" secp256k1 = "0.31.1" +k256 = "0.13.3" rand = "0.8" borsh = "1.5.7" bytemuck = "1.13" diff --git a/nssa/core/Cargo.toml b/nssa/core/Cargo.toml index de7e4d6..a68a3f2 100644 --- a/nssa/core/Cargo.toml +++ b/nssa/core/Cargo.toml @@ -9,7 +9,6 @@ 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 = [] diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index e523f6f..2856d18 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -2,6 +2,7 @@ use chacha20::{ ChaCha20, cipher::{KeyIvInit, StreamCipher}, }; + use risc0_zkvm::{ serde::to_vec, sha::{Impl, Sha256}, @@ -25,14 +26,6 @@ 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; @@ -66,56 +59,23 @@ pub fn compute_root_associated_to_path( 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); -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 SharedSecretKey = [u8; 32]; #[derive(Serialize, Deserialize)] #[cfg_attr(any(feature = "host", test), derive(Debug, Clone, PartialEq, Eq))] -pub struct EncryptedAccountData { - ciphertext: Vec, - epk: EphemeralPublicKey, - view_tag: u8, -} +pub struct Ciphertext(Vec); -impl EncryptedAccountData { +impl Ciphertext { #[cfg(feature = "host")] - pub fn decrypt(self, isk: &[u8; 32], output_index: u32) -> Option { - let ss_bytes = Self::ecdh(isk, &self.epk.0.clone().try_into().unwrap()); - let ipk = IncomingViewingPublicKey::from_scalar(*isk); - + pub fn decrypt(self, shared_secret: &[u8; 32], output_index: u32) -> Option { let key = Self::kdf( - ss_bytes, - &self.epk, - &ipk, + &shared_secret, + // &ipk, // &commitment.to_byte_array(), output_index, ); let mut cipher = ChaCha20::new(&key.into(), &[0; 12].into()); - let mut buffer = self.ciphertext; + let mut buffer = self.0; cipher.apply_keystream(&mut buffer); let mut cursor = Cursor::new(buffer.as_slice()); @@ -124,71 +84,45 @@ impl EncryptedAccountData { pub fn new( account: &Account, - // commitment: &Commitment, - esk: &EphemeralSecretKey, - npk: &NullifierPublicKey, - ipk: &IncomingViewingPublicKey, + shared_secret: &[u8; 32], + // 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, + shared_secret, + // 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, - } + // let view_tag = Self::view_tag(&npk, &ipk); + Self(buffer) } pub fn kdf( - ss_bytes: [u8; 32], - epk: &EphemeralPublicKey, - ipk: &IncomingViewingPublicKey, + 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(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 { let mut u32_bytes = [0; 4]; @@ -198,33 +132,24 @@ impl EncryptedAccountData { 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 epk_bytes = vec![0; 33]; + // cursor.read_exact(&mut epk_bytes)?; + // + // let mut tag_bytes = [0; 1]; + // cursor.read_exact(&mut tag_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 + Ok(Self(ciphertext)) } } -impl EncryptedAccountData { +impl Ciphertext { pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); - let ciphertext_length: u32 = self.ciphertext.len() as u32; + let ciphertext_length: u32 = self.0.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.extend_from_slice(&self.0); + // bytes.extend_from_slice(&self.epk.0); + // bytes.push(self.view_tag); bytes } @@ -237,8 +162,9 @@ pub struct PrivacyPreservingCircuitInput { pub private_account_nonces: Vec, pub private_account_keys: Vec<( NullifierPublicKey, - IncomingViewingPublicKey, - EphemeralSecretKey, + SharedSecretKey, + // IncomingViewingPublicKey, + // EphemeralSecretKey, )>, pub private_account_auth: Vec<(NullifierSecretKey, MembershipProof)>, pub program_id: ProgramId, @@ -249,7 +175,7 @@ pub struct PrivacyPreservingCircuitInput { pub struct PrivacyPreservingCircuitOutput { pub public_pre_states: Vec, pub public_post_states: Vec, - pub encrypted_private_post_states: Vec, + pub ciphertexts: Vec, pub new_commitments: Vec, pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, } @@ -268,7 +194,7 @@ mod tests { use risc0_zkvm::serde::from_slice; use crate::{ - EncryptedAccountData, EphemeralPublicKey, PrivacyPreservingCircuitOutput, Secp256k1Point, + Ciphertext, PrivacyPreservingCircuitOutput, account::{Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey}, }; @@ -301,11 +227,10 @@ mod tests { 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, - }], + ciphertexts: vec![ + 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(), @@ -324,15 +249,12 @@ mod tests { } #[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, - }; + 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 = EncryptedAccountData::from_cursor(&mut cursor).unwrap(); + let data_from_cursor = Ciphertext::from_cursor(&mut cursor).unwrap(); assert_eq!(data, data_from_cursor); } } diff --git a/nssa/program_methods/guest/Cargo.lock b/nssa/program_methods/guest/Cargo.lock index 380583f..b37fcb9 100644 --- a/nssa/program_methods/guest/Cargo.lock +++ b/nssa/program_methods/guest/Cargo.lock @@ -297,24 +297,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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" - [[package]] name = "bincode" version = "1.3.3" @@ -568,18 +556,6 @@ 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" @@ -625,16 +601,6 @@ dependencies = [ "syn 2.0.104", ] -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "zeroize", -] - [[package]] name = "derivative" version = "2.2.0" @@ -764,20 +730,6 @@ dependencies = [ "proc-macro-error", ] -[[package]] -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" version = "0.6.0" @@ -802,25 +754,6 @@ 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" @@ -884,16 +817,6 @@ 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" @@ -1006,7 +929,6 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -1042,17 +964,6 @@ 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.14.5" @@ -1104,15 +1015,6 @@ 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" @@ -1403,20 +1305,6 @@ 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" @@ -1588,7 +1476,6 @@ name = "nssa-core" version = "0.1.0" dependencies = [ "chacha20", - "k256", "risc0-zkvm", "serde", ] @@ -1675,16 +1562,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "postcard" version = "1.1.3" @@ -1903,9 +1780,6 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", -] [[package]] name = "rand_core" @@ -1998,16 +1872,6 @@ 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" @@ -2329,20 +2193,6 @@ dependencies = [ "yaml-rust2", ] -[[package]] -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]] name = "semver" version = "1.0.26" @@ -2422,16 +2272,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - [[package]] name = "slab" version = "0.4.10" @@ -2470,16 +2310,6 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "stability" version = "0.2.1" @@ -3394,8 +3224,3 @@ 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" diff --git a/nssa/program_methods/guest/Cargo.toml b/nssa/program_methods/guest/Cargo.toml index 79a0c1a..4b377c8 100644 --- a/nssa/program_methods/guest/Cargo.toml +++ b/nssa/program_methods/guest/Cargo.toml @@ -8,7 +8,3 @@ edition = "2021" [dependencies] risc0-zkvm = { version = "2.3.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" } - 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 51108af..44071f4 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -4,8 +4,7 @@ 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, + Ciphertext, CommitmentSetDigest, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, }; fn main() { @@ -42,7 +41,7 @@ fn main() { // and will be populated next. let mut public_pre_states: Vec = Vec::new(); let mut public_post_states: Vec = Vec::new(); - let mut encrypted_private_post_states: Vec = Vec::new(); + let mut ciphertexts: Vec = Vec::new(); let mut new_commitments: Vec = Vec::new(); let mut new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)> = Vec::new(); @@ -67,7 +66,8 @@ fn main() { } 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"); + // let (Npk, Ipk, esk) = private_keys_iter.next().expect("Missing keys"); + let (Npk, shared_secret) = private_keys_iter.next().expect("Missing keys"); if visibility_mask[i] == 1 { // Private account with authentication @@ -116,17 +116,18 @@ fn main() { let commitment_post = Commitment::new(Npk, &post_with_updated_values); // Encrypt and push post state - let encrypted_account = EncryptedAccountData::new( + let encrypted_account = Ciphertext::new( &post_with_updated_values, + shared_secret, // &commitment_post, - esk, - Npk, - Ipk, + // esk, + // Npk, + // Ipk, output_index, ); new_commitments.push(commitment_post); - encrypted_private_post_states.push(encrypted_account); + ciphertexts.push(encrypted_account); output_index += 1; } _ => panic!("Invalid visibility mask value"), @@ -148,7 +149,7 @@ fn main() { let output = PrivacyPreservingCircuitOutput { public_pre_states, public_post_states, - encrypted_private_post_states, + ciphertexts, new_commitments, new_nullifiers, }; diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 031790a..01eb144 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -1,6 +1,6 @@ use nssa_core::{ - CommitmentSetDigest, EphemeralSecretKey, IncomingViewingPublicKey, MembershipProof, - PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, + CommitmentSetDigest, MembershipProof, PrivacyPreservingCircuitInput, + PrivacyPreservingCircuitOutput, SharedSecretKey, account::{Account, AccountWithMetadata, Nonce, NullifierPublicKey, NullifierSecretKey}, program::{InstructionData, ProgramId, ProgramOutput}, }; @@ -28,8 +28,9 @@ pub fn execute_and_prove( private_account_nonces: &[u128], private_account_keys: &[( NullifierPublicKey, - IncomingViewingPublicKey, - EphemeralSecretKey, + SharedSecretKey, + // IncomingViewingPublicKey, + // EphemeralSecretKey, )], private_account_auth: &[(NullifierSecretKey, MembershipProof)], program: &Program, @@ -90,7 +91,7 @@ fn execute_and_prove_program( #[cfg(test)] mod tests { use nssa_core::{ - EncryptedAccountData, + Ciphertext, account::{ Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, @@ -101,7 +102,10 @@ mod tests { use crate::{ Address, V01State, merkle_tree::MerkleTree, - privacy_preserving_transaction::circuit::{Proof, execute_and_prove}, + privacy_preserving_transaction::{ + circuit::{Proof, execute_and_prove}, + message::{EncryptedAccountData, EphemeralPublicKey}, + }, program::Program, state::{ CommitmentSet, @@ -147,12 +151,18 @@ mod tests { let expected_sender_pre = sender.clone(); let recipient_keys = test_private_account_keys_1(); + + let esk = [3; 32]; + let shared_secret = + EncryptedAccountData::compute_shared_secret(&esk, &recipient_keys.ivk()); + let epk = EphemeralPublicKey::from_scalar(esk); + 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])], + &[(recipient_keys.npk(), shared_secret)], &[], &Program::authenticated_transfer_program(), ) @@ -166,11 +176,11 @@ mod tests { 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); + assert_eq!(output.ciphertexts.len(), 1); - let recipient_post = output.encrypted_private_post_states[0] + let recipient_post = output.ciphertexts[0] .clone() - .decrypt(&recipient_keys.isk, 0) + .decrypt(&shared_secret, 0) .unwrap(); assert_eq!(recipient_post, expected_recipient_post); } @@ -222,14 +232,24 @@ mod tests { Commitment::new(&recipient_keys.npk(), &expected_private_account_2), ]; + let esk_1 = [3; 32]; + let shared_secret_1 = + EncryptedAccountData::compute_shared_secret(&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 epk_2 = EphemeralPublicKey::from_scalar(esk_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.npk(), shared_secret_1), + (recipient_keys.npk(), shared_secret_2), ], &[( sender_keys.nsk, @@ -244,17 +264,17 @@ mod tests { 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); + assert_eq!(output.ciphertexts.len(), 2); - let recipient_post_1 = output.encrypted_private_post_states[0] + let recipient_post_1 = output.ciphertexts[0] .clone() - .decrypt(&sender_keys.isk, 0) + .decrypt(&shared_secret_1, 0) .unwrap(); assert_eq!(recipient_post_1, expected_private_account_1); - let recipient_post_2 = output.encrypted_private_post_states[1] + let recipient_post_2 = output.ciphertexts[1] .clone() - .decrypt(&recipient_keys.isk, 1) + .decrypt(&shared_secret_2, 1) .unwrap(); assert_eq!(recipient_post_2, expected_private_account_2); } diff --git a/nssa/src/privacy_preserving_transaction/encoding.rs b/nssa/src/privacy_preserving_transaction/encoding.rs index 0ab2db8..fd818e2 100644 --- a/nssa/src/privacy_preserving_transaction/encoding.rs +++ b/nssa/src/privacy_preserving_transaction/encoding.rs @@ -3,17 +3,50 @@ use std::io::{Cursor, Read}; use nssa_core::{ - EncryptedAccountData, + Ciphertext, account::{Account, Commitment, Nullifier}, }; -use crate::{Address, error::NssaError}; +use crate::{ + Address, + error::NssaError, + privacy_preserving_transaction::message::{ + EncryptedAccountData, EphemeralPublicKey, Secp256k1Point, + }, +}; use super::message::Message; const MESSAGE_ENCODING_PREFIX_LEN: usize = 22; const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"\x01/NSSA/v0.1/TxMessage/"; +impl EncryptedAccountData { + pub(crate) fn to_bytes(&self) -> Vec { + let mut bytes = self.ciphertext.to_bytes(); + bytes.extend_from_slice(&self.epk.0); + 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 mut tag_bytes = [0; 1]; + cursor.read_exact(&mut tag_bytes)?; + let view_tag = tag_bytes[0]; + + Ok(Self { + ciphertext, + epk, + view_tag, + }) + } +} + impl Message { pub(crate) fn to_bytes(&self) -> Vec { let mut bytes = MESSAGE_ENCODING_PREFIX.to_vec(); diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index b4de4a7..8d31bc9 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -1,9 +1,80 @@ +use std::io::Cursor; + +use k256::{ + AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint, PublicKey, Scalar, + elliptic_curve::{ + PrimeField, + sec1::{FromEncodedPoint, ToEncodedPoint}, + }, +}; use nssa_core::{ - CommitmentSetDigest, EncryptedAccountData, + Ciphertext, CommitmentSetDigest, PrivacyPreservingCircuitOutput, SharedSecretKey, account::{Account, Commitment, Nonce, Nullifier}, }; +use serde::{Deserialize, Serialize}; -use crate::Address; +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, + pub(crate) epk: EphemeralPublicKey, + pub(crate) view_tag: u8, +} + +impl EncryptedAccountData { + pub fn decrypt( + self, + isk: &[u8; 32], + epk: &EphemeralPublicKey, + output_index: u32, + ) -> Option { + let shared_secret = Self::compute_shared_secret(isk, &epk); + self.ciphertext.decrypt(&shared_secret, 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 { @@ -16,22 +87,36 @@ pub struct Message { } impl Message { - pub fn new( + pub fn try_from_circuit_output( public_addresses: Vec
, nonces: Vec, - public_post_states: Vec, - encrypted_private_post_states: Vec, - new_commitments: Vec, - new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, - ) -> Self { - Self { + ephemeral_public_keys: Vec, + output: PrivacyPreservingCircuitOutput, + ) -> Result { + if ephemeral_public_keys.len() != output.ciphertexts.len() { + return Err(NssaError::InvalidInput( + "Ephemeral public keys and ciphertexts length mismatch".into(), + )); + } + + let encrypted_private_post_states = output + .ciphertexts + .into_iter() + .zip(ephemeral_public_keys) + .map(|(ciphertext, epk)| EncryptedAccountData { + ciphertext, + epk, + view_tag: 0, // TODO: implement + }) + .collect(); + Ok(Self { public_addresses, nonces, - public_post_states, + public_post_states: output.public_post_states, encrypted_private_post_states, - new_commitments, - new_nullifiers, - } + new_commitments: output.new_commitments, + new_nullifiers: output.new_nullifiers, + }) } } @@ -78,23 +163,6 @@ pub mod tests { } } - #[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(); diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index 1d36118..d773395 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; +pub use message::{Message, IncomingViewingPublicKey, EphemeralPublicKey, EphemeralSecretKey, 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 49f3d8d..adb0c66 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -1,10 +1,11 @@ use std::collections::{HashMap, HashSet}; use nssa_core::account::{Account, AccountWithMetadata, Commitment, Nullifier}; -use nssa_core::{CommitmentSetDigest, EncryptedAccountData, PrivacyPreservingCircuitOutput}; +use nssa_core::{Ciphertext, CommitmentSetDigest, PrivacyPreservingCircuitOutput}; use crate::error::NssaError; use crate::privacy_preserving_transaction::circuit::Proof; +use crate::privacy_preserving_transaction::message::EncryptedAccountData; use crate::{Address, V01State}; use super::message::Message; @@ -145,7 +146,11 @@ fn check_privacy_preserving_circuit_proof_is_valid( 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(), + ciphertexts: encrypted_private_post_states + .iter() + .cloned() + .map(|value| value.ciphertext) + .collect(), new_commitments: new_commitments.to_vec(), new_nullifiers: new_nullifiers.to_vec(), }; diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 3ac1e15..35f9719 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -222,14 +222,15 @@ pub mod tests { Address, PublicKey, PublicTransaction, V01State, error::NssaError, privacy_preserving_transaction::{ - Message, PrivacyPreservingTransaction, WitnessSet, circuit, + EncryptedAccountData, EphemeralPublicKey, IncomingViewingPublicKey, Message, + PrivacyPreservingTransaction, WitnessSet, circuit, }, program::Program, public_transaction, signature::PrivateKey, }; use nssa_core::{ - IncomingViewingPublicKey, + Ciphertext, account::{ Account, AccountWithMetadata, Commitment, Nonce, Nullifier, NullifierPublicKey, NullifierSecretKey, @@ -780,11 +781,11 @@ pub mod tests { 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 { @@ -792,25 +793,28 @@ pub mod tests { is_authorized: false, }; + let esk = [3; 32]; + let shared_secret = + EncryptedAccountData::compute_shared_secret(&esk, &recipient_keys.ivk()); + let epk = EphemeralPublicKey::from_scalar(esk); + 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)], + &[(recipient_keys.npk(), shared_secret)], &[], &Program::authenticated_transfer_program(), ) .unwrap(); - let message = Message::new( + let message = Message::try_from_circuit_output( vec![sender_keys.address()], vec![sender_nonce], - output.public_post_states, - output.encrypted_private_post_states, - output.new_commitments.clone(), - output.new_nullifiers, - ); + vec![epk], + output, + ).unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[&sender_keys.signing_key]); PrivacyPreservingTransaction::new(message, witness_set) @@ -835,14 +839,24 @@ pub mod tests { is_authorized: false, }; + let esk_1 = [3; 32]; + let shared_secret_1 = + EncryptedAccountData::compute_shared_secret(&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 epk_2 = EphemeralPublicKey::from_scalar(esk_2); + 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.npk(), shared_secret_1), + (recipient_keys.npk(), shared_secret_2), ], &[( sender_keys.nsk, @@ -856,14 +870,7 @@ pub mod tests { ) .unwrap(); - let message = Message::new( - vec![], - vec![], - output.public_post_states, - output.encrypted_private_post_states, - output.new_commitments.clone(), - output.new_nullifiers, - ); + let message = Message::try_from_circuit_output(vec![], vec![], vec![epk_1, epk_2], output).unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[]); @@ -889,12 +896,16 @@ pub mod tests { is_authorized: false, }; + let esk = [3; 32]; + let shared_secret = EncryptedAccountData::compute_shared_secret(&esk, &sender_keys.ivk()); + let epk = EphemeralPublicKey::from_scalar(esk); + 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.npk(), shared_secret)], &[( sender_keys.nsk, state @@ -907,14 +918,12 @@ pub mod tests { ) .unwrap(); - let message = Message::new( + let message = Message::try_from_circuit_output( vec![recipient_address.clone()], vec![], - output.public_post_states, - output.encrypted_private_post_states, - output.new_commitments.clone(), - output.new_nullifiers, - ); + vec![epk], + output, + ).unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[]);