solana_zk_sdk/encryption/pod/
elgamal.rs

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