solana_zk_token_sdk/zk_token_elgamal/pod/
elgamal.rs

1//! Plain Old Data types for the ElGamal encryption scheme.
2
3#[cfg(not(target_os = "solana"))]
4use {
5    crate::{
6        encryption::elgamal::{self as decoded},
7        errors::ElGamalError,
8    },
9    curve25519_dalek::ristretto::CompressedRistretto,
10};
11use {
12    crate::{
13        zk_token_elgamal::pod::{impl_from_str, pedersen::PEDERSEN_COMMITMENT_LEN},
14        RISTRETTO_POINT_LEN,
15    },
16    base64::{prelude::BASE64_STANDARD, Engine},
17    bytemuck::Zeroable,
18    std::fmt,
19};
20
21/// Byte length of an ElGamal public key
22const ELGAMAL_PUBKEY_LEN: usize = RISTRETTO_POINT_LEN;
23
24/// Maximum length of a base64 encoded ElGamal public key
25const ELGAMAL_PUBKEY_MAX_BASE64_LEN: usize = 44;
26
27/// Byte length of a decrypt handle
28pub(crate) const DECRYPT_HANDLE_LEN: usize = RISTRETTO_POINT_LEN;
29
30/// Byte length of an ElGamal ciphertext
31pub(crate) const ELGAMAL_CIPHERTEXT_LEN: usize = PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN;
32
33/// Maximum length of a base64 encoded ElGamal ciphertext
34const ELGAMAL_CIPHERTEXT_MAX_BASE64_LEN: usize = 88;
35
36/// The `ElGamalCiphertext` type as a `Pod`.
37#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)]
38#[repr(transparent)]
39pub struct ElGamalCiphertext(pub [u8; ELGAMAL_CIPHERTEXT_LEN]);
40
41impl fmt::Debug for ElGamalCiphertext {
42    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43        write!(f, "{:?}", self.0)
44    }
45}
46
47impl fmt::Display for ElGamalCiphertext {
48    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49        write!(f, "{}", BASE64_STANDARD.encode(self.0))
50    }
51}
52
53impl Default for ElGamalCiphertext {
54    fn default() -> Self {
55        Self::zeroed()
56    }
57}
58
59impl_from_str!(
60    TYPE = ElGamalCiphertext,
61    BYTES_LEN = ELGAMAL_CIPHERTEXT_LEN,
62    BASE64_LEN = ELGAMAL_CIPHERTEXT_MAX_BASE64_LEN
63);
64
65#[cfg(not(target_os = "solana"))]
66impl From<decoded::ElGamalCiphertext> for ElGamalCiphertext {
67    fn from(decoded_ciphertext: decoded::ElGamalCiphertext) -> Self {
68        Self(decoded_ciphertext.to_bytes())
69    }
70}
71
72#[cfg(not(target_os = "solana"))]
73impl TryFrom<ElGamalCiphertext> for decoded::ElGamalCiphertext {
74    type Error = ElGamalError;
75
76    fn try_from(pod_ciphertext: ElGamalCiphertext) -> Result<Self, Self::Error> {
77        Self::from_bytes(&pod_ciphertext.0).ok_or(ElGamalError::CiphertextDeserialization)
78    }
79}
80
81/// The `ElGamalPubkey` type as a `Pod`.
82#[derive(Clone, Copy, Default, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)]
83#[repr(transparent)]
84pub struct ElGamalPubkey(pub [u8; ELGAMAL_PUBKEY_LEN]);
85
86impl fmt::Debug for ElGamalPubkey {
87    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88        write!(f, "{:?}", self.0)
89    }
90}
91
92impl fmt::Display for ElGamalPubkey {
93    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
94        write!(f, "{}", BASE64_STANDARD.encode(self.0))
95    }
96}
97
98impl_from_str!(
99    TYPE = ElGamalPubkey,
100    BYTES_LEN = ELGAMAL_PUBKEY_LEN,
101    BASE64_LEN = ELGAMAL_PUBKEY_MAX_BASE64_LEN
102);
103
104#[cfg(not(target_os = "solana"))]
105impl From<decoded::ElGamalPubkey> for ElGamalPubkey {
106    fn from(decoded_pubkey: decoded::ElGamalPubkey) -> Self {
107        Self(decoded_pubkey.into())
108    }
109}
110
111#[cfg(not(target_os = "solana"))]
112impl TryFrom<ElGamalPubkey> for decoded::ElGamalPubkey {
113    type Error = ElGamalError;
114
115    fn try_from(pod_pubkey: ElGamalPubkey) -> Result<Self, Self::Error> {
116        Self::try_from(pod_pubkey.0.as_slice())
117    }
118}
119
120/// The `DecryptHandle` type as a `Pod`.
121#[derive(Clone, Copy, Default, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)]
122#[repr(transparent)]
123pub struct DecryptHandle(pub [u8; DECRYPT_HANDLE_LEN]);
124
125impl fmt::Debug for DecryptHandle {
126    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127        write!(f, "{:?}", self.0)
128    }
129}
130
131#[cfg(not(target_os = "solana"))]
132impl From<decoded::DecryptHandle> for DecryptHandle {
133    fn from(decoded_handle: decoded::DecryptHandle) -> Self {
134        Self(decoded_handle.to_bytes())
135    }
136}
137
138// For proof verification, interpret pod::DecryptHandle as CompressedRistretto
139#[cfg(not(target_os = "solana"))]
140impl From<DecryptHandle> for CompressedRistretto {
141    fn from(pod_handle: DecryptHandle) -> Self {
142        Self(pod_handle.0)
143    }
144}
145
146#[cfg(not(target_os = "solana"))]
147impl TryFrom<DecryptHandle> for decoded::DecryptHandle {
148    type Error = ElGamalError;
149
150    fn try_from(pod_handle: DecryptHandle) -> Result<Self, Self::Error> {
151        Self::from_bytes(&pod_handle.0).ok_or(ElGamalError::CiphertextDeserialization)
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use {super::*, crate::encryption::elgamal::ElGamalKeypair, std::str::FromStr};
158
159    #[test]
160    fn elgamal_pubkey_fromstr() {
161        let elgamal_keypair = ElGamalKeypair::new_rand();
162        let expected_elgamal_pubkey: ElGamalPubkey = (*elgamal_keypair.pubkey()).into();
163
164        let elgamal_pubkey_base64_str = format!("{}", expected_elgamal_pubkey);
165        let computed_elgamal_pubkey = ElGamalPubkey::from_str(&elgamal_pubkey_base64_str).unwrap();
166
167        assert_eq!(expected_elgamal_pubkey, computed_elgamal_pubkey);
168    }
169
170    #[test]
171    fn elgamal_ciphertext_fromstr() {
172        let elgamal_keypair = ElGamalKeypair::new_rand();
173        let expected_elgamal_ciphertext: ElGamalCiphertext =
174            elgamal_keypair.pubkey().encrypt(0_u64).into();
175
176        let elgamal_ciphertext_base64_str = format!("{}", expected_elgamal_ciphertext);
177        let computed_elgamal_ciphertext =
178            ElGamalCiphertext::from_str(&elgamal_ciphertext_base64_str).unwrap();
179
180        assert_eq!(expected_elgamal_ciphertext, computed_elgamal_ciphertext);
181    }
182}