ElGamal: add From impls; deprecate from/to_bytes (#246)

* ElGamal: add From impls; deprecate from/to_bytes

* variable names
This commit is contained in:
abcalphabet 2024-04-04 20:47:07 -03:00 committed by GitHub
parent 87fc227b51
commit 855a0c1a92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 130 additions and 28 deletions

View File

@ -68,7 +68,7 @@ const ELGAMAL_PUBKEY_LEN: usize = RISTRETTO_POINT_LEN;
const ELGAMAL_SECRET_KEY_LEN: usize = SCALAR_LEN; const ELGAMAL_SECRET_KEY_LEN: usize = SCALAR_LEN;
/// Byte length of an ElGamal keypair /// 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)] #[derive(Error, Clone, Debug, Eq, PartialEq)]
pub enum ElGamalError { pub enum ElGamalError {
@ -82,6 +82,10 @@ pub enum ElGamalError {
CiphertextDeserialization, CiphertextDeserialization,
#[error("failed to deserialize public key")] #[error("failed to deserialize public key")]
PubkeyDeserialization, PubkeyDeserialization,
#[error("failed to deserialize keypair")]
KeypairDeserialization,
#[error("failed to deserialize secret key")]
SecretKeyDeserialization,
} }
/// Algorithm handle for the twisted ElGamal encryption scheme /// Algorithm handle for the twisted ElGamal encryption scheme
@ -235,6 +239,8 @@ impl ElGamalKeypair {
&self.secret &self.secret
} }
#[deprecated(note = "please use `into()` instead")]
#[allow(deprecated)]
pub fn to_bytes(&self) -> [u8; ELGAMAL_KEYPAIR_LEN] { pub fn to_bytes(&self) -> [u8; ELGAMAL_KEYPAIR_LEN] {
let mut bytes = [0u8; ELGAMAL_KEYPAIR_LEN]; let mut bytes = [0u8; ELGAMAL_KEYPAIR_LEN];
bytes[..ELGAMAL_PUBKEY_LEN].copy_from_slice(&self.public.to_bytes()); bytes[..ELGAMAL_PUBKEY_LEN].copy_from_slice(&self.public.to_bytes());
@ -242,6 +248,8 @@ impl ElGamalKeypair {
bytes bytes
} }
#[deprecated(note = "please use `try_from()` instead")]
#[allow(deprecated)]
pub fn from_bytes(bytes: &[u8]) -> Option<Self> { pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != ELGAMAL_KEYPAIR_LEN { if bytes.len() != ELGAMAL_KEYPAIR_LEN {
return None; return None;
@ -256,7 +264,7 @@ impl ElGamalKeypair {
/// Reads a JSON-encoded keypair from a `Reader` implementor /// Reads a JSON-encoded keypair from a `Reader` implementor
pub fn read_json<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> { pub fn read_json<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> {
let bytes: Vec<u8> = serde_json::from_reader(reader)?; let bytes: Vec<u8> = 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() 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 /// Writes to a `Write` implementer with JSON-encoding
pub fn write_json<W: Write>(&self, writer: &mut W) -> Result<String, Box<dyn error::Error>> { pub fn write_json<W: Write>(&self, writer: &mut W) -> Result<String, Box<dyn error::Error>> {
let bytes = self.to_bytes(); let json =
let json = serde_json::to_string(&bytes.to_vec())?; serde_json::to_string(&Into::<[u8; ELGAMAL_KEYPAIR_LEN]>::into(self).as_slice())?;
writer.write_all(&json.clone().into_bytes())?; writer.write_all(&json.clone().into_bytes())?;
Ok(json) Ok(json)
} }
@ -293,6 +301,40 @@ impl EncodableKey for ElGamalKeypair {
} }
} }
impl TryFrom<&[u8]> for ElGamalKeypair {
type Error = ElGamalError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
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<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 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 { impl SeedDerivable for ElGamalKeypair {
fn from_seed(seed: &[u8]) -> Result<Self, Box<dyn error::Error>> { fn from_seed(seed: &[u8]) -> Result<Self, Box<dyn error::Error>> {
let secret = ElGamalSecretKey::from_seed(seed)?; let secret = ElGamalSecretKey::from_seed(seed)?;
@ -343,10 +385,12 @@ impl ElGamalPubkey {
&self.0 &self.0
} }
#[deprecated(note = "please use `into()` instead")]
pub fn to_bytes(&self) -> [u8; ELGAMAL_PUBKEY_LEN] { pub fn to_bytes(&self) -> [u8; ELGAMAL_PUBKEY_LEN] {
self.0.compress().to_bytes() self.0.compress().to_bytes()
} }
#[deprecated(note = "please use `try_from()` instead")]
pub fn from_bytes(bytes: &[u8]) -> Option<ElGamalPubkey> { pub fn from_bytes(bytes: &[u8]) -> Option<ElGamalPubkey> {
if bytes.len() != ELGAMAL_PUBKEY_LEN { if bytes.len() != ELGAMAL_PUBKEY_LEN {
return None; return None;
@ -384,13 +428,13 @@ impl ElGamalPubkey {
impl EncodableKey for ElGamalPubkey { impl EncodableKey for ElGamalPubkey {
fn read<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> { fn read<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> {
let bytes: Vec<u8> = serde_json::from_reader(reader)?; let bytes: Vec<u8> = 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() std::io::Error::new(std::io::ErrorKind::Other, "Invalid ElGamalPubkey").into()
}) })
} }
fn write<W: Write>(&self, writer: &mut W) -> Result<String, Box<dyn error::Error>> { fn write<W: Write>(&self, writer: &mut W) -> Result<String, Box<dyn error::Error>> {
let bytes = self.to_bytes(); let bytes = Into::<[u8; ELGAMAL_PUBKEY_LEN]>::into(*self);
let json = serde_json::to_string(&bytes.to_vec())?; let json = serde_json::to_string(&bytes.to_vec())?;
writer.write_all(&json.clone().into_bytes())?; writer.write_all(&json.clone().into_bytes())?;
Ok(json) Ok(json)
@ -399,7 +443,38 @@ impl EncodableKey for ElGamalPubkey {
impl fmt::Display for ElGamalPubkey { impl fmt::Display for ElGamalPubkey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 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<Self, Self::Error> {
if bytes.len() != ELGAMAL_PUBKEY_LEN {
return Err(ElGamalError::PubkeyDeserialization);
}
Ok(ElGamalPubkey(
CompressedRistretto::from_slice(bytes)
.decompress()
.ok_or(ElGamalError::PubkeyDeserialization)?,
))
}
}
impl From<ElGamalPubkey> 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() self.0.as_bytes()
} }
#[deprecated(note = "please use `into()` instead")]
pub fn to_bytes(&self) -> [u8; ELGAMAL_SECRET_KEY_LEN] { pub fn to_bytes(&self) -> [u8; ELGAMAL_SECRET_KEY_LEN] {
self.0.to_bytes() self.0.to_bytes()
} }
#[deprecated(note = "please use `try_from()` instead")]
pub fn from_bytes(bytes: &[u8]) -> Option<ElGamalSecretKey> { pub fn from_bytes(bytes: &[u8]) -> Option<ElGamalSecretKey> {
match bytes.try_into() { match bytes.try_into() {
Ok(bytes) => Scalar::from_canonical_bytes(bytes).map(ElGamalSecretKey), Ok(bytes) => Scalar::from_canonical_bytes(bytes).map(ElGamalSecretKey),
@ -502,13 +579,13 @@ impl ElGamalSecretKey {
impl EncodableKey for ElGamalSecretKey { impl EncodableKey for ElGamalSecretKey {
fn read<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> { fn read<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> {
let bytes: Vec<u8> = serde_json::from_reader(reader)?; let bytes: Vec<u8> = 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() std::io::Error::new(std::io::ErrorKind::Other, "Invalid ElGamalSecretKey").into()
}) })
} }
fn write<W: Write>(&self, writer: &mut W) -> Result<String, Box<dyn error::Error>> { fn write<W: Write>(&self, writer: &mut W) -> Result<String, Box<dyn error::Error>> {
let bytes = self.to_bytes(); let bytes = Into::<[u8; ELGAMAL_SECRET_KEY_LEN]>::into(self);
let json = serde_json::to_string(&bytes.to_vec())?; let json = serde_json::to_string(&bytes.to_vec())?;
writer.write_all(&json.clone().into_bytes())?; writer.write_all(&json.clone().into_bytes())?;
Ok(json) Ok(json)
@ -546,6 +623,31 @@ impl From<Scalar> for ElGamalSecretKey {
} }
} }
impl TryFrom<&[u8]> for ElGamalSecretKey {
type Error = ElGamalError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
match bytes.try_into() {
Ok(bytes) => Ok(ElGamalSecretKey::from(
Scalar::from_canonical_bytes(bytes)
.ok_or(ElGamalError::SecretKeyDeserialization)?,
)),
_ => Err(ElGamalError::SecretKeyDeserialization),
}
}
}
impl From<ElGamalSecretKey> 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 Eq for ElGamalSecretKey {}
impl PartialEq for ElGamalSecretKey { impl PartialEq for ElGamalSecretKey {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
@ -954,10 +1056,10 @@ mod tests {
assert!(Path::new(&outfile).exists()); assert!(Path::new(&outfile).exists());
assert_eq!( assert_eq!(
keypair_vec, keypair_vec,
ElGamalKeypair::read_json_file(&outfile) Into::<[u8; ELGAMAL_KEYPAIR_LEN]>::into(
.unwrap() ElGamalKeypair::read_json_file(&outfile).unwrap()
.to_bytes() )
.to_vec() .to_vec()
); );
#[cfg(unix)] #[cfg(unix)]

View File

@ -70,8 +70,8 @@ impl BatchedGroupedCiphertext2HandlesValidityProofData {
opening_lo: &PedersenOpening, opening_lo: &PedersenOpening,
opening_hi: &PedersenOpening, opening_hi: &PedersenOpening,
) -> Result<Self, ProofGenerationError> { ) -> Result<Self, ProofGenerationError> {
let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes()); let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.into());
let pod_auditor_pubkey = pod::ElGamalPubkey(auditor_pubkey.to_bytes()); let pod_auditor_pubkey = pod::ElGamalPubkey(auditor_pubkey.into());
let pod_grouped_ciphertext_lo = (*grouped_ciphertext_lo).into(); let pod_grouped_ciphertext_lo = (*grouped_ciphertext_lo).into();
let pod_grouped_ciphertext_hi = (*grouped_ciphertext_hi).into(); let pod_grouped_ciphertext_hi = (*grouped_ciphertext_hi).into();

View File

@ -66,8 +66,8 @@ impl CiphertextCiphertextEqualityProofData {
destination_opening: &PedersenOpening, destination_opening: &PedersenOpening,
amount: u64, amount: u64,
) -> Result<Self, ProofGenerationError> { ) -> Result<Self, ProofGenerationError> {
let pod_source_pubkey = pod::ElGamalPubkey(source_keypair.pubkey().to_bytes()); let pod_source_pubkey = pod::ElGamalPubkey(source_keypair.pubkey().into());
let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes()); let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.into());
let pod_source_ciphertext = pod::ElGamalCiphertext(source_ciphertext.to_bytes()); let pod_source_ciphertext = pod::ElGamalCiphertext(source_ciphertext.to_bytes());
let pod_destination_ciphertext = pod::ElGamalCiphertext(destination_ciphertext.to_bytes()); let pod_destination_ciphertext = pod::ElGamalCiphertext(destination_ciphertext.to_bytes());

View File

@ -62,7 +62,7 @@ impl CiphertextCommitmentEqualityProofData {
amount: u64, amount: u64,
) -> Result<Self, ProofGenerationError> { ) -> Result<Self, ProofGenerationError> {
let context = CiphertextCommitmentEqualityProofContext { let context = CiphertextCommitmentEqualityProofContext {
pubkey: pod::ElGamalPubkey(keypair.pubkey().to_bytes()), pubkey: pod::ElGamalPubkey(keypair.pubkey().into()),
ciphertext: pod::ElGamalCiphertext(ciphertext.to_bytes()), ciphertext: pod::ElGamalCiphertext(ciphertext.to_bytes()),
commitment: pod::PedersenCommitment(commitment.to_bytes()), commitment: pod::PedersenCommitment(commitment.to_bytes()),
}; };

View File

@ -63,8 +63,8 @@ impl GroupedCiphertext2HandlesValidityProofData {
amount: u64, amount: u64,
opening: &PedersenOpening, opening: &PedersenOpening,
) -> Result<Self, ProofGenerationError> { ) -> Result<Self, ProofGenerationError> {
let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes()); let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.into());
let pod_auditor_pubkey = pod::ElGamalPubkey(auditor_pubkey.to_bytes()); let pod_auditor_pubkey = pod::ElGamalPubkey(auditor_pubkey.into());
let pod_grouped_ciphertext = (*grouped_ciphertext).into(); let pod_grouped_ciphertext = (*grouped_ciphertext).into();
let context = GroupedCiphertext2HandlesValidityProofContext { let context = GroupedCiphertext2HandlesValidityProofContext {

View File

@ -50,7 +50,7 @@ pub struct PubkeyValidityProofContext {
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
impl PubkeyValidityData { impl PubkeyValidityData {
pub fn new(keypair: &ElGamalKeypair) -> Result<Self, ProofGenerationError> { pub fn new(keypair: &ElGamalKeypair) -> Result<Self, ProofGenerationError> {
let pod_pubkey = pod::ElGamalPubkey(keypair.pubkey().to_bytes()); let pod_pubkey = pod::ElGamalPubkey(keypair.pubkey().into());
let context = PubkeyValidityProofContext { pubkey: pod_pubkey }; let context = PubkeyValidityProofContext { pubkey: pod_pubkey };

View File

@ -69,7 +69,7 @@ impl WithdrawData {
// current source balance // current source balance
let final_ciphertext = current_ciphertext - &ElGamal::encode(amount); 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 pod_final_ciphertext: pod::ElGamalCiphertext = final_ciphertext.into();
let context = WithdrawProofContext { let context = WithdrawProofContext {

View File

@ -54,7 +54,7 @@ impl ZeroBalanceProofData {
keypair: &ElGamalKeypair, keypair: &ElGamalKeypair,
ciphertext: &ElGamalCiphertext, ciphertext: &ElGamalCiphertext,
) -> Result<Self, ProofGenerationError> { ) -> Result<Self, ProofGenerationError> {
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 pod_ciphertext = pod::ElGamalCiphertext(ciphertext.to_bytes());
let context = ZeroBalanceProofContext { let context = ZeroBalanceProofContext {

View File

@ -309,7 +309,7 @@ mod test {
#[test] #[test]
fn test_ciphertext_commitment_equality_proof_edge_cases() { fn test_ciphertext_commitment_equality_proof_edge_cases() {
// if ElGamal public key zero (public key is invalid), then the proof should always reject // 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 secret = ElGamalSecretKey::new_rand();
let elgamal_keypair = ElGamalKeypair::new_for_tests(public, secret); let elgamal_keypair = ElGamalKeypair::new_for_tests(public, secret);

View File

@ -274,7 +274,7 @@ mod test {
#[test] #[test]
fn test_grouped_ciphertext_validity_proof_edge_cases() { fn test_grouped_ciphertext_validity_proof_edge_cases() {
// if destination public key zeroed, then the proof should always reject // 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_keypair = ElGamalKeypair::new_rand();
let auditor_pubkey = auditor_keypair.pubkey(); let auditor_pubkey = auditor_keypair.pubkey();

View File

@ -286,7 +286,7 @@ mod test {
let mut prover_transcript = Transcript::new(b"test"); let mut prover_transcript = Transcript::new(b"test");
let mut verifier_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 ciphertext = public.encrypt(0_u64);
let proof = ZeroBalanceProof::new(&source_keypair, &ciphertext, &mut prover_transcript); let proof = ZeroBalanceProof::new(&source_keypair, &ciphertext, &mut prover_transcript);

View File

@ -100,7 +100,7 @@ impl_from_str!(
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
impl From<decoded::ElGamalPubkey> for ElGamalPubkey { impl From<decoded::ElGamalPubkey> for ElGamalPubkey {
fn from(decoded_pubkey: decoded::ElGamalPubkey) -> Self { fn from(decoded_pubkey: decoded::ElGamalPubkey) -> Self {
Self(decoded_pubkey.to_bytes()) Self(decoded_pubkey.into())
} }
} }
@ -109,7 +109,7 @@ impl TryFrom<ElGamalPubkey> for decoded::ElGamalPubkey {
type Error = ElGamalError; type Error = ElGamalError;
fn try_from(pod_pubkey: ElGamalPubkey) -> Result<Self, Self::Error> { fn try_from(pod_pubkey: ElGamalPubkey) -> Result<Self, Self::Error> {
Self::from_bytes(&pod_pubkey.0).ok_or(ElGamalError::PubkeyDeserialization) Self::try_from(pod_pubkey.0.as_slice())
} }
} }