From ebe616247f0a63a1b1374861a5292c5c82a06ae1 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Thu, 4 Sep 2025 17:49:55 +0300 Subject: [PATCH 01/11] fix: seed generation from mnemonic --- Cargo.toml | 1 + key_protocol/Cargo.toml | 1 + key_protocol/src/key_management/secret_holders.rs | 11 ++++++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2b98867..5f2ee32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ ark-bn254 = "0.5.0" ark-ff = "0.5.0" tiny-keccak = { version = "2.0.2", features = ["keccak"] } base64 = "0.22.1" +bip39 = "2.2.0" rocksdb = { version = "0.21.0", default-features = false, features = [ "snappy", diff --git a/key_protocol/Cargo.toml b/key_protocol/Cargo.toml index 947bd7a..8b9701a 100644 --- a/key_protocol/Cargo.toml +++ b/key_protocol/Cargo.toml @@ -15,6 +15,7 @@ elliptic-curve.workspace = true hex.workspace = true aes-gcm.workspace = true lazy_static.workspace = true +bip39.workspace = true [dependencies.common] path = "../common" diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 47342ec..4fc1a63 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -1,3 +1,4 @@ +use bip39::Mnemonic; use common::merkle_tree_public::TreeHashType; use elliptic_curve::PrimeField; use k256::{AffinePoint, FieldBytes, Scalar}; @@ -29,12 +30,16 @@ pub struct UTXOSecretKeyHolder { impl SeedHolder { pub fn new_os_random() -> Self { - let mut bytes = FieldBytes::default(); + let mut enthopy_bytes: [u8; 32] = [0; 32]; + OsRng.fill_bytes(&mut enthopy_bytes); - OsRng.fill_bytes(&mut bytes); + let mnemonic = Mnemonic::from_entropy(&enthopy_bytes).unwrap(); + let seed = mnemonic.to_seed(""); + + let field_bytes = FieldBytes::from_slice(&seed); Self { - seed: Scalar::from_repr(bytes).unwrap(), + seed: Scalar::from_repr(*field_bytes).unwrap(), } } From 4a44b12384ccf2bfc01ca08c4da2a18feb8bd1c9 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 4 Sep 2025 12:44:22 -0300 Subject: [PATCH 02/11] wip --- .../guest/src/bin/authenticated_transfer.rs | 1 + nssa/program_methods/guest/src/bin/pinata.rs | 68 +++++++++++++++++++ nssa/src/program.rs | 9 ++- nssa/src/state.rs | 36 ++++++++++ sequencer_core/src/sequencer_store/mod.rs | 6 +- sequencer_runner/src/lib.rs | 4 +- 6 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 nssa/program_methods/guest/src/bin/pinata.rs diff --git a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs index 9e7f399..89b84fd 100644 --- a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs @@ -34,3 +34,4 @@ fn main() { write_nssa_outputs(vec![sender, receiver], vec![sender_post, receiver_post]); } + diff --git a/nssa/program_methods/guest/src/bin/pinata.rs b/nssa/program_methods/guest/src/bin/pinata.rs new file mode 100644 index 0000000..43b0190 --- /dev/null +++ b/nssa/program_methods/guest/src/bin/pinata.rs @@ -0,0 +1,68 @@ +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput}; +use risc0_zkvm::sha::{Impl, Sha256}; + +const PRIZE: u128 = 150; + +type Instruction = u128; + +struct Challenge { + difficulty: u8, + seed: [u8; 32], +} + +impl Challenge { + fn new(bytes: &[u8]) -> Self { + assert_eq!(bytes.len(), 33); + let difficulty = bytes[0]; + assert!(difficulty <= 32); + + let mut seed = [0; 32]; + seed.copy_from_slice(&bytes[1..]); + Self { difficulty, seed } + } + + fn is_nonce_valid(&self, nonce: Instruction) -> bool { + let mut bytes = [0; 32 + 16]; + bytes.copy_from_slice(&self.seed); + bytes[32..].copy_from_slice(&nonce.to_le_bytes()); + let digest: [u8; 32] = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap(); + let difficulty = self.difficulty as usize; + digest[..difficulty].iter().all(|&b| b == 0) + } + + fn next_data(self) -> [u8; 33] { + let mut result = [0; 33]; + result[0] = self.difficulty; + result.copy_from_slice(Impl::hash_bytes(&self.seed).as_bytes()); + result + } +} + +/// A pinata program +fn main() { + // Read input accounts. + // It is expected to receive only two accounts: [pinata_account, winner_account] + let ProgramInput { + pre_states, + instruction: nonce, + } = read_nssa_inputs::(); + + let [pinata, winner] = match pre_states.try_into() { + Ok(array) => array, + Err(_) => return, + }; + + let data = Challenge::new(&pinata.account.data); + + if !data.is_nonce_valid(nonce) { + return; + } + + let mut pinata_post = pinata.account.clone(); + let mut winner_post = winner.account.clone(); + pinata_post.balance -= PRIZE; + pinata_post.data = data.next_data().to_vec(); + winner_post.balance += PRIZE; + + write_nssa_outputs(vec![pinata, winner], vec![pinata_post, winner_post]); +} diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 66358e9..63b7dc4 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -2,7 +2,7 @@ use nssa_core::{ account::{Account, AccountWithMetadata}, program::{InstructionData, ProgramId, ProgramOutput}, }; -use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID}; +use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID}; use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec}; use serde::Serialize; @@ -73,6 +73,13 @@ impl Program { elf: AUTHENTICATED_TRANSFER_ELF, } } + + pub fn pinata() -> Self { + Self { + id: PINATA_ID, + elf: PINATA_ELF + } + } } #[cfg(test)] diff --git a/nssa/src/state.rs b/nssa/src/state.rs index f975804..ba6c49b 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -8,6 +8,7 @@ use nssa_core::{ account::Account, program::{DEFAULT_PROGRAM_ID, ProgramId}, }; +use rand::{Rng, RngCore, rngs::OsRng}; use std::collections::{HashMap, HashSet}; pub(crate) struct CommitmentSet { @@ -85,6 +86,41 @@ impl V01State { this } + pub fn add_pinata_accounts(&mut self) { + self.insert_program(Program::pinata()); + + let mut rng = OsRng; + let mut seed = [0; 32]; + + rng.fill_bytes(&mut seed); + self.public_state.insert( + "6a79aee868a1c641ea895582af7ddd6f2da339e3091a67eddcbfdaa1b9010001" + .parse() + .unwrap(), + Account { + program_owner: Program::pinata().id(), + balance: 1500, + // Difficulty: 3 + data: std::iter::once(3).chain(seed).collect(), + nonce: 0, + }, + ); + + rng.fill_bytes(&mut seed); + self.public_state.insert( + "6a79aee868a1c641ea895582af7ddd6f2da339e3091a67eddcbfdaa1b9010002" + .parse() + .unwrap(), + Account { + program_owner: Program::pinata().id(), + balance: 1500, + // Difficulty: 4 + data: std::iter::once(4).chain(seed).collect(), + nonce: 0, + }, + ); + } + pub(crate) fn insert_program(&mut self, program: Program) { self.builtin_programs.insert(program.id(), program); } diff --git a/sequencer_core/src/sequencer_store/mod.rs b/sequencer_core/src/sequencer_store/mod.rs index 4254ed7..374e7d5 100644 --- a/sequencer_core/src/sequencer_store/mod.rs +++ b/sequencer_core/src/sequencer_store/mod.rs @@ -26,7 +26,11 @@ impl SequecerChainStore { .map(|acc_data| (acc_data.addr.parse().unwrap(), acc_data.balance)) .collect(); - let state = nssa::V01State::new_with_genesis_accounts(&init_accs); + let state = { + let mut this = nssa::V01State::new_with_genesis_accounts(&init_accs); + this.add_pinata_accounts(); + this + }; let mut data = [0; 32]; let mut prev_block_hash = [0; 32]; diff --git a/sequencer_runner/src/lib.rs b/sequencer_runner/src/lib.rs index 625350b..d7a574e 100644 --- a/sequencer_runner/src/lib.rs +++ b/sequencer_runner/src/lib.rs @@ -75,7 +75,9 @@ pub async fn main_runner() -> Result<()> { } //ToDo: Add restart on failures - let (_, _) = startup_sequencer(app_config).await?; + let (_, main_loop_handle) = startup_sequencer(app_config).await?; + + main_loop_handle.await??; Ok(()) } From 324f477b631aba96623d83d296a48ffed636ee56 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Fri, 5 Sep 2025 14:47:58 +0300 Subject: [PATCH 03/11] fix: keys corect generatin --- Cargo.toml | 1 + key_protocol/Cargo.toml | 1 + .../src/key_management/constants_types.rs | 22 --- .../key_management/ephemeral_key_holder.rs | 2 +- key_protocol/src/key_management/mod.rs | 86 +++++------ .../src/key_management/secret_holders.rs | 137 +++++++++++++----- key_protocol/src/key_management/types.rs | 8 + 7 files changed, 150 insertions(+), 107 deletions(-) delete mode 100644 key_protocol/src/key_management/constants_types.rs create mode 100644 key_protocol/src/key_management/types.rs diff --git a/Cargo.toml b/Cargo.toml index 5f2ee32..c21c9a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ ark-ff = "0.5.0" tiny-keccak = { version = "2.0.2", features = ["keccak"] } base64 = "0.22.1" bip39 = "2.2.0" +hmac-sha512 = "1.1.7" rocksdb = { version = "0.21.0", default-features = false, features = [ "snappy", diff --git a/key_protocol/Cargo.toml b/key_protocol/Cargo.toml index 8b9701a..d624505 100644 --- a/key_protocol/Cargo.toml +++ b/key_protocol/Cargo.toml @@ -16,6 +16,7 @@ hex.workspace = true aes-gcm.workspace = true lazy_static.workspace = true bip39.workspace = true +hmac-sha512.workspace = true [dependencies.common] path = "../common" diff --git a/key_protocol/src/key_management/constants_types.rs b/key_protocol/src/key_management/constants_types.rs deleted file mode 100644 index f4fca86..0000000 --- a/key_protocol/src/key_management/constants_types.rs +++ /dev/null @@ -1,22 +0,0 @@ -use elliptic_curve::{ - consts::{B0, B1}, - generic_array::GenericArray, -}; -use lazy_static::lazy_static; -use sha2::digest::typenum::{UInt, UTerm}; - -lazy_static! { - pub static ref NULLIFIER_SECRET_CONST: [u8; 32] = - hex::decode(std::env::var("NULLIFIER_SECRET_CONST").unwrap()) - .unwrap() - .try_into() - .unwrap(); - pub static ref VIEWING_SECRET_CONST: [u8; 32] = - hex::decode(std::env::var("VIEWING_SECRET_CONST").unwrap()) - .unwrap() - .try_into() - .unwrap(); -} - -pub type CipherText = Vec; -pub type Nonce = GenericArray, B1>, B0>, B0>>; diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index ecfb09e..6392678 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -5,7 +5,7 @@ use k256::{AffinePoint, FieldBytes, Scalar}; use log::info; use rand::{rngs::OsRng, RngCore}; -use super::constants_types::{CipherText, Nonce}; +use super::types::{CipherText, Nonce}; #[derive(Debug)] ///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender. diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index bc6d14c..fb75dff 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -1,29 +1,32 @@ use std::collections::HashMap; use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit}; -use constants_types::{CipherText, Nonce}; +use common::merkle_tree_public::TreeHashType; +use elliptic_curve::group::GroupEncoding; use elliptic_curve::point::AffineCoordinates; use k256::AffinePoint; use log::info; -use secret_holders::{SeedHolder, TopSecretKeyHolder, UTXOSecretKeyHolder}; +use secret_holders::{PrivateKeyHolder, SeedHolder, TopSecretKeyHolder}; use serde::{Deserialize, Serialize}; +use sha2::{digest::FixedOutput, Digest}; +use types::{CipherText, Nonce}; use crate::key_protocol_core::PublicKey; pub type PublicAccountSigningKey = [u8; 32]; -pub mod constants_types; pub mod ephemeral_key_holder; pub mod secret_holders; +pub mod types; #[derive(Serialize, Deserialize, Clone, Debug)] ///Entrypoint to key management pub struct KeyChain { top_secret_key_holder: TopSecretKeyHolder, - pub utxo_secret_key_holder: UTXOSecretKeyHolder, + pub private_key_holder: PrivateKeyHolder, ///Map for all users accounts pub_account_signing_keys: HashMap, - pub nullifer_public_key: PublicKey, - pub viewing_public_key: PublicKey, + pub nullifer_public_key: [u8; 32], + pub incoming_viewing_public_key: PublicKey, } impl KeyChain { @@ -33,16 +36,16 @@ impl KeyChain { let seed_holder = SeedHolder::new_os_random(); let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder(); + let private_key_holder = top_secret_key_holder.produce_private_key_holder(); - let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key(); - let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key(); + let nullifer_public_key = private_key_holder.generate_nullifier_public_key(); + let incoming_viewing_public_key = private_key_holder.generate_incoming_viewing_public_key(); Self { top_secret_key_holder, - utxo_secret_key_holder, + private_key_holder, nullifer_public_key, - viewing_public_key, + incoming_viewing_public_key, pub_account_signing_keys: HashMap::new(), } } @@ -53,20 +56,29 @@ impl KeyChain { let seed_holder = SeedHolder::new_os_random(); let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder(); + let private_key_holder = top_secret_key_holder.produce_private_key_holder(); - let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key(); - let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key(); + let nullifer_public_key = private_key_holder.generate_nullifier_public_key(); + let incoming_viewing_public_key = private_key_holder.generate_incoming_viewing_public_key(); Self { top_secret_key_holder, - utxo_secret_key_holder, + private_key_holder, nullifer_public_key, - viewing_public_key, + incoming_viewing_public_key, pub_account_signing_keys: accounts, } } + pub fn produce_user_address(&self) -> [u8; 32] { + let mut hasher = sha2::Sha256::new(); + + hasher.update(&self.nullifer_public_key); + hasher.update(&self.incoming_viewing_public_key.to_bytes()); + + ::from(hasher.finalize_fixed()) + } + pub fn generate_new_private_key(&mut self) -> nssa::Address { let private_key = nssa::PrivateKey::new_os_random(); let address = nssa::Address::from(&nssa::PublicKey::new_from_private_key(&private_key)); @@ -112,14 +124,18 @@ impl KeyChain { ); info!( "Nulifier secret key is {:?}", + hex::encode(serde_json::to_vec(&self.private_key_holder.nullifier_secret_key).unwrap()), + ); + info!( + "Viewing secret key is {:?}", hex::encode( - serde_json::to_vec(&self.utxo_secret_key_holder.nullifier_secret_key).unwrap() + serde_json::to_vec(&self.private_key_holder.incoming_viewing_secret_key).unwrap() ), ); info!( "Viewing secret key is {:?}", hex::encode( - serde_json::to_vec(&self.utxo_secret_key_holder.viewing_secret_key).unwrap() + serde_json::to_vec(&self.private_key_holder.outgoing_viewing_secret_key).unwrap() ), ); info!( @@ -128,7 +144,7 @@ impl KeyChain { ); info!( "Viewing public key is {:?}", - hex::encode(serde_json::to_vec(&self.viewing_public_key).unwrap()), + hex::encode(serde_json::to_vec(&self.incoming_viewing_public_key).unwrap()), ); } } @@ -139,12 +155,11 @@ mod tests { aead::{Aead, KeyInit, OsRng}, Aes256Gcm, }; - use constants_types::{CipherText, Nonce}; - use constants_types::{NULLIFIER_SECRET_CONST, VIEWING_SECRET_CONST}; use elliptic_curve::ff::Field; use elliptic_curve::group::prime::PrimeCurveAffine; use elliptic_curve::point::AffineCoordinates; use k256::{AffinePoint, ProjectivePoint, Scalar}; + use types::{CipherText, Nonce}; use crate::key_management::ephemeral_key_holder::EphemeralKeyHolder; @@ -156,11 +171,9 @@ mod tests { let address_key_holder = KeyChain::new_os_random(); // Check that key holder fields are initialized with expected types + assert_ne!(address_key_holder.nullifer_public_key, [0u8; 32]); assert!(!Into::::into( - address_key_holder.nullifer_public_key.is_identity() - )); - assert!(!Into::::into( - address_key_holder.viewing_public_key.is_identity() + address_key_holder.incoming_viewing_public_key.is_identity() )); } @@ -211,20 +224,6 @@ mod tests { assert_eq!(decrypted_data, plaintext); } - #[test] - fn test_new_os_random_initialization() { - // Ensure that KeyChain is initialized correctly - let address_key_holder = KeyChain::new_os_random(); - - // Check that key holder fields are initialized with expected types and values - assert!(!Into::::into( - address_key_holder.nullifer_public_key.is_identity() - )); - assert!(!Into::::into( - address_key_holder.viewing_public_key.is_identity() - )); - } - #[test] fn test_calculate_shared_secret_with_identity_point() { let address_key_holder = KeyChain::new_os_random(); @@ -359,10 +358,10 @@ mod tests { let seed_holder = SeedHolder::new_os_random(); let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder(); + let utxo_secret_key_holder = top_secret_key_holder.produce_private_key_holder(); let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key(); - let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key(); + let viewing_public_key = utxo_secret_key_holder.generate_incoming_viewing_public_key(); let pub_account_signing_key = nssa::PrivateKey::new_os_random(); @@ -377,11 +376,6 @@ mod tests { "Group generator {:?}", hex::encode(serde_json::to_vec(&AffinePoint::GENERATOR).unwrap()) ); - println!( - "Nullifier constant {:?}", - hex::encode(*NULLIFIER_SECRET_CONST) - ); - println!("Viewing constatnt {:?}", hex::encode(*VIEWING_SECRET_CONST)); println!(); println!("======Holders======"); diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 4fc1a63..a5258b5 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -1,31 +1,31 @@ use bip39::Mnemonic; use common::merkle_tree_public::TreeHashType; use elliptic_curve::PrimeField; -use k256::{AffinePoint, FieldBytes, Scalar}; +use k256::{AffinePoint, Scalar}; use rand::{rngs::OsRng, RngCore}; use serde::{Deserialize, Serialize}; use sha2::{digest::FixedOutput, Digest}; -use super::constants_types::{NULLIFIER_SECRET_CONST, VIEWING_SECRET_CONST}; - #[derive(Debug)] ///Seed holder. Non-clonable to ensure that different holders use different seeds. /// Produces `TopSecretKeyHolder` objects. pub struct SeedHolder { - seed: Scalar, + //ToDo: Needs to be vec as serde derives is not implemented for [u8; 64] + pub(crate) seed: Vec, } #[derive(Serialize, Deserialize, Debug, Clone)] -///Secret spending key holder. Produces `UTXOSecretKeyHolder` objects. +///Secret spending key holder. Produces `PrivateKeyHolder` objects. pub struct TopSecretKeyHolder { - pub secret_spending_key: Scalar, + pub(crate) secret_spending_key: [u8; 32], } #[derive(Serialize, Deserialize, Debug, Clone)] -///Nullifier secret key and viewing secret key holder. Produces public keys. Can produce address. Can produce shared secret for recepient. -pub struct UTXOSecretKeyHolder { - pub nullifier_secret_key: Scalar, - pub viewing_secret_key: Scalar, +///Private key holder. Produces public keys. Can produce address. Can produce shared secret for recepient. +pub struct PrivateKeyHolder { + pub(crate) nullifier_secret_key: [u8; 32], + pub(crate) incoming_viewing_secret_key: Scalar, + pub(crate) outgoing_viewing_secret_key: Scalar, } impl SeedHolder { @@ -34,73 +34,134 @@ impl SeedHolder { OsRng.fill_bytes(&mut enthopy_bytes); let mnemonic = Mnemonic::from_entropy(&enthopy_bytes).unwrap(); - let seed = mnemonic.to_seed(""); - - let field_bytes = FieldBytes::from_slice(&seed); + let seed_wide = mnemonic.to_seed("mnemonic"); Self { - seed: Scalar::from_repr(*field_bytes).unwrap(), + seed: seed_wide.to_vec(), } } pub fn generate_secret_spending_key_hash(&self) -> TreeHashType { - let mut hasher = sha2::Sha256::new(); + let mut hash = hmac_sha512::HMAC::mac(&self.seed, "NSSA_seed"); - hasher.update(self.seed.to_bytes()); + for _ in 1..2048 { + hash = hmac_sha512::HMAC::mac(&hash, "NSSA_seed"); + } - ::from(hasher.finalize_fixed()) - } - - pub fn generate_secret_spending_key_scalar(&self) -> Scalar { - let hash = self.generate_secret_spending_key_hash(); - - Scalar::from_repr(hash.into()).unwrap() + //Safe unwrap + *hash.first_chunk::<32>().unwrap() } pub fn produce_top_secret_key_holder(&self) -> TopSecretKeyHolder { TopSecretKeyHolder { - secret_spending_key: self.generate_secret_spending_key_scalar(), + secret_spending_key: self.generate_secret_spending_key_hash(), } } } impl TopSecretKeyHolder { - pub fn generate_nullifier_secret_key(&self) -> Scalar { + pub fn generate_nullifier_secret_key(&self) -> [u8; 32] { let mut hasher = sha2::Sha256::new(); - hasher.update(self.secret_spending_key.to_bytes()); - hasher.update(*NULLIFIER_SECRET_CONST); + hasher.update("NSSA_keys"); + hasher.update(&self.secret_spending_key); + hasher.update([1u8]); + hasher.update([0u8; 176]); + + ::from(hasher.finalize_fixed()) + } + + pub fn generate_incloming_viewing_secret_key(&self) -> Scalar { + let mut hasher = sha2::Sha256::new(); + + hasher.update("NSSA_keys"); + hasher.update(&self.secret_spending_key); + hasher.update([2u8]); + hasher.update([0u8; 176]); let hash = ::from(hasher.finalize_fixed()); Scalar::from_repr(hash.into()).unwrap() } - pub fn generate_viewing_secret_key(&self) -> Scalar { + pub fn generate_outgoing_viewing_secret_key(&self) -> Scalar { let mut hasher = sha2::Sha256::new(); - hasher.update(self.secret_spending_key.to_bytes()); - hasher.update(*VIEWING_SECRET_CONST); + hasher.update("NSSA_keys"); + hasher.update(&self.secret_spending_key); + hasher.update([3u8]); + hasher.update([0u8; 176]); let hash = ::from(hasher.finalize_fixed()); Scalar::from_repr(hash.into()).unwrap() } - pub fn produce_utxo_secret_holder(&self) -> UTXOSecretKeyHolder { - UTXOSecretKeyHolder { + pub fn produce_private_key_holder(&self) -> PrivateKeyHolder { + PrivateKeyHolder { nullifier_secret_key: self.generate_nullifier_secret_key(), - viewing_secret_key: self.generate_viewing_secret_key(), + incoming_viewing_secret_key: self.generate_incloming_viewing_secret_key(), + outgoing_viewing_secret_key: self.generate_outgoing_viewing_secret_key(), } } } -impl UTXOSecretKeyHolder { - pub fn generate_nullifier_public_key(&self) -> AffinePoint { - (AffinePoint::GENERATOR * self.nullifier_secret_key).into() +impl PrivateKeyHolder { + pub fn generate_nullifier_public_key(&self) -> [u8; 32] { + let mut hasher = sha2::Sha256::new(); + + hasher.update("NSSA_keys"); + hasher.update(&self.nullifier_secret_key); + hasher.update([7u8]); + hasher.update([0u8; 176]); + + ::from(hasher.finalize_fixed()) } - pub fn generate_viewing_public_key(&self) -> AffinePoint { - (AffinePoint::GENERATOR * self.viewing_secret_key).into() + pub fn generate_incoming_viewing_public_key(&self) -> AffinePoint { + (AffinePoint::GENERATOR * self.incoming_viewing_secret_key).into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn seed_generation_test() { + let seed_holder = SeedHolder::new_os_random(); + + assert_eq!(seed_holder.seed.len(), 64); + } + + #[test] + fn ssk_generation_test() { + let seed_holder = SeedHolder::new_os_random(); + + assert_eq!(seed_holder.seed.len(), 64); + + let _ = seed_holder.generate_secret_spending_key_hash(); + } + + #[test] + fn ivs_generation_test() { + let seed_holder = SeedHolder::new_os_random(); + + assert_eq!(seed_holder.seed.len(), 64); + + let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); + + let _ = top_secret_key_holder.generate_incloming_viewing_secret_key(); + } + + #[test] + fn ovs_generation_test() { + let seed_holder = SeedHolder::new_os_random(); + + assert_eq!(seed_holder.seed.len(), 64); + + let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); + + let _ = top_secret_key_holder.generate_outgoing_viewing_secret_key(); } } diff --git a/key_protocol/src/key_management/types.rs b/key_protocol/src/key_management/types.rs new file mode 100644 index 0000000..8878f25 --- /dev/null +++ b/key_protocol/src/key_management/types.rs @@ -0,0 +1,8 @@ +use elliptic_curve::{ + consts::{B0, B1}, + generic_array::GenericArray, +}; +use sha2::digest::typenum::{UInt, UTerm}; + +pub type CipherText = Vec; +pub type Nonce = GenericArray, B1>, B0>, B0>>; From a432019b23392ac27bdc2adbca73380464ce1f5c Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 4 Sep 2025 17:05:12 -0300 Subject: [PATCH 04/11] wip --- integration_tests/Cargo.toml | 1 + integration_tests/src/lib.rs | 48 ++++++++++++++++++ nssa/Cargo.toml | 3 ++ nssa/program_methods/guest/src/bin/pinata.rs | 16 +++--- nssa/src/program.rs | 9 +++- nssa/src/state.rs | 53 +++++++------------- sequencer_core/Cargo.toml | 4 ++ sequencer_core/src/sequencer_store/mod.rs | 6 ++- sequencer_runner/src/lib.rs | 4 +- wallet/src/lib.rs | 52 +++++++++++++++++-- 10 files changed, 145 insertions(+), 51 deletions(-) diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 16e1b2e..644d2e3 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -20,6 +20,7 @@ workspace = true [dependencies.sequencer_core] path = "../sequencer_core" +features = ["testnet"] [dependencies.sequencer_runner] path = "../sequencer_runner" diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 9b7b970..cd2260f 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -272,6 +272,50 @@ pub async fn test_success_two_transactions() { info!("Second TX Success!"); } +pub async fn test_pinata() { + let pinata_addr = "cafe".repeat(16); + let pinata_prize = 150; + let solution = 989106; + let command = Command::ClaimPinata { + pinata_addr: pinata_addr.clone(), + winner_addr: ACC_SENDER.to_string(), + solution, + }; + + let wallet_config = fetch_config().unwrap(); + + let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); + + let pinata_balance_pre = seq_client + .get_account_balance(pinata_addr.clone()) + .await + .unwrap() + .balance; + + wallet::execute_subcommand(command).await.unwrap(); + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move"); + let pinata_balance_post = seq_client + .get_account_balance(pinata_addr.clone()) + .await + .unwrap() + .balance; + + let winner_balance_post = seq_client + .get_account_balance(ACC_SENDER.to_string()) + .await + .unwrap() + .balance; + + assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); + assert_eq!(winner_balance_post, 10000 + pinata_prize); + + info!("Success!"); +} + macro_rules! test_cleanup_wrap { ($home_dir:ident, $test_func:ident) => {{ let res = pre_test($home_dir.clone()).await.unwrap(); @@ -307,11 +351,15 @@ pub async fn main_tests_runner() -> Result<()> { "test_success_two_transactions" => { test_cleanup_wrap!(home_dir, test_success_two_transactions); } + "test_pinata" => { + test_cleanup_wrap!(home_dir, test_pinata); + } "all" => { test_cleanup_wrap!(home_dir, test_success_move_to_another_account); test_cleanup_wrap!(home_dir, test_success); test_cleanup_wrap!(home_dir, test_failure); test_cleanup_wrap!(home_dir, test_success_two_transactions); + test_cleanup_wrap!(home_dir, test_pinata); } _ => { anyhow::bail!("Unknown test name"); diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index 3163902..25907a1 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -18,3 +18,6 @@ hex = "0.4.3" [dev-dependencies] test-program-methods = { path = "test_program_methods" } hex-literal = "1.0.0" + +[features] +default = [] diff --git a/nssa/program_methods/guest/src/bin/pinata.rs b/nssa/program_methods/guest/src/bin/pinata.rs index 43b0190..fbea167 100644 --- a/nssa/program_methods/guest/src/bin/pinata.rs +++ b/nssa/program_methods/guest/src/bin/pinata.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput}; +use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs}; use risc0_zkvm::sha::{Impl, Sha256}; const PRIZE: u128 = 150; @@ -21,10 +21,12 @@ impl Challenge { Self { difficulty, seed } } - fn is_nonce_valid(&self, nonce: Instruction) -> bool { + // Checks if the leftmost `self.difficulty` number of bytes of SHA256(self.data || solution) are + // zero. + fn validate_solution(&self, solution: Instruction) -> bool { let mut bytes = [0; 32 + 16]; - bytes.copy_from_slice(&self.seed); - bytes[32..].copy_from_slice(&nonce.to_le_bytes()); + bytes[..32].copy_from_slice(&self.seed); + bytes[32..].copy_from_slice(&solution.to_le_bytes()); let digest: [u8; 32] = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap(); let difficulty = self.difficulty as usize; digest[..difficulty].iter().all(|&b| b == 0) @@ -33,7 +35,7 @@ impl Challenge { fn next_data(self) -> [u8; 33] { let mut result = [0; 33]; result[0] = self.difficulty; - result.copy_from_slice(Impl::hash_bytes(&self.seed).as_bytes()); + result[1..].copy_from_slice(Impl::hash_bytes(&self.seed).as_bytes()); result } } @@ -44,7 +46,7 @@ fn main() { // It is expected to receive only two accounts: [pinata_account, winner_account] let ProgramInput { pre_states, - instruction: nonce, + instruction: solution, } = read_nssa_inputs::(); let [pinata, winner] = match pre_states.try_into() { @@ -54,7 +56,7 @@ fn main() { let data = Challenge::new(&pinata.account.data); - if !data.is_nonce_valid(nonce) { + if !data.validate_solution(solution) { return; } diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 63b7dc4..4828246 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -2,7 +2,9 @@ use nssa_core::{ account::{Account, AccountWithMetadata}, program::{InstructionData, ProgramId, ProgramOutput}, }; -use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID}; +use program_methods::{ + AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID, +}; use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec}; use serde::Serialize; @@ -73,11 +75,14 @@ impl Program { elf: AUTHENTICATED_TRANSFER_ELF, } } +} +// TODO: This is for testnet only, consider refactoring to have this not compiled for mainnet +impl Program { pub fn pinata() -> Self { Self { id: PINATA_ID, - elf: PINATA_ELF + elf: PINATA_ELF, } } } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index ba6c49b..ef90f37 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -86,41 +86,6 @@ impl V01State { this } - pub fn add_pinata_accounts(&mut self) { - self.insert_program(Program::pinata()); - - let mut rng = OsRng; - let mut seed = [0; 32]; - - rng.fill_bytes(&mut seed); - self.public_state.insert( - "6a79aee868a1c641ea895582af7ddd6f2da339e3091a67eddcbfdaa1b9010001" - .parse() - .unwrap(), - Account { - program_owner: Program::pinata().id(), - balance: 1500, - // Difficulty: 3 - data: std::iter::once(3).chain(seed).collect(), - nonce: 0, - }, - ); - - rng.fill_bytes(&mut seed); - self.public_state.insert( - "6a79aee868a1c641ea895582af7ddd6f2da339e3091a67eddcbfdaa1b9010002" - .parse() - .unwrap(), - Account { - program_owner: Program::pinata().id(), - balance: 1500, - // Difficulty: 4 - data: std::iter::once(4).chain(seed).collect(), - nonce: 0, - }, - ); - } - pub(crate) fn insert_program(&mut self, program: Program) { self.builtin_programs.insert(program.id(), program); } @@ -242,6 +207,24 @@ impl V01State { } } +// TODO: This is for testnet only, consider refactoring to have this not compiled for mainnet +impl V01State { + pub fn add_pinata_program(&mut self, address: Address) { + self.insert_program(Program::pinata()); + + self.public_state.insert( + address, + Account { + program_owner: Program::pinata().id(), + balance: 1500, + // Difficulty: 3 + data: vec![3; 33], + nonce: 0, + }, + ); + } +} + #[cfg(test)] pub mod tests { diff --git a/sequencer_core/Cargo.toml b/sequencer_core/Cargo.toml index 1c56f11..aca92ff 100644 --- a/sequencer_core/Cargo.toml +++ b/sequencer_core/Cargo.toml @@ -22,3 +22,7 @@ path = "../common" [dependencies.nssa] path = "../nssa" + +[features] +default = [] +testnet = [] diff --git a/sequencer_core/src/sequencer_store/mod.rs b/sequencer_core/src/sequencer_store/mod.rs index f9c86cd..8d8ef23 100644 --- a/sequencer_core/src/sequencer_store/mod.rs +++ b/sequencer_core/src/sequencer_store/mod.rs @@ -26,9 +26,13 @@ impl SequecerChainStore { .map(|acc_data| (acc_data.addr.parse().unwrap(), acc_data.balance)) .collect(); + #[cfg(not(feature = "testnet"))] + let state = nssa::V01State::new_with_genesis_accounts(&init_accs); + + #[cfg(feature = "testnet")] let state = { let mut this = nssa::V01State::new_with_genesis_accounts(&init_accs); - this.add_pinata_accounts(); + this.add_pinata_program("cafe".repeat(16).parse().unwrap()); this }; diff --git a/sequencer_runner/src/lib.rs b/sequencer_runner/src/lib.rs index 068d3d7..b682a18 100644 --- a/sequencer_runner/src/lib.rs +++ b/sequencer_runner/src/lib.rs @@ -77,9 +77,7 @@ pub async fn main_runner() -> Result<()> { } //ToDo: Add restart on failures - let (_, main_loop_handle) = startup_sequencer(app_config).await?; - - main_loop_handle.await??; + let (_, _) = startup_sequencer(app_config).await?; Ok(()) } diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 3814925..171dda9 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -84,6 +84,24 @@ impl WalletCore { None } + pub async fn claim_pinata( + &self, + pinata_addr: Address, + winner_addr: Address, + solution: u128, + ) -> Result { + let addresses = vec![pinata_addr, winner_addr]; + let program_id = nssa::program::Program::pinata().id(); + let message = + nssa::public_transaction::Message::try_new(program_id, addresses, vec![], solution) + .unwrap(); + + let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx(tx).await?) + } + pub async fn send_public_native_token_transfer( &self, from: Address, @@ -191,6 +209,20 @@ pub enum Command { #[arg(short, long)] addr: String, }, + + // TODO: This is for testnet only, consider refactoring to have this not compiled for mainnet + // Claim piñata prize + ClaimPinata { + ///pinata_addr - valid 32 byte hex string + #[arg(long)] + pinata_addr: String, + ///winner_addr - valid 32 byte hex string + #[arg(long)] + winner_addr: String, + ///solution - solution to pinata challenge + #[arg(long)] + solution: u128, + }, } ///To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config @@ -228,7 +260,7 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { let key = wallet_core.storage.user_data.get_account_signing_key(&addr); - info!("Generated new account with addr {addr:#?}"); + println!("Generated new account with addr {addr}"); info!("With key {key:#?}"); } Command::FetchTx { tx_hash } => { @@ -243,13 +275,27 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { let addr = Address::from_str(&addr)?; let balance = wallet_core.get_account_balance(addr).await?; - info!("Accounts {addr:#?} balance is {balance}"); + println!("Accounts {addr} balance is {balance}"); } Command::GetAccountNonce { addr } => { let addr = Address::from_str(&addr)?; let nonce = wallet_core.get_accounts_nonces(vec![addr]).await?[0]; - info!("Accounts {addr:#?} nonce is {nonce}"); + println!("Accounts {addr} nonce is {nonce}"); + } + Command::ClaimPinata { + pinata_addr, + winner_addr, + solution, + } => { + let res = wallet_core + .claim_pinata( + pinata_addr.parse().unwrap(), + winner_addr.parse().unwrap(), + solution, + ) + .await?; + info!("Results of tx send is {res:#?}"); } } From e2d596be2061ddd863b40bd3fb4eb81a5adfec13 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 5 Sep 2025 23:45:44 -0300 Subject: [PATCH 05/11] clippy --- nssa/src/program.rs | 2 +- nssa/src/state.rs | 3 +-- wallet/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 4828246..9577411 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -77,7 +77,7 @@ impl Program { } } -// TODO: This is for testnet only, consider refactoring to have this not compiled for mainnet +// TODO: Testnet only. Refactor to prevent compilation on mainnet. impl Program { pub fn pinata() -> Self { Self { diff --git a/nssa/src/state.rs b/nssa/src/state.rs index ef90f37..3fe0b93 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -8,7 +8,6 @@ use nssa_core::{ account::Account, program::{DEFAULT_PROGRAM_ID, ProgramId}, }; -use rand::{Rng, RngCore, rngs::OsRng}; use std::collections::{HashMap, HashSet}; pub(crate) struct CommitmentSet { @@ -207,7 +206,7 @@ impl V01State { } } -// TODO: This is for testnet only, consider refactoring to have this not compiled for mainnet +// TODO: Testnet only. Refactor to prevent compilation on mainnet. impl V01State { pub fn add_pinata_program(&mut self, address: Address) { self.insert_program(Program::pinata()); diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 171dda9..0ea3892 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -210,7 +210,7 @@ pub enum Command { addr: String, }, - // TODO: This is for testnet only, consider refactoring to have this not compiled for mainnet + // TODO: Testnet only. Refactor to prevent compilation on mainnet. // Claim piñata prize ClaimPinata { ///pinata_addr - valid 32 byte hex string From 33783e06d8a6d8e4c1072f45e7bb6fec01ae1f15 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 8 Sep 2025 14:48:58 +0300 Subject: [PATCH 06/11] fix: keys structures updates --- .../key_management/ephemeral_key_holder.rs | 45 ++++----- key_protocol/src/key_management/mod.rs | 79 ++++------------ .../src/key_management/secret_holders.rs | 10 +- key_protocol/src/key_protocol_core/mod.rs | 92 +++++++++++++++---- wallet/src/chain_storage/mod.rs | 2 +- wallet/src/lib.rs | 6 +- 6 files changed, 123 insertions(+), 111 deletions(-) diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index 6392678..108a41e 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -1,11 +1,7 @@ -use aes_gcm::{aead::Aead, AeadCore, Aes256Gcm, KeyInit}; -use elliptic_curve::point::AffineCoordinates; use elliptic_curve::PrimeField; -use k256::{AffinePoint, FieldBytes, Scalar}; +use k256::{AffinePoint, Scalar}; use log::info; -use rand::{rngs::OsRng, RngCore}; - -use super::types::{CipherText, Nonce}; +use sha2::Digest; #[derive(Debug)] ///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender. @@ -14,13 +10,24 @@ pub struct EphemeralKeyHolder { } impl EphemeralKeyHolder { - pub fn new_os_random() -> Self { - let mut bytes = FieldBytes::default(); + pub fn new( + receiver_nullifier_public_key: [u8; 32], + sender_outgoing_viewing_secret_key: Scalar, + nonce: u64, + ) -> Self { + let mut hasher = sha2::Sha256::new(); + hasher.update(receiver_nullifier_public_key); + hasher.update(nonce.to_le_bytes()); + hasher.update([0; 192]); - OsRng.fill_bytes(&mut bytes); + let hash_recepient = hasher.finalize(); + + let mut hasher = sha2::Sha256::new(); + hasher.update(sender_outgoing_viewing_secret_key.to_bytes()); + hasher.update(hash_recepient); Self { - ephemeral_secret_key: Scalar::from_repr(bytes).unwrap(), + ephemeral_secret_key: Scalar::from_repr(hasher.finalize()).unwrap(), } } @@ -30,21 +37,9 @@ impl EphemeralKeyHolder { pub fn calculate_shared_secret_sender( &self, - viewing_public_key_receiver: AffinePoint, - ) -> AffinePoint { - (viewing_public_key_receiver * self.ephemeral_secret_key).into() - } - - pub fn encrypt_data( - &self, - viewing_public_key_receiver: AffinePoint, - data: &[u8], - ) -> (CipherText, Nonce) { - let shared_secret = self.calculate_shared_secret_sender(viewing_public_key_receiver); - let cipher = Aes256Gcm::new(&shared_secret.x()); - let nonce = Aes256Gcm::generate_nonce(&mut OsRng); - - (cipher.encrypt(&nonce, data).unwrap(), nonce) + receiver_incoming_viewing_public_key: Scalar, + ) -> Scalar { + receiver_incoming_viewing_public_key * self.ephemeral_secret_key } pub fn log(&self) { diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index fb75dff..d3a4b08 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit}; use common::merkle_tree_public::TreeHashType; use elliptic_curve::group::GroupEncoding; @@ -23,8 +21,6 @@ pub mod types; pub struct KeyChain { top_secret_key_holder: TopSecretKeyHolder, pub private_key_holder: PrivateKeyHolder, - ///Map for all users accounts - pub_account_signing_keys: HashMap, pub nullifer_public_key: [u8; 32], pub incoming_viewing_public_key: PublicKey, } @@ -46,61 +42,27 @@ impl KeyChain { private_key_holder, nullifer_public_key, incoming_viewing_public_key, - pub_account_signing_keys: HashMap::new(), - } - } - - pub fn new_os_random_with_accounts(accounts: HashMap) -> Self { - //Currently dropping SeedHolder at the end of initialization. - //Now entirely sure if we need it in the future. - let seed_holder = SeedHolder::new_os_random(); - let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - - let private_key_holder = top_secret_key_holder.produce_private_key_holder(); - - let nullifer_public_key = private_key_holder.generate_nullifier_public_key(); - let incoming_viewing_public_key = private_key_holder.generate_incoming_viewing_public_key(); - - Self { - top_secret_key_holder, - private_key_holder, - nullifer_public_key, - incoming_viewing_public_key, - pub_account_signing_keys: accounts, } } pub fn produce_user_address(&self) -> [u8; 32] { let mut hasher = sha2::Sha256::new(); - hasher.update(&self.nullifer_public_key); - hasher.update(&self.incoming_viewing_public_key.to_bytes()); + hasher.update(self.nullifer_public_key); + hasher.update(self.incoming_viewing_public_key.to_bytes()); ::from(hasher.finalize_fixed()) } - pub fn generate_new_private_key(&mut self) -> nssa::Address { - let private_key = nssa::PrivateKey::new_os_random(); - let address = nssa::Address::from(&nssa::PublicKey::new_from_private_key(&private_key)); - - self.pub_account_signing_keys.insert(address, private_key); - - address - } - - /// Returns the signing key for public transaction signatures - pub fn get_pub_account_signing_key( - &self, - address: &nssa::Address, - ) -> Option<&nssa::PrivateKey> { - self.pub_account_signing_keys.get(address) - } - pub fn calculate_shared_secret_receiver( &self, ephemeral_public_key_sender: AffinePoint, ) -> AffinePoint { - (ephemeral_public_key_sender * self.utxo_secret_key_holder.viewing_secret_key).into() + (ephemeral_public_key_sender + * self + .top_secret_key_holder + .generate_incloming_viewing_secret_key()) + .into() } pub fn decrypt_data( @@ -197,9 +159,19 @@ mod tests { fn test_decrypt_data() { let address_key_holder = KeyChain::new_os_random(); + let test_receiver_nullifier_public_key = [42; 32]; + let sender_outgoing_viewing_key = address_key_holder + .top_secret_key_holder + .generate_outgoing_viewing_secret_key(); + let nonce = 0; + // Generate an ephemeral key and shared secret - let ephemeral_public_key_sender = - EphemeralKeyHolder::new_os_random().generate_ephemeral_public_key(); + let ephemeral_public_key_sender = EphemeralKeyHolder::new( + test_receiver_nullifier_public_key, + sender_outgoing_viewing_key, + nonce, + ) + .generate_ephemeral_public_key(); let shared_secret = address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); @@ -340,19 +312,6 @@ mod tests { assert_eq!(decrypted_data, plaintext); } - #[test] - fn test_get_public_account_signing_key() { - let mut address_key_holder = KeyChain::new_os_random(); - - let address = address_key_holder.generate_new_private_key(); - - let is_private_key_generated = address_key_holder - .get_pub_account_signing_key(&address) - .is_some(); - - assert!(is_private_key_generated); - } - #[test] fn key_generation_test() { let seed_holder = SeedHolder::new_os_random(); diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index a5258b5..5180efb 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -45,7 +45,7 @@ impl SeedHolder { let mut hash = hmac_sha512::HMAC::mac(&self.seed, "NSSA_seed"); for _ in 1..2048 { - hash = hmac_sha512::HMAC::mac(&hash, "NSSA_seed"); + hash = hmac_sha512::HMAC::mac(hash, "NSSA_seed"); } //Safe unwrap @@ -64,7 +64,7 @@ impl TopSecretKeyHolder { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(&self.secret_spending_key); + hasher.update(self.secret_spending_key); hasher.update([1u8]); hasher.update([0u8; 176]); @@ -75,7 +75,7 @@ impl TopSecretKeyHolder { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(&self.secret_spending_key); + hasher.update(self.secret_spending_key); hasher.update([2u8]); hasher.update([0u8; 176]); @@ -88,7 +88,7 @@ impl TopSecretKeyHolder { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(&self.secret_spending_key); + hasher.update(self.secret_spending_key); hasher.update([3u8]); hasher.update([0u8; 176]); @@ -111,7 +111,7 @@ impl PrivateKeyHolder { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(&self.nullifier_secret_key); + hasher.update(self.nullifier_secret_key); hasher.update([7u8]); hasher.update([0u8; 176]); diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 67cb52c..44d8ff6 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -10,17 +10,14 @@ pub type PublicKey = AffinePoint; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NSSAUserData { - pub key_holder: KeyChain, + ///Map for all user public accounts + pub_account_signing_keys: HashMap, + ///Map for all user private accounts + user_private_accounts: HashMap, } impl NSSAUserData { - pub fn new() -> Self { - let key_holder = KeyChain::new_os_random(); - - Self { key_holder } - } - - fn valid_key_transaction_pairing_check( + fn valid_public_key_transaction_pairing_check( accounts_keys_map: &HashMap, ) -> bool { let mut check_res = true; @@ -32,30 +29,78 @@ impl NSSAUserData { check_res } + fn valid_private_key_transaction_pairing_check( + accounts_keys_map: &HashMap, + ) -> bool { + let mut check_res = true; + for (addr, key) in accounts_keys_map { + if nssa::Address::new(key.produce_user_address()) != *addr { + check_res = false; + } + } + check_res + } + pub fn new_with_accounts( accounts_keys: HashMap, + accounts_key_chains: HashMap, ) -> Result { - if !Self::valid_key_transaction_pairing_check(&accounts_keys) { + if !Self::valid_public_key_transaction_pairing_check(&accounts_keys) { anyhow::bail!("Key transaction pairing check not satisfied, there is addresses, which is not derived from keys"); } - let key_holder = KeyChain::new_os_random_with_accounts(accounts_keys); + if !Self::valid_private_key_transaction_pairing_check(&accounts_key_chains) { + anyhow::bail!("Key transaction pairing check not satisfied, there is addresses, which is not derived from keys"); + } - Ok(Self { key_holder }) + Ok(Self { + pub_account_signing_keys: accounts_keys, + user_private_accounts: accounts_key_chains, + }) } - pub fn generate_new_account(&mut self) -> nssa::Address { - self.key_holder.generate_new_private_key() + /// Generated new private key for public transaction signatures + /// + /// Returns the address of new account + pub fn generate_new_public_transaction_private_key(&mut self) -> nssa::Address { + let private_key = nssa::PrivateKey::new_os_random(); + let address = nssa::Address::from(&nssa::PublicKey::new_from_private_key(&private_key)); + + self.pub_account_signing_keys.insert(address, private_key); + + address } - pub fn get_account_signing_key(&self, address: &nssa::Address) -> Option<&nssa::PrivateKey> { - self.key_holder.get_pub_account_signing_key(address) + /// Returns the signing key for public transaction signatures + pub fn get_pub_account_signing_key( + &self, + address: &nssa::Address, + ) -> Option<&nssa::PrivateKey> { + self.pub_account_signing_keys.get(address) + } + + /// Generated new private key for privacy preserving transactions + /// + /// Returns the address of new account + pub fn generate_new_privacy_preserving_transaction_key_chain(&mut self) -> nssa::Address { + let key_chain = KeyChain::new_os_random(); + let address = nssa::Address::new(key_chain.produce_user_address()); + + self.user_private_accounts.insert(address, key_chain); + + address + } + + /// Returns the signing key for public transaction signatures + pub fn get_private_account_key_chain(&self, address: &nssa::Address) -> Option<&KeyChain> { + self.user_private_accounts.get(address) } } impl Default for NSSAUserData { fn default() -> Self { - Self::new() + //Safe unwrap as maps are empty + Self::new_with_accounts(HashMap::default(), HashMap::default()).unwrap() } } @@ -65,8 +110,19 @@ mod tests { #[test] fn test_new_account() { - let mut user_data = NSSAUserData::new(); + let mut user_data = NSSAUserData::default(); - let _addr = user_data.generate_new_account(); + let addr_pub = user_data.generate_new_public_transaction_private_key(); + let addr_private = user_data.generate_new_privacy_preserving_transaction_key_chain(); + + let is_private_key_generated = user_data.get_pub_account_signing_key(&addr_pub).is_some(); + + assert!(is_private_key_generated); + + let is_key_chain_generated = user_data + .get_private_account_key_chain(&addr_private) + .is_some(); + + assert!(is_key_chain_generated); } } diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index 7dfb8a2..e68b4a8 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -24,7 +24,7 @@ impl WalletChainStore { let utxo_commitments_store = UTXOCommitmentsMerkleTree::new(vec![]); Ok(Self { - user_data: NSSAUserData::new_with_accounts(accounts_keys)?, + user_data: NSSAUserData::new_with_accounts(accounts_keys, HashMap::new())?, utxo_commitments_store, wallet_config: config, }) diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index e2213dc..1cbbb94 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -41,7 +41,9 @@ impl WalletCore { } pub fn create_new_account(&mut self) -> Address { - self.storage.user_data.generate_new_account() + self.storage + .user_data + .generate_new_public_transaction_private_key() } pub fn search_for_initial_account(&self, acc_addr: Address) -> Option { @@ -75,7 +77,7 @@ impl WalletCore { ) .unwrap(); - let signing_key = self.storage.user_data.get_account_signing_key(&from); + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); if let Some(signing_key) = signing_key { let witness_set = From 48374139899147eccb2f26a32c97f89f944c0ebc Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 8 Sep 2025 15:03:02 +0300 Subject: [PATCH 07/11] fix: merge fix --- key_protocol/src/key_management/ephemeral_key_holder.rs | 8 ++------ key_protocol/src/key_protocol_core/mod.rs | 2 +- wallet/src/chain_storage/mod.rs | 1 - wallet/src/helperfunctions.rs | 2 +- wallet/src/lib.rs | 4 ++-- 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index 55120a6..108a41e 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -1,11 +1,7 @@ -use aes_gcm::{AeadCore, Aes256Gcm, KeyInit, aead::Aead}; use elliptic_curve::PrimeField; -use elliptic_curve::point::AffineCoordinates; -use k256::{AffinePoint, FieldBytes, Scalar}; +use k256::{AffinePoint, Scalar}; use log::info; -use rand::{RngCore, rngs::OsRng}; - -use super::constants_types::{CipherText, Nonce}; +use sha2::Digest; #[derive(Debug)] ///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender. diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 44d8ff6..167f8fe 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -11,7 +11,7 @@ pub type PublicKey = AffinePoint; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NSSAUserData { ///Map for all user public accounts - pub_account_signing_keys: HashMap, + pub pub_account_signing_keys: HashMap, ///Map for all user private accounts user_private_accounts: HashMap, } diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index e08650c..9a6bd59 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -32,7 +32,6 @@ impl WalletChainStore { pub(crate) fn insert_account_data(&mut self, acc_data: PersistentAccountData) { self.user_data - .key_holder .pub_account_signing_keys .insert(acc_data.address, acc_data.pub_sign_key); } diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 8a7b7b6..45bb5f0 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -53,7 +53,7 @@ pub fn fetch_persistent_accounts() -> Result> { pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec { let mut vec_for_storage = vec![]; - for (addr, key) in &user_data.key_holder.pub_account_signing_keys { + for (addr, key) in &user_data.pub_account_signing_keys { vec_for_storage.push(PersistentAccountData { address: *addr, pub_sign_key: key.clone(), diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index a52b1c6..f9c5f24 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -111,7 +111,7 @@ impl WalletCore { ) .unwrap(); - let signing_key = self.storage.user_data.get_account_signing_key(&from); + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); let Some(signing_key) = signing_key else { return Err(ExecutionFailureKind::KeyNotFoundError); @@ -228,7 +228,7 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { Command::RegisterAccount {} => { let addr = wallet_core.create_new_account(); - let key = wallet_core.storage.user_data.get_account_signing_key(&addr); + let key = wallet_core.storage.user_data.get_pub_account_signing_key(&addr); info!("Generated new account with addr {addr:#?}"); info!("With key {key:#?}"); From 23e79d3e17dd743efd50696b9d7584008d86356f Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 8 Sep 2025 15:23:32 +0300 Subject: [PATCH 08/11] fix: fmt --- key_protocol/src/key_management/mod.rs | 4 ++-- key_protocol/src/key_management/secret_holders.rs | 2 +- key_protocol/src/key_protocol_core/mod.rs | 8 ++++++-- wallet/src/lib.rs | 5 ++++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index 9fc3f06..ea337dd 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -1,4 +1,4 @@ -use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit}; +use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead}; use common::merkle_tree_public::TreeHashType; use elliptic_curve::group::GroupEncoding; use elliptic_curve::point::AffineCoordinates; @@ -6,7 +6,7 @@ use k256::AffinePoint; use log::info; use secret_holders::{PrivateKeyHolder, SeedHolder, TopSecretKeyHolder}; use serde::{Deserialize, Serialize}; -use sha2::{digest::FixedOutput, Digest}; +use sha2::{Digest, digest::FixedOutput}; use types::{CipherText, Nonce}; use crate::key_protocol_core::PublicKey; diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 9387747..b6784cd 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -2,7 +2,7 @@ use bip39::Mnemonic; use common::merkle_tree_public::TreeHashType; use elliptic_curve::PrimeField; use k256::{AffinePoint, Scalar}; -use rand::{rngs::OsRng, RngCore}; +use rand::{RngCore, rngs::OsRng}; use serde::{Deserialize, Serialize}; use sha2::{Digest, digest::FixedOutput}; diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 167f8fe..58f60ce 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -46,11 +46,15 @@ impl NSSAUserData { accounts_key_chains: HashMap, ) -> Result { if !Self::valid_public_key_transaction_pairing_check(&accounts_keys) { - anyhow::bail!("Key transaction pairing check not satisfied, there is addresses, which is not derived from keys"); + anyhow::bail!( + "Key transaction pairing check not satisfied, there is addresses, which is not derived from keys" + ); } if !Self::valid_private_key_transaction_pairing_check(&accounts_key_chains) { - anyhow::bail!("Key transaction pairing check not satisfied, there is addresses, which is not derived from keys"); + anyhow::bail!( + "Key transaction pairing check not satisfied, there is addresses, which is not derived from keys" + ); } Ok(Self { diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index f9c5f24..5a24933 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -228,7 +228,10 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { Command::RegisterAccount {} => { let addr = wallet_core.create_new_account(); - let key = wallet_core.storage.user_data.get_pub_account_signing_key(&addr); + let key = wallet_core + .storage + .user_data + .get_pub_account_signing_key(&addr); info!("Generated new account with addr {addr:#?}"); info!("With key {key:#?}"); From d07b813739c658c629d40cb19c90c4c013a1327f Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 15 Sep 2025 14:04:49 +0300 Subject: [PATCH 09/11] fix: deviations adjustments --- key_protocol/Cargo.toml | 1 + .../key_management/ephemeral_key_holder.rs | 29 ++- key_protocol/src/key_management/mod.rs | 239 ++---------------- .../src/key_management/secret_holders.rs | 69 +++-- key_protocol/src/key_management/types.rs | 8 - nssa/Cargo.toml | 1 + .../src/encryption/shared_key_derivation.rs | 7 +- nssa/core/src/nullifier.rs | 6 + .../privacy_preserving_transaction/circuit.rs | 7 +- .../privacy_preserving_transaction/message.rs | 5 +- nssa/src/state.rs | 15 +- 11 files changed, 102 insertions(+), 285 deletions(-) delete mode 100644 key_protocol/src/key_management/types.rs diff --git a/key_protocol/Cargo.toml b/key_protocol/Cargo.toml index 3ce874d..d453753 100644 --- a/key_protocol/Cargo.toml +++ b/key_protocol/Cargo.toml @@ -17,6 +17,7 @@ aes-gcm.workspace = true lazy_static.workspace = true bip39.workspace = true hmac-sha512.workspace = true +nssa-core = { path = "../nssa/core", features = ["host"] } [dependencies.common] path = "../common" diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index 108a41e..e62d9b6 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -1,24 +1,30 @@ use elliptic_curve::PrimeField; -use k256::{AffinePoint, Scalar}; +use k256::Scalar; use log::info; +use nssa_core::{ + NullifierPublicKey, SharedSecretKey, + encryption::{EphemeralPublicKey, EphemeralSecretKey, IncomingViewingPublicKey}, +}; use sha2::Digest; +use crate::key_management::secret_holders::OutgoingViewingSecretKey; + #[derive(Debug)] ///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender. pub struct EphemeralKeyHolder { - ephemeral_secret_key: Scalar, + ephemeral_secret_key: EphemeralSecretKey, } impl EphemeralKeyHolder { pub fn new( - receiver_nullifier_public_key: [u8; 32], - sender_outgoing_viewing_secret_key: Scalar, + receiver_nullifier_public_key: NullifierPublicKey, + sender_outgoing_viewing_secret_key: OutgoingViewingSecretKey, nonce: u64, ) -> Self { let mut hasher = sha2::Sha256::new(); hasher.update(receiver_nullifier_public_key); hasher.update(nonce.to_le_bytes()); - hasher.update([0; 192]); + hasher.update([0; 24]); let hash_recepient = hasher.finalize(); @@ -31,15 +37,18 @@ impl EphemeralKeyHolder { } } - pub fn generate_ephemeral_public_key(&self) -> AffinePoint { - (AffinePoint::GENERATOR * self.ephemeral_secret_key).into() + pub fn generate_ephemeral_public_key(&self) -> EphemeralPublicKey { + EphemeralPublicKey::from_scalar(self.ephemeral_secret_key) } pub fn calculate_shared_secret_sender( &self, - receiver_incoming_viewing_public_key: Scalar, - ) -> Scalar { - receiver_incoming_viewing_public_key * self.ephemeral_secret_key + receiver_incoming_viewing_public_key: IncomingViewingPublicKey, + ) -> SharedSecretKey { + SharedSecretKey::new( + &self.ephemeral_secret_key, + &receiver_incoming_viewing_public_key, + ) } pub fn log(&self) { diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index 5de3826..bcd3796 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -1,28 +1,25 @@ -use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead}; use common::TreeHashType; -use elliptic_curve::group::GroupEncoding; -use elliptic_curve::point::AffineCoordinates; -use k256::AffinePoint; use log::info; -use secret_holders::{PrivateKeyHolder, SeedHolder, TopSecretKeyHolder}; +use nssa_core::{ + NullifierPublicKey, SharedSecretKey, + encryption::{EphemeralPublicKey, IncomingViewingPublicKey}, +}; +use secret_holders::{PrivateKeyHolder, SecretSpendingKey, SeedHolder}; use serde::{Deserialize, Serialize}; use sha2::{Digest, digest::FixedOutput}; -use types::{CipherText, Nonce}; -use crate::key_protocol_core::PublicKey; pub type PublicAccountSigningKey = [u8; 32]; pub mod ephemeral_key_holder; pub mod secret_holders; -pub mod types; #[derive(Serialize, Deserialize, Clone, Debug)] ///Entrypoint to key management pub struct KeyChain { - top_secret_key_holder: TopSecretKeyHolder, + secret_spending_key: SecretSpendingKey, pub private_key_holder: PrivateKeyHolder, - pub nullifer_public_key: [u8; 32], - pub incoming_viewing_public_key: PublicKey, + pub nullifer_public_key: NullifierPublicKey, + pub incoming_viewing_public_key: IncomingViewingPublicKey, } impl KeyChain { @@ -30,15 +27,15 @@ impl KeyChain { //Currently dropping SeedHolder at the end of initialization. //Now entirely sure if we need it in the future. let seed_holder = SeedHolder::new_os_random(); - let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); + let secret_spending_key = seed_holder.produce_top_secret_key_holder(); - let private_key_holder = top_secret_key_holder.produce_private_key_holder(); + let private_key_holder = secret_spending_key.produce_private_key_holder(); let nullifer_public_key = private_key_holder.generate_nullifier_public_key(); let incoming_viewing_public_key = private_key_holder.generate_incoming_viewing_public_key(); Self { - top_secret_key_holder, + secret_spending_key, private_key_holder, nullifer_public_key, incoming_viewing_public_key, @@ -48,7 +45,7 @@ impl KeyChain { pub fn produce_user_address(&self) -> [u8; 32] { let mut hasher = sha2::Sha256::new(); - hasher.update(self.nullifer_public_key); + hasher.update(&self.nullifer_public_key); hasher.update(self.incoming_viewing_public_key.to_bytes()); ::from(hasher.finalize_fixed()) @@ -56,33 +53,20 @@ impl KeyChain { pub fn calculate_shared_secret_receiver( &self, - ephemeral_public_key_sender: AffinePoint, - ) -> AffinePoint { - (ephemeral_public_key_sender - * self - .top_secret_key_holder - .generate_incloming_viewing_secret_key()) - .into() - } - - pub fn decrypt_data( - &self, - ephemeral_public_key_sender: AffinePoint, - ciphertext: CipherText, - nonce: Nonce, - ) -> Result, aes_gcm::Error> { - let shared_secret = self.calculate_shared_secret_receiver(ephemeral_public_key_sender); - let cipher = Aes256Gcm::new(&shared_secret.x()); - - cipher.decrypt(&nonce, ciphertext.as_slice()) + ephemeral_public_key_sender: EphemeralPublicKey, + ) -> SharedSecretKey { + SharedSecretKey::new( + &self + .secret_spending_key + .generate_incoming_viewing_secret_key(), + &ephemeral_public_key_sender, + ) } pub fn log(&self) { info!( "Secret spending key is {:?}", - hex::encode( - serde_json::to_vec(&self.top_secret_key_holder.secret_spending_key).unwrap() - ), + hex::encode(serde_json::to_vec(&self.secret_spending_key).unwrap()), ); info!( "Nulifier secret key is {:?}", @@ -113,17 +97,9 @@ impl KeyChain { #[cfg(test)] mod tests { - use aes_gcm::{ - Aes256Gcm, - aead::{Aead, KeyInit, OsRng}, - }; + use aes_gcm::aead::OsRng; use elliptic_curve::ff::Field; - use elliptic_curve::group::prime::PrimeCurveAffine; - use elliptic_curve::point::AffineCoordinates; - use k256::{AffinePoint, ProjectivePoint, Scalar}; - use types::{CipherText, Nonce}; - - use crate::key_management::ephemeral_key_holder::EphemeralKeyHolder; + use k256::{AffinePoint, Scalar}; use super::*; @@ -133,10 +109,7 @@ mod tests { let address_key_holder = KeyChain::new_os_random(); // Check that key holder fields are initialized with expected types - assert_ne!(address_key_holder.nullifer_public_key, [0u8; 32]); - assert!(!Into::::into( - address_key_holder.incoming_viewing_public_key.is_identity() - )); + assert_ne!(address_key_holder.nullifer_public_key.as_ref(), &[0u8; 32]); } #[test] @@ -145,171 +118,11 @@ mod tests { // Generate a random ephemeral public key sender let scalar = Scalar::random(&mut OsRng); - let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine(); + let ephemeral_public_key_sender = EphemeralPublicKey::from_scalar(scalar); // Calculate shared secret - let shared_secret = + let _shared_secret = address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); - - // Ensure the shared secret is not an identity point (suggesting non-zero output) - assert!(!Into::::into(shared_secret.is_identity())); - } - - #[test] - fn test_decrypt_data() { - let address_key_holder = KeyChain::new_os_random(); - - let test_receiver_nullifier_public_key = [42; 32]; - let sender_outgoing_viewing_key = address_key_holder - .top_secret_key_holder - .generate_outgoing_viewing_secret_key(); - let nonce = 0; - - // Generate an ephemeral key and shared secret - let ephemeral_public_key_sender = EphemeralKeyHolder::new( - test_receiver_nullifier_public_key, - sender_outgoing_viewing_key, - nonce, - ) - .generate_ephemeral_public_key(); - let shared_secret = - address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); - - // Encrypt sample data - let cipher = Aes256Gcm::new(&shared_secret.x()); - let nonce = Nonce::from_slice(b"unique nonce"); - let plaintext = b"Sensitive data"; - let ciphertext = cipher - .encrypt(nonce, plaintext.as_ref()) - .expect("encryption failure"); - - // Attempt decryption - let decrypted_data: Vec = address_key_holder - .decrypt_data( - ephemeral_public_key_sender, - CipherText::from(ciphertext), - *nonce, - ) - .unwrap(); - - // Verify decryption is successful and matches original plaintext - assert_eq!(decrypted_data, plaintext); - } - - #[test] - fn test_calculate_shared_secret_with_identity_point() { - let address_key_holder = KeyChain::new_os_random(); - - // Use identity point as ephemeral public key - let identity_point = AffinePoint::identity(); - - // Calculate shared secret - let shared_secret = address_key_holder.calculate_shared_secret_receiver(identity_point); - - // The shared secret with the identity point should also result in the identity point - assert!(Into::::into(shared_secret.is_identity())); - } - - #[test] - #[should_panic] - fn test_decrypt_data_with_incorrect_nonce() { - let address_key_holder = KeyChain::new_os_random(); - - // Generate ephemeral public key and shared secret - let scalar = Scalar::random(OsRng); - let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine(); - let shared_secret = - address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); - - // Encrypt sample data with a specific nonce - let cipher = Aes256Gcm::new(&shared_secret.x()); - let nonce = Nonce::from_slice(b"unique nonce"); - let plaintext = b"Sensitive data"; - let ciphertext = cipher - .encrypt(nonce, plaintext.as_ref()) - .expect("encryption failure"); - - // Attempt decryption with an incorrect nonce - let incorrect_nonce = Nonce::from_slice(b"wrong nonce"); - let decrypted_data = address_key_holder - .decrypt_data( - ephemeral_public_key_sender, - CipherText::from(ciphertext.clone()), - *incorrect_nonce, - ) - .unwrap(); - - // The decryption should fail or produce incorrect output due to nonce mismatch - assert_ne!(decrypted_data, plaintext); - } - - #[test] - #[should_panic] - fn test_decrypt_data_with_incorrect_ciphertext() { - let address_key_holder = KeyChain::new_os_random(); - - // Generate ephemeral public key and shared secret - let scalar = Scalar::random(OsRng); - let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine(); - let shared_secret = - address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); - - // Encrypt sample data - let cipher = Aes256Gcm::new(&shared_secret.x()); - let nonce = Nonce::from_slice(b"unique nonce"); - let plaintext = b"Sensitive data"; - let ciphertext = cipher - .encrypt(nonce, plaintext.as_ref()) - .expect("encryption failure"); - - // Tamper with the ciphertext to simulate corruption - let mut corrupted_ciphertext = ciphertext.clone(); - corrupted_ciphertext[0] ^= 1; // Flip a bit in the ciphertext - - // Attempt decryption - let result = address_key_holder - .decrypt_data( - ephemeral_public_key_sender, - CipherText::from(corrupted_ciphertext), - *nonce, - ) - .unwrap(); - - // The decryption should fail or produce incorrect output due to tampered ciphertext - assert_ne!(result, plaintext); - } - - #[test] - fn test_encryption_decryption_round_trip() { - let address_key_holder = KeyChain::new_os_random(); - - // Generate ephemeral key and shared secret - let scalar = Scalar::random(OsRng); - let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine(); - - // Encrypt sample data - let plaintext = b"Round-trip test data"; - let nonce = Nonce::from_slice(b"unique nonce"); - - let shared_secret = - address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); - let cipher = Aes256Gcm::new(&shared_secret.x()); - - let ciphertext = cipher - .encrypt(nonce, plaintext.as_ref()) - .expect("encryption failure"); - - // Decrypt the data using the `KeyChain` instance - let decrypted_data = address_key_holder - .decrypt_data( - ephemeral_public_key_sender, - CipherText::from(ciphertext), - *nonce, - ) - .unwrap(); - - // Verify the decrypted data matches the original plaintext - assert_eq!(decrypted_data, plaintext); } #[test] diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 5c2e522..eb59b40 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -1,7 +1,8 @@ use bip39::Mnemonic; use common::TreeHashType; use elliptic_curve::PrimeField; -use k256::{AffinePoint, Scalar}; +use k256::Scalar; +use nssa_core::{NullifierPublicKey, NullifierSecretKey, encryption::IncomingViewingPublicKey}; use rand::{RngCore, rngs::OsRng}; use serde::{Deserialize, Serialize}; use sha2::{Digest, digest::FixedOutput}; @@ -15,17 +16,18 @@ pub struct SeedHolder { } #[derive(Serialize, Deserialize, Debug, Clone)] -///Secret spending key holder. Produces `PrivateKeyHolder` objects. -pub struct TopSecretKeyHolder { - pub(crate) secret_spending_key: [u8; 32], -} +///Secret spending key object. Can produce `PrivateKeyHolder` objects. +pub struct SecretSpendingKey(pub(crate) [u8; 32]); + +pub type IncomingViewingSecretKey = Scalar; +pub type OutgoingViewingSecretKey = Scalar; #[derive(Serialize, Deserialize, Debug, Clone)] ///Private key holder. Produces public keys. Can produce address. Can produce shared secret for recepient. pub struct PrivateKeyHolder { - pub(crate) nullifier_secret_key: [u8; 32], - pub(crate) incoming_viewing_secret_key: Scalar, - pub(crate) outgoing_viewing_secret_key: Scalar, + pub(crate) nullifier_secret_key: NullifierSecretKey, + pub(crate) incoming_viewing_secret_key: IncomingViewingSecretKey, + pub(crate) outgoing_viewing_secret_key: OutgoingViewingSecretKey, } impl SeedHolder { @@ -52,74 +54,65 @@ impl SeedHolder { *hash.first_chunk::<32>().unwrap() } - pub fn produce_top_secret_key_holder(&self) -> TopSecretKeyHolder { - TopSecretKeyHolder { - secret_spending_key: self.generate_secret_spending_key_hash(), - } + pub fn produce_top_secret_key_holder(&self) -> SecretSpendingKey { + SecretSpendingKey(self.generate_secret_spending_key_hash()) } } -impl TopSecretKeyHolder { - pub fn generate_nullifier_secret_key(&self) -> [u8; 32] { +impl SecretSpendingKey { + pub fn generate_nullifier_secret_key(&self) -> NullifierSecretKey { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(self.secret_spending_key); + hasher.update(self.0); hasher.update([1u8]); - hasher.update([0u8; 176]); + hasher.update([0u8; 22]); - ::from(hasher.finalize_fixed()) + ::from(hasher.finalize_fixed()) } - pub fn generate_incloming_viewing_secret_key(&self) -> Scalar { + pub fn generate_incoming_viewing_secret_key(&self) -> IncomingViewingSecretKey { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(self.secret_spending_key); + hasher.update(self.0); hasher.update([2u8]); - hasher.update([0u8; 176]); + hasher.update([0u8; 22]); let hash = ::from(hasher.finalize_fixed()); - Scalar::from_repr(hash.into()).unwrap() + IncomingViewingSecretKey::from_repr(hash.into()).unwrap() } - pub fn generate_outgoing_viewing_secret_key(&self) -> Scalar { + pub fn generate_outgoing_viewing_secret_key(&self) -> OutgoingViewingSecretKey { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(self.secret_spending_key); + hasher.update(self.0); hasher.update([3u8]); - hasher.update([0u8; 176]); + hasher.update([0u8; 22]); let hash = ::from(hasher.finalize_fixed()); - Scalar::from_repr(hash.into()).unwrap() + OutgoingViewingSecretKey::from_repr(hash.into()).unwrap() } pub fn produce_private_key_holder(&self) -> PrivateKeyHolder { PrivateKeyHolder { nullifier_secret_key: self.generate_nullifier_secret_key(), - incoming_viewing_secret_key: self.generate_incloming_viewing_secret_key(), + incoming_viewing_secret_key: self.generate_incoming_viewing_secret_key(), outgoing_viewing_secret_key: self.generate_outgoing_viewing_secret_key(), } } } impl PrivateKeyHolder { - pub fn generate_nullifier_public_key(&self) -> [u8; 32] { - let mut hasher = sha2::Sha256::new(); - - hasher.update("NSSA_keys"); - hasher.update(self.nullifier_secret_key); - hasher.update([7u8]); - hasher.update([0u8; 176]); - - ::from(hasher.finalize_fixed()) + pub fn generate_nullifier_public_key(&self) -> NullifierPublicKey { + (&self.nullifier_secret_key).into() } - pub fn generate_incoming_viewing_public_key(&self) -> AffinePoint { - (AffinePoint::GENERATOR * self.incoming_viewing_secret_key).into() + pub fn generate_incoming_viewing_public_key(&self) -> IncomingViewingPublicKey { + IncomingViewingPublicKey::from_scalar(self.incoming_viewing_secret_key) } } @@ -151,7 +144,7 @@ mod tests { let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - let _ = top_secret_key_holder.generate_incloming_viewing_secret_key(); + let _ = top_secret_key_holder.generate_incoming_viewing_secret_key(); } #[test] diff --git a/key_protocol/src/key_management/types.rs b/key_protocol/src/key_management/types.rs deleted file mode 100644 index 8878f25..0000000 --- a/key_protocol/src/key_management/types.rs +++ /dev/null @@ -1,8 +0,0 @@ -use elliptic_curve::{ - consts::{B0, B1}, - generic_array::GenericArray, -}; -use sha2::digest::typenum::{UInt, UTerm}; - -pub type CipherText = Vec; -pub type Nonce = GenericArray, B1>, B0>, B0>>; diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index 3163902..96f6530 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -14,6 +14,7 @@ secp256k1 = "0.31.1" rand = "0.8" borsh = "1.5.7" hex = "0.4.3" +k256 = "0.13.3" [dev-dependencies] test-program-methods = { path = "test_program_methods" } diff --git a/nssa/core/src/encryption/shared_key_derivation.rs b/nssa/core/src/encryption/shared_key_derivation.rs index c735105..6392d6a 100644 --- a/nssa/core/src/encryption/shared_key_derivation.rs +++ b/nssa/core/src/encryption/shared_key_derivation.rs @@ -14,7 +14,7 @@ use crate::SharedSecretKey; pub struct Secp256k1Point(pub(crate) Vec); impl Secp256k1Point { - pub fn from_scalar(value: [u8; 32]) -> Secp256k1Point { + pub fn from_scalar(value: Scalar) -> Secp256k1Point { let x_bytes: FieldBytes = value.into(); let x = Scalar::from_repr(x_bytes).unwrap(); @@ -26,7 +26,7 @@ impl Secp256k1Point { } } -pub type EphemeralSecretKey = [u8; 32]; +pub type EphemeralSecretKey = Scalar; pub type EphemeralPublicKey = Secp256k1Point; pub type IncomingViewingPublicKey = Secp256k1Point; impl From<&EphemeralSecretKey> for EphemeralPublicKey { @@ -36,8 +36,7 @@ impl From<&EphemeralSecretKey> for EphemeralPublicKey { } impl SharedSecretKey { - pub fn new(scalar: &[u8; 32], point: &Secp256k1Point) -> Self { - let scalar = Scalar::from_repr((*scalar).into()).unwrap(); + pub fn new(scalar: &Scalar, point: &Secp256k1Point) -> Self { let point: [u8; 33] = point.0.clone().try_into().unwrap(); let encoded = EncodedPoint::from_bytes(point).unwrap(); diff --git a/nssa/core/src/nullifier.rs b/nssa/core/src/nullifier.rs index d1410de..bc73d8c 100644 --- a/nssa/core/src/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -7,6 +7,12 @@ use crate::Commitment; #[cfg_attr(any(feature = "host", test), derive(Debug, Clone, Hash))] pub struct NullifierPublicKey(pub(super) [u8; 32]); +impl AsRef<[u8]> for NullifierPublicKey { + fn as_ref(&self) -> &[u8] { + self.0.as_slice() + } +} + impl From<&NullifierSecretKey> for NullifierPublicKey { fn from(value: &NullifierSecretKey) -> Self { let mut bytes = Vec::new(); diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 1421f62..f350ccf 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -88,6 +88,7 @@ impl Proof { #[cfg(test)] mod tests { + use k256::{Scalar, elliptic_curve::PrimeField}; use nssa_core::{ Commitment, EncryptionScheme, Nullifier, account::{Account, AccountWithMetadata}, @@ -139,7 +140,7 @@ mod tests { let expected_sender_pre = sender.clone(); let recipient_keys = test_private_account_keys_1(); - let esk = [3; 32]; + let esk = Scalar::from_repr([3; 32].into()).unwrap(); let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk()); let (output, proof) = execute_and_prove( @@ -220,10 +221,10 @@ mod tests { Commitment::new(&recipient_keys.npk(), &expected_private_account_2), ]; - let esk_1 = [3; 32]; + let esk_1 = Scalar::from_repr([3; 32].into()).unwrap(); let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk()); - let esk_2 = [5; 32]; + let esk_2 = Scalar::from_repr([5; 32].into()).unwrap(); let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk()); let (output, proof) = execute_and_prove( diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 244a81f..9023e52 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -90,6 +90,7 @@ impl Message { #[cfg(test)] pub mod tests { + use k256::{Scalar, elliptic_curve::PrimeField}; use std::io::Cursor; use nssa_core::{ @@ -151,10 +152,10 @@ pub mod tests { #[test] fn test_encrypted_account_data_constructor() { let npk = NullifierPublicKey::from(&[1; 32]); - let ivk = IncomingViewingPublicKey::from(&[2; 32]); + let ivk = IncomingViewingPublicKey::from(&Scalar::from_repr([2; 32].into()).unwrap()); let account = Account::default(); let commitment = Commitment::new(&npk, &account); - let esk = [3; 32]; + let esk = Scalar::from_repr([3; 32].into()).unwrap(); let shared_secret = SharedSecretKey::new(&esk, &ivk); let epk = EphemeralPublicKey::from_scalar(esk); let ciphertext = EncryptionScheme::encrypt(&account, &shared_secret, &commitment, 2); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index f980370..71e5f5e 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -227,6 +227,7 @@ pub mod tests { signature::PrivateKey, }; + use k256::{Scalar, elliptic_curve::PrimeField}; use nssa_core::{ Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, account::{Account, AccountWithMetadata, Nonce}, @@ -743,7 +744,7 @@ pub mod tests { pub struct TestPrivateKeys { pub nsk: NullifierSecretKey, - pub isk: [u8; 32], + pub isk: Scalar, } impl TestPrivateKeys { @@ -759,14 +760,14 @@ pub mod tests { pub fn test_private_account_keys_1() -> TestPrivateKeys { TestPrivateKeys { nsk: [13; 32], - isk: [31; 32], + isk: Scalar::from_repr([31; 32].into()).unwrap(), } } pub fn test_private_account_keys_2() -> TestPrivateKeys { TestPrivateKeys { nsk: [38; 32], - isk: [83; 32], + isk: Scalar::from_repr([83; 32].into()).unwrap(), } } @@ -788,7 +789,7 @@ pub mod tests { is_authorized: false, }; - let esk = [3; 32]; + let esk = Scalar::from_repr([3; 32].into()).unwrap(); let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk()); let epk = EphemeralPublicKey::from_scalar(esk); @@ -834,11 +835,11 @@ pub mod tests { is_authorized: false, }; - let esk_1 = [3; 32]; + let esk_1 = Scalar::from_repr([3; 32].into()).unwrap(); 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 esk_2 = Scalar::from_repr([3; 32].into()).unwrap(); let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk()); let epk_2 = EphemeralPublicKey::from_scalar(esk_2); @@ -894,7 +895,7 @@ pub mod tests { is_authorized: false, }; - let esk = [3; 32]; + let esk = Scalar::from_repr([3; 32].into()).unwrap(); let shared_secret = SharedSecretKey::new(&esk, &sender_keys.ivk()); let epk = EphemeralPublicKey::from_scalar(esk); From 85a16a2f04c35929ced1052da89d9c781cc209c1 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Wed, 17 Sep 2025 08:59:14 +0300 Subject: [PATCH 10/11] fix: revers of scalar dep --- .../src/key_management/ephemeral_key_holder.rs | 6 ++---- key_protocol/src/key_management/mod.rs | 7 ++++--- key_protocol/src/key_management/secret_holders.rs | 15 ++++++--------- nssa/core/src/encryption/mod.rs | 2 ++ nssa/core/src/encryption/shared_key_derivation.rs | 7 ++++--- .../src/privacy_preserving_transaction/circuit.rs | 7 +++---- .../src/privacy_preserving_transaction/message.rs | 5 ++--- nssa/src/state.rs | 15 +++++++-------- 8 files changed, 30 insertions(+), 34 deletions(-) diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index e62d9b6..b4835ff 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -1,5 +1,3 @@ -use elliptic_curve::PrimeField; -use k256::Scalar; use log::info; use nssa_core::{ NullifierPublicKey, SharedSecretKey, @@ -29,11 +27,11 @@ impl EphemeralKeyHolder { let hash_recepient = hasher.finalize(); let mut hasher = sha2::Sha256::new(); - hasher.update(sender_outgoing_viewing_secret_key.to_bytes()); + hasher.update(sender_outgoing_viewing_secret_key); hasher.update(hash_recepient); Self { - ephemeral_secret_key: Scalar::from_repr(hasher.finalize()).unwrap(), + ephemeral_secret_key: hasher.finalize().into(), } } diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index bcd3796..5f5f2aa 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -98,8 +98,8 @@ impl KeyChain { #[cfg(test)] mod tests { use aes_gcm::aead::OsRng; - use elliptic_curve::ff::Field; - use k256::{AffinePoint, Scalar}; + use k256::AffinePoint; + use rand::RngCore; use super::*; @@ -117,7 +117,8 @@ mod tests { let address_key_holder = KeyChain::new_os_random(); // Generate a random ephemeral public key sender - let scalar = Scalar::random(&mut OsRng); + let mut scalar = [0; 32]; + OsRng.fill_bytes(&mut scalar); let ephemeral_public_key_sender = EphemeralPublicKey::from_scalar(scalar); // Calculate shared secret diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index eb59b40..80ec1b0 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -1,8 +1,9 @@ use bip39::Mnemonic; use common::TreeHashType; -use elliptic_curve::PrimeField; -use k256::Scalar; -use nssa_core::{NullifierPublicKey, NullifierSecretKey, encryption::IncomingViewingPublicKey}; +use nssa_core::{ + NullifierPublicKey, NullifierSecretKey, + encryption::{IncomingViewingPublicKey, Scalar}, +}; use rand::{RngCore, rngs::OsRng}; use serde::{Deserialize, Serialize}; use sha2::{Digest, digest::FixedOutput}; @@ -79,9 +80,7 @@ impl SecretSpendingKey { hasher.update([2u8]); hasher.update([0u8; 22]); - let hash = ::from(hasher.finalize_fixed()); - - IncomingViewingSecretKey::from_repr(hash.into()).unwrap() + ::from(hasher.finalize_fixed()) } pub fn generate_outgoing_viewing_secret_key(&self) -> OutgoingViewingSecretKey { @@ -92,9 +91,7 @@ impl SecretSpendingKey { hasher.update([3u8]); hasher.update([0u8; 22]); - let hash = ::from(hasher.finalize_fixed()); - - OutgoingViewingSecretKey::from_repr(hash.into()).unwrap() + ::from(hasher.finalize_fixed()) } pub fn produce_private_key_holder(&self) -> PrivateKeyHolder { diff --git a/nssa/core/src/encryption/mod.rs b/nssa/core/src/encryption/mod.rs index b79e75c..7b8d51f 100644 --- a/nssa/core/src/encryption/mod.rs +++ b/nssa/core/src/encryption/mod.rs @@ -13,6 +13,8 @@ pub use shared_key_derivation::{EphemeralPublicKey, EphemeralSecretKey, Incoming use crate::{Commitment, account::Account}; +pub type Scalar = [u8; 32]; + #[derive(Serialize, Deserialize, Clone)] pub struct SharedSecretKey([u8; 32]); diff --git a/nssa/core/src/encryption/shared_key_derivation.rs b/nssa/core/src/encryption/shared_key_derivation.rs index 6392d6a..a889123 100644 --- a/nssa/core/src/encryption/shared_key_derivation.rs +++ b/nssa/core/src/encryption/shared_key_derivation.rs @@ -1,14 +1,14 @@ use serde::{Deserialize, Serialize}; use k256::{ - AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint, Scalar, + AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint, elliptic_curve::{ PrimeField, sec1::{FromEncodedPoint, ToEncodedPoint}, }, }; -use crate::SharedSecretKey; +use crate::{SharedSecretKey, encryption::Scalar}; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct Secp256k1Point(pub(crate) Vec); @@ -16,7 +16,7 @@ pub struct Secp256k1Point(pub(crate) Vec); impl Secp256k1Point { pub fn from_scalar(value: Scalar) -> Secp256k1Point { let x_bytes: FieldBytes = value.into(); - let x = Scalar::from_repr(x_bytes).unwrap(); + let x = k256::Scalar::from_repr(x_bytes).unwrap(); let p = ProjectivePoint::GENERATOR * x; let q = AffinePoint::from(p); @@ -37,6 +37,7 @@ impl From<&EphemeralSecretKey> for EphemeralPublicKey { impl SharedSecretKey { pub fn new(scalar: &Scalar, point: &Secp256k1Point) -> Self { + let scalar = k256::Scalar::from_repr((*scalar).into()).unwrap(); let point: [u8; 33] = point.0.clone().try_into().unwrap(); let encoded = EncodedPoint::from_bytes(point).unwrap(); diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index f350ccf..fef44bc 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -88,7 +88,6 @@ impl Proof { #[cfg(test)] mod tests { - use k256::{Scalar, elliptic_curve::PrimeField}; use nssa_core::{ Commitment, EncryptionScheme, Nullifier, account::{Account, AccountWithMetadata}, @@ -140,7 +139,7 @@ mod tests { let expected_sender_pre = sender.clone(); let recipient_keys = test_private_account_keys_1(); - let esk = Scalar::from_repr([3; 32].into()).unwrap(); + let esk = [3; 32]; let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk()); let (output, proof) = execute_and_prove( @@ -221,10 +220,10 @@ mod tests { Commitment::new(&recipient_keys.npk(), &expected_private_account_2), ]; - let esk_1 = Scalar::from_repr([3; 32].into()).unwrap(); + let esk_1 = [3; 32].into(); let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk()); - let esk_2 = Scalar::from_repr([5; 32].into()).unwrap(); + let esk_2 = [5; 32]; let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk()); let (output, proof) = execute_and_prove( diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 9023e52..a769c72 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -90,7 +90,6 @@ impl Message { #[cfg(test)] pub mod tests { - use k256::{Scalar, elliptic_curve::PrimeField}; use std::io::Cursor; use nssa_core::{ @@ -152,10 +151,10 @@ pub mod tests { #[test] fn test_encrypted_account_data_constructor() { let npk = NullifierPublicKey::from(&[1; 32]); - let ivk = IncomingViewingPublicKey::from(&Scalar::from_repr([2; 32].into()).unwrap()); + let ivk = IncomingViewingPublicKey::from_scalar([2; 32]); let account = Account::default(); let commitment = Commitment::new(&npk, &account); - let esk = Scalar::from_repr([3; 32].into()).unwrap(); + let esk = [3; 32]; let shared_secret = SharedSecretKey::new(&esk, &ivk); let epk = EphemeralPublicKey::from_scalar(esk); let ciphertext = EncryptionScheme::encrypt(&account, &shared_secret, &commitment, 2); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 71e5f5e..7922bad 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -227,11 +227,10 @@ pub mod tests { signature::PrivateKey, }; - use k256::{Scalar, elliptic_curve::PrimeField}; use nssa_core::{ Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, account::{Account, AccountWithMetadata, Nonce}, - encryption::{EphemeralPublicKey, IncomingViewingPublicKey}, + encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar}, }; fn transfer_transaction( @@ -760,14 +759,14 @@ pub mod tests { pub fn test_private_account_keys_1() -> TestPrivateKeys { TestPrivateKeys { nsk: [13; 32], - isk: Scalar::from_repr([31; 32].into()).unwrap(), + isk: [31; 32], } } pub fn test_private_account_keys_2() -> TestPrivateKeys { TestPrivateKeys { nsk: [38; 32], - isk: Scalar::from_repr([83; 32].into()).unwrap(), + isk: [83; 32], } } @@ -789,7 +788,7 @@ pub mod tests { is_authorized: false, }; - let esk = Scalar::from_repr([3; 32].into()).unwrap(); + let esk = [3; 32]; let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk()); let epk = EphemeralPublicKey::from_scalar(esk); @@ -835,11 +834,11 @@ pub mod tests { is_authorized: false, }; - let esk_1 = Scalar::from_repr([3; 32].into()).unwrap(); + let esk_1 = [3; 32]; let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk()); let epk_1 = EphemeralPublicKey::from_scalar(esk_1); - let esk_2 = Scalar::from_repr([3; 32].into()).unwrap(); + let esk_2 = [3; 32]; let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk()); let epk_2 = EphemeralPublicKey::from_scalar(esk_2); @@ -895,7 +894,7 @@ pub mod tests { is_authorized: false, }; - let esk = Scalar::from_repr([3; 32].into()).unwrap(); + let esk = [3; 32]; let shared_secret = SharedSecretKey::new(&esk, &sender_keys.ivk()); let epk = EphemeralPublicKey::from_scalar(esk); From f75fab89b0c00637ea965f269a0769c32547fced Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Wed, 17 Sep 2025 09:38:46 +0300 Subject: [PATCH 11/11] fix: lint fix --- nssa/src/privacy_preserving_transaction/circuit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index fef44bc..1421f62 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -220,7 +220,7 @@ mod tests { Commitment::new(&recipient_keys.npk(), &expected_private_account_2), ]; - let esk_1 = [3; 32].into(); + let esk_1 = [3; 32]; let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk()); let esk_2 = [5; 32];