Skip to content

Commit

Permalink
ElGamal: add From impls; deprecate from/to_bytes (anza-xyz#246)
Browse files Browse the repository at this point in the history
* ElGamal: add From impls; deprecate from/to_bytes

* variable names
  • Loading branch information
abcalphabet authored Apr 4, 2024
1 parent 87fc227 commit 855a0c1
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 28 deletions.
128 changes: 115 additions & 13 deletions zk-token-sdk/src/encryption/elgamal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -235,13 +239,17 @@ 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());
bytes[ELGAMAL_PUBKEY_LEN..].copy_from_slice(self.secret.as_bytes());
bytes
}

#[deprecated(note = "please use `try_from()` instead")]
#[allow(deprecated)]
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != ELGAMAL_KEYPAIR_LEN {
return None;
Expand All @@ -256,7 +264,7 @@ impl ElGamalKeypair {
/// Reads a JSON-encoded keypair from a `Reader` implementor
pub fn read_json<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> {
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()
})
}
Expand All @@ -268,8 +276,8 @@ impl ElGamalKeypair {

/// Writes to a `Write` implementer with JSON-encoding
pub fn write_json<W: Write>(&self, writer: &mut W) -> Result<String, Box<dyn error::Error>> {
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)
}
Expand All @@ -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 {
fn from_seed(seed: &[u8]) -> Result<Self, Box<dyn error::Error>> {
let secret = ElGamalSecretKey::from_seed(seed)?;
Expand Down Expand Up @@ -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<ElGamalPubkey> {
if bytes.len() != ELGAMAL_PUBKEY_LEN {
return None;
Expand Down Expand Up @@ -384,13 +428,13 @@ impl ElGamalPubkey {
impl EncodableKey for ElGamalPubkey {
fn read<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> {
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()
})
}

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())?;
writer.write_all(&json.clone().into_bytes())?;
Ok(json)
Expand All @@ -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<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()
}
}

Expand Down Expand Up @@ -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<ElGamalSecretKey> {
match bytes.try_into() {
Ok(bytes) => Scalar::from_canonical_bytes(bytes).map(ElGamalSecretKey),
Expand All @@ -502,13 +579,13 @@ impl ElGamalSecretKey {
impl EncodableKey for ElGamalSecretKey {
fn read<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> {
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()
})
}

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())?;
writer.write_all(&json.clone().into_bytes())?;
Ok(json)
Expand Down Expand Up @@ -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 PartialEq for ElGamalSecretKey {
fn eq(&self, other: &Self) -> bool {
Expand Down Expand Up @@ -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)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ impl BatchedGroupedCiphertext2HandlesValidityProofData {
opening_lo: &PedersenOpening,
opening_hi: &PedersenOpening,
) -> Result<Self, ProofGenerationError> {
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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ impl CiphertextCiphertextEqualityProofData {
destination_opening: &PedersenOpening,
amount: u64,
) -> Result<Self, ProofGenerationError> {
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());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ impl CiphertextCommitmentEqualityProofData {
amount: u64,
) -> Result<Self, ProofGenerationError> {
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()),
};
Expand Down
4 changes: 2 additions & 2 deletions zk-token-sdk/src/instruction/grouped_ciphertext_validity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ impl GroupedCiphertext2HandlesValidityProofData {
amount: u64,
opening: &PedersenOpening,
) -> Result<Self, ProofGenerationError> {
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 {
Expand Down
2 changes: 1 addition & 1 deletion zk-token-sdk/src/instruction/pubkey_validity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub struct PubkeyValidityProofContext {
#[cfg(not(target_os = "solana"))]
impl PubkeyValidityData {
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 };

Expand Down
2 changes: 1 addition & 1 deletion zk-token-sdk/src/instruction/withdraw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion zk-token-sdk/src/instruction/zero_balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl ZeroBalanceProofData {
keypair: &ElGamalKeypair,
ciphertext: &ElGamalCiphertext,
) -> 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 context = ZeroBalanceProofContext {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion zk-token-sdk/src/sigma_proofs/zero_balance_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions zk-token-sdk/src/zk_token_elgamal/pod/elgamal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ impl_from_str!(
#[cfg(not(target_os = "solana"))]
impl From<decoded::ElGamalPubkey> for ElGamalPubkey {
fn from(decoded_pubkey: decoded::ElGamalPubkey) -> Self {
Self(decoded_pubkey.to_bytes())
Self(decoded_pubkey.into())
}
}

Expand All @@ -109,7 +109,7 @@ impl TryFrom<ElGamalPubkey> for decoded::ElGamalPubkey {
type Error = ElGamalError;

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())
}
}

Expand Down

0 comments on commit 855a0c1

Please sign in to comment.