From e621336acad4f5d6e5b860eaa1b074b01c99253c Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 10 May 2024 20:00:51 +0900 Subject: [PATCH] [zk-sdk] Add `pod` module (#1169) --- zk-sdk/src/encryption/mod.rs | 1 + zk-sdk/src/encryption/pod/auth_encryption.rs | 80 +++++++ zk-sdk/src/encryption/pod/elgamal.rs | 173 ++++++++++++++ zk-sdk/src/encryption/pod/grouped_elgamal.rs | 225 +++++++++++++++++++ zk-sdk/src/encryption/pod/mod.rs | 28 +++ zk-sdk/src/encryption/pod/pedersen.rs | 47 ++++ zk-sdk/src/errors.rs | 8 + zk-sdk/src/lib.rs | 1 + zk-sdk/src/pod.rs | 29 +++ 9 files changed, 592 insertions(+) create mode 100644 zk-sdk/src/encryption/pod/auth_encryption.rs create mode 100644 zk-sdk/src/encryption/pod/elgamal.rs create mode 100644 zk-sdk/src/encryption/pod/grouped_elgamal.rs create mode 100644 zk-sdk/src/encryption/pod/mod.rs create mode 100644 zk-sdk/src/encryption/pod/pedersen.rs create mode 100644 zk-sdk/src/pod.rs diff --git a/zk-sdk/src/encryption/mod.rs b/zk-sdk/src/encryption/mod.rs index 1f8f1c7aeef454..55eda670576712 100644 --- a/zk-sdk/src/encryption/mod.rs +++ b/zk-sdk/src/encryption/mod.rs @@ -25,6 +25,7 @@ pub mod elgamal; pub mod grouped_elgamal; #[cfg(not(target_os = "solana"))] pub mod pedersen; +pub mod pod; /// Byte length of an authenticated encryption secret key pub const AE_KEY_LEN: usize = 16; diff --git a/zk-sdk/src/encryption/pod/auth_encryption.rs b/zk-sdk/src/encryption/pod/auth_encryption.rs new file mode 100644 index 00000000000000..abfd8d782105a8 --- /dev/null +++ b/zk-sdk/src/encryption/pod/auth_encryption.rs @@ -0,0 +1,80 @@ +//! Plain Old Data types for the AES128-GCM-SIV authenticated encryption scheme. + +#[cfg(not(target_os = "solana"))] +use crate::{encryption::auth_encryption::AeCiphertext, errors::AuthenticatedEncryptionError}; +use { + crate::encryption::{pod::impl_from_str, AE_CIPHERTEXT_LEN}, + base64::{prelude::BASE64_STANDARD, Engine}, + bytemuck::{Pod, Zeroable}, + std::fmt, +}; + +/// Maximum length of a base64 encoded authenticated encryption ciphertext +const AE_CIPHERTEXT_MAX_BASE64_LEN: usize = 48; + +/// The `AeCiphertext` type as a `Pod`. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct PodAeCiphertext(pub(crate) [u8; AE_CIPHERTEXT_LEN]); + +// `PodAeCiphertext` is a wrapper type for a byte array, which is both `Pod` and `Zeroable`. However, +// the marker traits `bytemuck::Pod` and `bytemuck::Zeroable` can only be derived for power-of-two +// length byte arrays. Directly implement these traits for `PodAeCiphertext`. +unsafe impl Zeroable for PodAeCiphertext {} +unsafe impl Pod for PodAeCiphertext {} + +impl fmt::Debug for PodAeCiphertext { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl fmt::Display for PodAeCiphertext { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", BASE64_STANDARD.encode(self.0)) + } +} + +impl_from_str!( + TYPE = PodAeCiphertext, + BYTES_LEN = AE_CIPHERTEXT_LEN, + BASE64_LEN = AE_CIPHERTEXT_MAX_BASE64_LEN +); + +impl Default for PodAeCiphertext { + fn default() -> Self { + Self::zeroed() + } +} + +#[cfg(not(target_os = "solana"))] +impl From for PodAeCiphertext { + fn from(decoded_ciphertext: AeCiphertext) -> Self { + Self(decoded_ciphertext.to_bytes()) + } +} + +#[cfg(not(target_os = "solana"))] +impl TryFrom for AeCiphertext { + type Error = AuthenticatedEncryptionError; + + fn try_from(pod_ciphertext: PodAeCiphertext) -> Result { + Self::from_bytes(&pod_ciphertext.0).ok_or(AuthenticatedEncryptionError::Deserialization) + } +} + +#[cfg(test)] +mod tests { + use {super::*, crate::encryption::auth_encryption::AeKey, std::str::FromStr}; + + #[test] + fn ae_ciphertext_fromstr() { + let ae_key = AeKey::new_rand(); + let expected_ae_ciphertext: PodAeCiphertext = ae_key.encrypt(0_u64).into(); + + let ae_ciphertext_base64_str = format!("{}", expected_ae_ciphertext); + let computed_ae_ciphertext = PodAeCiphertext::from_str(&ae_ciphertext_base64_str).unwrap(); + + assert_eq!(expected_ae_ciphertext, computed_ae_ciphertext); + } +} diff --git a/zk-sdk/src/encryption/pod/elgamal.rs b/zk-sdk/src/encryption/pod/elgamal.rs new file mode 100644 index 00000000000000..8ec72c6f5837bd --- /dev/null +++ b/zk-sdk/src/encryption/pod/elgamal.rs @@ -0,0 +1,173 @@ +//! Plain Old Data types for the ElGamal encryption scheme. + +use { + crate::encryption::{ + pod::impl_from_str, DECRYPT_HANDLE_LEN, ELGAMAL_CIPHERTEXT_LEN, ELGAMAL_PUBKEY_LEN, + }, + base64::{prelude::BASE64_STANDARD, Engine}, + bytemuck::{Pod, Zeroable}, + std::fmt, +}; +#[cfg(not(target_os = "solana"))] +use { + crate::{ + encryption::elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalPubkey}, + errors::ElGamalError, + }, + curve25519_dalek::ristretto::CompressedRistretto, +}; + +/// Maximum length of a base64 encoded ElGamal public key +const ELGAMAL_PUBKEY_MAX_BASE64_LEN: usize = 44; + +/// Maximum length of a base64 encoded ElGamal ciphertext +const ELGAMAL_CIPHERTEXT_MAX_BASE64_LEN: usize = 88; + +/// The `ElGamalCiphertext` type as a `Pod`. +#[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] +#[repr(transparent)] +pub struct PodElGamalCiphertext(pub(crate) [u8; ELGAMAL_CIPHERTEXT_LEN]); + +impl fmt::Debug for PodElGamalCiphertext { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl fmt::Display for PodElGamalCiphertext { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", BASE64_STANDARD.encode(self.0)) + } +} + +impl Default for PodElGamalCiphertext { + fn default() -> Self { + Self::zeroed() + } +} + +impl_from_str!( + TYPE = PodElGamalCiphertext, + BYTES_LEN = ELGAMAL_CIPHERTEXT_LEN, + BASE64_LEN = ELGAMAL_CIPHERTEXT_MAX_BASE64_LEN +); + +#[cfg(not(target_os = "solana"))] +impl From for PodElGamalCiphertext { + fn from(decoded_ciphertext: ElGamalCiphertext) -> Self { + Self(decoded_ciphertext.to_bytes()) + } +} + +#[cfg(not(target_os = "solana"))] +impl TryFrom for ElGamalCiphertext { + type Error = ElGamalError; + + fn try_from(pod_ciphertext: PodElGamalCiphertext) -> Result { + Self::from_bytes(&pod_ciphertext.0).ok_or(ElGamalError::CiphertextDeserialization) + } +} + +/// The `ElGamalPubkey` type as a `Pod`. +#[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)] +#[repr(transparent)] +pub struct PodElGamalPubkey(pub(crate) [u8; ELGAMAL_PUBKEY_LEN]); + +impl fmt::Debug for PodElGamalPubkey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl fmt::Display for PodElGamalPubkey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", BASE64_STANDARD.encode(self.0)) + } +} + +impl_from_str!( + TYPE = PodElGamalPubkey, + BYTES_LEN = ELGAMAL_PUBKEY_LEN, + BASE64_LEN = ELGAMAL_PUBKEY_MAX_BASE64_LEN +); + +#[cfg(not(target_os = "solana"))] +impl From for PodElGamalPubkey { + fn from(decoded_pubkey: ElGamalPubkey) -> Self { + Self(decoded_pubkey.into()) + } +} + +#[cfg(not(target_os = "solana"))] +impl TryFrom for ElGamalPubkey { + type Error = ElGamalError; + + fn try_from(pod_pubkey: PodElGamalPubkey) -> Result { + Self::try_from(pod_pubkey.0.as_slice()) + } +} + +/// The `DecryptHandle` type as a `Pod`. +#[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)] +#[repr(transparent)] +pub struct PodDecryptHandle(pub(crate) [u8; DECRYPT_HANDLE_LEN]); + +impl fmt::Debug for PodDecryptHandle { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} + +#[cfg(not(target_os = "solana"))] +impl From for PodDecryptHandle { + fn from(decoded_handle: DecryptHandle) -> Self { + Self(decoded_handle.to_bytes()) + } +} + +// For proof verification, interpret pod::DecryptHandle as CompressedRistretto +#[cfg(not(target_os = "solana"))] +impl From for CompressedRistretto { + fn from(pod_handle: PodDecryptHandle) -> Self { + Self(pod_handle.0) + } +} + +#[cfg(not(target_os = "solana"))] +impl TryFrom for DecryptHandle { + type Error = ElGamalError; + + fn try_from(pod_handle: PodDecryptHandle) -> Result { + Self::from_bytes(&pod_handle.0).ok_or(ElGamalError::CiphertextDeserialization) + } +} + +#[cfg(test)] +mod tests { + use {super::*, crate::encryption::elgamal::ElGamalKeypair, std::str::FromStr}; + + #[test] + fn elgamal_pubkey_fromstr() { + let elgamal_keypair = ElGamalKeypair::new_rand(); + let expected_elgamal_pubkey: PodElGamalPubkey = (*elgamal_keypair.pubkey()).into(); + + let elgamal_pubkey_base64_str = format!("{}", expected_elgamal_pubkey); + let computed_elgamal_pubkey = + PodElGamalPubkey::from_str(&elgamal_pubkey_base64_str).unwrap(); + + assert_eq!(expected_elgamal_pubkey, computed_elgamal_pubkey); + } + + #[test] + fn elgamal_ciphertext_fromstr() { + let elgamal_keypair = ElGamalKeypair::new_rand(); + let expected_elgamal_ciphertext: PodElGamalCiphertext = + elgamal_keypair.pubkey().encrypt(0_u64).into(); + + let elgamal_ciphertext_base64_str = format!("{}", expected_elgamal_ciphertext); + let computed_elgamal_ciphertext = + PodElGamalCiphertext::from_str(&elgamal_ciphertext_base64_str).unwrap(); + + assert_eq!(expected_elgamal_ciphertext, computed_elgamal_ciphertext); + } +} diff --git a/zk-sdk/src/encryption/pod/grouped_elgamal.rs b/zk-sdk/src/encryption/pod/grouped_elgamal.rs new file mode 100644 index 00000000000000..9202f23098858d --- /dev/null +++ b/zk-sdk/src/encryption/pod/grouped_elgamal.rs @@ -0,0 +1,225 @@ +//! Plain Old Data types for the Grouped ElGamal encryption scheme. + +#[cfg(not(target_os = "solana"))] +use crate::encryption::grouped_elgamal::GroupedElGamalCiphertext; +use { + crate::{ + encryption::{ + pod::{elgamal::PodElGamalCiphertext, pedersen::PodPedersenCommitment}, + DECRYPT_HANDLE_LEN, ELGAMAL_CIPHERTEXT_LEN, PEDERSEN_COMMITMENT_LEN, + }, + errors::ElGamalError, + }, + bytemuck::{Pod, Zeroable}, + std::fmt, +}; + +macro_rules! impl_extract { + (TYPE = $type:ident) => { + impl $type { + /// Extract the commitment component from a grouped ciphertext + pub fn extract_commitment(&self) -> PodPedersenCommitment { + // `GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES` guaranteed to be at least `PEDERSEN_COMMITMENT_LEN` + let commitment = self.0[..PEDERSEN_COMMITMENT_LEN].try_into().unwrap(); + PodPedersenCommitment(commitment) + } + + /// Extract a regular ElGamal ciphertext using the decrypt handle at a specified index. + pub fn try_extract_ciphertext( + &self, + index: usize, + ) -> Result { + let mut ciphertext_bytes = [0u8; ELGAMAL_CIPHERTEXT_LEN]; + ciphertext_bytes[..PEDERSEN_COMMITMENT_LEN] + .copy_from_slice(&self.0[..PEDERSEN_COMMITMENT_LEN]); + + let handle_start = DECRYPT_HANDLE_LEN + .checked_mul(index) + .and_then(|n| n.checked_add(PEDERSEN_COMMITMENT_LEN)) + .ok_or(ElGamalError::CiphertextDeserialization)?; + let handle_end = handle_start + .checked_add(DECRYPT_HANDLE_LEN) + .ok_or(ElGamalError::CiphertextDeserialization)?; + ciphertext_bytes[PEDERSEN_COMMITMENT_LEN..].copy_from_slice( + self.0 + .get(handle_start..handle_end) + .ok_or(ElGamalError::CiphertextDeserialization)?, + ); + + Ok(PodElGamalCiphertext(ciphertext_bytes)) + } + } + }; +} + +/// Byte length of a grouped ElGamal ciphertext with 2 handles +const GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES: usize = + PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN; + +/// Byte length of a grouped ElGamal ciphertext with 3 handles +const GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES: usize = + PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN; + +/// The `GroupedElGamalCiphertext` type with two decryption handles as a `Pod` +#[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] +#[repr(transparent)] +pub struct PodGroupedElGamalCiphertext2Handles( + pub(crate) [u8; GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES], +); + +impl fmt::Debug for PodGroupedElGamalCiphertext2Handles { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl Default for PodGroupedElGamalCiphertext2Handles { + fn default() -> Self { + Self::zeroed() + } +} +#[cfg(not(target_os = "solana"))] +impl From> for PodGroupedElGamalCiphertext2Handles { + fn from(decoded_ciphertext: GroupedElGamalCiphertext<2>) -> Self { + Self(decoded_ciphertext.to_bytes().try_into().unwrap()) + } +} + +#[cfg(not(target_os = "solana"))] +impl TryFrom for GroupedElGamalCiphertext<2> { + type Error = ElGamalError; + + fn try_from(pod_ciphertext: PodGroupedElGamalCiphertext2Handles) -> Result { + Self::from_bytes(&pod_ciphertext.0).ok_or(ElGamalError::CiphertextDeserialization) + } +} + +impl_extract!(TYPE = PodGroupedElGamalCiphertext2Handles); + +/// The `GroupedElGamalCiphertext` type with three decryption handles as a `Pod` +#[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] +#[repr(transparent)] +pub struct PodGroupedElGamalCiphertext3Handles( + pub(crate) [u8; GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES], +); + +impl fmt::Debug for PodGroupedElGamalCiphertext3Handles { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl Default for PodGroupedElGamalCiphertext3Handles { + fn default() -> Self { + Self::zeroed() + } +} + +#[cfg(not(target_os = "solana"))] +impl From> for PodGroupedElGamalCiphertext3Handles { + fn from(decoded_ciphertext: GroupedElGamalCiphertext<3>) -> Self { + Self(decoded_ciphertext.to_bytes().try_into().unwrap()) + } +} + +#[cfg(not(target_os = "solana"))] +impl TryFrom for GroupedElGamalCiphertext<3> { + type Error = ElGamalError; + + fn try_from(pod_ciphertext: PodGroupedElGamalCiphertext3Handles) -> Result { + Self::from_bytes(&pod_ciphertext.0).ok_or(ElGamalError::CiphertextDeserialization) + } +} + +impl_extract!(TYPE = PodGroupedElGamalCiphertext3Handles); + +#[cfg(test)] +mod tests { + use { + super::*, + crate::encryption::{ + elgamal::ElGamalKeypair, grouped_elgamal::GroupedElGamal, pedersen::Pedersen, + pod::pedersen::PodPedersenCommitment, + }, + }; + + #[test] + fn test_2_handles_ciphertext_extraction() { + let elgamal_keypair_0 = ElGamalKeypair::new_rand(); + let elgamal_keypair_1 = ElGamalKeypair::new_rand(); + + let amount: u64 = 10; + let (commitment, opening) = Pedersen::new(amount); + + let grouped_ciphertext = GroupedElGamal::encrypt_with( + [elgamal_keypair_0.pubkey(), elgamal_keypair_1.pubkey()], + amount, + &opening, + ); + let pod_grouped_ciphertext: PodGroupedElGamalCiphertext2Handles = grouped_ciphertext.into(); + + let expected_pod_commitment: PodPedersenCommitment = commitment.into(); + let actual_pod_commitment = pod_grouped_ciphertext.extract_commitment(); + assert_eq!(expected_pod_commitment, actual_pod_commitment); + + let expected_ciphertext_0 = elgamal_keypair_0.pubkey().encrypt_with(amount, &opening); + let expected_pod_ciphertext_0: PodElGamalCiphertext = expected_ciphertext_0.into(); + let actual_pod_ciphertext_0 = pod_grouped_ciphertext.try_extract_ciphertext(0).unwrap(); + assert_eq!(expected_pod_ciphertext_0, actual_pod_ciphertext_0); + + let expected_ciphertext_1 = elgamal_keypair_1.pubkey().encrypt_with(amount, &opening); + let expected_pod_ciphertext_1: PodElGamalCiphertext = expected_ciphertext_1.into(); + let actual_pod_ciphertext_1 = pod_grouped_ciphertext.try_extract_ciphertext(1).unwrap(); + assert_eq!(expected_pod_ciphertext_1, actual_pod_ciphertext_1); + + let err = pod_grouped_ciphertext + .try_extract_ciphertext(2) + .unwrap_err(); + assert_eq!(err, ElGamalError::CiphertextDeserialization); + } + + #[test] + fn test_3_handles_ciphertext_extraction() { + let elgamal_keypair_0 = ElGamalKeypair::new_rand(); + let elgamal_keypair_1 = ElGamalKeypair::new_rand(); + let elgamal_keypair_2 = ElGamalKeypair::new_rand(); + + let amount: u64 = 10; + let (commitment, opening) = Pedersen::new(amount); + + let grouped_ciphertext = GroupedElGamal::encrypt_with( + [ + elgamal_keypair_0.pubkey(), + elgamal_keypair_1.pubkey(), + elgamal_keypair_2.pubkey(), + ], + amount, + &opening, + ); + let pod_grouped_ciphertext: PodGroupedElGamalCiphertext3Handles = grouped_ciphertext.into(); + + let expected_pod_commitment: PodPedersenCommitment = commitment.into(); + let actual_pod_commitment = pod_grouped_ciphertext.extract_commitment(); + assert_eq!(expected_pod_commitment, actual_pod_commitment); + + let expected_ciphertext_0 = elgamal_keypair_0.pubkey().encrypt_with(amount, &opening); + let expected_pod_ciphertext_0: PodElGamalCiphertext = expected_ciphertext_0.into(); + let actual_pod_ciphertext_0 = pod_grouped_ciphertext.try_extract_ciphertext(0).unwrap(); + assert_eq!(expected_pod_ciphertext_0, actual_pod_ciphertext_0); + + let expected_ciphertext_1 = elgamal_keypair_1.pubkey().encrypt_with(amount, &opening); + let expected_pod_ciphertext_1: PodElGamalCiphertext = expected_ciphertext_1.into(); + let actual_pod_ciphertext_1 = pod_grouped_ciphertext.try_extract_ciphertext(1).unwrap(); + assert_eq!(expected_pod_ciphertext_1, actual_pod_ciphertext_1); + + let expected_ciphertext_2 = elgamal_keypair_2.pubkey().encrypt_with(amount, &opening); + let expected_pod_ciphertext_2: PodElGamalCiphertext = expected_ciphertext_2.into(); + let actual_pod_ciphertext_2 = pod_grouped_ciphertext.try_extract_ciphertext(2).unwrap(); + assert_eq!(expected_pod_ciphertext_2, actual_pod_ciphertext_2); + + let err = pod_grouped_ciphertext + .try_extract_ciphertext(3) + .unwrap_err(); + assert_eq!(err, ElGamalError::CiphertextDeserialization); + } +} diff --git a/zk-sdk/src/encryption/pod/mod.rs b/zk-sdk/src/encryption/pod/mod.rs new file mode 100644 index 00000000000000..850a9a40aa42d9 --- /dev/null +++ b/zk-sdk/src/encryption/pod/mod.rs @@ -0,0 +1,28 @@ +pub mod auth_encryption; +pub mod elgamal; +pub mod grouped_elgamal; +pub mod pedersen; + +macro_rules! impl_from_str { + (TYPE = $type:ident, BYTES_LEN = $bytes_len:expr, BASE64_LEN = $base64_len:expr) => { + impl std::str::FromStr for $type { + type Err = crate::errors::ParseError; + + fn from_str(s: &str) -> Result { + if s.len() > $base64_len { + return Err(Self::Err::WrongSize); + } + let mut bytes = [0u8; $bytes_len]; + let decoded_len = BASE64_STANDARD + .decode_slice(s, &mut bytes) + .map_err(|_| Self::Err::Invalid)?; + if decoded_len != $bytes_len { + Err(Self::Err::WrongSize) + } else { + Ok($type(bytes)) + } + } + } + }; +} +pub(crate) use impl_from_str; diff --git a/zk-sdk/src/encryption/pod/pedersen.rs b/zk-sdk/src/encryption/pod/pedersen.rs new file mode 100644 index 00000000000000..2d90b100cbbe96 --- /dev/null +++ b/zk-sdk/src/encryption/pod/pedersen.rs @@ -0,0 +1,47 @@ +//! Plain Old Data type for the Pedersen commitment scheme. + +use { + crate::encryption::PEDERSEN_COMMITMENT_LEN, + bytemuck::{Pod, Zeroable}, + std::fmt, +}; +#[cfg(not(target_os = "solana"))] +use { + crate::{encryption::pedersen::PedersenCommitment, errors::ElGamalError}, + curve25519_dalek::ristretto::CompressedRistretto, +}; + +/// The `PedersenCommitment` type as a `Pod`. +#[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)] +#[repr(transparent)] +pub struct PodPedersenCommitment(pub(crate) [u8; PEDERSEN_COMMITMENT_LEN]); + +impl fmt::Debug for PodPedersenCommitment { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} + +#[cfg(not(target_os = "solana"))] +impl From for PodPedersenCommitment { + fn from(decoded_commitment: PedersenCommitment) -> Self { + Self(decoded_commitment.to_bytes()) + } +} + +// For proof verification, interpret pod::PedersenCommitment directly as CompressedRistretto +#[cfg(not(target_os = "solana"))] +impl From for CompressedRistretto { + fn from(pod_commitment: PodPedersenCommitment) -> Self { + Self(pod_commitment.0) + } +} + +#[cfg(not(target_os = "solana"))] +impl TryFrom for PedersenCommitment { + type Error = ElGamalError; + + fn try_from(pod_commitment: PodPedersenCommitment) -> Result { + Self::from_bytes(&pod_commitment.0).ok_or(ElGamalError::CiphertextDeserialization) + } +} diff --git a/zk-sdk/src/errors.rs b/zk-sdk/src/errors.rs index fcd8e248cb9f78..90beaf31a12102 100644 --- a/zk-sdk/src/errors.rs +++ b/zk-sdk/src/errors.rs @@ -36,3 +36,11 @@ pub enum TranscriptError { #[error("point is the identity")] ValidationError, } + +#[derive(Error, Debug, Clone, Eq, PartialEq)] +pub enum ParseError { + #[error("String is the wrong size")] + WrongSize, + #[error("Invalid Base64 string")] + Invalid, +} diff --git a/zk-sdk/src/lib.rs b/zk-sdk/src/lib.rs index 32c1e8afdc04d2..2994d14730c425 100644 --- a/zk-sdk/src/lib.rs +++ b/zk-sdk/src/lib.rs @@ -22,6 +22,7 @@ pub mod elgamal_program; pub mod encryption; pub mod errors; +pub mod pod; mod range_proof; mod sigma_proofs; mod transcript; diff --git a/zk-sdk/src/pod.rs b/zk-sdk/src/pod.rs new file mode 100644 index 00000000000000..416df4c58be767 --- /dev/null +++ b/zk-sdk/src/pod.rs @@ -0,0 +1,29 @@ +use bytemuck::{Pod, Zeroable}; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Pod, Zeroable)] +#[repr(transparent)] +pub struct PodU16([u8; 2]); +impl From for PodU16 { + fn from(n: u16) -> Self { + Self(n.to_le_bytes()) + } +} +impl From for u16 { + fn from(pod: PodU16) -> Self { + Self::from_le_bytes(pod.0) + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Pod, Zeroable)] +#[repr(transparent)] +pub struct PodU64([u8; 8]); +impl From for PodU64 { + fn from(n: u64) -> Self { + Self(n.to_le_bytes()) + } +} +impl From for u64 { + fn from(pod: PodU64) -> Self { + Self::from_le_bytes(pod.0) + } +}