diff --git a/zk-token-sdk/src/encryption/auth_encryption.rs b/zk-token-sdk/src/encryption/auth_encryption.rs index 14e2f3cf297cbb..4445a40dfc1689 100644 --- a/zk-token-sdk/src/encryption/auth_encryption.rs +++ b/zk-token-sdk/src/encryption/auth_encryption.rs @@ -31,6 +31,19 @@ use { zeroize::Zeroize, }; +/// Byte length of an authenticated encryption secret key +const AE_KEY_LEN: usize = 16; + +/// Byte length of an authenticated encryption nonce component +const NONCE_LEN: usize = 12; + +/// Byte lenth of an authenticated encryption ciphertext component +const CIPHERTEXT_LEN: usize = 24; + +/// Byte length of a complete authenticated encryption ciphertext component that includes the +/// ciphertext and nonce components +const AE_CIPHERTEXT_LEN: usize = 36; + #[derive(Error, Clone, Debug, Eq, PartialEq)] pub enum AuthenticatedEncryptionError { #[error("key derivation method not supported")] @@ -46,7 +59,7 @@ impl AuthenticatedEncryption { /// This function is randomized. It internally samples a 128-bit key using `OsRng`. #[cfg(not(target_os = "solana"))] fn keygen() -> AeKey { - AeKey(OsRng.gen::<[u8; 16]>()) + AeKey(OsRng.gen::<[u8; AE_KEY_LEN]>()) } /// On input of an authenticated encryption key and an amount, the function returns a @@ -54,7 +67,7 @@ impl AuthenticatedEncryption { #[cfg(not(target_os = "solana"))] fn encrypt(key: &AeKey, balance: u64) -> AeCiphertext { let mut plaintext = balance.to_le_bytes(); - let nonce: Nonce = OsRng.gen::<[u8; 12]>(); + let nonce: Nonce = OsRng.gen::<[u8; NONCE_LEN]>(); // The balance and the nonce have fixed length and therefore, encryption should not fail. let ciphertext = Aes128GcmSiv::new(&key.0.into()) @@ -86,7 +99,7 @@ impl AuthenticatedEncryption { } #[derive(Debug, Zeroize)] -pub struct AeKey([u8; 16]); +pub struct AeKey([u8; AE_KEY_LEN]); impl AeKey { /// Deterministically derives an authenticated encryption key from a Solana signer and a public /// seed. @@ -144,7 +157,7 @@ impl AeKey { impl EncodableKey for AeKey { fn read(reader: &mut R) -> Result> { - let bytes: [u8; 16] = serde_json::from_reader(reader)?; + let bytes: [u8; AE_KEY_LEN] = serde_json::from_reader(reader)?; Ok(Self(bytes)) } @@ -158,7 +171,7 @@ impl EncodableKey for AeKey { impl SeedDerivable for AeKey { fn from_seed(seed: &[u8]) -> Result> { - const MINIMUM_SEED_LEN: usize = 16; + const MINIMUM_SEED_LEN: usize = AE_KEY_LEN; if seed.len() < MINIMUM_SEED_LEN { return Err(AuthenticatedEncryptionError::SeedLengthTooShort.into()); @@ -168,7 +181,7 @@ impl SeedDerivable for AeKey { hasher.update(seed); let result = hasher.finalize(); - Ok(Self(result[..16].try_into()?)) + Ok(Self(result[..AE_KEY_LEN].try_into()?)) } fn from_seed_and_derivation_path( @@ -191,8 +204,8 @@ impl SeedDerivable for AeKey { /// For the purpose of encrypting balances for the spl token accounts, the nonce and ciphertext /// sizes should always be fixed. -type Nonce = [u8; 12]; -type Ciphertext = [u8; 24]; +type Nonce = [u8; NONCE_LEN]; +type Ciphertext = [u8; CIPHERTEXT_LEN]; /// Authenticated encryption nonce and ciphertext #[derive(Debug, Default, Clone)] @@ -205,20 +218,20 @@ impl AeCiphertext { AuthenticatedEncryption::decrypt(key, self) } - pub fn to_bytes(&self) -> [u8; 36] { - let mut buf = [0_u8; 36]; - buf[..12].copy_from_slice(&self.nonce); - buf[12..].copy_from_slice(&self.ciphertext); + pub fn to_bytes(&self) -> [u8; AE_CIPHERTEXT_LEN] { + let mut buf = [0_u8; AE_CIPHERTEXT_LEN]; + buf[..NONCE_LEN].copy_from_slice(&self.nonce); + buf[NONCE_LEN..].copy_from_slice(&self.ciphertext); buf } pub fn from_bytes(bytes: &[u8]) -> Option { - if bytes.len() != 36 { + if bytes.len() != AE_CIPHERTEXT_LEN { return None; } - let nonce = bytes[..32].try_into().ok()?; - let ciphertext = bytes[32..].try_into().ok()?; + let nonce = bytes[..NONCE_LEN].try_into().ok()?; + let ciphertext = bytes[NONCE_LEN..].try_into().ok()?; Some(AeCiphertext { nonce, ciphertext }) } diff --git a/zk-token-sdk/src/encryption/discrete_log.rs b/zk-token-sdk/src/encryption/discrete_log.rs index 668dbecce9ebff..7f98918823225a 100644 --- a/zk-token-sdk/src/encryption/discrete_log.rs +++ b/zk-token-sdk/src/encryption/discrete_log.rs @@ -17,6 +17,7 @@ #![cfg(not(target_os = "solana"))] use { + crate::RISTRETTO_POINT_LEN, curve25519_dalek::{ constants::RISTRETTO_BASEPOINT_POINT as G, ristretto::RistrettoPoint, @@ -32,6 +33,9 @@ use { const TWO16: u64 = 65536; // 2^16 const TWO17: u64 = 131072; // 2^17 +/// Maximum number of threads permitted for discrete log computation +const MAX_THREAD: usize = 65536; + #[derive(Error, Clone, Debug, Eq, PartialEq)] pub enum DiscreteLogError { #[error("discrete log number of threads not power-of-two")] @@ -61,7 +65,7 @@ pub struct DiscreteLog { } #[derive(Serialize, Deserialize, Default)] -pub struct DecodePrecomputation(HashMap<[u8; 32], u16>); +pub struct DecodePrecomputation(HashMap<[u8; RISTRETTO_POINT_LEN], u16>); /// Builds a HashMap of 2^16 elements #[allow(dead_code)] @@ -110,7 +114,7 @@ impl DiscreteLog { /// Adjusts number of threads in a discrete log instance. pub fn num_threads(&mut self, num_threads: usize) -> Result<(), DiscreteLogError> { // number of threads must be a positive power-of-two integer - if num_threads == 0 || (num_threads & (num_threads - 1)) != 0 || num_threads > 65536 { + if num_threads == 0 || (num_threads & (num_threads - 1)) != 0 || num_threads > MAX_THREAD { return Err(DiscreteLogError::DiscreteLogThreads); } diff --git a/zk-token-sdk/src/encryption/elgamal.rs b/zk-token-sdk/src/encryption/elgamal.rs index 339454c4d7c88b..c57a10b740024e 100644 --- a/zk-token-sdk/src/encryption/elgamal.rs +++ b/zk-token-sdk/src/encryption/elgamal.rs @@ -14,9 +14,14 @@ //! discrete log to recover the originally encrypted value. use { - crate::encryption::{ - discrete_log::DiscreteLog, - pedersen::{Pedersen, PedersenCommitment, PedersenOpening, G, H}, + crate::{ + encryption::{ + discrete_log::DiscreteLog, + pedersen::{ + Pedersen, PedersenCommitment, PedersenOpening, G, H, PEDERSEN_COMMITMENT_LEN, + }, + }, + RISTRETTO_POINT_LEN, SCALAR_LEN, }, base64::{prelude::BASE64_STANDARD, Engine}, core::ops::{Add, Mul, Sub}, @@ -50,6 +55,21 @@ use { }, }; +/// Byte length of a decrypt handle +const DECRYPT_HANDLE_LEN: usize = RISTRETTO_POINT_LEN; + +/// Byte length of an ElGamal ciphertext +const ELGAMAL_CIPHERTEXT_LEN: usize = PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN; + +/// Byte length of an ElGamal public key +const ELGAMAL_PUBKEY_LEN: usize = RISTRETTO_POINT_LEN; + +/// Byte length of an ElGamal secret key +const ELGAMAL_SECRET_KEY_LEN: usize = SCALAR_LEN; + +/// Byte length of an ElGamal keypair +const ELGAMAL_KEYPAIR_LEN: usize = ELGAMAL_PUBKEY_LEN + ELGAMAL_SECRET_KEY_LEN; + #[derive(Error, Clone, Debug, Eq, PartialEq)] pub enum ElGamalError { #[error("key derivation method not supported")] @@ -209,21 +229,21 @@ impl ElGamalKeypair { &self.secret } - pub fn to_bytes(&self) -> [u8; 64] { - let mut bytes = [0u8; 64]; - bytes[..32].copy_from_slice(&self.public.to_bytes()); - bytes[32..].copy_from_slice(self.secret.as_bytes()); + pub fn to_bytes(&self) -> [u8; ELGAMAL_KEYPAIR_LEN] { + let mut bytes = [0u8; ELGAMAL_KEYPAIR_LEN]; + bytes[..ELGAMAL_PUBKEY_LEN].copy_from_slice(&self.public.to_bytes()); + bytes[ELGAMAL_PUBKEY_LEN..].copy_from_slice(self.secret.as_bytes()); bytes } pub fn from_bytes(bytes: &[u8]) -> Option { - if bytes.len() != 64 { + if bytes.len() != ELGAMAL_KEYPAIR_LEN { return None; } Some(Self { - public: ElGamalPubkey::from_bytes(&bytes[..32])?, - secret: ElGamalSecretKey::from_bytes(bytes[32..].try_into().ok()?)?, + public: ElGamalPubkey::from_bytes(&bytes[..ELGAMAL_PUBKEY_LEN])?, + secret: ElGamalSecretKey::from_bytes(bytes[ELGAMAL_PUBKEY_LEN..].try_into().ok()?)?, }) } @@ -317,12 +337,12 @@ impl ElGamalPubkey { &self.0 } - pub fn to_bytes(&self) -> [u8; 32] { + pub fn to_bytes(&self) -> [u8; ELGAMAL_PUBKEY_LEN] { self.0.compress().to_bytes() } pub fn from_bytes(bytes: &[u8]) -> Option { - if bytes.len() != 32 { + if bytes.len() != ELGAMAL_PUBKEY_LEN { return None; } @@ -428,7 +448,7 @@ impl ElGamalSecretKey { /// Derive an ElGamal secret key from an entropy seed. pub fn from_seed(seed: &[u8]) -> Result { - const MINIMUM_SEED_LEN: usize = 32; + const MINIMUM_SEED_LEN: usize = ELGAMAL_SECRET_KEY_LEN; if seed.len() < MINIMUM_SEED_LEN { return Err(ElGamalError::SeedLengthTooShort); @@ -453,11 +473,11 @@ impl ElGamalSecretKey { ElGamal::decrypt_u32(self, ciphertext) } - pub fn as_bytes(&self) -> &[u8; 32] { + pub fn as_bytes(&self) -> &[u8; ELGAMAL_SECRET_KEY_LEN] { self.0.as_bytes() } - pub fn to_bytes(&self) -> [u8; 32] { + pub fn to_bytes(&self) -> [u8; ELGAMAL_SECRET_KEY_LEN] { self.0.to_bytes() } @@ -554,21 +574,21 @@ impl ElGamalCiphertext { } } - pub fn to_bytes(&self) -> [u8; 64] { - let mut bytes = [0u8; 64]; - bytes[..32].copy_from_slice(&self.commitment.to_bytes()); - bytes[32..].copy_from_slice(&self.handle.to_bytes()); + pub fn to_bytes(&self) -> [u8; ELGAMAL_CIPHERTEXT_LEN] { + let mut bytes = [0u8; ELGAMAL_CIPHERTEXT_LEN]; + bytes[..PEDERSEN_COMMITMENT_LEN].copy_from_slice(&self.commitment.to_bytes()); + bytes[PEDERSEN_COMMITMENT_LEN..].copy_from_slice(&self.handle.to_bytes()); bytes } pub fn from_bytes(bytes: &[u8]) -> Option { - if bytes.len() != 64 { + if bytes.len() != ELGAMAL_CIPHERTEXT_LEN { return None; } Some(ElGamalCiphertext { - commitment: PedersenCommitment::from_bytes(&bytes[..32])?, - handle: DecryptHandle::from_bytes(&bytes[32..])?, + commitment: PedersenCommitment::from_bytes(&bytes[..PEDERSEN_COMMITMENT_LEN])?, + handle: DecryptHandle::from_bytes(&bytes[PEDERSEN_COMMITMENT_LEN..])?, }) } @@ -676,12 +696,12 @@ impl DecryptHandle { &self.0 } - pub fn to_bytes(&self) -> [u8; 32] { + pub fn to_bytes(&self) -> [u8; DECRYPT_HANDLE_LEN] { self.0.compress().to_bytes() } pub fn from_bytes(bytes: &[u8]) -> Option { - if bytes.len() != 32 { + if bytes.len() != DECRYPT_HANDLE_LEN { return None; } diff --git a/zk-token-sdk/src/encryption/grouped_elgamal.rs b/zk-token-sdk/src/encryption/grouped_elgamal.rs index 1038c87fed17d1..c73e7bf2772ddb 100644 --- a/zk-token-sdk/src/encryption/grouped_elgamal.rs +++ b/zk-token-sdk/src/encryption/grouped_elgamal.rs @@ -12,10 +12,13 @@ //! use { - crate::encryption::{ - discrete_log::DiscreteLog, - elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalPubkey, ElGamalSecretKey}, - pedersen::{Pedersen, PedersenCommitment, PedersenOpening}, + crate::{ + encryption::{ + discrete_log::DiscreteLog, + elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalPubkey, ElGamalSecretKey}, + pedersen::{Pedersen, PedersenCommitment, PedersenOpening}, + }, + RISTRETTO_POINT_LEN, }, curve25519_dalek::scalar::Scalar, thiserror::Error, @@ -163,7 +166,7 @@ impl GroupedElGamalCiphertext { /// `(N+1) * 32`. fn expected_byte_length() -> usize { N.checked_add(1) - .and_then(|length| length.checked_mul(32)) + .and_then(|length| length.checked_mul(RISTRETTO_POINT_LEN)) .unwrap() } @@ -181,7 +184,7 @@ impl GroupedElGamalCiphertext { return None; } - let mut iter = bytes.chunks(32); + let mut iter = bytes.chunks(RISTRETTO_POINT_LEN); let commitment = PedersenCommitment::from_bytes(iter.next()?)?; let mut handles = Vec::with_capacity(N); diff --git a/zk-token-sdk/src/encryption/pedersen.rs b/zk-token-sdk/src/encryption/pedersen.rs index 3a6db74a4eb0e1..2de593771590e6 100644 --- a/zk-token-sdk/src/encryption/pedersen.rs +++ b/zk-token-sdk/src/encryption/pedersen.rs @@ -3,6 +3,7 @@ #[cfg(not(target_os = "solana"))] use rand::rngs::OsRng; use { + crate::{RISTRETTO_POINT_LEN, SCALAR_LEN}, core::ops::{Add, Mul, Sub}, curve25519_dalek::{ constants::{RISTRETTO_BASEPOINT_COMPRESSED, RISTRETTO_BASEPOINT_POINT}, @@ -17,6 +18,12 @@ use { zeroize::Zeroize, }; +/// Byte length of a Pedersen opening. +const PEDERSEN_OPENING_LEN: usize = SCALAR_LEN; + +/// Byte length of a Pedersen commitment. +pub(crate) const PEDERSEN_COMMITMENT_LEN: usize = RISTRETTO_POINT_LEN; + lazy_static::lazy_static! { /// Pedersen base point for encoding messages to be committed. pub static ref G: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; @@ -82,11 +89,11 @@ impl PedersenOpening { PedersenOpening(Scalar::random(&mut OsRng)) } - pub fn as_bytes(&self) -> &[u8; 32] { + pub fn as_bytes(&self) -> &[u8; PEDERSEN_OPENING_LEN] { self.0.as_bytes() } - pub fn to_bytes(&self) -> [u8; 32] { + pub fn to_bytes(&self) -> [u8; PEDERSEN_OPENING_LEN] { self.0.to_bytes() } @@ -177,12 +184,12 @@ impl PedersenCommitment { &self.0 } - pub fn to_bytes(&self) -> [u8; 32] { + pub fn to_bytes(&self) -> [u8; PEDERSEN_COMMITMENT_LEN] { self.0.compress().to_bytes() } pub fn from_bytes(bytes: &[u8]) -> Option { - if bytes.len() != 32 { + if bytes.len() != PEDERSEN_COMMITMENT_LEN { return None; } diff --git a/zk-token-sdk/src/lib.rs b/zk-token-sdk/src/lib.rs index 14ce5b4380af47..9f4e9b7a5dcbac 100644 --- a/zk-token-sdk/src/lib.rs +++ b/zk-token-sdk/src/lib.rs @@ -38,3 +38,10 @@ pub mod zk_token_elgamal; pub mod zk_token_proof_instruction; pub mod zk_token_proof_program; pub mod zk_token_proof_state; + +/// Byte length of a compressed Ristretto point or scalar in Curve255519 +const UNIT_LEN: usize = 32; +/// Byte length of a compressed Ristretto point in Curve25519 +const RISTRETTO_POINT_LEN: usize = UNIT_LEN; +/// Byte length of a scalar in Curve25519 +const SCALAR_LEN: usize = UNIT_LEN; diff --git a/zk-token-sdk/src/sigma_proofs/ciphertext_ciphertext_equality_proof.rs b/zk-token-sdk/src/sigma_proofs/ciphertext_ciphertext_equality_proof.rs index 0ed819465021e7..d33587373c3072 100644 --- a/zk-token-sdk/src/sigma_proofs/ciphertext_ciphertext_equality_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/ciphertext_ciphertext_equality_proof.rs @@ -11,7 +11,8 @@ use { pedersen::{PedersenOpening, G, H}, }, errors::ProofVerificationError, - sigma_proofs::canonical_scalar_from_slice, + sigma_proofs::{canonical_scalar_from_optional_slice, ristretto_point_from_optional_slice}, + UNIT_LEN, }, curve25519_dalek::traits::MultiscalarMul, rand::rngs::OsRng, @@ -27,6 +28,9 @@ use { merlin::Transcript, }; +/// Byte length of a ciphertext-ciphertext equality proof. +const CIPHERTEXT_CIPHERTEXT_EQUALITY_PROOF_LEN: usize = UNIT_LEN * 7; + /// The ciphertext-ciphertext equality proof. /// /// Contains all the elliptic curve and scalar components that make up the sigma protocol. @@ -221,30 +225,31 @@ impl CiphertextCiphertextEqualityProof { } } - pub fn to_bytes(&self) -> [u8; 224] { - let mut buf = [0_u8; 224]; - buf[..32].copy_from_slice(self.Y_0.as_bytes()); - buf[32..64].copy_from_slice(self.Y_1.as_bytes()); - buf[64..96].copy_from_slice(self.Y_2.as_bytes()); - buf[96..128].copy_from_slice(self.Y_3.as_bytes()); - buf[128..160].copy_from_slice(self.z_s.as_bytes()); - buf[160..192].copy_from_slice(self.z_x.as_bytes()); - buf[192..224].copy_from_slice(self.z_r.as_bytes()); + pub fn to_bytes(&self) -> [u8; CIPHERTEXT_CIPHERTEXT_EQUALITY_PROOF_LEN] { + let mut buf = [0_u8; CIPHERTEXT_CIPHERTEXT_EQUALITY_PROOF_LEN]; + let mut chunks = buf.chunks_mut(UNIT_LEN); + + chunks.next().unwrap().copy_from_slice(self.Y_0.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.Y_1.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.Y_2.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.Y_3.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.z_s.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.z_x.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.z_r.as_bytes()); + buf } pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != 224 { - return Err(ProofVerificationError::Deserialization.into()); - } - - let Y_0 = CompressedRistretto::from_slice(&bytes[..32]); - let Y_1 = CompressedRistretto::from_slice(&bytes[32..64]); - let Y_2 = CompressedRistretto::from_slice(&bytes[64..96]); - let Y_3 = CompressedRistretto::from_slice(&bytes[96..128]); - let z_s = canonical_scalar_from_slice(&bytes[128..160])?; - let z_x = canonical_scalar_from_slice(&bytes[160..192])?; - let z_r = canonical_scalar_from_slice(&bytes[192..224])?; + let mut chunks = bytes.chunks(UNIT_LEN); + + let Y_0 = ristretto_point_from_optional_slice(chunks.next())?; + let Y_1 = ristretto_point_from_optional_slice(chunks.next())?; + let Y_2 = ristretto_point_from_optional_slice(chunks.next())?; + let Y_3 = ristretto_point_from_optional_slice(chunks.next())?; + let z_s = canonical_scalar_from_optional_slice(chunks.next())?; + let z_x = canonical_scalar_from_optional_slice(chunks.next())?; + let z_r = canonical_scalar_from_optional_slice(chunks.next())?; Ok(CiphertextCiphertextEqualityProof { Y_0, diff --git a/zk-token-sdk/src/sigma_proofs/ciphertext_commitment_equality_proof.rs b/zk-token-sdk/src/sigma_proofs/ciphertext_commitment_equality_proof.rs index b3c2d577ddf0b5..2217246ea36061 100644 --- a/zk-token-sdk/src/sigma_proofs/ciphertext_commitment_equality_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/ciphertext_commitment_equality_proof.rs @@ -16,7 +16,8 @@ use { pedersen::{PedersenCommitment, PedersenOpening, G, H}, }, errors::ProofVerificationError, - sigma_proofs::canonical_scalar_from_slice, + sigma_proofs::{canonical_scalar_from_optional_slice, ristretto_point_from_optional_slice}, + UNIT_LEN, }, curve25519_dalek::traits::MultiscalarMul, rand::rngs::OsRng, @@ -32,6 +33,9 @@ use { merlin::Transcript, }; +/// Byte length of a ciphertext-commitment equality proof. +const CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN: usize = UNIT_LEN * 6; + /// Equality proof. /// /// Contains all the elliptic curve and scalar components that make up the sigma protocol. @@ -203,28 +207,26 @@ impl CiphertextCommitmentEqualityProof { } } - pub fn to_bytes(&self) -> [u8; 192] { - let mut buf = [0_u8; 192]; - buf[..32].copy_from_slice(self.Y_0.as_bytes()); - buf[32..64].copy_from_slice(self.Y_1.as_bytes()); - buf[64..96].copy_from_slice(self.Y_2.as_bytes()); - buf[96..128].copy_from_slice(self.z_s.as_bytes()); - buf[128..160].copy_from_slice(self.z_x.as_bytes()); - buf[160..192].copy_from_slice(self.z_r.as_bytes()); + pub fn to_bytes(&self) -> [u8; CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN] { + let mut buf = [0_u8; CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN]; + let mut chunks = buf.chunks_mut(UNIT_LEN); + chunks.next().unwrap().copy_from_slice(self.Y_0.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.Y_1.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.Y_2.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.z_s.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.z_x.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.z_r.as_bytes()); buf } pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != 192 { - return Err(ProofVerificationError::Deserialization.into()); - } - - let Y_0 = CompressedRistretto::from_slice(&bytes[..32]); - let Y_1 = CompressedRistretto::from_slice(&bytes[32..64]); - let Y_2 = CompressedRistretto::from_slice(&bytes[64..96]); - let z_s = canonical_scalar_from_slice(&bytes[96..128])?; - let z_x = canonical_scalar_from_slice(&bytes[128..160])?; - let z_r = canonical_scalar_from_slice(&bytes[160..192])?; + let mut chunks = bytes.chunks(UNIT_LEN); + let Y_0 = ristretto_point_from_optional_slice(chunks.next())?; + let Y_1 = ristretto_point_from_optional_slice(chunks.next())?; + let Y_2 = ristretto_point_from_optional_slice(chunks.next())?; + let z_s = canonical_scalar_from_optional_slice(chunks.next())?; + let z_x = canonical_scalar_from_optional_slice(chunks.next())?; + let z_r = canonical_scalar_from_optional_slice(chunks.next())?; Ok(CiphertextCommitmentEqualityProof { Y_0, diff --git a/zk-token-sdk/src/sigma_proofs/fee_proof.rs b/zk-token-sdk/src/sigma_proofs/fee_proof.rs index 13ae7d8b9b3d6d..a811758be51049 100644 --- a/zk-token-sdk/src/sigma_proofs/fee_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/fee_proof.rs @@ -14,7 +14,8 @@ use { crate::{ encryption::pedersen::{PedersenCommitment, PedersenOpening, G, H}, - sigma_proofs::canonical_scalar_from_slice, + sigma_proofs::{canonical_scalar_from_optional_slice, ristretto_point_from_optional_slice}, + UNIT_LEN, }, rand::rngs::OsRng, }; @@ -32,6 +33,9 @@ use { subtle::{Choice, ConditionallySelectable, ConstantTimeGreater}, }; +/// Byte length of a fee sigma proof. +const FEE_SIGMA_PROOF_LEN: usize = UNIT_LEN * 8; + /// Fee sigma proof. /// /// The proof consists of two main components: `fee_max_proof` and `fee_equality_proof`. If the fee @@ -387,33 +391,55 @@ impl FeeSigmaProof { } } - pub fn to_bytes(&self) -> [u8; 256] { - let mut buf = [0_u8; 256]; - buf[..32].copy_from_slice(self.fee_max_proof.Y_max_proof.as_bytes()); - buf[32..64].copy_from_slice(self.fee_max_proof.z_max_proof.as_bytes()); - buf[64..96].copy_from_slice(self.fee_max_proof.c_max_proof.as_bytes()); - buf[96..128].copy_from_slice(self.fee_equality_proof.Y_delta.as_bytes()); - buf[128..160].copy_from_slice(self.fee_equality_proof.Y_claimed.as_bytes()); - buf[160..192].copy_from_slice(self.fee_equality_proof.z_x.as_bytes()); - buf[192..224].copy_from_slice(self.fee_equality_proof.z_delta.as_bytes()); - buf[224..256].copy_from_slice(self.fee_equality_proof.z_claimed.as_bytes()); + pub fn to_bytes(&self) -> [u8; FEE_SIGMA_PROOF_LEN] { + let mut buf = [0_u8; FEE_SIGMA_PROOF_LEN]; + let mut chunks = buf.chunks_mut(UNIT_LEN); + chunks + .next() + .unwrap() + .copy_from_slice(self.fee_max_proof.Y_max_proof.as_bytes()); + chunks + .next() + .unwrap() + .copy_from_slice(self.fee_max_proof.z_max_proof.as_bytes()); + chunks + .next() + .unwrap() + .copy_from_slice(self.fee_max_proof.c_max_proof.as_bytes()); + chunks + .next() + .unwrap() + .copy_from_slice(self.fee_equality_proof.Y_delta.as_bytes()); + chunks + .next() + .unwrap() + .copy_from_slice(self.fee_equality_proof.Y_claimed.as_bytes()); + chunks + .next() + .unwrap() + .copy_from_slice(self.fee_equality_proof.z_x.as_bytes()); + chunks + .next() + .unwrap() + .copy_from_slice(self.fee_equality_proof.z_delta.as_bytes()); + chunks + .next() + .unwrap() + .copy_from_slice(self.fee_equality_proof.z_claimed.as_bytes()); buf } pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != 256 { - return Err(ProofVerificationError::Deserialization.into()); - } - - let Y_max_proof = CompressedRistretto::from_slice(&bytes[..32]); - let z_max_proof = canonical_scalar_from_slice(&bytes[32..64])?; - let c_max_proof = canonical_scalar_from_slice(&bytes[64..96])?; - - let Y_delta = CompressedRistretto::from_slice(&bytes[96..128]); - let Y_claimed = CompressedRistretto::from_slice(&bytes[128..160]); - let z_x = canonical_scalar_from_slice(&bytes[160..192])?; - let z_delta = canonical_scalar_from_slice(&bytes[192..224])?; - let z_claimed = canonical_scalar_from_slice(&bytes[224..256])?; + let mut chunks = bytes.chunks(UNIT_LEN); + let Y_max_proof = ristretto_point_from_optional_slice(chunks.next())?; + let z_max_proof = canonical_scalar_from_optional_slice(chunks.next())?; + let c_max_proof = canonical_scalar_from_optional_slice(chunks.next())?; + + let Y_delta = ristretto_point_from_optional_slice(chunks.next())?; + let Y_claimed = ristretto_point_from_optional_slice(chunks.next())?; + let z_x = canonical_scalar_from_optional_slice(chunks.next())?; + let z_delta = canonical_scalar_from_optional_slice(chunks.next())?; + let z_claimed = canonical_scalar_from_optional_slice(chunks.next())?; Ok(Self { fee_max_proof: FeeMaxProof { diff --git a/zk-token-sdk/src/sigma_proofs/grouped_ciphertext_validity_proof.rs b/zk-token-sdk/src/sigma_proofs/grouped_ciphertext_validity_proof.rs index cee5f3bee31d05..b22fd661aa77fb 100644 --- a/zk-token-sdk/src/sigma_proofs/grouped_ciphertext_validity_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/grouped_ciphertext_validity_proof.rs @@ -16,7 +16,8 @@ use { pedersen::{PedersenCommitment, PedersenOpening, G, H}, }, errors::ProofVerificationError, - sigma_proofs::canonical_scalar_from_slice, + sigma_proofs::{canonical_scalar_from_optional_slice, ristretto_point_from_optional_slice}, + UNIT_LEN, }, curve25519_dalek::traits::MultiscalarMul, rand::rngs::OsRng, @@ -32,6 +33,9 @@ use { merlin::Transcript, }; +/// Byte length of a grouped ciphertext validity proof for 2 handles +const GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN: usize = UNIT_LEN * 5; + /// The grouped ciphertext validity proof for 2 handles. /// /// Contains all the elliptic curve and scalar components that make up the sigma protocol. @@ -194,26 +198,24 @@ impl GroupedCiphertext2HandlesValidityProof { } } - pub fn to_bytes(&self) -> [u8; 160] { - let mut buf = [0_u8; 160]; - buf[..32].copy_from_slice(self.Y_0.as_bytes()); - buf[32..64].copy_from_slice(self.Y_1.as_bytes()); - buf[64..96].copy_from_slice(self.Y_2.as_bytes()); - buf[96..128].copy_from_slice(self.z_r.as_bytes()); - buf[128..160].copy_from_slice(self.z_x.as_bytes()); + pub fn to_bytes(&self) -> [u8; GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN] { + let mut buf = [0_u8; GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN]; + let mut chunks = buf.chunks_mut(UNIT_LEN); + chunks.next().unwrap().copy_from_slice(self.Y_0.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.Y_1.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.Y_2.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.z_r.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.z_x.as_bytes()); buf } pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != 160 { - return Err(ProofVerificationError::Deserialization.into()); - } - - let Y_0 = CompressedRistretto::from_slice(&bytes[..32]); - let Y_1 = CompressedRistretto::from_slice(&bytes[32..64]); - let Y_2 = CompressedRistretto::from_slice(&bytes[64..96]); - let z_r = canonical_scalar_from_slice(&bytes[96..128])?; - let z_x = canonical_scalar_from_slice(&bytes[128..160])?; + let mut chunks = bytes.chunks(UNIT_LEN); + let Y_0 = ristretto_point_from_optional_slice(chunks.next())?; + let Y_1 = ristretto_point_from_optional_slice(chunks.next())?; + let Y_2 = ristretto_point_from_optional_slice(chunks.next())?; + let z_r = canonical_scalar_from_optional_slice(chunks.next())?; + let z_x = canonical_scalar_from_optional_slice(chunks.next())?; Ok(GroupedCiphertext2HandlesValidityProof { Y_0, diff --git a/zk-token-sdk/src/sigma_proofs/mod.rs b/zk-token-sdk/src/sigma_proofs/mod.rs index db17c10fda2b88..f2c68a1bde7391 100644 --- a/zk-token-sdk/src/sigma_proofs/mod.rs +++ b/zk-token-sdk/src/sigma_proofs/mod.rs @@ -15,19 +15,36 @@ pub mod pubkey_proof; pub mod zero_balance_proof; #[cfg(not(target_os = "solana"))] -use {crate::errors::ProofVerificationError, curve25519_dalek::scalar::Scalar}; +use { + crate::{errors::ProofVerificationError, RISTRETTO_POINT_LEN, SCALAR_LEN}, + curve25519_dalek::{ristretto::CompressedRistretto, scalar::Scalar}, +}; +/// Deserializes an optional slice of bytes to a compressed Ristretto point. +/// +/// This is a helper function for deserializing byte encodings of sigma proofs. It is designed to +/// be used with `std::slice::Chunks`. #[cfg(not(target_os = "solana"))] -fn canonical_scalar_from_slice(bytes: &[u8]) -> Result { - if bytes.len() != 32 { - return Err(ProofVerificationError::Deserialization); - } - - let scalar_bytes = bytes[..32] - .try_into() - .map_err(|_| ProofVerificationError::Deserialization)?; +fn ristretto_point_from_optional_slice( + optional_slice: Option<&[u8]>, +) -> Result { + optional_slice + .and_then(|slice| (slice.len() == RISTRETTO_POINT_LEN).then_some(slice)) + .map(CompressedRistretto::from_slice) + .ok_or(ProofVerificationError::Deserialization) +} - let scalar = Scalar::from_canonical_bytes(scalar_bytes) - .ok_or(ProofVerificationError::Deserialization)?; - Ok(scalar) +/// Deserializes an optional slice of bytes to a scalar. +/// +/// This is a helper function for deserializing byte encodings of sigma proofs. It is designed to +/// be used with `std::slice::Chunks`. +#[cfg(not(target_os = "solana"))] +fn canonical_scalar_from_optional_slice( + optional_slice: Option<&[u8]>, +) -> Result { + optional_slice + .and_then(|slice| (slice.len() == SCALAR_LEN).then_some(slice)) // if chunk is the wrong length, convert to None + .and_then(|slice| slice.try_into().ok()) // convert to array + .and_then(Scalar::from_canonical_bytes) + .ok_or(ProofVerificationError::Deserialization) } diff --git a/zk-token-sdk/src/sigma_proofs/pubkey_proof.rs b/zk-token-sdk/src/sigma_proofs/pubkey_proof.rs index 2ac49178cbcc7a..450b7bb3e2e1e7 100644 --- a/zk-token-sdk/src/sigma_proofs/pubkey_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/pubkey_proof.rs @@ -10,7 +10,8 @@ use { elgamal::{ElGamalKeypair, ElGamalPubkey}, pedersen::H, }, - sigma_proofs::canonical_scalar_from_slice, + sigma_proofs::{canonical_scalar_from_optional_slice, ristretto_point_from_optional_slice}, + UNIT_LEN, }, rand::rngs::OsRng, zeroize::Zeroize, @@ -28,6 +29,9 @@ use { merlin::Transcript, }; +/// Byte length of a public key validity proof. +const PUBKEY_VALIDITY_PROOF_LEN: usize = UNIT_LEN * 2; + /// Public-key proof. /// /// Contains all the elliptic curve and scalar components that make up the sigma protocol. @@ -116,21 +120,18 @@ impl PubkeyValidityProof { } } - pub fn to_bytes(&self) -> [u8; 64] { - let mut buf = [0_u8; 64]; - buf[..32].copy_from_slice(self.Y.as_bytes()); - buf[32..64].copy_from_slice(self.z.as_bytes()); + pub fn to_bytes(&self) -> [u8; PUBKEY_VALIDITY_PROOF_LEN] { + let mut buf = [0_u8; PUBKEY_VALIDITY_PROOF_LEN]; + let mut chunks = buf.chunks_mut(UNIT_LEN); + chunks.next().unwrap().copy_from_slice(self.Y.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.z.as_bytes()); buf } pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != 64 { - return Err(ProofVerificationError::Deserialization.into()); - } - - let Y = CompressedRistretto::from_slice(&bytes[..32]); - let z = canonical_scalar_from_slice(&bytes[32..64])?; - + let mut chunks = bytes.chunks(UNIT_LEN); + let Y = ristretto_point_from_optional_slice(chunks.next())?; + let z = canonical_scalar_from_optional_slice(chunks.next())?; Ok(PubkeyValidityProof { Y, z }) } } diff --git a/zk-token-sdk/src/sigma_proofs/zero_balance_proof.rs b/zk-token-sdk/src/sigma_proofs/zero_balance_proof.rs index 128181573dfe6c..04120b7c7bd451 100644 --- a/zk-token-sdk/src/sigma_proofs/zero_balance_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/zero_balance_proof.rs @@ -11,7 +11,8 @@ use { pedersen::H, }, errors::ProofVerificationError, - sigma_proofs::canonical_scalar_from_slice, + sigma_proofs::{canonical_scalar_from_optional_slice, ristretto_point_from_optional_slice}, + UNIT_LEN, }, curve25519_dalek::traits::MultiscalarMul, rand::rngs::OsRng, @@ -27,6 +28,9 @@ use { merlin::Transcript, }; +/// Byte length of a zero-balance proof. +const ZERO_BALANCE_PROOF_LEN: usize = UNIT_LEN * 3; + /// Zero-balance proof. /// /// Contains all the elliptic curve and scalar components that make up the sigma protocol. @@ -152,23 +156,20 @@ impl ZeroBalanceProof { } } - pub fn to_bytes(&self) -> [u8; 96] { - let mut buf = [0_u8; 96]; - buf[..32].copy_from_slice(self.Y_P.as_bytes()); - buf[32..64].copy_from_slice(self.Y_D.as_bytes()); - buf[64..96].copy_from_slice(self.z.as_bytes()); + pub fn to_bytes(&self) -> [u8; ZERO_BALANCE_PROOF_LEN] { + let mut buf = [0_u8; ZERO_BALANCE_PROOF_LEN]; + let mut chunks = buf.chunks_mut(UNIT_LEN); + chunks.next().unwrap().copy_from_slice(self.Y_P.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.Y_D.as_bytes()); + chunks.next().unwrap().copy_from_slice(self.z.as_bytes()); buf } pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != 96 { - return Err(ProofVerificationError::Deserialization.into()); - } - - let Y_P = CompressedRistretto::from_slice(&bytes[..32]); - let Y_D = CompressedRistretto::from_slice(&bytes[32..64]); - let z = canonical_scalar_from_slice(&bytes[64..96])?; - + let mut chunks = bytes.chunks(UNIT_LEN); + let Y_P = ristretto_point_from_optional_slice(chunks.next())?; + let Y_D = ristretto_point_from_optional_slice(chunks.next())?; + let z = canonical_scalar_from_optional_slice(chunks.next())?; Ok(ZeroBalanceProof { Y_P, Y_D, z }) } } diff --git a/zk-token-sdk/src/zk_token_elgamal/pod/auth_encryption.rs b/zk-token-sdk/src/zk_token_elgamal/pod/auth_encryption.rs index 6666ffe648c5b6..fe2b89f2d688c8 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod/auth_encryption.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod/auth_encryption.rs @@ -8,10 +8,13 @@ use { std::fmt, }; +/// Byte length of an authenticated encryption ciphertext +const AE_CIPHERTEXT_LEN: usize = 36; + /// The `AeCiphertext` type as a `Pod`. #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] -pub struct AeCiphertext(pub [u8; 36]); +pub struct AeCiphertext(pub [u8; AE_CIPHERTEXT_LEN]); // `AeCiphertext` is a wrapper type for a byte array, which is both `Pod` and `Zeroable`. However, // the marker traits `bytemuck::Pod` and `bytemuck::Zeroable` can only be derived for power-of-two diff --git a/zk-token-sdk/src/zk_token_elgamal/pod/elgamal.rs b/zk-token-sdk/src/zk_token_elgamal/pod/elgamal.rs index 78b7559af6e26d..e0ff2a3d186fd0 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod/elgamal.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod/elgamal.rs @@ -1,20 +1,32 @@ //! Plain Old Data types for the ElGamal encryption scheme. -use { - crate::zk_token_elgamal::pod::{Pod, Zeroable}, - base64::{prelude::BASE64_STANDARD, Engine}, - std::fmt, -}; #[cfg(not(target_os = "solana"))] use { crate::{encryption::elgamal as decoded, errors::ProofError}, curve25519_dalek::ristretto::CompressedRistretto, }; +use { + crate::{ + zk_token_elgamal::pod::{pedersen::PEDERSEN_COMMITMENT_LEN, Pod, Zeroable}, + RISTRETTO_POINT_LEN, + }, + base64::{prelude::BASE64_STANDARD, Engine}, + std::fmt, +}; + +/// Byte length of an ElGamal public key +const ELGAMAL_PUBKEY_LEN: usize = RISTRETTO_POINT_LEN; + +/// Byte length of a decrypt handle +pub(crate) const DECRYPT_HANDLE_LEN: usize = RISTRETTO_POINT_LEN; + +/// Byte length of an ElGamal ciphertext +const ELGAMAL_CIPHERTEXT_LEN: usize = PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN; /// The `ElGamalCiphertext` type as a `Pod`. #[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] #[repr(transparent)] -pub struct ElGamalCiphertext(pub [u8; 64]); +pub struct ElGamalCiphertext(pub [u8; ELGAMAL_CIPHERTEXT_LEN]); impl fmt::Debug for ElGamalCiphertext { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -53,7 +65,7 @@ impl TryFrom for decoded::ElGamalCiphertext { /// The `ElGamalPubkey` type as a `Pod`. #[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)] #[repr(transparent)] -pub struct ElGamalPubkey(pub [u8; 32]); +pub struct ElGamalPubkey(pub [u8; ELGAMAL_PUBKEY_LEN]); impl fmt::Debug for ElGamalPubkey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -86,7 +98,7 @@ impl TryFrom for decoded::ElGamalPubkey { /// The `DecryptHandle` type as a `Pod`. #[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)] #[repr(transparent)] -pub struct DecryptHandle(pub [u8; 32]); +pub struct DecryptHandle(pub [u8; DECRYPT_HANDLE_LEN]); impl fmt::Debug for DecryptHandle { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/zk-token-sdk/src/zk_token_elgamal/pod/grouped_elgamal.rs b/zk-token-sdk/src/zk_token_elgamal/pod/grouped_elgamal.rs index e0bbd2c5bcfad2..e76133b971f78e 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod/grouped_elgamal.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod/grouped_elgamal.rs @@ -3,14 +3,24 @@ #[cfg(not(target_os = "solana"))] use crate::{encryption::grouped_elgamal::GroupedElGamalCiphertext, errors::ProofError}; use { - crate::zk_token_elgamal::pod::{Pod, Zeroable}, + crate::zk_token_elgamal::pod::{ + elgamal::DECRYPT_HANDLE_LEN, pedersen::PEDERSEN_COMMITMENT_LEN, Pod, Zeroable, + }, std::fmt, }; +/// Byte length of a grouped ElGamal ciphertext with 2 handles +const GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES: usize = + PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN; + +/// Byte length of a grouped ElGamal ciphertext with 3 handles +const GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES: usize = + PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN; + /// The `GroupedElGamalCiphertext` type with two decryption handles as a `Pod` #[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] #[repr(transparent)] -pub struct GroupedElGamalCiphertext2Handles(pub [u8; 96]); +pub struct GroupedElGamalCiphertext2Handles(pub [u8; GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES]); impl fmt::Debug for GroupedElGamalCiphertext2Handles { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -42,7 +52,7 @@ impl TryFrom for GroupedElGamalCiphertext<2> { /// The `GroupedElGamalCiphertext` type with three decryption handles as a `Pod` #[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] #[repr(transparent)] -pub struct GroupedElGamalCiphertext3Handles(pub [u8; 128]); +pub struct GroupedElGamalCiphertext3Handles(pub [u8; GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES]); impl fmt::Debug for GroupedElGamalCiphertext3Handles { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/zk-token-sdk/src/zk_token_elgamal/pod/pedersen.rs b/zk-token-sdk/src/zk_token_elgamal/pod/pedersen.rs index b4effd0c8d7e14..64749e9ebbe72d 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod/pedersen.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod/pedersen.rs @@ -1,19 +1,25 @@ //! Plain Old Data type for the Pedersen commitment scheme. -use { - crate::zk_token_elgamal::pod::{Pod, Zeroable}, - std::fmt, -}; #[cfg(not(target_os = "solana"))] use { crate::{encryption::pedersen as decoded, errors::ProofError}, curve25519_dalek::ristretto::CompressedRistretto, }; +use { + crate::{ + zk_token_elgamal::pod::{Pod, Zeroable}, + RISTRETTO_POINT_LEN, + }, + std::fmt, +}; + +/// Byte length of a Pedersen commitment +pub(crate) const PEDERSEN_COMMITMENT_LEN: usize = RISTRETTO_POINT_LEN; /// The `PedersenCommitment` type as a `Pod`. #[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)] #[repr(transparent)] -pub struct PedersenCommitment(pub [u8; 32]); +pub struct PedersenCommitment(pub [u8; PEDERSEN_COMMITMENT_LEN]); impl fmt::Debug for PedersenCommitment { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/zk-token-sdk/src/zk_token_elgamal/pod/range_proof.rs b/zk-token-sdk/src/zk_token_elgamal/pod/range_proof.rs index 5f4065e1ea72a1..5a727ed93f0a51 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod/range_proof.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod/range_proof.rs @@ -1,35 +1,58 @@ //! Plain Old Data types for range proofs. -use crate::zk_token_elgamal::pod::{Pod, Zeroable}; #[cfg(not(target_os = "solana"))] use crate::{ errors::ProofVerificationError, range_proof::{self as decoded, errors::RangeProofError}, + UNIT_LEN, }; +use crate::{ + zk_token_elgamal::pod::{Pod, Zeroable}, + RISTRETTO_POINT_LEN, SCALAR_LEN, +}; + +/// Byte length of a range proof excluding the inner-product proof component +const RANGE_PROOF_MODULO_INNER_PRODUCT_PROOF_LEN: usize = 5 * RISTRETTO_POINT_LEN + 2 * SCALAR_LEN; + +/// Byte length of an inner-product proof for a vector of length 64 +const INNER_PRODUCT_PROOF_U64_LEN: usize = 448; + +/// Byte length of a range proof for an unsigned 64-bit number +const RANGE_PROOF_U64_LEN: usize = + INNER_PRODUCT_PROOF_U64_LEN + RANGE_PROOF_MODULO_INNER_PRODUCT_PROOF_LEN; + +/// Byte length of an inner-product proof for a vector of length 128 +const INNER_PRODUCT_PROOF_U128_LEN: usize = 512; + +/// Byte length of a range proof for an unsigned 128-bit number +const RANGE_PROOF_U128_LEN: usize = + INNER_PRODUCT_PROOF_U128_LEN + RANGE_PROOF_MODULO_INNER_PRODUCT_PROOF_LEN; + +/// Byte length of an inner-product proof for a vector of length 256 +const INNER_PRODUCT_PROOF_U256_LEN: usize = 576; + +/// Byte length of a range proof for an unsigned 256-bit number +const RANGE_PROOF_U256_LEN: usize = + INNER_PRODUCT_PROOF_U256_LEN + RANGE_PROOF_MODULO_INNER_PRODUCT_PROOF_LEN; /// The `RangeProof` type as a `Pod` restricted to proofs on 64-bit numbers. #[derive(Clone, Copy)] #[repr(transparent)] -pub struct RangeProofU64(pub [u8; 672]); +pub struct RangeProofU64(pub [u8; RANGE_PROOF_U64_LEN]); #[cfg(not(target_os = "solana"))] impl TryFrom for RangeProofU64 { type Error = RangeProofError; fn try_from(decoded_proof: decoded::RangeProof) -> Result { - if decoded_proof.ipp_proof.serialized_size() != 448 { + if decoded_proof.ipp_proof.serialized_size() != INNER_PRODUCT_PROOF_U64_LEN { return Err(ProofVerificationError::Deserialization.into()); } - let mut buf = [0_u8; 672]; - buf[..32].copy_from_slice(decoded_proof.A.as_bytes()); - buf[32..64].copy_from_slice(decoded_proof.S.as_bytes()); - buf[64..96].copy_from_slice(decoded_proof.T_1.as_bytes()); - buf[96..128].copy_from_slice(decoded_proof.T_2.as_bytes()); - buf[128..160].copy_from_slice(decoded_proof.t_x.as_bytes()); - buf[160..192].copy_from_slice(decoded_proof.t_x_blinding.as_bytes()); - buf[192..224].copy_from_slice(decoded_proof.e_blinding.as_bytes()); - buf[224..672].copy_from_slice(&decoded_proof.ipp_proof.to_bytes()); + let mut buf = [0_u8; RANGE_PROOF_U64_LEN]; + copy_range_proof_modulo_inner_product_proof(&decoded_proof, &mut buf); + buf[RANGE_PROOF_MODULO_INNER_PRODUCT_PROOF_LEN..RANGE_PROOF_U64_LEN] + .copy_from_slice(&decoded_proof.ipp_proof.to_bytes()); Ok(RangeProofU64(buf)) } } @@ -46,26 +69,21 @@ impl TryFrom for decoded::RangeProof { /// The `RangeProof` type as a `Pod` restricted to proofs on 128-bit numbers. #[derive(Clone, Copy)] #[repr(transparent)] -pub struct RangeProofU128(pub [u8; 736]); +pub struct RangeProofU128(pub [u8; RANGE_PROOF_U128_LEN]); #[cfg(not(target_os = "solana"))] impl TryFrom for RangeProofU128 { type Error = RangeProofError; fn try_from(decoded_proof: decoded::RangeProof) -> Result { - if decoded_proof.ipp_proof.serialized_size() != 512 { + if decoded_proof.ipp_proof.serialized_size() != INNER_PRODUCT_PROOF_U128_LEN { return Err(ProofVerificationError::Deserialization.into()); } - let mut buf = [0_u8; 736]; - buf[..32].copy_from_slice(decoded_proof.A.as_bytes()); - buf[32..64].copy_from_slice(decoded_proof.S.as_bytes()); - buf[64..96].copy_from_slice(decoded_proof.T_1.as_bytes()); - buf[96..128].copy_from_slice(decoded_proof.T_2.as_bytes()); - buf[128..160].copy_from_slice(decoded_proof.t_x.as_bytes()); - buf[160..192].copy_from_slice(decoded_proof.t_x_blinding.as_bytes()); - buf[192..224].copy_from_slice(decoded_proof.e_blinding.as_bytes()); - buf[224..736].copy_from_slice(&decoded_proof.ipp_proof.to_bytes()); + let mut buf = [0_u8; RANGE_PROOF_U128_LEN]; + copy_range_proof_modulo_inner_product_proof(&decoded_proof, &mut buf); + buf[RANGE_PROOF_MODULO_INNER_PRODUCT_PROOF_LEN..RANGE_PROOF_U128_LEN] + .copy_from_slice(&decoded_proof.ipp_proof.to_bytes()); Ok(RangeProofU128(buf)) } } @@ -82,26 +100,21 @@ impl TryFrom for decoded::RangeProof { /// The `RangeProof` type as a `Pod` restricted to proofs on 256-bit numbers. #[derive(Clone, Copy)] #[repr(transparent)] -pub struct RangeProofU256(pub [u8; 800]); +pub struct RangeProofU256(pub [u8; RANGE_PROOF_U256_LEN]); #[cfg(not(target_os = "solana"))] impl TryFrom for RangeProofU256 { type Error = RangeProofError; fn try_from(decoded_proof: decoded::RangeProof) -> Result { - if decoded_proof.ipp_proof.serialized_size() != 576 { + if decoded_proof.ipp_proof.serialized_size() != INNER_PRODUCT_PROOF_U256_LEN { return Err(ProofVerificationError::Deserialization.into()); } - let mut buf = [0_u8; 800]; - buf[..32].copy_from_slice(decoded_proof.A.as_bytes()); - buf[32..64].copy_from_slice(decoded_proof.S.as_bytes()); - buf[64..96].copy_from_slice(decoded_proof.T_1.as_bytes()); - buf[96..128].copy_from_slice(decoded_proof.T_2.as_bytes()); - buf[128..160].copy_from_slice(decoded_proof.t_x.as_bytes()); - buf[160..192].copy_from_slice(decoded_proof.t_x_blinding.as_bytes()); - buf[192..224].copy_from_slice(decoded_proof.e_blinding.as_bytes()); - buf[224..800].copy_from_slice(&decoded_proof.ipp_proof.to_bytes()); + let mut buf = [0_u8; RANGE_PROOF_U256_LEN]; + copy_range_proof_modulo_inner_product_proof(&decoded_proof, &mut buf); + buf[RANGE_PROOF_MODULO_INNER_PRODUCT_PROOF_LEN..RANGE_PROOF_U256_LEN] + .copy_from_slice(&decoded_proof.ipp_proof.to_bytes()); Ok(RangeProofU256(buf)) } } @@ -115,6 +128,24 @@ impl TryFrom for decoded::RangeProof { } } +#[cfg(not(target_os = "solana"))] +fn copy_range_proof_modulo_inner_product_proof(proof: &decoded::RangeProof, buf: &mut [u8]) { + let mut chunks = buf.chunks_mut(UNIT_LEN); + chunks.next().unwrap().copy_from_slice(proof.A.as_bytes()); + chunks.next().unwrap().copy_from_slice(proof.S.as_bytes()); + chunks.next().unwrap().copy_from_slice(proof.T_1.as_bytes()); + chunks.next().unwrap().copy_from_slice(proof.T_2.as_bytes()); + chunks.next().unwrap().copy_from_slice(proof.t_x.as_bytes()); + chunks + .next() + .unwrap() + .copy_from_slice(proof.t_x_blinding.as_bytes()); + chunks + .next() + .unwrap() + .copy_from_slice(proof.e_blinding.as_bytes()); +} + // The range proof pod types are wrappers for byte arrays, which are both `Pod` and `Zeroable`. However, // the marker traits `bytemuck::Pod` and `bytemuck::Zeroable` can only be derived for power-of-two // length byte arrays. Directly implement these traits for the range proof pod types. diff --git a/zk-token-sdk/src/zk_token_elgamal/pod/sigma_proofs.rs b/zk-token-sdk/src/zk_token_elgamal/pod/sigma_proofs.rs index f77d43b5baaca9..878f2e9afd8aa8 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod/sigma_proofs.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod/sigma_proofs.rs @@ -12,10 +12,31 @@ use crate::sigma_proofs::{ }; use crate::zk_token_elgamal::pod::{Pod, Zeroable}; +/// Byte length of a ciphertext-commitment equality proof +const CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN: usize = 192; + +/// Byte length of a ciphertext-ciphertext equality proof +const CIPHERTEXT_CIPHERTEXT_EQUALITY_PROOF_LEN: usize = 224; + +/// Byte length of a grouped ciphertext for 2 handles validity proof +const GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN: usize = 160; + +/// Byte length of a batched grouped ciphertext for 2 handles validity proof +const BATCHED_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN: usize = 160; + +/// Byte length of a zero-balance proof +const ZERO_BALANCE_PROOF_LEN: usize = 96; + +/// Byte length of a fee sigma proof +const FEE_SIGMA_PROOF_LEN: usize = 256; + +/// Byte length of a public key validity proof +const PUBKEY_VALIDITY_PROOF_LEN: usize = 64; + /// The `CiphertextCommitmentEqualityProof` type as a `Pod`. #[derive(Clone, Copy)] #[repr(transparent)] -pub struct CiphertextCommitmentEqualityProof(pub [u8; 192]); +pub struct CiphertextCommitmentEqualityProof(pub [u8; CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN]); #[cfg(not(target_os = "solana"))] impl From for CiphertextCommitmentEqualityProof { @@ -36,7 +57,7 @@ impl TryFrom for DecodedCiphertextCommitmentE /// The `CiphertextCiphertextEqualityProof` type as a `Pod`. #[derive(Clone, Copy)] #[repr(transparent)] -pub struct CiphertextCiphertextEqualityProof(pub [u8; 224]); +pub struct CiphertextCiphertextEqualityProof(pub [u8; CIPHERTEXT_CIPHERTEXT_EQUALITY_PROOF_LEN]); #[cfg(not(target_os = "solana"))] impl From for CiphertextCiphertextEqualityProof { @@ -57,7 +78,9 @@ impl TryFrom for DecodedCiphertextCiphertextE /// The `GroupedCiphertext2HandlesValidityProof` type as a `Pod`. #[derive(Clone, Copy)] #[repr(transparent)] -pub struct GroupedCiphertext2HandlesValidityProof(pub [u8; 160]); +pub struct GroupedCiphertext2HandlesValidityProof( + pub [u8; GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN], +); #[cfg(not(target_os = "solana"))] impl From @@ -82,7 +105,9 @@ impl TryFrom /// The `BatchedGroupedCiphertext2HandlesValidityProof` type as a `Pod`. #[derive(Clone, Copy)] #[repr(transparent)] -pub struct BatchedGroupedCiphertext2HandlesValidityProof(pub [u8; 160]); +pub struct BatchedGroupedCiphertext2HandlesValidityProof( + pub [u8; BATCHED_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN], +); #[cfg(not(target_os = "solana"))] impl From @@ -109,7 +134,7 @@ impl TryFrom /// The `ZeroBalanceProof` type as a `Pod`. #[derive(Clone, Copy)] #[repr(transparent)] -pub struct ZeroBalanceProof(pub [u8; 96]); +pub struct ZeroBalanceProof(pub [u8; ZERO_BALANCE_PROOF_LEN]); #[cfg(not(target_os = "solana"))] impl From for ZeroBalanceProof { @@ -130,7 +155,7 @@ impl TryFrom for DecodedZeroBalanceProof { /// The `FeeSigmaProof` type as a `Pod`. #[derive(Clone, Copy, Pod, Zeroable)] #[repr(transparent)] -pub struct FeeSigmaProof(pub [u8; 256]); +pub struct FeeSigmaProof(pub [u8; FEE_SIGMA_PROOF_LEN]); #[cfg(not(target_os = "solana"))] impl From for FeeSigmaProof { @@ -151,7 +176,7 @@ impl TryFrom for DecodedFeeSigmaProof { /// The `PubkeyValidityProof` type as a `Pod`. #[derive(Clone, Copy, Pod, Zeroable)] #[repr(transparent)] -pub struct PubkeyValidityProof(pub [u8; 64]); +pub struct PubkeyValidityProof(pub [u8; PUBKEY_VALIDITY_PROOF_LEN]); #[cfg(not(target_os = "solana"))] impl From for PubkeyValidityProof {