solana_zk_sdk/encryption/pod/
elgamal.rs1#[cfg(not(target_os = "solana"))]
4use {
5 crate::{
6 encryption::elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalPubkey},
7 errors::ElGamalError,
8 },
9 curve25519_dalek::ristretto::CompressedRistretto,
10};
11use {
12 crate::{
13 encryption::{DECRYPT_HANDLE_LEN, ELGAMAL_CIPHERTEXT_LEN, ELGAMAL_PUBKEY_LEN},
14 pod::{impl_from_bytes, impl_from_str},
15 },
16 base64::{prelude::BASE64_STANDARD, Engine},
17 bytemuck::Zeroable,
18 std::fmt,
19};
20#[cfg(target_arch = "wasm32")]
21use {
22 js_sys::{Array, Uint8Array},
23 wasm_bindgen::prelude::*,
24};
25
26const ELGAMAL_PUBKEY_MAX_BASE64_LEN: usize = 44;
28
29const ELGAMAL_CIPHERTEXT_MAX_BASE64_LEN: usize = 88;
31
32const DECRYPT_HANDLE_MAX_BASE64_LEN: usize = 44;
34
35#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)]
37#[repr(transparent)]
38pub struct PodElGamalCiphertext(pub(crate) [u8; ELGAMAL_CIPHERTEXT_LEN]);
39
40impl fmt::Debug for PodElGamalCiphertext {
41 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42 write!(f, "{:?}", self.0)
43 }
44}
45
46impl fmt::Display for PodElGamalCiphertext {
47 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48 write!(f, "{}", BASE64_STANDARD.encode(self.0))
49 }
50}
51
52impl Default for PodElGamalCiphertext {
53 fn default() -> Self {
54 Self::zeroed()
55 }
56}
57
58impl_from_str!(
59 TYPE = PodElGamalCiphertext,
60 BYTES_LEN = ELGAMAL_CIPHERTEXT_LEN,
61 BASE64_LEN = ELGAMAL_CIPHERTEXT_MAX_BASE64_LEN
62);
63
64impl_from_bytes!(
65 TYPE = PodElGamalCiphertext,
66 BYTES_LEN = ELGAMAL_CIPHERTEXT_LEN
67);
68
69#[cfg(not(target_os = "solana"))]
70impl From<ElGamalCiphertext> for PodElGamalCiphertext {
71 fn from(decoded_ciphertext: ElGamalCiphertext) -> Self {
72 Self(decoded_ciphertext.to_bytes())
73 }
74}
75
76#[cfg(not(target_os = "solana"))]
77impl TryFrom<PodElGamalCiphertext> for ElGamalCiphertext {
78 type Error = ElGamalError;
79
80 fn try_from(pod_ciphertext: PodElGamalCiphertext) -> Result<Self, Self::Error> {
81 Self::from_bytes(&pod_ciphertext.0).ok_or(ElGamalError::CiphertextDeserialization)
82 }
83}
84
85#[derive(Clone, Copy, Default, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)]
87#[repr(transparent)]
88#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
89pub struct PodElGamalPubkey(pub(crate) [u8; ELGAMAL_PUBKEY_LEN]);
90
91#[cfg(target_arch = "wasm32")]
92#[allow(non_snake_case)]
93#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
94impl PodElGamalPubkey {
95 #[wasm_bindgen(constructor)]
99 pub fn constructor(value: JsValue) -> Result<PodElGamalPubkey, JsValue> {
100 if let Some(base64_str) = value.as_string() {
101 base64_str
102 .parse::<PodElGamalPubkey>()
103 .map_err(|e| e.to_string().into())
104 } else if let Some(uint8_array) = value.dyn_ref::<Uint8Array>() {
105 bytemuck::try_from_bytes(&uint8_array.to_vec())
106 .map_err(|err| JsValue::from(format!("Invalid Uint8Array ElGamalPubkey: {err:?}")))
107 .map(|pubkey| *pubkey)
108 } else if let Some(array) = value.dyn_ref::<Array>() {
109 let mut bytes = vec![];
110 let iterator = js_sys::try_iter(&array.values())?.expect("array to be iterable");
111 for x in iterator {
112 let x = x?;
113
114 if let Some(n) = x.as_f64() {
115 if (0. ..=255.).contains(&n) {
116 bytes.push(n as u8);
117 continue;
118 }
119 }
120 return Err(format!("Invalid array argument: {:?}", x).into());
121 }
122
123 bytemuck::try_from_bytes(&bytes)
124 .map_err(|err| JsValue::from(format!("Invalid Array pubkey: {err:?}")))
125 .map(|pubkey| *pubkey)
126 } else if value.is_undefined() {
127 Ok(PodElGamalPubkey::default())
128 } else {
129 Err("Unsupported argument".into())
130 }
131 }
132
133 pub fn toString(&self) -> String {
135 self.to_string()
136 }
137
138 pub fn equals(&self, other: &PodElGamalPubkey) -> bool {
140 self == other
141 }
142
143 pub fn toBytes(&self) -> Box<[u8]> {
145 self.0.into()
146 }
147
148 pub fn compressed(decoded: &ElGamalPubkey) -> PodElGamalPubkey {
149 (*decoded).into()
150 }
151
152 pub fn decompressed(&self) -> Result<ElGamalPubkey, JsValue> {
153 (*self)
154 .try_into()
155 .map_err(|err| JsValue::from(format!("Invalid ElGamalPubkey: {err:?}")))
156 }
157}
158
159impl fmt::Debug for PodElGamalPubkey {
160 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
161 write!(f, "{:?}", self.0)
162 }
163}
164
165impl fmt::Display for PodElGamalPubkey {
166 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
167 write!(f, "{}", BASE64_STANDARD.encode(self.0))
168 }
169}
170
171impl_from_str!(
172 TYPE = PodElGamalPubkey,
173 BYTES_LEN = ELGAMAL_PUBKEY_LEN,
174 BASE64_LEN = ELGAMAL_PUBKEY_MAX_BASE64_LEN
175);
176
177impl_from_bytes!(TYPE = PodElGamalPubkey, BYTES_LEN = ELGAMAL_PUBKEY_LEN);
178
179#[cfg(not(target_os = "solana"))]
180impl From<ElGamalPubkey> for PodElGamalPubkey {
181 fn from(decoded_pubkey: ElGamalPubkey) -> Self {
182 Self(decoded_pubkey.into())
183 }
184}
185
186#[cfg(not(target_os = "solana"))]
187impl TryFrom<PodElGamalPubkey> for ElGamalPubkey {
188 type Error = ElGamalError;
189
190 fn try_from(pod_pubkey: PodElGamalPubkey) -> Result<Self, Self::Error> {
191 Self::try_from(pod_pubkey.0.as_slice())
192 }
193}
194
195#[derive(Clone, Copy, Default, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)]
197#[repr(transparent)]
198pub struct PodDecryptHandle(pub(crate) [u8; DECRYPT_HANDLE_LEN]);
199
200impl fmt::Debug for PodDecryptHandle {
201 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
202 write!(f, "{:?}", self.0)
203 }
204}
205
206#[cfg(not(target_os = "solana"))]
207impl From<DecryptHandle> for PodDecryptHandle {
208 fn from(decoded_handle: DecryptHandle) -> Self {
209 Self(decoded_handle.to_bytes())
210 }
211}
212
213#[cfg(not(target_os = "solana"))]
215impl From<PodDecryptHandle> for CompressedRistretto {
216 fn from(pod_handle: PodDecryptHandle) -> Self {
217 Self(pod_handle.0)
218 }
219}
220
221#[cfg(not(target_os = "solana"))]
222impl TryFrom<PodDecryptHandle> for DecryptHandle {
223 type Error = ElGamalError;
224
225 fn try_from(pod_handle: PodDecryptHandle) -> Result<Self, Self::Error> {
226 Self::from_bytes(&pod_handle.0).ok_or(ElGamalError::CiphertextDeserialization)
227 }
228}
229
230impl fmt::Display for PodDecryptHandle {
231 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
232 write!(f, "{}", BASE64_STANDARD.encode(self.0))
233 }
234}
235
236impl_from_str!(
237 TYPE = PodDecryptHandle,
238 BYTES_LEN = DECRYPT_HANDLE_LEN,
239 BASE64_LEN = DECRYPT_HANDLE_MAX_BASE64_LEN
240);
241
242impl_from_bytes!(TYPE = PodDecryptHandle, BYTES_LEN = DECRYPT_HANDLE_LEN);
243
244#[cfg(test)]
245mod tests {
246 use {super::*, crate::encryption::elgamal::ElGamalKeypair, std::str::FromStr};
247
248 #[test]
249 fn elgamal_pubkey_fromstr() {
250 let elgamal_keypair = ElGamalKeypair::new_rand();
251 let expected_elgamal_pubkey: PodElGamalPubkey = (*elgamal_keypair.pubkey()).into();
252
253 let elgamal_pubkey_base64_str = format!("{}", expected_elgamal_pubkey);
254 let computed_elgamal_pubkey =
255 PodElGamalPubkey::from_str(&elgamal_pubkey_base64_str).unwrap();
256
257 assert_eq!(expected_elgamal_pubkey, computed_elgamal_pubkey);
258 }
259
260 #[test]
261 fn elgamal_ciphertext_fromstr() {
262 let elgamal_keypair = ElGamalKeypair::new_rand();
263 let expected_elgamal_ciphertext: PodElGamalCiphertext =
264 elgamal_keypair.pubkey().encrypt(0_u64).into();
265
266 let elgamal_ciphertext_base64_str = format!("{}", expected_elgamal_ciphertext);
267 let computed_elgamal_ciphertext =
268 PodElGamalCiphertext::from_str(&elgamal_ciphertext_base64_str).unwrap();
269
270 assert_eq!(expected_elgamal_ciphertext, computed_elgamal_ciphertext);
271 }
272}