solana_zk_sdk/encryption/pod/
grouped_elgamal.rs1#[cfg(not(target_os = "solana"))]
4use crate::encryption::grouped_elgamal::GroupedElGamalCiphertext;
5use {
6 crate::{
7 encryption::{
8 pod::{elgamal::PodElGamalCiphertext, pedersen::PodPedersenCommitment},
9 DECRYPT_HANDLE_LEN, ELGAMAL_CIPHERTEXT_LEN, PEDERSEN_COMMITMENT_LEN,
10 },
11 errors::ElGamalError,
12 pod::{impl_from_bytes, impl_from_str},
13 },
14 base64::{prelude::BASE64_STANDARD, Engine},
15 bytemuck::Zeroable,
16 std::fmt,
17};
18
19const GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES_MAX_BASE64_LEN: usize = 132;
21
22const GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES_MAX_BASE64_LEN: usize = 176;
24
25macro_rules! impl_extract {
26 (TYPE = $type:ident) => {
27 impl $type {
28 pub fn extract_commitment(&self) -> PodPedersenCommitment {
30 let commitment = self.0[..PEDERSEN_COMMITMENT_LEN].try_into().unwrap();
32 PodPedersenCommitment(commitment)
33 }
34
35 pub fn try_extract_ciphertext(
37 &self,
38 index: usize,
39 ) -> Result<PodElGamalCiphertext, ElGamalError> {
40 let mut ciphertext_bytes = [0u8; ELGAMAL_CIPHERTEXT_LEN];
41 ciphertext_bytes[..PEDERSEN_COMMITMENT_LEN]
42 .copy_from_slice(&self.0[..PEDERSEN_COMMITMENT_LEN]);
43
44 let handle_start = DECRYPT_HANDLE_LEN
45 .checked_mul(index)
46 .and_then(|n| n.checked_add(PEDERSEN_COMMITMENT_LEN))
47 .ok_or(ElGamalError::CiphertextDeserialization)?;
48 let handle_end = handle_start
49 .checked_add(DECRYPT_HANDLE_LEN)
50 .ok_or(ElGamalError::CiphertextDeserialization)?;
51 ciphertext_bytes[PEDERSEN_COMMITMENT_LEN..].copy_from_slice(
52 self.0
53 .get(handle_start..handle_end)
54 .ok_or(ElGamalError::CiphertextDeserialization)?,
55 );
56
57 Ok(PodElGamalCiphertext(ciphertext_bytes))
58 }
59 }
60 };
61}
62
63const GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES: usize =
65 PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN;
66
67const GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES: usize =
69 PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN;
70
71#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)]
73#[repr(transparent)]
74pub struct PodGroupedElGamalCiphertext2Handles(
75 pub(crate) [u8; GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES],
76);
77
78impl fmt::Debug for PodGroupedElGamalCiphertext2Handles {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80 write!(f, "{:?}", self.0)
81 }
82}
83
84impl Default for PodGroupedElGamalCiphertext2Handles {
85 fn default() -> Self {
86 Self::zeroed()
87 }
88}
89
90impl fmt::Display for PodGroupedElGamalCiphertext2Handles {
91 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92 write!(f, "{}", BASE64_STANDARD.encode(self.0))
93 }
94}
95
96impl_from_str!(
97 TYPE = PodGroupedElGamalCiphertext2Handles,
98 BYTES_LEN = GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES,
99 BASE64_LEN = GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES_MAX_BASE64_LEN
100);
101
102impl_from_bytes!(
103 TYPE = PodGroupedElGamalCiphertext2Handles,
104 BYTES_LEN = GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES
105);
106
107#[cfg(not(target_os = "solana"))]
108impl From<GroupedElGamalCiphertext<2>> for PodGroupedElGamalCiphertext2Handles {
109 fn from(decoded_ciphertext: GroupedElGamalCiphertext<2>) -> Self {
110 Self(decoded_ciphertext.to_bytes().try_into().unwrap())
111 }
112}
113
114#[cfg(not(target_os = "solana"))]
115impl TryFrom<PodGroupedElGamalCiphertext2Handles> for GroupedElGamalCiphertext<2> {
116 type Error = ElGamalError;
117
118 fn try_from(pod_ciphertext: PodGroupedElGamalCiphertext2Handles) -> Result<Self, Self::Error> {
119 Self::from_bytes(&pod_ciphertext.0).ok_or(ElGamalError::CiphertextDeserialization)
120 }
121}
122
123impl_extract!(TYPE = PodGroupedElGamalCiphertext2Handles);
124
125#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)]
127#[repr(transparent)]
128pub struct PodGroupedElGamalCiphertext3Handles(
129 pub(crate) [u8; GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES],
130);
131
132impl fmt::Debug for PodGroupedElGamalCiphertext3Handles {
133 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134 write!(f, "{:?}", self.0)
135 }
136}
137
138impl Default for PodGroupedElGamalCiphertext3Handles {
139 fn default() -> Self {
140 Self::zeroed()
141 }
142}
143
144impl fmt::Display for PodGroupedElGamalCiphertext3Handles {
145 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146 write!(f, "{}", BASE64_STANDARD.encode(self.0))
147 }
148}
149
150impl_from_str!(
151 TYPE = PodGroupedElGamalCiphertext3Handles,
152 BYTES_LEN = GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES,
153 BASE64_LEN = GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES_MAX_BASE64_LEN
154);
155
156impl_from_bytes!(
157 TYPE = PodGroupedElGamalCiphertext3Handles,
158 BYTES_LEN = GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES
159);
160
161#[cfg(not(target_os = "solana"))]
162impl From<GroupedElGamalCiphertext<3>> for PodGroupedElGamalCiphertext3Handles {
163 fn from(decoded_ciphertext: GroupedElGamalCiphertext<3>) -> Self {
164 Self(decoded_ciphertext.to_bytes().try_into().unwrap())
165 }
166}
167
168#[cfg(not(target_os = "solana"))]
169impl TryFrom<PodGroupedElGamalCiphertext3Handles> for GroupedElGamalCiphertext<3> {
170 type Error = ElGamalError;
171
172 fn try_from(pod_ciphertext: PodGroupedElGamalCiphertext3Handles) -> Result<Self, Self::Error> {
173 Self::from_bytes(&pod_ciphertext.0).ok_or(ElGamalError::CiphertextDeserialization)
174 }
175}
176
177impl_extract!(TYPE = PodGroupedElGamalCiphertext3Handles);
178
179#[cfg(test)]
180mod tests {
181 use {
182 super::*,
183 crate::encryption::{
184 elgamal::ElGamalKeypair, grouped_elgamal::GroupedElGamal, pedersen::Pedersen,
185 pod::pedersen::PodPedersenCommitment,
186 },
187 };
188
189 #[test]
190 fn test_2_handles_ciphertext_extraction() {
191 let elgamal_keypair_0 = ElGamalKeypair::new_rand();
192 let elgamal_keypair_1 = ElGamalKeypair::new_rand();
193
194 let amount: u64 = 10;
195 let (commitment, opening) = Pedersen::new(amount);
196
197 let grouped_ciphertext = GroupedElGamal::encrypt_with(
198 [elgamal_keypair_0.pubkey(), elgamal_keypair_1.pubkey()],
199 amount,
200 &opening,
201 );
202 let pod_grouped_ciphertext: PodGroupedElGamalCiphertext2Handles = grouped_ciphertext.into();
203
204 let expected_pod_commitment: PodPedersenCommitment = commitment.into();
205 let actual_pod_commitment = pod_grouped_ciphertext.extract_commitment();
206 assert_eq!(expected_pod_commitment, actual_pod_commitment);
207
208 let expected_ciphertext_0 = elgamal_keypair_0.pubkey().encrypt_with(amount, &opening);
209 let expected_pod_ciphertext_0: PodElGamalCiphertext = expected_ciphertext_0.into();
210 let actual_pod_ciphertext_0 = pod_grouped_ciphertext.try_extract_ciphertext(0).unwrap();
211 assert_eq!(expected_pod_ciphertext_0, actual_pod_ciphertext_0);
212
213 let expected_ciphertext_1 = elgamal_keypair_1.pubkey().encrypt_with(amount, &opening);
214 let expected_pod_ciphertext_1: PodElGamalCiphertext = expected_ciphertext_1.into();
215 let actual_pod_ciphertext_1 = pod_grouped_ciphertext.try_extract_ciphertext(1).unwrap();
216 assert_eq!(expected_pod_ciphertext_1, actual_pod_ciphertext_1);
217
218 let err = pod_grouped_ciphertext
219 .try_extract_ciphertext(2)
220 .unwrap_err();
221 assert_eq!(err, ElGamalError::CiphertextDeserialization);
222 }
223
224 #[test]
225 fn test_3_handles_ciphertext_extraction() {
226 let elgamal_keypair_0 = ElGamalKeypair::new_rand();
227 let elgamal_keypair_1 = ElGamalKeypair::new_rand();
228 let elgamal_keypair_2 = ElGamalKeypair::new_rand();
229
230 let amount: u64 = 10;
231 let (commitment, opening) = Pedersen::new(amount);
232
233 let grouped_ciphertext = GroupedElGamal::encrypt_with(
234 [
235 elgamal_keypair_0.pubkey(),
236 elgamal_keypair_1.pubkey(),
237 elgamal_keypair_2.pubkey(),
238 ],
239 amount,
240 &opening,
241 );
242 let pod_grouped_ciphertext: PodGroupedElGamalCiphertext3Handles = grouped_ciphertext.into();
243
244 let expected_pod_commitment: PodPedersenCommitment = commitment.into();
245 let actual_pod_commitment = pod_grouped_ciphertext.extract_commitment();
246 assert_eq!(expected_pod_commitment, actual_pod_commitment);
247
248 let expected_ciphertext_0 = elgamal_keypair_0.pubkey().encrypt_with(amount, &opening);
249 let expected_pod_ciphertext_0: PodElGamalCiphertext = expected_ciphertext_0.into();
250 let actual_pod_ciphertext_0 = pod_grouped_ciphertext.try_extract_ciphertext(0).unwrap();
251 assert_eq!(expected_pod_ciphertext_0, actual_pod_ciphertext_0);
252
253 let expected_ciphertext_1 = elgamal_keypair_1.pubkey().encrypt_with(amount, &opening);
254 let expected_pod_ciphertext_1: PodElGamalCiphertext = expected_ciphertext_1.into();
255 let actual_pod_ciphertext_1 = pod_grouped_ciphertext.try_extract_ciphertext(1).unwrap();
256 assert_eq!(expected_pod_ciphertext_1, actual_pod_ciphertext_1);
257
258 let expected_ciphertext_2 = elgamal_keypair_2.pubkey().encrypt_with(amount, &opening);
259 let expected_pod_ciphertext_2: PodElGamalCiphertext = expected_ciphertext_2.into();
260 let actual_pod_ciphertext_2 = pod_grouped_ciphertext.try_extract_ciphertext(2).unwrap();
261 assert_eq!(expected_pod_ciphertext_2, actual_pod_ciphertext_2);
262
263 let err = pod_grouped_ciphertext
264 .try_extract_ciphertext(3)
265 .unwrap_err();
266 assert_eq!(err, ElGamalError::CiphertextDeserialization);
267 }
268}