diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index a719a64a9..f51aed2af 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -4531,9 +4531,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" dependencies = [ "zeroize_derive", ] diff --git a/zk-token-sdk/Cargo.toml b/zk-token-sdk/Cargo.toml index eac149b61..88e8e3418 100644 --- a/zk-token-sdk/Cargo.toml +++ b/zk-token-sdk/Cargo.toml @@ -31,7 +31,7 @@ sha3 = "0.9" solana-sdk = { path = "../sdk", version = "=1.10.0" } subtle = "2" thiserror = "1" -zeroize = { version = "1.2.0", default-features = false, features = ["zeroize_derive"] } +zeroize = { version = "1.3", default-features = false, features = ["zeroize_derive"] } [dev-dependencies] time = "0.1.40" diff --git a/zk-token-sdk/src/encryption/elgamal.rs b/zk-token-sdk/src/encryption/elgamal.rs index bcdb090e2..a233c7a2d 100644 --- a/zk-token-sdk/src/encryption/elgamal.rs +++ b/zk-token-sdk/src/encryption/elgamal.rs @@ -1,12 +1,25 @@ +//! The twisted ElGamal encryption implementation. +//! +//! The message space consists of any number that is representable as a scalar (a.k.a. "exponent") +//! for Curve25519. +//! +//! A twisted ElGamal ciphertext consists of two components: +//! - A Pedersen commitment that encodes a message to be encrypted +//! - A "decryption handle" that binds the Pedersen opening to a specific public key +//! In contrast to the traditional ElGamal encryption scheme, the twisted ElGamal encodes messages +//! directly as a Pedersen commitment. Therefore, proof systems that are designed specifically for +//! Pedersen commitments can be used on the twisted ElGamal ciphertexts. +//! +//! As the messages are encrypted as scalar elements (a.k.a. in the "exponent"), the encryption +//! scheme requires solving discrete log to recover the original plaintext. + use { crate::encryption::{ discrete_log::{DecodeU32Precomputation, DiscreteLog}, - pedersen::{ - Pedersen, PedersenBase, PedersenCommitment, PedersenDecryptHandle, PedersenOpening, - }, + pedersen::{Pedersen, PedersenCommitment, PedersenOpening, G, H}, }, arrayref::{array_ref, array_refs}, - core::ops::{Add, Div, Mul, Sub}, + core::ops::{Add, Mul, Sub}, curve25519_dalek::{ ristretto::{CompressedRistretto, RistrettoPoint}, scalar::Scalar, @@ -25,7 +38,7 @@ use { }; #[cfg(not(target_arch = "bpf"))] use { - rand::{rngs::OsRng, CryptoRng, RngCore}, + rand::rngs::OsRng, sha3::Sha3_512, std::{ fmt, @@ -35,109 +48,96 @@ use { }, }; +/// Algorithm handle for the twisted ElGamal encryption scheme struct ElGamal; impl ElGamal { - /// The function generates the public and secret keys for ElGamal encryption from the provided - /// randomness generator + /// Generates an ElGamal keypair. + /// + /// This function is randomized. It internally samples a scalar element using `OsRng`. #[cfg(not(target_arch = "bpf"))] #[allow(non_snake_case)] - fn keygen(rng: &mut T) -> ElGamalKeypair { - // sample a non-zero scalar - let mut s: Scalar; - loop { - s = Scalar::random(rng); + fn keygen() -> ElGamalKeypair { + // secret scalar should be zero with negligible probability + let mut s = Scalar::random(&mut OsRng); + let keypair = Self::keygen_with_scalar(&s); - if s != Scalar::zero() { - break; - } - } - - Self::keygen_with_scalar(s) + s.zeroize(); + keypair } - /// Generates the public and secret keys for ElGamal encryption from a non-zero Scalar + /// Generates an ElGamal keypair from a scalar input that determines the ElGamal private key. #[cfg(not(target_arch = "bpf"))] #[allow(non_snake_case)] - fn keygen_with_scalar(s: Scalar) -> ElGamalKeypair { - assert!(s != Scalar::zero()); + fn keygen_with_scalar(s: &Scalar) -> ElGamalKeypair { + assert!(s != &Scalar::zero()); - let H = PedersenBase::default().H; - let P = s.invert() * H; + let P = s.invert() * &(*H); ElGamalKeypair { public: ElGamalPubkey(P), - secret: ElGamalSecretKey(s), + secret: ElGamalSecretKey(*s), } } - /// On input a public key and a message to be encrypted, the function - /// returns an ElGamal ciphertext of the message under the public key. + /// On input an ElGamal public key and a mesage to be encrypted, the function returns a + /// corresponding ElGamal ciphertext. + /// + /// This function is randomized. It internally samples a scalar element using `OsRng`. #[cfg(not(target_arch = "bpf"))] fn encrypt>(public: &ElGamalPubkey, amount: T) -> ElGamalCiphertext { - let (message_comm, open) = Pedersen::new(amount); - let decrypt_handle = public.decrypt_handle(&open); + let (commitment, opening) = Pedersen::new(amount); + let handle = public.decrypt_handle(&opening); - ElGamalCiphertext { - message_comm, - decrypt_handle, - } + ElGamalCiphertext { commitment, handle } } /// On input a public key, message, and Pedersen opening, the function - /// returns an ElGamal ciphertext of the message under the public key using - /// the opening. + /// returns the corresponding ElGamal ciphertext. fn encrypt_with>( - public: &ElGamalPubkey, amount: T, - open: &PedersenOpening, + public: &ElGamalPubkey, + opening: &PedersenOpening, ) -> ElGamalCiphertext { - let message_comm = Pedersen::with(amount, open); - let decrypt_handle = public.decrypt_handle(open); + let commitment = Pedersen::with(amount, opening); + let handle = public.decrypt_handle(opening); - ElGamalCiphertext { - message_comm, - decrypt_handle, - } + ElGamalCiphertext { commitment, handle } } - /// On input a secret key and a ciphertext, the function decrypts the ciphertext. + /// On input a secret key and a ciphertext, the function returns the decrypted message. /// - /// The output of the function is of type `DiscreteLog`. The exact message - /// can be recovered via the DiscreteLog's decode method. - fn decrypt(secret: &ElGamalSecretKey, ct: &ElGamalCiphertext) -> DiscreteLog { - let ElGamalSecretKey(s) = secret; - let ElGamalCiphertext { - message_comm, - decrypt_handle, - } = ct; - + /// The output of this function is of type `DiscreteLog`. To recover, the originally encrypted + /// message, use `DiscreteLog::decode`. + fn decrypt(secret: &ElGamalSecretKey, ciphertext: &ElGamalCiphertext) -> DiscreteLog { DiscreteLog { - generator: PedersenBase::default().G, - target: message_comm.get_point() - s * decrypt_handle.get_point(), + generator: *G, + target: &ciphertext.commitment.0 - &(&secret.0 * &ciphertext.handle.0), } } - /// On input a secret key and a ciphertext, the function decrypts the - /// ciphertext for a u32 value. - fn decrypt_u32(secret: &ElGamalSecretKey, ct: &ElGamalCiphertext) -> Option { - let discrete_log_instance = Self::decrypt(secret, ct); + /// On input a secret key and a ciphertext, the function returns the decrypted message + /// interpretted as type `u32`. + fn decrypt_u32(secret: &ElGamalSecretKey, ciphertext: &ElGamalCiphertext) -> Option { + let discrete_log_instance = Self::decrypt(secret, ciphertext); discrete_log_instance.decode_u32() } - /// On input a secret key, ciphertext, and hashmap, the function decrypts the - /// ciphertext for a u32 value. + /// On input a secret key, a ciphertext, and a pre-computed hashmap, the function returns the + /// decrypted message interpretted as type `u32`. fn decrypt_u32_online( secret: &ElGamalSecretKey, - ct: &ElGamalCiphertext, + ciphertext: &ElGamalCiphertext, hashmap: &DecodeU32Precomputation, ) -> Option { - let discrete_log_instance = Self::decrypt(secret, ct); + let discrete_log_instance = Self::decrypt(secret, ciphertext); discrete_log_instance.decode_u32_online(hashmap) } } /// A (twisted) ElGamal encryption keypair. -#[derive(PartialEq)] +/// +/// The instances of the secret key are zeroized on drop. +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Zeroize)] pub struct ElGamalKeypair { /// The public half of this keypair. pub public: ElGamalPubkey, @@ -146,8 +146,7 @@ pub struct ElGamalKeypair { } impl ElGamalKeypair { - /// Generates the public and secret keys for ElGamal encryption from Ed25519 signing key and an - /// address. + /// Deterministically derives an ElGamal keypair from an Ed25519 signing key and a Solana address. #[cfg(not(target_arch = "bpf"))] #[allow(non_snake_case)] pub fn new(signer: &dyn Signer, address: &Pubkey) -> Result { @@ -167,29 +166,28 @@ impl ElGamalKeypair { return Err(SignerError::Custom("Rejecting default signature".into())); } - let scalar = Scalar::hash_from_bytes::(signature.as_ref()); - Ok(ElGamal::keygen_with_scalar(scalar)) + let mut scalar = Scalar::hash_from_bytes::(signature.as_ref()); + let keypair = ElGamal::keygen_with_scalar(&scalar); + + // TODO: zeroize signature? + scalar.zeroize(); + Ok(keypair) } /// Generates the public and secret keys for ElGamal encryption. + /// + /// This function is randomized. It internally samples a scalar element using `OsRng`. #[cfg(not(target_arch = "bpf"))] #[allow(clippy::new_ret_no_self)] - pub fn default() -> Self { - Self::with(&mut OsRng) // using OsRng for now - } - - /// On input a randomness generator, the function generates the public and - /// secret keys for ElGamal encryption. - #[cfg(not(target_arch = "bpf"))] - #[allow(non_snake_case)] - pub fn with(rng: &mut T) -> Self { - ElGamal::keygen(rng) + pub fn new_rand() -> Self { + ElGamal::keygen() } pub fn to_bytes(&self) -> [u8; 64] { - let mut bytes = self.public.to_bytes().to_vec(); - bytes.extend(self.secret.to_bytes()); - bytes.try_into().expect("incorrect length") + let mut bytes = [0u8; 64]; + bytes[..32].copy_from_slice(&self.public.to_bytes()); + bytes[32..].copy_from_slice(self.secret.as_bytes()); + bytes } pub fn from_bytes(bytes: &[u8]) -> Option { @@ -256,18 +254,17 @@ impl ElGamalKeypair { } /// Public key for the ElGamal encryption scheme. -#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Zeroize)] pub struct ElGamalPubkey(RistrettoPoint); impl ElGamalPubkey { - /// Derive the `ElGamalPubkey` that uniquely corresponds to an `ElGamalSecretKey` + /// Derives the `ElGamalPubkey` that uniquely corresponds to an `ElGamalSecretKey`. #[allow(non_snake_case)] pub fn new(secret: &ElGamalSecretKey) -> Self { - let H = PedersenBase::default().H; - ElGamalPubkey(secret.0 * H) + ElGamalPubkey(&secret.0 * &(*H)) } - pub fn get_point(&self) -> RistrettoPoint { - self.0 + pub fn get_point(&self) -> &RistrettoPoint { + &self.0 } #[allow(clippy::wrong_self_convention)] @@ -281,31 +278,27 @@ impl ElGamalPubkey { )) } - /// Utility method for code ergonomics. + /// Encrypts an amount under the public key. + /// + /// This function is randomized. It internally samples a scalar element using `OsRng`. #[cfg(not(target_arch = "bpf"))] - pub fn encrypt>(&self, msg: T) -> ElGamalCiphertext { - ElGamal::encrypt(self, msg) + pub fn encrypt>(&self, amount: T) -> ElGamalCiphertext { + ElGamal::encrypt(self, amount) } - /// Utility method for code ergonomics. + /// Encrypts an amount under the public key and an input Pedersen opening. pub fn encrypt_with>( &self, - msg: T, - open: &PedersenOpening, + amount: T, + opening: &PedersenOpening, ) -> ElGamalCiphertext { - ElGamal::encrypt_with(self, msg, open) + ElGamal::encrypt_with(amount, self, opening) } - /// Generate a decryption token from an ElGamal public key and a Pedersen + /// Generates a decryption handle for an ElGamal public key under a Pedersen /// opening. - pub fn decrypt_handle(self, open: &PedersenOpening) -> PedersenDecryptHandle { - PedersenDecryptHandle::new(&self, open) - } -} - -impl From for ElGamalPubkey { - fn from(point: RistrettoPoint) -> ElGamalPubkey { - ElGamalPubkey(point) + pub fn decrypt_handle(self, opening: &PedersenOpening) -> DecryptHandle { + DecryptHandle::new(&self, opening) } } @@ -316,10 +309,14 @@ impl fmt::Display for ElGamalPubkey { } /// Secret key for the ElGamal encryption scheme. -#[derive(Serialize, Deserialize, Debug, Zeroize)] +/// +/// Instances of ElGamal secret key are zeroized on drop. +#[derive(Clone, Debug, Deserialize, Serialize, Zeroize)] #[zeroize(drop)] pub struct ElGamalSecretKey(Scalar); impl ElGamalSecretKey { + /// Deterministically derives an ElGamal keypair from an Ed25519 signing key and a Solana + /// address. pub fn new(signer: &dyn Signer, address: &Pubkey) -> Result { let message = Message::new( &[Instruction::new_with_bytes( @@ -342,27 +339,42 @@ impl ElGamalSecretKey { } } - pub fn get_scalar(&self) -> Scalar { - self.0 + /// Randomly samples an ElGamal secret key. + /// + /// This function is randomized. It internally samples a scalar element using `OsRng`. + pub fn new_rand() -> Self { + ElGamalSecretKey(Scalar::random(&mut OsRng)) } - /// Utility method for code ergonomics. - pub fn decrypt(&self, ct: &ElGamalCiphertext) -> DiscreteLog { - ElGamal::decrypt(self, ct) + pub fn get_scalar(&self) -> &Scalar { + &self.0 } - /// Utility method for code ergonomics. - pub fn decrypt_u32(&self, ct: &ElGamalCiphertext) -> Option { - ElGamal::decrypt_u32(self, ct) + /// Decrypts a ciphertext using the ElGamal secret key. + /// + /// The output of this function is of type `DiscreteLog`. To recover, the originally encrypted + /// message, use `DiscreteLog::decode`. + pub fn decrypt(&self, ciphertext: &ElGamalCiphertext) -> DiscreteLog { + ElGamal::decrypt(self, ciphertext) } - /// Utility method for code ergonomics. + /// Decrypts a ciphertext using the ElGamal secret key interpretting the message as type `u32`. + pub fn decrypt_u32(&self, ciphertext: &ElGamalCiphertext) -> Option { + ElGamal::decrypt_u32(self, ciphertext) + } + + /// Decrypts a ciphertext using the ElGamal secret key and a pre-computed hashmap. It + /// interprets the decrypted message as type `u32`. pub fn decrypt_u32_online( &self, - ct: &ElGamalCiphertext, + ciphertext: &ElGamalCiphertext, hashmap: &DecodeU32Precomputation, ) -> Option { - ElGamal::decrypt_u32_online(self, ct, hashmap) + ElGamal::decrypt_u32_online(self, ciphertext, hashmap) + } + + pub fn as_bytes(&self) -> &[u8; 32] { + self.0.as_bytes() } pub fn to_bytes(&self) -> [u8; 32] { @@ -394,61 +406,64 @@ impl ConstantTimeEq for ElGamalSecretKey { /// Ciphertext for the ElGamal encryption scheme. #[allow(non_snake_case)] -#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct ElGamalCiphertext { - pub message_comm: PedersenCommitment, - pub decrypt_handle: PedersenDecryptHandle, + pub commitment: PedersenCommitment, + pub handle: DecryptHandle, } impl ElGamalCiphertext { - pub fn add_to_msg>(&self, message: T) -> Self { - let diff_comm = Pedersen::with(message, &PedersenOpening::default()); + pub fn add_amount>(&self, amount: T) -> Self { + let commitment_to_add = PedersenCommitment(amount.into() * &(*G)); ElGamalCiphertext { - message_comm: self.message_comm + diff_comm, - decrypt_handle: self.decrypt_handle, + commitment: &self.commitment + &commitment_to_add, + handle: self.handle, } } - pub fn sub_to_msg>(&self, message: T) -> Self { - let diff_comm = Pedersen::with(message, &PedersenOpening::default()); + pub fn subtract_amount>(&self, amount: T) -> Self { + let commitment_to_subtract = PedersenCommitment(amount.into() * &(*G)); ElGamalCiphertext { - message_comm: self.message_comm - diff_comm, - decrypt_handle: self.decrypt_handle, + commitment: &self.commitment - &commitment_to_subtract, + handle: self.handle, } } #[allow(clippy::wrong_self_convention)] pub fn to_bytes(&self) -> [u8; 64] { let mut bytes = [0u8; 64]; - - bytes[..32].copy_from_slice(self.message_comm.get_point().compress().as_bytes()); - bytes[32..].copy_from_slice(self.decrypt_handle.get_point().compress().as_bytes()); + bytes[..32].copy_from_slice(&self.commitment.to_bytes()); + bytes[32..].copy_from_slice(&self.handle.to_bytes()); bytes } pub fn from_bytes(bytes: &[u8]) -> Option { let bytes = array_ref![bytes, 0, 64]; - let (message_comm, decrypt_handle) = array_refs![bytes, 32, 32]; + let (commitment, handle) = array_refs![bytes, 32, 32]; - let message_comm = CompressedRistretto::from_slice(message_comm).decompress()?; - let decrypt_handle = CompressedRistretto::from_slice(decrypt_handle).decompress()?; + let commitment = CompressedRistretto::from_slice(commitment).decompress()?; + let handle = CompressedRistretto::from_slice(handle).decompress()?; Some(ElGamalCiphertext { - message_comm: PedersenCommitment(message_comm), - decrypt_handle: PedersenDecryptHandle(decrypt_handle), + commitment: PedersenCommitment(commitment), + handle: DecryptHandle(handle), }) } - /// Utility method for code ergonomics. + /// Decrypts the ciphertext using an ElGamal secret key. + /// + /// The output of this function is of type `DiscreteLog`. To recover, the originally encrypted + /// message, use `DiscreteLog::decode`. pub fn decrypt(&self, secret: &ElGamalSecretKey) -> DiscreteLog { ElGamal::decrypt(secret, self) } - /// Utility method for code ergonomics. + /// Decrypts the ciphertext using an ElGamal secret key interpretting the message as type `u32`. pub fn decrypt_u32(&self, secret: &ElGamalSecretKey) -> Option { ElGamal::decrypt_u32(secret, self) } - /// Utility method for code ergonomics. + /// Decrypts the ciphertext using an ElGamal secret key and a pre-computed hashmap. It + /// interprets the decrypted message as type `u32`. pub fn decrypt_u32_online( &self, secret: &ElGamalSecretKey, @@ -458,22 +473,13 @@ impl ElGamalCiphertext { } } -impl From<(PedersenCommitment, PedersenDecryptHandle)> for ElGamalCiphertext { - fn from((comm, handle): (PedersenCommitment, PedersenDecryptHandle)) -> Self { - ElGamalCiphertext { - message_comm: comm, - decrypt_handle: handle, - } - } -} - impl<'a, 'b> Add<&'b ElGamalCiphertext> for &'a ElGamalCiphertext { type Output = ElGamalCiphertext; fn add(self, other: &'b ElGamalCiphertext) -> ElGamalCiphertext { ElGamalCiphertext { - message_comm: self.message_comm + other.message_comm, - decrypt_handle: self.decrypt_handle + other.decrypt_handle, + commitment: &self.commitment + &other.commitment, + handle: &self.handle + &other.handle, } } } @@ -489,8 +495,8 @@ impl<'a, 'b> Sub<&'b ElGamalCiphertext> for &'a ElGamalCiphertext { fn sub(self, other: &'b ElGamalCiphertext) -> ElGamalCiphertext { ElGamalCiphertext { - message_comm: self.message_comm - other.message_comm, - decrypt_handle: self.decrypt_handle - other.decrypt_handle, + commitment: &self.commitment - &other.commitment, + handle: &self.handle - &other.handle, } } } @@ -506,8 +512,8 @@ impl<'a, 'b> Mul<&'b Scalar> for &'a ElGamalCiphertext { fn mul(self, other: &'b Scalar) -> ElGamalCiphertext { ElGamalCiphertext { - message_comm: self.message_comm * other, - decrypt_handle: self.decrypt_handle * other, + commitment: &self.commitment * other, + handle: &self.handle * other, } } } @@ -518,178 +524,215 @@ define_mul_variants!( Output = ElGamalCiphertext ); -impl<'a, 'b> Div<&'b Scalar> for &'a ElGamalCiphertext { - type Output = ElGamalCiphertext; +/// Decryption handle for Pedersen commitment. +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct DecryptHandle(RistrettoPoint); +impl DecryptHandle { + pub fn new(public: &ElGamalPubkey, opening: &PedersenOpening) -> Self { + Self(&public.0 * &opening.0) + } - fn div(self, other: &'b Scalar) -> ElGamalCiphertext { - ElGamalCiphertext { - message_comm: self.message_comm * other.invert(), - decrypt_handle: self.decrypt_handle * other.invert(), - } + pub fn get_point(&self) -> &RistrettoPoint { + &self.0 + } + + #[allow(clippy::wrong_self_convention)] + pub fn to_bytes(&self) -> [u8; 32] { + self.0.compress().to_bytes() + } + + pub fn from_bytes(bytes: &[u8]) -> Option { + Some(DecryptHandle( + CompressedRistretto::from_slice(bytes).decompress()?, + )) } } -define_div_variants!( - LHS = ElGamalCiphertext, - RHS = Scalar, - Output = ElGamalCiphertext +impl<'a, 'b> Add<&'b DecryptHandle> for &'a DecryptHandle { + type Output = DecryptHandle; + + fn add(self, other: &'b DecryptHandle) -> DecryptHandle { + DecryptHandle(&self.0 + &other.0) + } +} + +define_add_variants!( + LHS = DecryptHandle, + RHS = DecryptHandle, + Output = DecryptHandle ); +impl<'a, 'b> Sub<&'b DecryptHandle> for &'a DecryptHandle { + type Output = DecryptHandle; + + fn sub(self, other: &'b DecryptHandle) -> DecryptHandle { + DecryptHandle(&self.0 - &other.0) + } +} + +define_sub_variants!( + LHS = DecryptHandle, + RHS = DecryptHandle, + Output = DecryptHandle +); + +impl<'a, 'b> Mul<&'b Scalar> for &'a DecryptHandle { + type Output = DecryptHandle; + + fn mul(self, other: &'b Scalar) -> DecryptHandle { + DecryptHandle(&self.0 * other) + } +} + +define_mul_variants!(LHS = DecryptHandle, RHS = Scalar, Output = DecryptHandle); + #[cfg(test)] mod tests { use { super::*, - crate::encryption::pedersen::Pedersen, + crate::encryption::{discrete_log::DECODE_U32_PRECOMPUTATION_FOR_G, pedersen::Pedersen}, solana_sdk::{signature::Keypair, signer::null_signer::NullSigner}, }; #[test] fn test_encrypt_decrypt_correctness() { - let ElGamalKeypair { public, secret } = ElGamalKeypair::default(); - let msg: u32 = 57; - let ct = ElGamal::encrypt(&public, msg); + let ElGamalKeypair { public, secret } = ElGamalKeypair::new_rand(); + let amount: u32 = 57; + let ciphertext = ElGamal::encrypt(&public, amount); let expected_instance = DiscreteLog { - generator: PedersenBase::default().G, - target: Scalar::from(msg) * PedersenBase::default().G, + generator: *G, + target: Scalar::from(amount) * &(*G), }; - assert_eq!(expected_instance, ElGamal::decrypt(&secret, &ct)); - - // Commenting out for faster testing - // assert_eq!(msg, ElGamalKeypair::decrypt_u32(&secret, &ct).unwrap()); + assert_eq!(expected_instance, ElGamal::decrypt(&secret, &ciphertext)); + assert_eq!( + 57_u32, + secret + .decrypt_u32_online(&ciphertext, &(*DECODE_U32_PRECOMPUTATION_FOR_G)) + .unwrap() + ); } #[test] fn test_decrypt_handle() { let ElGamalKeypair { - public: pk_1, - secret: sk_1, - } = ElGamalKeypair::default(); + public: public_0, + secret: secret_0, + } = ElGamalKeypair::new_rand(); let ElGamalKeypair { - public: pk_2, - secret: sk_2, - } = ElGamalKeypair::default(); + public: public_1, + secret: secret_1, + } = ElGamalKeypair::new_rand(); - let msg: u32 = 77; - let (comm, open) = Pedersen::new(msg); + let amount: u32 = 77; + let (commitment, opening) = Pedersen::new(amount); - let decrypt_handle_1 = pk_1.decrypt_handle(&open); - let decrypt_handle_2 = pk_2.decrypt_handle(&open); + let handle_0 = public_0.decrypt_handle(&opening); + let handle_1 = public_1.decrypt_handle(&opening); - let ct_1: ElGamalCiphertext = (comm, decrypt_handle_1).into(); - let ct_2: ElGamalCiphertext = (comm, decrypt_handle_2).into(); - - let expected_instance = DiscreteLog { - generator: PedersenBase::default().G, - target: Scalar::from(msg) * PedersenBase::default().G, + let ciphertext_0 = ElGamalCiphertext { + commitment, + handle: handle_0, + }; + let ciphertext_1 = ElGamalCiphertext { + commitment, + handle: handle_1, }; - assert_eq!(expected_instance, sk_1.decrypt(&ct_1)); - assert_eq!(expected_instance, sk_2.decrypt(&ct_2)); + let expected_instance = DiscreteLog { + generator: *G, + target: Scalar::from(amount) * (*G), + }; - // Commenting out for faster testing - // assert_eq!(msg, sk_1.decrypt_u32(&ct_1).unwrap()); - // assert_eq!(msg, sk_2.decrypt_u32(&ct_2).unwrap()); + assert_eq!(expected_instance, secret_0.decrypt(&ciphertext_0)); + assert_eq!(expected_instance, secret_1.decrypt(&ciphertext_1)); } #[test] fn test_homomorphic_addition() { - let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::default(); - let msg_0: u64 = 57; - let msg_1: u64 = 77; + let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand(); + let amount_0: u64 = 57; + let amount_1: u64 = 77; // Add two ElGamal ciphertexts - let open_0 = PedersenOpening::random(&mut OsRng); - let open_1 = PedersenOpening::random(&mut OsRng); + let opening_0 = PedersenOpening::new_rand(); + let opening_1 = PedersenOpening::new_rand(); - let ct_0 = ElGamal::encrypt_with(&public, msg_0, &open_0); - let ct_1 = ElGamal::encrypt_with(&public, msg_1, &open_1); + let ciphertext_0 = ElGamal::encrypt_with(amount_0, &public, &opening_0); + let ciphertext_1 = ElGamal::encrypt_with(amount_1, &public, &opening_1); - let ct_sum = ElGamal::encrypt_with(&public, msg_0 + msg_1, &(open_0 + open_1)); + let ciphertext_sum = + ElGamal::encrypt_with(amount_0 + amount_1, &public, &(&opening_0 + &opening_1)); - assert_eq!(ct_sum, ct_0 + ct_1); + assert_eq!(ciphertext_sum, ciphertext_0 + ciphertext_1); // Add to ElGamal ciphertext - let open = PedersenOpening::random(&mut OsRng); - let ct = ElGamal::encrypt_with(&public, msg_0, &open); - let ct_sum = ElGamal::encrypt_with(&public, msg_0 + msg_1, &open); + let opening = PedersenOpening::new_rand(); + let ciphertext = ElGamal::encrypt_with(amount_0, &public, &opening); + let ciphertext_sum = ElGamal::encrypt_with(amount_0 + amount_1, &public, &opening); - assert_eq!(ct_sum, ct.add_to_msg(msg_1)); + assert_eq!(ciphertext_sum, ciphertext.add_amount(amount_1)); } #[test] fn test_homomorphic_subtraction() { - let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::default(); - let msg_0: u64 = 77; - let msg_1: u64 = 55; + let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand(); + let amount_0: u64 = 77; + let amount_1: u64 = 55; // Subtract two ElGamal ciphertexts - let open_0 = PedersenOpening::random(&mut OsRng); - let open_1 = PedersenOpening::random(&mut OsRng); + let opening_0 = PedersenOpening::new_rand(); + let opening_1 = PedersenOpening::new_rand(); - let ct_0 = ElGamal::encrypt_with(&public, msg_0, &open_0); - let ct_1 = ElGamal::encrypt_with(&public, msg_1, &open_1); + let ciphertext_0 = ElGamal::encrypt_with(amount_0, &public, &opening_0); + let ciphertext_1 = ElGamal::encrypt_with(amount_1, &public, &opening_1); - let ct_sub = ElGamal::encrypt_with(&public, msg_0 - msg_1, &(open_0 - open_1)); + let ciphertext_sub = + ElGamal::encrypt_with(amount_0 - amount_1, &public, &(&opening_0 - &opening_1)); - assert_eq!(ct_sub, ct_0 - ct_1); + assert_eq!(ciphertext_sub, ciphertext_0 - ciphertext_1); // Subtract to ElGamal ciphertext - let open = PedersenOpening::random(&mut OsRng); - let ct = ElGamal::encrypt_with(&public, msg_0, &open); - let ct_sub = ElGamal::encrypt_with(&public, msg_0 - msg_1, &open); + let opening = PedersenOpening::new_rand(); + let ciphertext = ElGamal::encrypt_with(amount_0, &public, &opening); + let ciphertext_sub = ElGamal::encrypt_with(amount_0 - amount_1, &public, &opening); - assert_eq!(ct_sub, ct.sub_to_msg(msg_1)); + assert_eq!(ciphertext_sub, ciphertext.subtract_amount(amount_1)); } #[test] fn test_homomorphic_multiplication() { - let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::default(); - let msg_0: u64 = 57; - let msg_1: u64 = 77; + let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand(); + let amount_0: u64 = 57; + let amount_1: u64 = 77; - let open = PedersenOpening::random(&mut OsRng); + let opening = PedersenOpening::new_rand(); - let ct = ElGamal::encrypt_with(&public, msg_0, &open); - let scalar = Scalar::from(msg_1); + let ciphertext = ElGamal::encrypt_with(amount_0, &public, &opening); + let scalar = Scalar::from(amount_1); - let ct_prod = ElGamal::encrypt_with(&public, msg_0 * msg_1, &(open * scalar)); + let ciphertext_prod = + ElGamal::encrypt_with(amount_0 * amount_1, &public, &(&opening * scalar)); - assert_eq!(ct_prod, ct * scalar); - } - - #[test] - fn test_homomorphic_division() { - let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::default(); - let msg_0: u64 = 55; - let msg_1: u64 = 5; - - let open = PedersenOpening::random(&mut OsRng); - - let ct = ElGamal::encrypt_with(&public, msg_0, &open); - let scalar = Scalar::from(msg_1); - - let ct_div = ElGamal::encrypt_with(&public, msg_0 / msg_1, &(open / scalar)); - - assert_eq!(ct_div, ct / scalar); + assert_eq!(ciphertext_prod, ciphertext * scalar); } #[test] fn test_serde_ciphertext() { - let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::default(); - let msg: u64 = 77; - let ct = public.encrypt(msg); + let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand(); + let amount: u64 = 77; + let ciphertext = public.encrypt(amount); - let encoded = bincode::serialize(&ct).unwrap(); + let encoded = bincode::serialize(&ciphertext).unwrap(); let decoded: ElGamalCiphertext = bincode::deserialize(&encoded).unwrap(); - assert_eq!(ct, decoded); + assert_eq!(ciphertext, decoded); } #[test] fn test_serde_pubkey() { - let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::default(); + let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand(); let encoded = bincode::serialize(&public).unwrap(); let decoded: ElGamalPubkey = bincode::deserialize(&encoded).unwrap(); @@ -699,7 +742,7 @@ mod tests { #[test] fn test_serde_secretkey() { - let ElGamalKeypair { public: _, secret } = ElGamalKeypair::default(); + let ElGamalKeypair { public: _, secret } = ElGamalKeypair::new_rand(); let encoded = bincode::serialize(&secret).unwrap(); let decoded: ElGamalSecretKey = bincode::deserialize(&encoded).unwrap(); @@ -710,14 +753,16 @@ mod tests { fn tmp_file_path(name: &str) -> String { use std::env; let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string()); - let keypair = ElGamalKeypair::default(); + let keypair = ElGamalKeypair::new_rand(); format!("{}/tmp/{}-{}", out_dir, name, keypair.public) } #[test] fn test_write_keypair_file() { let outfile = tmp_file_path("test_write_keypair_file.json"); - let serialized_keypair = ElGamalKeypair::default().write_json_file(&outfile).unwrap(); + let serialized_keypair = ElGamalKeypair::new_rand() + .write_json_file(&outfile) + .unwrap(); let keypair_vec: Vec = serde_json::from_str(&serialized_keypair).unwrap(); assert!(Path::new(&outfile).exists()); assert_eq!( @@ -749,15 +794,21 @@ mod tests { fn test_write_keypair_file_overwrite_ok() { let outfile = tmp_file_path("test_write_keypair_file_overwrite_ok.json"); - ElGamalKeypair::default().write_json_file(&outfile).unwrap(); - ElGamalKeypair::default().write_json_file(&outfile).unwrap(); + ElGamalKeypair::new_rand() + .write_json_file(&outfile) + .unwrap(); + ElGamalKeypair::new_rand() + .write_json_file(&outfile) + .unwrap(); } #[test] fn test_write_keypair_file_truncate() { let outfile = tmp_file_path("test_write_keypair_file_truncate.json"); - ElGamalKeypair::default().write_json_file(&outfile).unwrap(); + ElGamalKeypair::new_rand() + .write_json_file(&outfile) + .unwrap(); ElGamalKeypair::read_json_file(&outfile).unwrap(); // Ensure outfile is truncated @@ -766,7 +817,9 @@ mod tests { f.write_all(String::from_utf8([b'a'; 2048].to_vec()).unwrap().as_bytes()) .unwrap(); } - ElGamalKeypair::default().write_json_file(&outfile).unwrap(); + ElGamalKeypair::new_rand() + .write_json_file(&outfile) + .unwrap(); ElGamalKeypair::read_json_file(&outfile).unwrap(); } @@ -787,4 +840,24 @@ mod tests { let null_signer = NullSigner::new(&Pubkey::default()); assert!(ElGamalSecretKey::new(&null_signer, &Pubkey::default()).is_err()); } + + #[test] + fn test_decrypt_handle_bytes() { + let handle = DecryptHandle(RistrettoPoint::default()); + + let encoded = handle.to_bytes(); + let decoded = DecryptHandle::from_bytes(&encoded).unwrap(); + + assert_eq!(handle, decoded); + } + + #[test] + fn test_serde_decrypt_handle() { + let handle = DecryptHandle(RistrettoPoint::default()); + + let encoded = bincode::serialize(&handle).unwrap(); + let decoded: DecryptHandle = bincode::deserialize(&encoded).unwrap(); + + assert_eq!(handle, decoded); + } } diff --git a/zk-token-sdk/src/encryption/mod.rs b/zk-token-sdk/src/encryption/mod.rs index 8792bd9a0..a90b88a5f 100644 --- a/zk-token-sdk/src/encryption/mod.rs +++ b/zk-token-sdk/src/encryption/mod.rs @@ -1,3 +1,15 @@ +//! Collection of encryption-related data structures and algorithms used in the Solana zk-token +//! protocol. +//! +//! The module contains implementations of the following cryptographic objects: +//! - Pedersen commitments that uses the prime-order Ristretto representation of Curve25519. +//! [curve25519-dalek](https://docs.rs/curve25519-dalek/latest/curve25519_dalek/ristretto/index.html) +//! is used for the Ristretto group implementation. +//! - The twisted ElGamal scheme, which converts Pedersen commitments into a public-key encryption +//! scheme. +//! - Basic type-wrapper around the AES-GCM-SIV symmetric authenticated encryption scheme +//! implemented by [aes-gcm-siv](https://docs.rs/aes-gcm-siv/latest/aes_gcm_siv/) crate. + pub mod auth_encryption; pub mod discrete_log; pub mod elgamal; diff --git a/zk-token-sdk/src/encryption/pedersen.rs b/zk-token-sdk/src/encryption/pedersen.rs index 7f17a6b7b..8f80b5c6a 100644 --- a/zk-token-sdk/src/encryption/pedersen.rs +++ b/zk-token-sdk/src/encryption/pedersen.rs @@ -1,8 +1,9 @@ +//! Pedersen commitment implementation using the Ristretto prime-order group. + #[cfg(not(target_arch = "bpf"))] -use rand::{rngs::OsRng, CryptoRng, RngCore}; +use rand::rngs::OsRng; use { - crate::encryption::elgamal::ElGamalPubkey, - core::ops::{Add, Div, Mul, Sub}, + core::ops::{Add, Mul, Sub}, curve25519_dalek::{ constants::{RISTRETTO_BASEPOINT_COMPRESSED, RISTRETTO_BASEPOINT_POINT}, ristretto::{CompressedRistretto, RistrettoPoint}, @@ -16,71 +17,62 @@ use { zeroize::Zeroize, }; -/// Curve basepoints for which Pedersen commitment is defined over. -/// -/// These points should be fixed for the entire system. -/// TODO: Consider setting these points as constants? -#[allow(non_snake_case)] -#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)] -pub struct PedersenBase { - pub G: RistrettoPoint, - pub H: RistrettoPoint, -} -/// Default PedersenBase. This is set arbitrarily for now, but it should be fixed -/// for the entire system. -/// -/// `G` is a constant point in the curve25519_dalek library -/// `H` is the Sha3 hash of `G` interpretted as a RistrettoPoint -impl Default for PedersenBase { - #[allow(non_snake_case)] - fn default() -> PedersenBase { - let G = RISTRETTO_BASEPOINT_POINT; - let H = - RistrettoPoint::hash_from_bytes::(RISTRETTO_BASEPOINT_COMPRESSED.as_bytes()); - - PedersenBase { G, H } - } +lazy_static::lazy_static! { + /// Pedersen base point for encoding messages to be committed. + pub static ref G: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; + /// Pedersen base point for encoding the commitment openings. + pub static ref H: RistrettoPoint = + RistrettoPoint::hash_from_bytes::(RISTRETTO_BASEPOINT_COMPRESSED.as_bytes()); } -/// Handle for the Pedersen commitment scheme +/// Algorithm handle for the Pedersen commitment scheme. pub struct Pedersen; impl Pedersen { - /// Given a number as input, the function returns a Pedersen commitment of - /// the number and its corresponding opening. + /// On input a message, the function returns a Pedersen commitment of the message and the + /// corresponding opening. + /// + /// This function is randomized. It internally samples a Pedersen opening using `OsRng`. #[cfg(not(target_arch = "bpf"))] #[allow(clippy::new_ret_no_self)] - pub fn new>(amount: T) -> (PedersenCommitment, PedersenOpening) { - let open = PedersenOpening(Scalar::random(&mut OsRng)); - let comm = Pedersen::with(amount, &open); + pub fn new>(message: T) -> (PedersenCommitment, PedersenOpening) { + let opening = PedersenOpening::new_rand(); + let commitment = Pedersen::with(message, &opening); - (comm, open) + (commitment, opening) } - /// Given a number and an opening as inputs, the function returns their - /// Pedersen commitment. + /// On input a message and a Pedersen opening, the function returns the corresponding Pedersen + /// commitment. + /// + /// This function is deterministic. #[allow(non_snake_case)] pub fn with>(amount: T, open: &PedersenOpening) -> PedersenCommitment { - let G = PedersenBase::default().G; - let H = PedersenBase::default().H; - let x: Scalar = amount.into(); let r = open.get_scalar(); - PedersenCommitment(RistrettoPoint::multiscalar_mul(&[x, r], &[G, H])) + PedersenCommitment(RistrettoPoint::multiscalar_mul(&[x, *r], &[*G, *H])) } } -#[derive(Serialize, Deserialize, Default, Clone, Debug, Zeroize)] +/// Pedersen opening type. +/// +/// Instances of Pedersen openings are zeroized on drop. +#[derive(Clone, Debug, Default, Serialize, Deserialize, Zeroize)] #[zeroize(drop)] pub struct PedersenOpening(pub(crate) Scalar); impl PedersenOpening { - pub fn get_scalar(&self) -> Scalar { - self.0 + pub fn get_scalar(&self) -> &Scalar { + &self.0 } #[cfg(not(target_arch = "bpf"))] - pub fn random(rng: &mut T) -> Self { - PedersenOpening(Scalar::random(rng)) + pub fn new_rand() -> Self { + PedersenOpening(Scalar::random(&mut OsRng)) + } + + #[allow(clippy::wrong_self_convention)] + pub fn as_bytes(&self) -> &[u8; 32] { + self.0.as_bytes() } #[allow(clippy::wrong_self_convention)] @@ -111,7 +103,7 @@ impl<'a, 'b> Add<&'b PedersenOpening> for &'a PedersenOpening { type Output = PedersenOpening; fn add(self, other: &'b PedersenOpening) -> PedersenOpening { - PedersenOpening(self.get_scalar() + other.get_scalar()) + PedersenOpening(&self.0 + &other.0) } } @@ -125,7 +117,7 @@ impl<'a, 'b> Sub<&'b PedersenOpening> for &'a PedersenOpening { type Output = PedersenOpening; fn sub(self, other: &'b PedersenOpening) -> PedersenOpening { - PedersenOpening(self.get_scalar() - other.get_scalar()) + PedersenOpening(&self.0 - &other.0) } } @@ -139,7 +131,7 @@ impl<'a, 'b> Mul<&'b Scalar> for &'a PedersenOpening { type Output = PedersenOpening; fn mul(self, other: &'b Scalar) -> PedersenOpening { - PedersenOpening(self.get_scalar() * other) + PedersenOpening(&self.0 * other) } } @@ -149,26 +141,12 @@ define_mul_variants!( Output = PedersenOpening ); -impl<'a, 'b> Div<&'b Scalar> for &'a PedersenOpening { - type Output = PedersenOpening; - - #[allow(clippy::suspicious_arithmetic_impl)] - fn div(self, other: &'b Scalar) -> PedersenOpening { - PedersenOpening(self.get_scalar() * other.invert()) - } -} - -define_div_variants!( - LHS = PedersenOpening, - RHS = Scalar, - Output = PedersenOpening -); - -#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, Eq, PartialEq)] +/// Pedersen commitment type. +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct PedersenCommitment(pub(crate) RistrettoPoint); impl PedersenCommitment { - pub fn get_point(&self) -> RistrettoPoint { - self.0 + pub fn get_point(&self) -> &RistrettoPoint { + &self.0 } #[allow(clippy::wrong_self_convention)] @@ -187,7 +165,7 @@ impl<'a, 'b> Add<&'b PedersenCommitment> for &'a PedersenCommitment { type Output = PedersenCommitment; fn add(self, other: &'b PedersenCommitment) -> PedersenCommitment { - PedersenCommitment(self.get_point() + other.get_point()) + PedersenCommitment(&self.0 + &other.0) } } @@ -201,7 +179,7 @@ impl<'a, 'b> Sub<&'b PedersenCommitment> for &'a PedersenCommitment { type Output = PedersenCommitment; fn sub(self, other: &'b PedersenCommitment) -> PedersenCommitment { - PedersenCommitment(self.get_point() - other.get_point()) + PedersenCommitment(&self.0 - &other.0) } } @@ -215,7 +193,7 @@ impl<'a, 'b> Mul<&'b Scalar> for &'a PedersenCommitment { type Output = PedersenCommitment; fn mul(self, other: &'b Scalar) -> PedersenCommitment { - PedersenCommitment(self.get_point() * other) + PedersenCommitment(&self.0 * other) } } @@ -225,111 +203,12 @@ define_mul_variants!( Output = PedersenCommitment ); -impl<'a, 'b> Div<&'b Scalar> for &'a PedersenCommitment { - type Output = PedersenCommitment; - - #[allow(clippy::suspicious_arithmetic_impl)] - fn div(self, other: &'b Scalar) -> PedersenCommitment { - PedersenCommitment(self.get_point() * other.invert()) - } -} - -define_div_variants!( - LHS = PedersenCommitment, - RHS = Scalar, - Output = PedersenCommitment -); - -/// Decryption handle for Pedersen commitment. -/// -/// A decryption handle can be combined with Pedersen commitments to form an -/// ElGamal ciphertext. -#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, Eq, PartialEq)] -pub struct PedersenDecryptHandle(pub(crate) RistrettoPoint); -impl PedersenDecryptHandle { - pub fn new(pk: &ElGamalPubkey, open: &PedersenOpening) -> Self { - Self(pk.get_point() * open.get_scalar()) - } - - pub fn get_point(&self) -> RistrettoPoint { - self.0 - } - - #[allow(clippy::wrong_self_convention)] - pub fn to_bytes(&self) -> [u8; 32] { - self.0.compress().to_bytes() - } - - pub fn from_bytes(bytes: &[u8]) -> Option { - Some(PedersenDecryptHandle( - CompressedRistretto::from_slice(bytes).decompress()?, - )) - } -} - -impl<'a, 'b> Add<&'b PedersenDecryptHandle> for &'a PedersenDecryptHandle { - type Output = PedersenDecryptHandle; - - fn add(self, other: &'b PedersenDecryptHandle) -> PedersenDecryptHandle { - PedersenDecryptHandle(self.get_point() + other.get_point()) - } -} - -define_add_variants!( - LHS = PedersenDecryptHandle, - RHS = PedersenDecryptHandle, - Output = PedersenDecryptHandle -); - -impl<'a, 'b> Sub<&'b PedersenDecryptHandle> for &'a PedersenDecryptHandle { - type Output = PedersenDecryptHandle; - - fn sub(self, other: &'b PedersenDecryptHandle) -> PedersenDecryptHandle { - PedersenDecryptHandle(self.get_point() - other.get_point()) - } -} - -define_sub_variants!( - LHS = PedersenDecryptHandle, - RHS = PedersenDecryptHandle, - Output = PedersenDecryptHandle -); - -impl<'a, 'b> Mul<&'b Scalar> for &'a PedersenDecryptHandle { - type Output = PedersenDecryptHandle; - - fn mul(self, other: &'b Scalar) -> PedersenDecryptHandle { - PedersenDecryptHandle(self.get_point() * other) - } -} - -define_mul_variants!( - LHS = PedersenDecryptHandle, - RHS = Scalar, - Output = PedersenDecryptHandle -); - -impl<'a, 'b> Div<&'b Scalar> for &'a PedersenDecryptHandle { - type Output = PedersenDecryptHandle; - - #[allow(clippy::suspicious_arithmetic_impl)] - fn div(self, other: &'b Scalar) -> PedersenDecryptHandle { - PedersenDecryptHandle(self.get_point() * other.invert()) - } -} - -define_div_variants!( - LHS = PedersenDecryptHandle, - RHS = Scalar, - Output = PedersenDecryptHandle -); - #[cfg(test)] mod tests { use super::*; #[test] - fn test_homomorphic_addition() { + fn test_pedersen_homomorphic_addition() { let amt_0: u64 = 77; let amt_1: u64 = 57; @@ -345,7 +224,7 @@ mod tests { } #[test] - fn test_homomorphic_subtraction() { + fn test_pedersen_homomorphic_subtraction() { let amt_0: u64 = 77; let amt_1: u64 = 57; @@ -361,7 +240,7 @@ mod tests { } #[test] - fn test_homomorphic_multiplication() { + fn test_pedersen_homomorphic_multiplication() { let amt_0: u64 = 77; let amt_1: u64 = 57; @@ -373,19 +252,7 @@ mod tests { } #[test] - fn test_homomorphic_division() { - let amt_0: u64 = 77; - let amt_1: u64 = 7; - - let (comm, open) = Pedersen::new(amt_0); - let scalar = Scalar::from(amt_1); - let comm_addition = Pedersen::with(amt_0 / amt_1, &(open / scalar)); - - assert_eq!(comm_addition, comm / scalar); - } - - #[test] - fn test_commitment_bytes() { + fn test_pedersen_commitment_bytes() { let amt: u64 = 77; let (comm, _) = Pedersen::new(amt); @@ -396,7 +263,7 @@ mod tests { } #[test] - fn test_opening_bytes() { + fn test_pedersen_opening_bytes() { let open = PedersenOpening(Scalar::random(&mut OsRng)); let encoded = open.to_bytes(); @@ -406,17 +273,7 @@ mod tests { } #[test] - fn test_decrypt_handle_bytes() { - let handle = PedersenDecryptHandle(RistrettoPoint::default()); - - let encoded = handle.to_bytes(); - let decoded = PedersenDecryptHandle::from_bytes(&encoded).unwrap(); - - assert_eq!(handle, decoded); - } - - #[test] - fn test_serde_commitment() { + fn test_serde_pedersen_commitment() { let amt: u64 = 77; let (comm, _) = Pedersen::new(amt); @@ -427,7 +284,7 @@ mod tests { } #[test] - fn test_serde_opening() { + fn test_serde_pedersen_opening() { let open = PedersenOpening(Scalar::random(&mut OsRng)); let encoded = bincode::serialize(&open).unwrap(); @@ -435,14 +292,4 @@ mod tests { assert_eq!(open, decoded); } - - #[test] - fn test_serde_decrypt_handle() { - let handle = PedersenDecryptHandle(RistrettoPoint::default()); - - let encoded = bincode::serialize(&handle).unwrap(); - let decoded: PedersenDecryptHandle = bincode::deserialize(&encoded).unwrap(); - - assert_eq!(handle, decoded); - } } diff --git a/zk-token-sdk/src/instruction/close_account.rs b/zk-token-sdk/src/instruction/close_account.rs index ca56b273e..9c285e4ea 100644 --- a/zk-token-sdk/src/instruction/close_account.rs +++ b/zk-token-sdk/src/instruction/close_account.rs @@ -109,14 +109,14 @@ mod test { use { super::*, crate::encryption::{ - elgamal::ElGamalKeypair, - pedersen::{Pedersen, PedersenDecryptHandle, PedersenOpening}, + elgamal::{DecryptHandle, ElGamalKeypair}, + pedersen::{Pedersen, PedersenOpening}, }, }; #[test] fn test_close_account_correctness() { - let source_keypair = ElGamalKeypair::default(); + let source_keypair = ElGamalKeypair::new_rand(); // general case: encryption of 0 let balance = source_keypair.public.encrypt(0_u64); @@ -135,11 +135,11 @@ mod test { // edge cases: only C or D is zero - such ciphertext is always invalid let zeroed_comm = Pedersen::with(0_u64, &PedersenOpening::default()); - let handle = balance.decrypt_handle; + let handle = balance.handle; let zeroed_comm_ciphertext = ElGamalCiphertext { - message_comm: zeroed_comm, - decrypt_handle: handle, + commitment: zeroed_comm, + handle, }; let proof = CloseAccountProof::new(&source_keypair, &zeroed_comm_ciphertext); @@ -148,8 +148,8 @@ mod test { .is_err()); let zeroed_handle_ciphertext = ElGamalCiphertext { - message_comm: balance.message_comm, - decrypt_handle: PedersenDecryptHandle::default(), + commitment: balance.commitment, + handle: DecryptHandle::default(), }; let proof = CloseAccountProof::new(&source_keypair, &zeroed_handle_ciphertext); diff --git a/zk-token-sdk/src/instruction/transfer.rs b/zk-token-sdk/src/instruction/transfer.rs index cca5c33a1..5e6c09d60 100644 --- a/zk-token-sdk/src/instruction/transfer.rs +++ b/zk-token-sdk/src/instruction/transfer.rs @@ -7,8 +7,10 @@ use { crate::{ encryption::{ discrete_log::*, - elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey}, - pedersen::{Pedersen, PedersenCommitment, PedersenDecryptHandle, PedersenOpening}, + elgamal::{ + DecryptHandle, ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey, + }, + pedersen::{Pedersen, PedersenCommitment, PedersenOpening}, }, errors::ProofError, instruction::{Role, Verifiable}, @@ -128,8 +130,8 @@ impl TransferData { }; // subtract transfer amount from the spendable ciphertext - let spendable_comm = spendable_balance_ciphertext.message_comm; - let spendable_handle = spendable_balance_ciphertext.decrypt_handle; + let spendable_comm = spendable_balance_ciphertext.commitment; + let spendable_handle = spendable_balance_ciphertext.handle; let new_spendable_balance = spendable_balance - transfer_amount; let new_spendable_comm = spendable_comm - combine_u32_comms(comm_lo, comm_hi); @@ -137,8 +139,8 @@ impl TransferData { spendable_handle - combine_u32_handles(handle_source_lo, handle_source_hi); let new_spendable_ct = ElGamalCiphertext { - message_comm: new_spendable_comm, - decrypt_handle: new_spendable_handle, + commitment: new_spendable_comm, + handle: new_spendable_handle, }; // range_proof and validity_proof should be generated together @@ -172,7 +174,10 @@ impl TransferData { } .try_into()?; - Ok((transfer_comm_lo, decryption_handle_lo).into()) + Ok(ElGamalCiphertext { + commitment: transfer_comm_lo, + handle: decryption_handle_lo, + }) } /// Extracts the lo ciphertexts associated with a transfer data @@ -187,7 +192,10 @@ impl TransferData { } .try_into()?; - Ok((transfer_comm_hi, decryption_handle_hi).into()) + Ok(ElGamalCiphertext { + commitment: transfer_comm_hi, + handle: decryption_handle_hi, + }) } /// Decrypts transfer amount from transfer data @@ -274,8 +282,8 @@ impl TransferProof { // extract the relevant scalar and Ristretto points from the inputs let P_EG = source_keypair.public.get_point(); - let C_EG = source_new_balance_ct.message_comm.get_point(); - let D_EG = source_new_balance_ct.decrypt_handle.get_point(); + let C_EG = source_new_balance_ct.commitment.get_point(); + let D_EG = source_new_balance_ct.handle.get_point(); let C_Ped = source_commitment.get_point(); // append all current state to the transcript @@ -339,8 +347,8 @@ impl TransferProof { let new_spendable_ct: ElGamalCiphertext = (*new_spendable_ct).try_into()?; let P_EG = source_pk.get_point(); - let C_EG = new_spendable_ct.message_comm.get_point(); - let D_EG = new_spendable_ct.decrypt_handle.get_point(); + let C_EG = new_spendable_ct.commitment.get_point(); + let D_EG = new_spendable_ct.handle.get_point(); let C_Ped = commitment.get_point(); // append all current state to the transcript @@ -361,11 +369,11 @@ impl TransferProof { let amount_comm_lo: PedersenCommitment = amount_comms.lo.try_into()?; let amount_comm_hi: PedersenCommitment = amount_comms.hi.try_into()?; - let handle_lo_dest: PedersenDecryptHandle = decryption_handles_lo.dest.try_into()?; - let handle_hi_dest: PedersenDecryptHandle = decryption_handles_hi.dest.try_into()?; + let handle_lo_dest: DecryptHandle = decryption_handles_lo.dest.try_into()?; + let handle_hi_dest: DecryptHandle = decryption_handles_hi.dest.try_into()?; - let handle_lo_auditor: PedersenDecryptHandle = decryption_handles_lo.auditor.try_into()?; - let handle_hi_auditor: PedersenDecryptHandle = decryption_handles_hi.auditor.try_into()?; + let handle_lo_auditor: DecryptHandle = decryption_handles_lo.auditor.try_into()?; + let handle_hi_auditor: DecryptHandle = decryption_handles_hi.auditor.try_into()?; // TODO: validity proof validity_proof.verify( @@ -419,9 +427,9 @@ pub struct EncryptedTransferAmount { #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct TransferDecryptHandles { - pub source: pod::PedersenDecryptHandle, // 32 bytes - pub dest: pod::PedersenDecryptHandle, // 32 bytes - pub auditor: pod::PedersenDecryptHandle, // 32 bytes + pub source: pod::DecryptHandle, // 32 bytes + pub dest: pod::DecryptHandle, // 32 bytes + pub auditor: pod::DecryptHandle, // 32 bytes } #[derive(Clone, Copy, Pod, Zeroable)] @@ -437,9 +445,9 @@ pub struct EncryptedTransferFee { /// The transfer fee commitment pub fee_comm: pod::PedersenCommitment, /// The decryption handle for destination ElGamal pubkey - pub decrypt_handle_dest: pod::PedersenDecryptHandle, + pub decrypt_handle_dest: pod::DecryptHandle, /// The decryption handle for fee collector ElGamal pubkey - pub decrypt_handle_fee_collector: pod::PedersenDecryptHandle, + pub decrypt_handle_fee_collector: pod::DecryptHandle, } /// Split u64 number into two u32 numbers @@ -464,10 +472,7 @@ pub fn combine_u32_comms( } #[cfg(not(target_arch = "bpf"))] -pub fn combine_u32_handles( - handle_lo: PedersenDecryptHandle, - handle_hi: PedersenDecryptHandle, -) -> PedersenDecryptHandle { +pub fn combine_u32_handles(handle_lo: DecryptHandle, handle_hi: DecryptHandle) -> DecryptHandle { handle_lo + handle_hi * Scalar::from(TWO_32) } @@ -483,9 +488,9 @@ mod test { #[test] fn test_transfer_correctness() { // ElGamalKeypair keys for source, destination, and auditor accounts - let source_keypair = ElGamalKeypair::default(); - let dest_pk = ElGamalKeypair::default().public; - let auditor_pk = ElGamalKeypair::default().public; + let source_keypair = ElGamalKeypair::new_rand(); + let dest_pk = ElGamalKeypair::new_rand().public; + let auditor_pk = ElGamalKeypair::new_rand().public; // create source account spendable ciphertext let spendable_balance: u64 = 77; @@ -510,17 +515,17 @@ mod test { #[test] fn test_source_dest_ciphertext() { // ElGamalKeypair keys for source, destination, and auditor accounts - let source_keypair = ElGamalKeypair::default(); + let source_keypair = ElGamalKeypair::new_rand(); let ElGamalKeypair { public: dest_pk, secret: dest_sk, - } = ElGamalKeypair::default(); + } = ElGamalKeypair::new_rand(); let ElGamalKeypair { public: auditor_pk, secret: auditor_sk, - } = ElGamalKeypair::default(); + } = ElGamalKeypair::new_rand(); // create source account spendable ciphertext let spendable_balance: u64 = 77; diff --git a/zk-token-sdk/src/instruction/withdraw.rs b/zk-token-sdk/src/instruction/withdraw.rs index 4eed0b34e..def1a6930 100644 --- a/zk-token-sdk/src/instruction/withdraw.rs +++ b/zk-token-sdk/src/instruction/withdraw.rs @@ -117,8 +117,8 @@ impl WithdrawProof { // extract the relevant scalar and Ristretto points from the inputs let P_EG = source_keypair.public.get_point(); - let C_EG = final_balance_ct.message_comm.get_point(); - let D_EG = final_balance_ct.decrypt_handle.get_point(); + let C_EG = final_balance_ct.commitment.get_point(); + let D_EG = final_balance_ct.handle.get_point(); let C_Ped = commitment.get_point(); // append all current state to the transcript @@ -166,8 +166,8 @@ impl WithdrawProof { // extract the relevant scalar and Ristretto points from the inputs let P_EG = source_pk.get_point(); - let C_EG = final_balance_ct.message_comm.get_point(); - let D_EG = final_balance_ct.decrypt_handle.get_point(); + let C_EG = final_balance_ct.commitment.get_point(); + let D_EG = final_balance_ct.handle.get_point(); let C_Ped = commitment.get_point(); // append all current state to the transcript @@ -201,7 +201,7 @@ mod test { #[test] fn test_withdraw_correctness() { // generate and verify proof for the proper setting - let elgamal_keypair = ElGamalKeypair::default(); + let elgamal_keypair = ElGamalKeypair::new_rand(); let current_balance: u64 = 77; let current_balance_ct = elgamal_keypair.public.encrypt(current_balance); diff --git a/zk-token-sdk/src/lib.rs b/zk-token-sdk/src/lib.rs index 7bd75d32d..ab623efa2 100644 --- a/zk-token-sdk/src/lib.rs +++ b/zk-token-sdk/src/lib.rs @@ -1,4 +1,21 @@ -#![allow(clippy::integer_arithmetic)] +#![allow(clippy::integer_arithmetic, clippy::op_ref)] + +// The warning `clippy::op_ref` is disabled to allow efficient operator arithmetic of structs that +// implement the `Copy` trait. +// +// ``` +// let opening_0: PedersenOpening = PedersenOpening::new_rand(); +// let opening_1: PedersenOpening = PedersenOpening::new_rand(); +// +// // since PedersenOpening implement `Copy`, `opening_0` and `opening_1` will be copied as +// // parameters before `opening_sum` is computed. +// let opening_sum = opening_0 + opening_1; +// +// // if passed in as references, the extra copies will not occur +// let opening_sum = &opening_0 + &opening_1; +// ``` +// +// `clippy::op_ref` is turned off to prevent clippy from warning that this is not idiomatic code. #[cfg(not(target_arch = "bpf"))] #[macro_use] diff --git a/zk-token-sdk/src/macros.rs b/zk-token-sdk/src/macros.rs index 1b6c48b8d..6351ba942 100644 --- a/zk-token-sdk/src/macros.rs +++ b/zk-token-sdk/src/macros.rs @@ -74,28 +74,3 @@ macro_rules! define_mul_variants { } }; } - -macro_rules! define_div_variants { - (LHS = $lhs:ty, RHS = $rhs:ty, Output = $out:ty) => { - impl<'b> Div<&'b $rhs> for $lhs { - type Output = $out; - fn div(self, rhs: &'b $rhs) -> $out { - &self / rhs - } - } - - impl<'a> Div<$rhs> for &'a $lhs { - type Output = $out; - fn div(self, rhs: $rhs) -> $out { - self / &rhs - } - } - - impl Div<$rhs> for $lhs { - type Output = $out; - fn div(self, rhs: $rhs) -> $out { - &self / &rhs - } - } - }; -} diff --git a/zk-token-sdk/src/range_proof/mod.rs b/zk-token-sdk/src/range_proof/mod.rs index 87ce4b6cf..68edfa2e5 100644 --- a/zk-token-sdk/src/range_proof/mod.rs +++ b/zk-token-sdk/src/range_proof/mod.rs @@ -7,7 +7,7 @@ use { }; use { crate::{ - encryption::pedersen::PedersenBase, + encryption::pedersen::{G, H}, range_proof::{ errors::RangeProofError, generators::BulletproofGens, inner_product::InnerProductProof, }, @@ -71,12 +71,10 @@ impl RangeProof { // TODO: precompute generators // TODO: double check Pedersen generators and range proof generators does not interfere let bp_gens = BulletproofGens::new(nm); - let G = PedersenBase::default().G; - let H = PedersenBase::default().H; // bit-decompose values and generate their Pedersen vector commitment let a_blinding = Scalar::random(&mut OsRng); - let mut A = a_blinding * H; + let mut A = a_blinding * &(*H); let mut gens_iter = bp_gens.G(nm).zip(bp_gens.H(nm)); for (amount_i, n_i) in amounts.iter().zip(bit_lengths.iter()) { @@ -100,7 +98,7 @@ impl RangeProof { let S = RistrettoPoint::multiscalar_mul( iter::once(&s_blinding).chain(s_L.iter()).chain(s_R.iter()), - iter::once(&H).chain(bp_gens.G(nm)).chain(bp_gens.H(nm)), + iter::once(&(*H)).chain(bp_gens.G(nm)).chain(bp_gens.H(nm)), ) .compress(); @@ -168,8 +166,8 @@ impl RangeProof { let t_blinding_poly = util::Poly2( agg_opening, - t_1_blinding.get_scalar(), - t_2_blinding.get_scalar(), + *t_1_blinding.get_scalar(), + *t_2_blinding.get_scalar(), ); let t_x = t_poly.eval(x); @@ -188,7 +186,7 @@ impl RangeProof { // compute the inner product argument on the commitment: // P = + + *Q let w = transcript.challenge_scalar(b"w"); - let Q = w * G; + let Q = w * &(*G); let G_factors: Vec = iter::repeat(Scalar::one()).take(nm).collect(); let H_factors: Vec = util::exp_iter(y.invert()).take(nm).collect(); @@ -229,9 +227,6 @@ impl RangeProof { // commitments and bit-lengths must be same length vectors assert_eq!(comms.len(), bit_lengths.len()); - let G = PedersenBase::default().G; - let H = PedersenBase::default().H; - let m = bit_lengths.len(); let nm: usize = bit_lengths.iter().sum(); let bp_gens = BulletproofGens::new(nm); @@ -307,8 +302,8 @@ impl RangeProof { .chain(iter::once(self.S.decompress())) .chain(iter::once(self.T_1.decompress())) .chain(iter::once(self.T_2.decompress())) - .chain(iter::once(Some(H))) - .chain(iter::once(Some(G))) + .chain(iter::once(Some(*H))) + .chain(iter::once(Some(*G))) .chain(self.ipp_proof.L_vec.iter().map(|L| L.decompress())) .chain(self.ipp_proof.R_vec.iter().map(|R| R.decompress())) .chain(bp_gens.G(nm).map(|&x| Some(x))) diff --git a/zk-token-sdk/src/sigma_proofs/equality_proof.rs b/zk-token-sdk/src/sigma_proofs/equality_proof.rs index 88d5098f4..1d96b63c6 100644 --- a/zk-token-sdk/src/sigma_proofs/equality_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/equality_proof.rs @@ -2,7 +2,7 @@ use { crate::encryption::{ elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, - pedersen::{PedersenBase, PedersenCommitment, PedersenOpening}, + pedersen::{PedersenCommitment, PedersenOpening, G, H}, }, curve25519_dalek::traits::MultiscalarMul, rand::rngs::OsRng, @@ -40,11 +40,8 @@ impl EqualityProof { transcript: &mut Transcript, ) -> Self { // extract the relevant scalar and Ristretto points from the inputs - let G = PedersenBase::default().G; - let H = PedersenBase::default().H; - let P_EG = elgamal_keypair.public.get_point(); - let D_EG = ciphertext.decrypt_handle.get_point(); + let D_EG = ciphertext.handle.get_point(); let s = elgamal_keypair.secret.get_scalar(); let x = Scalar::from(message); @@ -56,8 +53,8 @@ impl EqualityProof { let y_r = Scalar::random(&mut OsRng); let Y_0 = (y_s * P_EG).compress(); - let Y_1 = RistrettoPoint::multiscalar_mul(vec![y_x, y_s], vec![G, D_EG]).compress(); - let Y_2 = RistrettoPoint::multiscalar_mul(vec![y_x, y_r], vec![G, H]).compress(); + let Y_1 = RistrettoPoint::multiscalar_mul(vec![y_x, y_s], vec![&(*G), D_EG]).compress(); + let Y_2 = RistrettoPoint::multiscalar_mul(vec![y_x, y_r], vec![&(*G), &(*H)]).compress(); // record masking factors in transcript transcript.append_point(b"Y_0", &Y_0); @@ -90,12 +87,9 @@ impl EqualityProof { transcript: &mut Transcript, ) -> Result<(), EqualityProofError> { // extract the relevant scalar and Ristretto points from the inputs - let G = PedersenBase::default().G; - let H = PedersenBase::default().H; - let P_EG = elgamal_pubkey.get_point(); - let C_EG = ciphertext.message_comm.get_point(); - let D_EG = ciphertext.decrypt_handle.get_point(); + let C_EG = ciphertext.commitment.get_point(); + let D_EG = ciphertext.handle.get_point(); let C_Ped = commitment.get_point(); @@ -127,7 +121,19 @@ impl EqualityProof { -ww * c, -ww, ], - vec![P_EG, H, Y_0, G, D_EG, C_EG, Y_1, G, H, C_Ped, Y_2], + vec![ + P_EG, + &(*H), + &Y_0, + &(*G), + D_EG, + C_EG, + &Y_1, + &(*G), + &(*H), + C_Ped, + &Y_2, + ], ); if check.is_identity() { @@ -179,7 +185,7 @@ mod test { #[test] fn test_equality_proof() { // success case - let elgamal_keypair = ElGamalKeypair::default(); + let elgamal_keypair = ElGamalKeypair::new_rand(); let message: u64 = 55; let ciphertext = elgamal_keypair.public.encrypt(message); @@ -206,7 +212,7 @@ mod test { .is_ok()); // fail case: encrypted and committed messages are different - let elgamal_keypair = ElGamalKeypair::default(); + let elgamal_keypair = ElGamalKeypair::new_rand(); let encrypted_message: u64 = 55; let committed_message: u64 = 77; diff --git a/zk-token-sdk/src/sigma_proofs/fee_proof.rs b/zk-token-sdk/src/sigma_proofs/fee_proof.rs index 5f939037e..018c86bd9 100644 --- a/zk-token-sdk/src/sigma_proofs/fee_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/fee_proof.rs @@ -1,6 +1,6 @@ #[cfg(not(target_arch = "bpf"))] use { - crate::encryption::pedersen::{PedersenBase, PedersenCommitment, PedersenOpening}, + crate::encryption::pedersen::{PedersenCommitment, PedersenOpening, G, H}, rand::rngs::OsRng, }; use { @@ -36,9 +36,6 @@ impl FeeProof { transcript: &mut Transcript, ) -> Self { // extract the relevant scalar and Ristretto points from the input - let G = PedersenBase::default().G; - let H = PedersenBase::default().H; - let x = Scalar::from(delta_fee); let m = Scalar::from(max_fee); @@ -65,9 +62,11 @@ impl FeeProof { // simulate max proof let z_max = Scalar::random(&mut OsRng); let c_max = Scalar::random(&mut OsRng); - let Y_max = - RistrettoPoint::multiscalar_mul(vec![z_max, -c_max, c_max * m], vec![H, C_max, G]) - .compress(); + let Y_max = RistrettoPoint::multiscalar_mul( + vec![z_max, -c_max, c_max * m], + vec![&(*H), C_max, &(*G)], + ) + .compress(); let fee_max_proof = FeeMaxProof { Y_max, @@ -81,9 +80,11 @@ impl FeeProof { let y_delta_claimed = Scalar::random(&mut OsRng); let Y_delta_real = - RistrettoPoint::multiscalar_mul(vec![y_x, y_delta_real], vec![G, H]).compress(); + RistrettoPoint::multiscalar_mul(vec![y_x, y_delta_real], vec![&(*G), &(*H)]) + .compress(); let Y_delta_claimed = - RistrettoPoint::multiscalar_mul(vec![y_x, y_delta_claimed], vec![G, H]).compress(); + RistrettoPoint::multiscalar_mul(vec![y_x, y_delta_claimed], vec![&(*G), &(*H)]) + .compress(); transcript.append_point(b"Y_max", &Y_max); transcript.append_point(b"Y_delta_real", &Y_delta_real); @@ -119,13 +120,13 @@ impl FeeProof { let Y_delta_real = RistrettoPoint::multiscalar_mul( vec![z_x, z_delta_real, -c_equality], - vec![G, H, C_delta_real], + vec![&(*G), &(*H), C_delta_real], ) .compress(); let Y_delta_claimed = RistrettoPoint::multiscalar_mul( vec![z_x, z_delta_claimed, -c_equality], - vec![G, H, C_delta_claimed], + vec![&(*G), &(*H), C_delta_claimed], ) .compress(); @@ -139,7 +140,7 @@ impl FeeProof { // generate max proof let y_max = Scalar::random(&mut OsRng); - let Y_max = (y_max * H).compress(); + let Y_max = (y_max * &(*H)).compress(); transcript.append_point(b"Y_max", &Y_max); transcript.append_point(b"Y_delta_real", &Y_delta_real); @@ -174,9 +175,6 @@ impl FeeProof { transcript: &mut Transcript, ) -> Result<(), FeeProofError> { // extract the relevant scalar and Ristretto points from the input - let G = PedersenBase::default().G; - let H = PedersenBase::default().H; - let m = Scalar::from(max_fee); let C_max = commitment_fee.get_point(); @@ -243,17 +241,17 @@ impl FeeProof { ], vec![ C_max, - G, - H, - Y_max, - G, - H, + &(*G), + &(*H), + &Y_max, + &(*G), + &(*H), C_delta_real, - Y_delta_real, - G, - H, + &Y_delta_real, + &(*G), + &(*H), C_delta_claimed, - Y_delta_claimed, + &Y_delta_claimed, ], ); diff --git a/zk-token-sdk/src/sigma_proofs/validity_proof.rs b/zk-token-sdk/src/sigma_proofs/validity_proof.rs index 361c57041..adeac231d 100644 --- a/zk-token-sdk/src/sigma_proofs/validity_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/validity_proof.rs @@ -1,8 +1,8 @@ #[cfg(not(target_arch = "bpf"))] use { crate::encryption::{ - elgamal::ElGamalPubkey, - pedersen::{PedersenBase, PedersenCommitment, PedersenDecryptHandle, PedersenOpening}, + elgamal::{DecryptHandle, ElGamalPubkey}, + pedersen::{PedersenCommitment, PedersenOpening, G, H}, }, curve25519_dalek::traits::MultiscalarMul, rand::rngs::OsRng, @@ -39,9 +39,6 @@ impl ValidityProof { transcript: &mut Transcript, ) -> Self { // extract the relevant scalar and Ristretto points from the inputs - let G = PedersenBase::default().G; - let H = PedersenBase::default().H; - let P_dest = elgamal_pubkey_dest.get_point(); let P_auditor = elgamal_pubkey_auditor.get_point(); @@ -49,7 +46,7 @@ impl ValidityProof { let y_r = Scalar::random(&mut OsRng); let y_x = Scalar::random(&mut OsRng); - let Y_0 = RistrettoPoint::multiscalar_mul(vec![y_r, y_x], vec![H, G]).compress(); + let Y_0 = RistrettoPoint::multiscalar_mul(vec![y_r, y_x], vec![&(*H), &(*G)]).compress(); let Y_1 = (y_r * P_dest).compress(); let Y_2 = (y_r * P_auditor).compress(); @@ -84,14 +81,10 @@ impl ValidityProof { elgamal_pubkey_dest: &ElGamalPubkey, elgamal_pubkey_auditor: &ElGamalPubkey, commitments: (&PedersenCommitment, &PedersenCommitment), - handle_dest: (&PedersenDecryptHandle, &PedersenDecryptHandle), - handle_auditor: (&PedersenDecryptHandle, &PedersenDecryptHandle), + handle_dest: (&DecryptHandle, &DecryptHandle), + handle_auditor: (&DecryptHandle, &DecryptHandle), transcript: &mut Transcript, ) -> Result<(), ValidityProofError> { - // extract the relevant scalar and Ristretto points from the inputs - let G = PedersenBase::default().G; - let H = PedersenBase::default().H; - // include Y_0, Y_1, Y_2 to transcript and extract challenges transcript.validate_and_append_point(b"Y_0", &self.Y_0)?; transcript.validate_and_append_point(b"Y_1", &self.Y_1)?; @@ -127,7 +120,18 @@ impl ValidityProof { -ww * c, -ww, ], - vec![H, G, C, Y_0, P_dest, D_dest, Y_1, P_auditor, D_auditor, Y_2], + vec![ + &(*H), + &(*G), + &C, + &Y_0, + P_dest, + &D_dest, + &Y_1, + P_auditor, + &D_auditor, + &Y_2, + ], ); if check.is_identity() { @@ -175,8 +179,8 @@ mod test { #[test] fn test_validity_proof() { - let elgamal_pubkey_dest = ElGamalKeypair::default().public; - let elgamal_pubkey_auditor = ElGamalKeypair::default().public; + let elgamal_pubkey_dest = ElGamalKeypair::new_rand().public; + let elgamal_pubkey_auditor = ElGamalKeypair::new_rand().public; let x_lo: u64 = 55; let x_hi: u64 = 77; 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 6259f0f04..6027d5e6f 100644 --- a/zk-token-sdk/src/sigma_proofs/zero_balance_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/zero_balance_proof.rs @@ -2,7 +2,7 @@ use { crate::encryption::{ elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, - pedersen::PedersenBase, + pedersen::H, }, curve25519_dalek::traits::MultiscalarMul, rand::rngs::OsRng, @@ -38,8 +38,8 @@ impl ZeroBalanceProof { let P = elgamal_keypair.public.get_point(); let s = elgamal_keypair.secret.get_scalar(); - let C = elgamal_ciphertext.message_comm.get_point(); - let D = elgamal_ciphertext.decrypt_handle.get_point(); + let C = elgamal_ciphertext.commitment.get_point(); + let D = elgamal_ciphertext.handle.get_point(); // record ElGamal pubkey and ciphertext in the transcript transcript.append_point(b"P", &P.compress()); @@ -71,10 +71,8 @@ impl ZeroBalanceProof { ) -> Result<(), ZeroBalanceProofError> { // extract the relevant scalar and Ristretto points from the input let P = elgamal_pubkey.get_point(); - let C = elgamal_ciphertext.message_comm.get_point(); - let D = elgamal_ciphertext.decrypt_handle.get_point(); - - let H = PedersenBase::default().H; + let C = elgamal_ciphertext.commitment.get_point(); + let D = elgamal_ciphertext.handle.get_point(); // record ElGamal pubkey and ciphertext in the transcript transcript.validate_and_append_point(b"P", &P.compress())?; @@ -96,7 +94,7 @@ impl ZeroBalanceProof { // check the required algebraic relation let check = RistrettoPoint::multiscalar_mul( vec![z, -c, -Scalar::one(), w * z, -w * c, -w], - vec![P, H, Y_P, D, C, Y_D], + vec![P, &(*H), &Y_P, D, C, &Y_D], ); if check.is_identity() { @@ -131,13 +129,13 @@ impl ZeroBalanceProof { mod test { use super::*; use crate::encryption::{ - elgamal::ElGamalKeypair, - pedersen::{Pedersen, PedersenDecryptHandle, PedersenOpening}, + elgamal::{DecryptHandle, ElGamalKeypair}, + pedersen::{Pedersen, PedersenOpening}, }; #[test] fn test_zero_balance_proof() { - let source_keypair = ElGamalKeypair::default(); + let source_keypair = ElGamalKeypair::new_rand(); let mut transcript_prover = Transcript::new(b"test"); let mut transcript_verifier = Transcript::new(b"test"); @@ -175,11 +173,11 @@ mod test { // edge cases: only C or D is zero - such ciphertext is always invalid let zeroed_comm = Pedersen::with(0_u64, &PedersenOpening::default()); - let handle = elgamal_ciphertext.decrypt_handle; + let handle = elgamal_ciphertext.handle; let zeroed_comm_ciphertext = ElGamalCiphertext { - message_comm: zeroed_comm, - decrypt_handle: handle, + commitment: zeroed_comm, + handle, }; let proof = ZeroBalanceProof::new( @@ -197,8 +195,8 @@ mod test { let (zero_comm, _) = Pedersen::new(0_u64); let zeroed_handle_ciphertext = ElGamalCiphertext { - message_comm: zero_comm, - decrypt_handle: PedersenDecryptHandle::default(), + commitment: zero_comm, + handle: DecryptHandle::default(), }; let proof = ZeroBalanceProof::new( diff --git a/zk-token-sdk/src/zk_token_elgamal/convert.rs b/zk-token-sdk/src/zk_token_elgamal/convert.rs index ce9ebc59c..2d8ee1955 100644 --- a/zk-token-sdk/src/zk_token_elgamal/convert.rs +++ b/zk-token-sdk/src/zk_token_elgamal/convert.rs @@ -1,8 +1,8 @@ use super::pod; pub use target_arch::*; -impl From<(pod::PedersenCommitment, pod::PedersenDecryptHandle)> for pod::ElGamalCiphertext { - fn from((comm, decrypt_handle): (pod::PedersenCommitment, pod::PedersenDecryptHandle)) -> Self { +impl From<(pod::PedersenCommitment, pod::DecryptHandle)> for pod::ElGamalCiphertext { + fn from((comm, decrypt_handle): (pod::PedersenCommitment, pod::DecryptHandle)) -> Self { let mut buf = [0_u8; 64]; buf[..32].copy_from_slice(&comm.0); buf[32..].copy_from_slice(&decrypt_handle.0); @@ -17,8 +17,8 @@ mod target_arch { crate::{ encryption::{ auth_encryption::AeCiphertext, - elgamal::{ElGamalCiphertext, ElGamalPubkey}, - pedersen::{PedersenCommitment, PedersenDecryptHandle}, + elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalPubkey}, + pedersen::PedersenCommitment, }, errors::ProofError, range_proof::{errors::RangeProofError, RangeProof}, @@ -107,25 +107,25 @@ mod target_arch { } #[cfg(not(target_arch = "bpf"))] - impl From for pod::PedersenDecryptHandle { - fn from(handle: PedersenDecryptHandle) -> Self { + impl From for pod::DecryptHandle { + fn from(handle: DecryptHandle) -> Self { Self(handle.to_bytes()) } } // For proof verification, interpret pod::PedersenDecHandle as CompressedRistretto #[cfg(not(target_arch = "bpf"))] - impl From for CompressedRistretto { - fn from(pod: pod::PedersenDecryptHandle) -> Self { + impl From for CompressedRistretto { + fn from(pod: pod::DecryptHandle) -> Self { Self(pod.0) } } #[cfg(not(target_arch = "bpf"))] - impl TryFrom for PedersenDecryptHandle { + impl TryFrom for DecryptHandle { type Error = ProofError; - fn try_from(pod: pod::PedersenDecryptHandle) -> Result { + fn try_from(pod: pod::DecryptHandle) -> Result { Self::from_bytes(&pod.0).ok_or(ProofError::InconsistentCTData) } } diff --git a/zk-token-sdk/src/zk_token_elgamal/ops.rs b/zk-token-sdk/src/zk_token_elgamal/ops.rs index f77ef43d5..df92a9de5 100644 --- a/zk-token-sdk/src/zk_token_elgamal/ops.rs +++ b/zk-token-sdk/src/zk_token_elgamal/ops.rs @@ -241,7 +241,6 @@ mod tests { }, bytemuck::Zeroable, curve25519_dalek::scalar::Scalar, - rand::rngs::OsRng, std::convert::TryInto, }; @@ -252,7 +251,7 @@ mod tests { // spendable_ct should be an encryption of 0 for any public key when // `PedersenOpen::default()` is used - let public = ElGamalKeypair::default().public; + let public = ElGamalKeypair::new_rand().public; let balance: u64 = 0; assert_eq!( spendable_ct, @@ -260,7 +259,7 @@ mod tests { ); // homomorphism should work like any other ciphertext - let open = PedersenOpening::random(&mut OsRng); + let open = PedersenOpening::new_rand(); let transfer_amount_ct = public.encrypt_with(55_u64, &open); let transfer_amount_pod: pod::ElGamalCiphertext = transfer_amount_ct.into(); @@ -276,7 +275,7 @@ mod tests { let added_ct = ops::add_to(&spendable_balance, 55).unwrap(); - let public = ElGamalKeypair::default().public; + let public = ElGamalKeypair::new_rand().public; let expected: pod::ElGamalCiphertext = public .encrypt_with(55_u64, &PedersenOpening::default()) .into(); @@ -287,8 +286,8 @@ mod tests { #[test] fn test_subtract_from() { let amount = 77_u64; - let public = ElGamalKeypair::default().public; - let open = PedersenOpening::random(&mut OsRng); + let public = ElGamalKeypair::new_rand().public; + let open = PedersenOpening::new_rand(); let encrypted_amount: pod::ElGamalCiphertext = public.encrypt_with(amount, &open).into(); let subtracted_ct = ops::subtract_from(&encrypted_amount, 55).unwrap(); @@ -313,9 +312,9 @@ mod tests { let (amount_lo, amount_hi) = split_u64_into_u32(transfer_amount); // generate public keys - let source_pk = ElGamalKeypair::default().public; - let dest_pk = ElGamalKeypair::default().public; - let auditor_pk = ElGamalKeypair::default().public; + let source_pk = ElGamalKeypair::new_rand().public; + let dest_pk = ElGamalKeypair::new_rand().public; + let auditor_pk = ElGamalKeypair::new_rand().public; // commitments associated with TransferRangeProof let (comm_lo, open_lo) = Pedersen::new(amount_lo); @@ -325,21 +324,17 @@ mod tests { let comm_hi: pod::PedersenCommitment = comm_hi.into(); // decryption handles associated with TransferValidityProof - let handle_source_lo: pod::PedersenDecryptHandle = - source_pk.decrypt_handle(&open_lo).into(); - let handle_dest_lo: pod::PedersenDecryptHandle = dest_pk.decrypt_handle(&open_lo).into(); - let _handle_auditor_lo: pod::PedersenDecryptHandle = - auditor_pk.decrypt_handle(&open_lo).into(); + let handle_source_lo: pod::DecryptHandle = source_pk.decrypt_handle(&open_lo).into(); + let handle_dest_lo: pod::DecryptHandle = dest_pk.decrypt_handle(&open_lo).into(); + let _handle_auditor_lo: pod::DecryptHandle = auditor_pk.decrypt_handle(&open_lo).into(); - let handle_source_hi: pod::PedersenDecryptHandle = - source_pk.decrypt_handle(&open_hi).into(); - let handle_dest_hi: pod::PedersenDecryptHandle = dest_pk.decrypt_handle(&open_hi).into(); - let _handle_auditor_hi: pod::PedersenDecryptHandle = - auditor_pk.decrypt_handle(&open_hi).into(); + let handle_source_hi: pod::DecryptHandle = source_pk.decrypt_handle(&open_hi).into(); + let handle_dest_hi: pod::DecryptHandle = dest_pk.decrypt_handle(&open_hi).into(); + let _handle_auditor_hi: pod::DecryptHandle = auditor_pk.decrypt_handle(&open_hi).into(); // source spendable and recipient pending - let source_open = PedersenOpening::random(&mut OsRng); - let dest_open = PedersenOpening::random(&mut OsRng); + let source_open = PedersenOpening::new_rand(); + let dest_open = PedersenOpening::new_rand(); let source_spendable_ct: pod::ElGamalCiphertext = source_pk.encrypt_with(77_u64, &source_open).into(); diff --git a/zk-token-sdk/src/zk_token_elgamal/pod.rs b/zk-token-sdk/src/zk_token_elgamal/pod.rs index 147e2ea29..4cd650db6 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod.rs @@ -47,9 +47,9 @@ impl fmt::Debug for PedersenCommitment { #[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq)] #[repr(transparent)] -pub struct PedersenDecryptHandle(pub [u8; 32]); +pub struct DecryptHandle(pub [u8; 32]); -impl fmt::Debug for PedersenDecryptHandle { +impl fmt::Debug for DecryptHandle { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self.0) }