From 855a0c1a92bd1a495ddff9dcb213473eaba2b980 Mon Sep 17 00:00:00 2001 From: abcalphabet Date: Thu, 4 Apr 2024 20:47:07 -0300 Subject: [PATCH] ElGamal: add From impls; deprecate from/to_bytes (#246) * ElGamal: add From impls; deprecate from/to_bytes * variable names --- zk-token-sdk/src/encryption/elgamal.rs | 128 ++++++++++++++++-- .../batched_grouped_ciphertext_validity.rs | 4 +- .../ciphertext_ciphertext_equality.rs | 4 +- .../ciphertext_commitment_equality.rs | 2 +- .../grouped_ciphertext_validity.rs | 4 +- .../src/instruction/pubkey_validity.rs | 2 +- zk-token-sdk/src/instruction/withdraw.rs | 2 +- zk-token-sdk/src/instruction/zero_balance.rs | 2 +- .../ciphertext_commitment_equality_proof.rs | 2 +- .../grouped_ciphertext_validity_proof.rs | 2 +- .../src/sigma_proofs/zero_balance_proof.rs | 2 +- .../src/zk_token_elgamal/pod/elgamal.rs | 4 +- 12 files changed, 130 insertions(+), 28 deletions(-) diff --git a/zk-token-sdk/src/encryption/elgamal.rs b/zk-token-sdk/src/encryption/elgamal.rs index 0bc9eb0511..5e94904e48 100644 --- a/zk-token-sdk/src/encryption/elgamal.rs +++ b/zk-token-sdk/src/encryption/elgamal.rs @@ -68,7 +68,7 @@ const ELGAMAL_PUBKEY_LEN: usize = RISTRETTO_POINT_LEN; 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; +pub const ELGAMAL_KEYPAIR_LEN: usize = ELGAMAL_PUBKEY_LEN + ELGAMAL_SECRET_KEY_LEN; #[derive(Error, Clone, Debug, Eq, PartialEq)] pub enum ElGamalError { @@ -82,6 +82,10 @@ pub enum ElGamalError { CiphertextDeserialization, #[error("failed to deserialize public key")] PubkeyDeserialization, + #[error("failed to deserialize keypair")] + KeypairDeserialization, + #[error("failed to deserialize secret key")] + SecretKeyDeserialization, } /// Algorithm handle for the twisted ElGamal encryption scheme @@ -235,6 +239,8 @@ impl ElGamalKeypair { &self.secret } + #[deprecated(note = "please use `into()` instead")] + #[allow(deprecated)] 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()); @@ -242,6 +248,8 @@ impl ElGamalKeypair { bytes } + #[deprecated(note = "please use `try_from()` instead")] + #[allow(deprecated)] pub fn from_bytes(bytes: &[u8]) -> Option { if bytes.len() != ELGAMAL_KEYPAIR_LEN { return None; @@ -256,7 +264,7 @@ impl ElGamalKeypair { /// Reads a JSON-encoded keypair from a `Reader` implementor pub fn read_json(reader: &mut R) -> Result> { let bytes: Vec = serde_json::from_reader(reader)?; - Self::from_bytes(&bytes).ok_or_else(|| { + Self::try_from(bytes.as_slice()).ok().ok_or_else(|| { std::io::Error::new(std::io::ErrorKind::Other, "Invalid ElGamalKeypair").into() }) } @@ -268,8 +276,8 @@ impl ElGamalKeypair { /// Writes to a `Write` implementer with JSON-encoding pub fn write_json(&self, writer: &mut W) -> Result> { - let bytes = self.to_bytes(); - let json = serde_json::to_string(&bytes.to_vec())?; + let json = + serde_json::to_string(&Into::<[u8; ELGAMAL_KEYPAIR_LEN]>::into(self).as_slice())?; writer.write_all(&json.clone().into_bytes())?; Ok(json) } @@ -293,6 +301,40 @@ impl EncodableKey for ElGamalKeypair { } } +impl TryFrom<&[u8]> for ElGamalKeypair { + type Error = ElGamalError; + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() != ELGAMAL_KEYPAIR_LEN { + return Err(ElGamalError::KeypairDeserialization); + } + + Ok(Self { + public: ElGamalPubkey::try_from(&bytes[..ELGAMAL_PUBKEY_LEN])?, + secret: ElGamalSecretKey::try_from(&bytes[ELGAMAL_PUBKEY_LEN..])?, + }) + } +} + +impl From for [u8; ELGAMAL_KEYPAIR_LEN] { + fn from(keypair: ElGamalKeypair) -> Self { + let mut bytes = [0u8; ELGAMAL_KEYPAIR_LEN]; + bytes[..ELGAMAL_PUBKEY_LEN] + .copy_from_slice(&Into::<[u8; ELGAMAL_PUBKEY_LEN]>::into(keypair.public)); + bytes[ELGAMAL_PUBKEY_LEN..].copy_from_slice(keypair.secret.as_bytes()); + bytes + } +} + +impl From<&ElGamalKeypair> for [u8; ELGAMAL_KEYPAIR_LEN] { + fn from(keypair: &ElGamalKeypair) -> Self { + let mut bytes = [0u8; ELGAMAL_KEYPAIR_LEN]; + bytes[..ELGAMAL_PUBKEY_LEN] + .copy_from_slice(&Into::<[u8; ELGAMAL_PUBKEY_LEN]>::into(keypair.public)); + bytes[ELGAMAL_PUBKEY_LEN..].copy_from_slice(keypair.secret.as_bytes()); + bytes + } +} + impl SeedDerivable for ElGamalKeypair { fn from_seed(seed: &[u8]) -> Result> { let secret = ElGamalSecretKey::from_seed(seed)?; @@ -343,10 +385,12 @@ impl ElGamalPubkey { &self.0 } + #[deprecated(note = "please use `into()` instead")] pub fn to_bytes(&self) -> [u8; ELGAMAL_PUBKEY_LEN] { self.0.compress().to_bytes() } + #[deprecated(note = "please use `try_from()` instead")] pub fn from_bytes(bytes: &[u8]) -> Option { if bytes.len() != ELGAMAL_PUBKEY_LEN { return None; @@ -384,13 +428,13 @@ impl ElGamalPubkey { impl EncodableKey for ElGamalPubkey { fn read(reader: &mut R) -> Result> { let bytes: Vec = serde_json::from_reader(reader)?; - Self::from_bytes(&bytes).ok_or_else(|| { + Self::try_from(bytes.as_slice()).ok().ok_or_else(|| { std::io::Error::new(std::io::ErrorKind::Other, "Invalid ElGamalPubkey").into() }) } fn write(&self, writer: &mut W) -> Result> { - let bytes = self.to_bytes(); + let bytes = Into::<[u8; ELGAMAL_PUBKEY_LEN]>::into(*self); let json = serde_json::to_string(&bytes.to_vec())?; writer.write_all(&json.clone().into_bytes())?; Ok(json) @@ -399,7 +443,38 @@ impl EncodableKey for ElGamalPubkey { impl fmt::Display for ElGamalPubkey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", BASE64_STANDARD.encode(self.to_bytes())) + write!( + f, + "{}", + BASE64_STANDARD.encode(Into::<[u8; ELGAMAL_PUBKEY_LEN]>::into(*self)) + ) + } +} + +impl TryFrom<&[u8]> for ElGamalPubkey { + type Error = ElGamalError; + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() != ELGAMAL_PUBKEY_LEN { + return Err(ElGamalError::PubkeyDeserialization); + } + + Ok(ElGamalPubkey( + CompressedRistretto::from_slice(bytes) + .decompress() + .ok_or(ElGamalError::PubkeyDeserialization)?, + )) + } +} + +impl From for [u8; ELGAMAL_PUBKEY_LEN] { + fn from(pubkey: ElGamalPubkey) -> Self { + pubkey.0.compress().to_bytes() + } +} + +impl From<&ElGamalPubkey> for [u8; ELGAMAL_PUBKEY_LEN] { + fn from(pubkey: &ElGamalPubkey) -> Self { + pubkey.0.compress().to_bytes() } } @@ -487,10 +562,12 @@ impl ElGamalSecretKey { self.0.as_bytes() } + #[deprecated(note = "please use `into()` instead")] pub fn to_bytes(&self) -> [u8; ELGAMAL_SECRET_KEY_LEN] { self.0.to_bytes() } + #[deprecated(note = "please use `try_from()` instead")] pub fn from_bytes(bytes: &[u8]) -> Option { match bytes.try_into() { Ok(bytes) => Scalar::from_canonical_bytes(bytes).map(ElGamalSecretKey), @@ -502,13 +579,13 @@ impl ElGamalSecretKey { impl EncodableKey for ElGamalSecretKey { fn read(reader: &mut R) -> Result> { let bytes: Vec = serde_json::from_reader(reader)?; - Self::from_bytes(&bytes).ok_or_else(|| { + Self::try_from(bytes.as_slice()).ok().ok_or_else(|| { std::io::Error::new(std::io::ErrorKind::Other, "Invalid ElGamalSecretKey").into() }) } fn write(&self, writer: &mut W) -> Result> { - let bytes = self.to_bytes(); + let bytes = Into::<[u8; ELGAMAL_SECRET_KEY_LEN]>::into(self); let json = serde_json::to_string(&bytes.to_vec())?; writer.write_all(&json.clone().into_bytes())?; Ok(json) @@ -546,6 +623,31 @@ impl From for ElGamalSecretKey { } } +impl TryFrom<&[u8]> for ElGamalSecretKey { + type Error = ElGamalError; + fn try_from(bytes: &[u8]) -> Result { + match bytes.try_into() { + Ok(bytes) => Ok(ElGamalSecretKey::from( + Scalar::from_canonical_bytes(bytes) + .ok_or(ElGamalError::SecretKeyDeserialization)?, + )), + _ => Err(ElGamalError::SecretKeyDeserialization), + } + } +} + +impl From for [u8; ELGAMAL_SECRET_KEY_LEN] { + fn from(secret_key: ElGamalSecretKey) -> Self { + secret_key.0.to_bytes() + } +} + +impl From<&ElGamalSecretKey> for [u8; ELGAMAL_SECRET_KEY_LEN] { + fn from(secret_key: &ElGamalSecretKey) -> Self { + secret_key.0.to_bytes() + } +} + impl Eq for ElGamalSecretKey {} impl PartialEq for ElGamalSecretKey { fn eq(&self, other: &Self) -> bool { @@ -954,10 +1056,10 @@ mod tests { assert!(Path::new(&outfile).exists()); assert_eq!( keypair_vec, - ElGamalKeypair::read_json_file(&outfile) - .unwrap() - .to_bytes() - .to_vec() + Into::<[u8; ELGAMAL_KEYPAIR_LEN]>::into( + ElGamalKeypair::read_json_file(&outfile).unwrap() + ) + .to_vec() ); #[cfg(unix)] diff --git a/zk-token-sdk/src/instruction/batched_grouped_ciphertext_validity.rs b/zk-token-sdk/src/instruction/batched_grouped_ciphertext_validity.rs index 10a9d79080..0be760691f 100644 --- a/zk-token-sdk/src/instruction/batched_grouped_ciphertext_validity.rs +++ b/zk-token-sdk/src/instruction/batched_grouped_ciphertext_validity.rs @@ -70,8 +70,8 @@ impl BatchedGroupedCiphertext2HandlesValidityProofData { opening_lo: &PedersenOpening, opening_hi: &PedersenOpening, ) -> Result { - let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes()); - let pod_auditor_pubkey = pod::ElGamalPubkey(auditor_pubkey.to_bytes()); + let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.into()); + let pod_auditor_pubkey = pod::ElGamalPubkey(auditor_pubkey.into()); let pod_grouped_ciphertext_lo = (*grouped_ciphertext_lo).into(); let pod_grouped_ciphertext_hi = (*grouped_ciphertext_hi).into(); diff --git a/zk-token-sdk/src/instruction/ciphertext_ciphertext_equality.rs b/zk-token-sdk/src/instruction/ciphertext_ciphertext_equality.rs index 5d5fa7e54f..aa31db0a43 100644 --- a/zk-token-sdk/src/instruction/ciphertext_ciphertext_equality.rs +++ b/zk-token-sdk/src/instruction/ciphertext_ciphertext_equality.rs @@ -66,8 +66,8 @@ impl CiphertextCiphertextEqualityProofData { destination_opening: &PedersenOpening, amount: u64, ) -> Result { - let pod_source_pubkey = pod::ElGamalPubkey(source_keypair.pubkey().to_bytes()); - let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes()); + let pod_source_pubkey = pod::ElGamalPubkey(source_keypair.pubkey().into()); + let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.into()); let pod_source_ciphertext = pod::ElGamalCiphertext(source_ciphertext.to_bytes()); let pod_destination_ciphertext = pod::ElGamalCiphertext(destination_ciphertext.to_bytes()); diff --git a/zk-token-sdk/src/instruction/ciphertext_commitment_equality.rs b/zk-token-sdk/src/instruction/ciphertext_commitment_equality.rs index 4f4f49fab5..5cd2b3cbc5 100644 --- a/zk-token-sdk/src/instruction/ciphertext_commitment_equality.rs +++ b/zk-token-sdk/src/instruction/ciphertext_commitment_equality.rs @@ -62,7 +62,7 @@ impl CiphertextCommitmentEqualityProofData { amount: u64, ) -> Result { let context = CiphertextCommitmentEqualityProofContext { - pubkey: pod::ElGamalPubkey(keypair.pubkey().to_bytes()), + pubkey: pod::ElGamalPubkey(keypair.pubkey().into()), ciphertext: pod::ElGamalCiphertext(ciphertext.to_bytes()), commitment: pod::PedersenCommitment(commitment.to_bytes()), }; diff --git a/zk-token-sdk/src/instruction/grouped_ciphertext_validity.rs b/zk-token-sdk/src/instruction/grouped_ciphertext_validity.rs index bb13a0805a..a99a733c74 100644 --- a/zk-token-sdk/src/instruction/grouped_ciphertext_validity.rs +++ b/zk-token-sdk/src/instruction/grouped_ciphertext_validity.rs @@ -63,8 +63,8 @@ impl GroupedCiphertext2HandlesValidityProofData { amount: u64, opening: &PedersenOpening, ) -> Result { - let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes()); - let pod_auditor_pubkey = pod::ElGamalPubkey(auditor_pubkey.to_bytes()); + let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.into()); + let pod_auditor_pubkey = pod::ElGamalPubkey(auditor_pubkey.into()); let pod_grouped_ciphertext = (*grouped_ciphertext).into(); let context = GroupedCiphertext2HandlesValidityProofContext { diff --git a/zk-token-sdk/src/instruction/pubkey_validity.rs b/zk-token-sdk/src/instruction/pubkey_validity.rs index b0251fbf56..3c264f0cdd 100644 --- a/zk-token-sdk/src/instruction/pubkey_validity.rs +++ b/zk-token-sdk/src/instruction/pubkey_validity.rs @@ -50,7 +50,7 @@ pub struct PubkeyValidityProofContext { #[cfg(not(target_os = "solana"))] impl PubkeyValidityData { pub fn new(keypair: &ElGamalKeypair) -> Result { - let pod_pubkey = pod::ElGamalPubkey(keypair.pubkey().to_bytes()); + let pod_pubkey = pod::ElGamalPubkey(keypair.pubkey().into()); let context = PubkeyValidityProofContext { pubkey: pod_pubkey }; diff --git a/zk-token-sdk/src/instruction/withdraw.rs b/zk-token-sdk/src/instruction/withdraw.rs index 3dee9ffc61..530b6de0d7 100644 --- a/zk-token-sdk/src/instruction/withdraw.rs +++ b/zk-token-sdk/src/instruction/withdraw.rs @@ -69,7 +69,7 @@ impl WithdrawData { // current source balance let final_ciphertext = current_ciphertext - &ElGamal::encode(amount); - let pod_pubkey = pod::ElGamalPubkey(keypair.pubkey().to_bytes()); + let pod_pubkey = pod::ElGamalPubkey(keypair.pubkey().into()); let pod_final_ciphertext: pod::ElGamalCiphertext = final_ciphertext.into(); let context = WithdrawProofContext { diff --git a/zk-token-sdk/src/instruction/zero_balance.rs b/zk-token-sdk/src/instruction/zero_balance.rs index d5a51bb3aa..7d52b80063 100644 --- a/zk-token-sdk/src/instruction/zero_balance.rs +++ b/zk-token-sdk/src/instruction/zero_balance.rs @@ -54,7 +54,7 @@ impl ZeroBalanceProofData { keypair: &ElGamalKeypair, ciphertext: &ElGamalCiphertext, ) -> Result { - let pod_pubkey = pod::ElGamalPubkey(keypair.pubkey().to_bytes()); + let pod_pubkey = pod::ElGamalPubkey(keypair.pubkey().into()); let pod_ciphertext = pod::ElGamalCiphertext(ciphertext.to_bytes()); let context = ZeroBalanceProofContext { 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 6451d46d19..768b07b216 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 @@ -309,7 +309,7 @@ mod test { #[test] fn test_ciphertext_commitment_equality_proof_edge_cases() { // if ElGamal public key zero (public key is invalid), then the proof should always reject - let public = ElGamalPubkey::from_bytes(&[0u8; 32]).unwrap(); + let public = ElGamalPubkey::try_from([0u8; 32].as_slice()).unwrap(); let secret = ElGamalSecretKey::new_rand(); let elgamal_keypair = ElGamalKeypair::new_for_tests(public, secret); 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 9f1df14c31..1c1a57997e 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 @@ -274,7 +274,7 @@ mod test { #[test] fn test_grouped_ciphertext_validity_proof_edge_cases() { // if destination public key zeroed, then the proof should always reject - let destination_pubkey = ElGamalPubkey::from_bytes(&[0u8; 32]).unwrap(); + let destination_pubkey = ElGamalPubkey::try_from([0u8; 32].as_slice()).unwrap(); let auditor_keypair = ElGamalKeypair::new_rand(); let auditor_pubkey = auditor_keypair.pubkey(); 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 cab8759f4f..3585978c76 100644 --- a/zk-token-sdk/src/sigma_proofs/zero_balance_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/zero_balance_proof.rs @@ -286,7 +286,7 @@ mod test { let mut prover_transcript = Transcript::new(b"test"); let mut verifier_transcript = Transcript::new(b"test"); - let public = ElGamalPubkey::from_bytes(&[0u8; 32]).unwrap(); + let public = ElGamalPubkey::try_from([0u8; 32].as_slice()).unwrap(); let ciphertext = public.encrypt(0_u64); let proof = ZeroBalanceProof::new(&source_keypair, &ciphertext, &mut prover_transcript); 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 251729c085..2123d716a4 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod/elgamal.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod/elgamal.rs @@ -100,7 +100,7 @@ impl_from_str!( #[cfg(not(target_os = "solana"))] impl From for ElGamalPubkey { fn from(decoded_pubkey: decoded::ElGamalPubkey) -> Self { - Self(decoded_pubkey.to_bytes()) + Self(decoded_pubkey.into()) } } @@ -109,7 +109,7 @@ impl TryFrom for decoded::ElGamalPubkey { type Error = ElGamalError; fn try_from(pod_pubkey: ElGamalPubkey) -> Result { - Self::from_bytes(&pod_pubkey.0).ok_or(ElGamalError::PubkeyDeserialization) + Self::try_from(pod_pubkey.0.as_slice()) } }