1use {
17 crate::encryption::{
18 discrete_log::DiscreteLog,
19 pedersen::{Pedersen, PedersenCommitment, PedersenOpening, G, H},
20 },
21 core::ops::{Add, Mul, Sub},
22 curve25519_dalek::{
23 ristretto::{CompressedRistretto, RistrettoPoint},
24 scalar::Scalar,
25 traits::Identity,
26 },
27 serde::{Deserialize, Serialize},
28 solana_sdk::{
29 instruction::Instruction,
30 message::Message,
31 pubkey::Pubkey,
32 signature::Signature,
33 signer::{Signer, SignerError},
34 },
35 std::convert::TryInto,
36 subtle::{Choice, ConstantTimeEq},
37 zeroize::Zeroize,
38};
39#[cfg(not(target_os = "solana"))]
40use {
41 rand::rngs::OsRng,
42 sha3::Sha3_512,
43 std::{
44 fmt,
45 fs::{self, File, OpenOptions},
46 io::{Read, Write},
47 path::Path,
48 },
49};
50
51pub struct ElGamal;
53impl ElGamal {
54 #[cfg(not(target_os = "solana"))]
58 #[allow(non_snake_case)]
59 fn keygen() -> ElGamalKeypair {
60 let mut s = Scalar::random(&mut OsRng);
62 let keypair = Self::keygen_with_scalar(&s);
63
64 s.zeroize();
65 keypair
66 }
67
68 #[cfg(not(target_os = "solana"))]
72 #[allow(non_snake_case)]
73 fn keygen_with_scalar(s: &Scalar) -> ElGamalKeypair {
74 let secret = ElGamalSecretKey(*s);
75 let public = ElGamalPubkey::new(&secret);
76
77 ElGamalKeypair { public, secret }
78 }
79
80 #[cfg(not(target_os = "solana"))]
85 fn encrypt<T: Into<Scalar>>(public: &ElGamalPubkey, amount: T) -> ElGamalCiphertext {
86 let (commitment, opening) = Pedersen::new(amount);
87 let handle = public.decrypt_handle(&opening);
88
89 ElGamalCiphertext { commitment, handle }
90 }
91
92 #[cfg(not(target_os = "solana"))]
95 fn encrypt_with<T: Into<Scalar>>(
96 amount: T,
97 public: &ElGamalPubkey,
98 opening: &PedersenOpening,
99 ) -> ElGamalCiphertext {
100 let commitment = Pedersen::with(amount, opening);
101 let handle = public.decrypt_handle(opening);
102
103 ElGamalCiphertext { commitment, handle }
104 }
105
106 #[cfg(not(target_os = "solana"))]
110 pub fn encode<T: Into<Scalar>>(amount: T) -> ElGamalCiphertext {
111 let commitment = Pedersen::encode(amount);
112 let handle = DecryptHandle(RistrettoPoint::identity());
113
114 ElGamalCiphertext { commitment, handle }
115 }
116
117 #[cfg(not(target_os = "solana"))]
123 fn decrypt(secret: &ElGamalSecretKey, ciphertext: &ElGamalCiphertext) -> DiscreteLog {
124 DiscreteLog::new(
125 *G,
126 &ciphertext.commitment.0 - &(&secret.0 * &ciphertext.handle.0),
127 )
128 }
129
130 #[cfg(not(target_os = "solana"))]
136 fn decrypt_u32(secret: &ElGamalSecretKey, ciphertext: &ElGamalCiphertext) -> Option<u64> {
137 let discrete_log_instance = Self::decrypt(secret, ciphertext);
138 discrete_log_instance.decode_u32()
139 }
140}
141
142#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Zeroize)]
146pub struct ElGamalKeypair {
147 pub public: ElGamalPubkey,
149 pub secret: ElGamalSecretKey,
151}
152
153impl ElGamalKeypair {
154 #[cfg(not(target_os = "solana"))]
169 #[allow(non_snake_case)]
170 pub fn new(signer: &dyn Signer, address: &Pubkey) -> Result<Self, SignerError> {
171 let secret = ElGamalSecretKey::new(signer, address)?;
172 let public = ElGamalPubkey::new(&secret);
173 Ok(ElGamalKeypair { public, secret })
174 }
175
176 #[cfg(not(target_os = "solana"))]
180 #[allow(clippy::new_ret_no_self)]
181 pub fn new_rand() -> Self {
182 ElGamal::keygen()
183 }
184
185 pub fn to_bytes(&self) -> [u8; 64] {
186 let mut bytes = [0u8; 64];
187 bytes[..32].copy_from_slice(&self.public.to_bytes());
188 bytes[32..].copy_from_slice(self.secret.as_bytes());
189 bytes
190 }
191
192 pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
193 if bytes.len() != 64 {
194 return None;
195 }
196
197 Some(Self {
198 public: ElGamalPubkey::from_bytes(&bytes[..32])?,
199 secret: ElGamalSecretKey::from_bytes(bytes[32..].try_into().ok()?)?,
200 })
201 }
202
203 pub fn read_json<R: Read>(reader: &mut R) -> Result<Self, Box<dyn std::error::Error>> {
205 let bytes: Vec<u8> = serde_json::from_reader(reader)?;
206 Self::from_bytes(&bytes).ok_or_else(|| {
207 std::io::Error::new(std::io::ErrorKind::Other, "Invalid ElGamalKeypair").into()
208 })
209 }
210
211 pub fn read_json_file<F: AsRef<Path>>(path: F) -> Result<Self, Box<dyn std::error::Error>> {
213 let mut file = File::open(path.as_ref())?;
214 Self::read_json(&mut file)
215 }
216
217 pub fn write_json<W: Write>(
219 &self,
220 writer: &mut W,
221 ) -> Result<String, Box<dyn std::error::Error>> {
222 let bytes = self.to_bytes();
223 let json = serde_json::to_string(&bytes.to_vec())?;
224 writer.write_all(&json.clone().into_bytes())?;
225 Ok(json)
226 }
227
228 pub fn write_json_file<F: AsRef<Path>>(
230 &self,
231 outfile: F,
232 ) -> Result<String, Box<dyn std::error::Error>> {
233 let outfile = outfile.as_ref();
234
235 if let Some(outdir) = outfile.parent() {
236 fs::create_dir_all(outdir)?;
237 }
238
239 let mut f = {
240 #[cfg(not(unix))]
241 {
242 OpenOptions::new()
243 }
244 #[cfg(unix)]
245 {
246 use std::os::unix::fs::OpenOptionsExt;
247 OpenOptions::new().mode(0o600)
248 }
249 }
250 .write(true)
251 .truncate(true)
252 .create(true)
253 .open(outfile)?;
254
255 self.write_json(&mut f)
256 }
257}
258
259#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Zeroize)]
261pub struct ElGamalPubkey(RistrettoPoint);
262impl ElGamalPubkey {
263 #[allow(non_snake_case)]
265 pub fn new(secret: &ElGamalSecretKey) -> Self {
266 let s = &secret.0;
267 assert!(s != &Scalar::zero());
268
269 ElGamalPubkey(s.invert() * &(*H))
270 }
271
272 pub fn get_point(&self) -> &RistrettoPoint {
273 &self.0
274 }
275
276 pub fn to_bytes(&self) -> [u8; 32] {
277 self.0.compress().to_bytes()
278 }
279
280 pub fn from_bytes(bytes: &[u8]) -> Option<ElGamalPubkey> {
281 if bytes.len() != 32 {
282 return None;
283 }
284
285 Some(ElGamalPubkey(
286 CompressedRistretto::from_slice(bytes).decompress()?,
287 ))
288 }
289
290 #[cfg(not(target_os = "solana"))]
294 pub fn encrypt<T: Into<Scalar>>(&self, amount: T) -> ElGamalCiphertext {
295 ElGamal::encrypt(self, amount)
296 }
297
298 pub fn encrypt_with<T: Into<Scalar>>(
300 &self,
301 amount: T,
302 opening: &PedersenOpening,
303 ) -> ElGamalCiphertext {
304 ElGamal::encrypt_with(amount, self, opening)
305 }
306
307 pub fn decrypt_handle(self, opening: &PedersenOpening) -> DecryptHandle {
310 DecryptHandle::new(&self, opening)
311 }
312}
313
314impl fmt::Display for ElGamalPubkey {
315 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
316 write!(f, "{}", base64::encode(self.to_bytes()))
317 }
318}
319
320#[derive(Clone, Debug, Deserialize, Serialize, Zeroize)]
324#[zeroize(drop)]
325pub struct ElGamalSecretKey(Scalar);
326impl ElGamalSecretKey {
327 pub fn new(signer: &dyn Signer, address: &Pubkey) -> Result<Self, SignerError> {
332 let message = Message::new(
333 &[Instruction::new_with_bytes(
334 *address,
335 b"ElGamalSecretKey",
336 vec![],
337 )],
338 Some(&signer.try_pubkey()?),
339 );
340 let signature = signer.try_sign_message(&message.serialize())?;
341
342 if bool::from(signature.as_ref().ct_eq(Signature::default().as_ref())) {
345 return Err(SignerError::Custom("Rejecting default signatures".into()));
346 }
347
348 Ok(ElGamalSecretKey(Scalar::hash_from_bytes::<Sha3_512>(
349 signature.as_ref(),
350 )))
351 }
352
353 pub fn new_rand() -> Self {
357 ElGamalSecretKey(Scalar::random(&mut OsRng))
358 }
359
360 pub fn get_scalar(&self) -> &Scalar {
361 &self.0
362 }
363
364 pub fn decrypt(&self, ciphertext: &ElGamalCiphertext) -> DiscreteLog {
369 ElGamal::decrypt(self, ciphertext)
370 }
371
372 pub fn decrypt_u32(&self, ciphertext: &ElGamalCiphertext) -> Option<u64> {
374 ElGamal::decrypt_u32(self, ciphertext)
375 }
376
377 pub fn as_bytes(&self) -> &[u8; 32] {
378 self.0.as_bytes()
379 }
380
381 pub fn to_bytes(&self) -> [u8; 32] {
382 self.0.to_bytes()
383 }
384
385 pub fn from_bytes(bytes: &[u8]) -> Option<ElGamalSecretKey> {
386 match bytes.try_into() {
387 Ok(bytes) => Scalar::from_canonical_bytes(bytes).map(ElGamalSecretKey),
388 _ => None,
389 }
390 }
391}
392
393impl From<Scalar> for ElGamalSecretKey {
394 fn from(scalar: Scalar) -> ElGamalSecretKey {
395 ElGamalSecretKey(scalar)
396 }
397}
398
399impl Eq for ElGamalSecretKey {}
400impl PartialEq for ElGamalSecretKey {
401 fn eq(&self, other: &Self) -> bool {
402 self.ct_eq(other).unwrap_u8() == 1u8
403 }
404}
405impl ConstantTimeEq for ElGamalSecretKey {
406 fn ct_eq(&self, other: &Self) -> Choice {
407 self.0.ct_eq(&other.0)
408 }
409}
410
411#[allow(non_snake_case)]
413#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
414pub struct ElGamalCiphertext {
415 pub commitment: PedersenCommitment,
416 pub handle: DecryptHandle,
417}
418impl ElGamalCiphertext {
419 pub fn add_amount<T: Into<Scalar>>(&self, amount: T) -> Self {
420 let commitment_to_add = PedersenCommitment(amount.into() * &(*G));
421 ElGamalCiphertext {
422 commitment: &self.commitment + &commitment_to_add,
423 handle: self.handle,
424 }
425 }
426
427 pub fn subtract_amount<T: Into<Scalar>>(&self, amount: T) -> Self {
428 let commitment_to_subtract = PedersenCommitment(amount.into() * &(*G));
429 ElGamalCiphertext {
430 commitment: &self.commitment - &commitment_to_subtract,
431 handle: self.handle,
432 }
433 }
434
435 pub fn to_bytes(&self) -> [u8; 64] {
436 let mut bytes = [0u8; 64];
437 bytes[..32].copy_from_slice(&self.commitment.to_bytes());
438 bytes[32..].copy_from_slice(&self.handle.to_bytes());
439 bytes
440 }
441
442 pub fn from_bytes(bytes: &[u8]) -> Option<ElGamalCiphertext> {
443 if bytes.len() != 64 {
444 return None;
445 }
446
447 Some(ElGamalCiphertext {
448 commitment: PedersenCommitment::from_bytes(&bytes[..32])?,
449 handle: DecryptHandle::from_bytes(&bytes[32..])?,
450 })
451 }
452
453 pub fn decrypt(&self, secret: &ElGamalSecretKey) -> DiscreteLog {
458 ElGamal::decrypt(secret, self)
459 }
460
461 pub fn decrypt_u32(&self, secret: &ElGamalSecretKey) -> Option<u64> {
467 ElGamal::decrypt_u32(secret, self)
468 }
469}
470
471impl fmt::Display for ElGamalCiphertext {
472 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
473 write!(f, "{}", base64::encode(self.to_bytes()))
474 }
475}
476
477impl<'a, 'b> Add<&'b ElGamalCiphertext> for &'a ElGamalCiphertext {
478 type Output = ElGamalCiphertext;
479
480 fn add(self, ciphertext: &'b ElGamalCiphertext) -> ElGamalCiphertext {
481 ElGamalCiphertext {
482 commitment: &self.commitment + &ciphertext.commitment,
483 handle: &self.handle + &ciphertext.handle,
484 }
485 }
486}
487
488define_add_variants!(
489 LHS = ElGamalCiphertext,
490 RHS = ElGamalCiphertext,
491 Output = ElGamalCiphertext
492);
493
494impl<'a, 'b> Sub<&'b ElGamalCiphertext> for &'a ElGamalCiphertext {
495 type Output = ElGamalCiphertext;
496
497 fn sub(self, ciphertext: &'b ElGamalCiphertext) -> ElGamalCiphertext {
498 ElGamalCiphertext {
499 commitment: &self.commitment - &ciphertext.commitment,
500 handle: &self.handle - &ciphertext.handle,
501 }
502 }
503}
504
505define_sub_variants!(
506 LHS = ElGamalCiphertext,
507 RHS = ElGamalCiphertext,
508 Output = ElGamalCiphertext
509);
510
511impl<'a, 'b> Mul<&'b Scalar> for &'a ElGamalCiphertext {
512 type Output = ElGamalCiphertext;
513
514 fn mul(self, scalar: &'b Scalar) -> ElGamalCiphertext {
515 ElGamalCiphertext {
516 commitment: &self.commitment * scalar,
517 handle: &self.handle * scalar,
518 }
519 }
520}
521
522define_mul_variants!(
523 LHS = ElGamalCiphertext,
524 RHS = Scalar,
525 Output = ElGamalCiphertext
526);
527
528impl<'a, 'b> Mul<&'b ElGamalCiphertext> for &'a Scalar {
529 type Output = ElGamalCiphertext;
530
531 fn mul(self, ciphertext: &'b ElGamalCiphertext) -> ElGamalCiphertext {
532 ElGamalCiphertext {
533 commitment: self * &ciphertext.commitment,
534 handle: self * &ciphertext.handle,
535 }
536 }
537}
538
539define_mul_variants!(
540 LHS = Scalar,
541 RHS = ElGamalCiphertext,
542 Output = ElGamalCiphertext
543);
544
545#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
547pub struct DecryptHandle(RistrettoPoint);
548impl DecryptHandle {
549 pub fn new(public: &ElGamalPubkey, opening: &PedersenOpening) -> Self {
550 Self(&public.0 * &opening.0)
551 }
552
553 pub fn get_point(&self) -> &RistrettoPoint {
554 &self.0
555 }
556
557 pub fn to_bytes(&self) -> [u8; 32] {
558 self.0.compress().to_bytes()
559 }
560
561 pub fn from_bytes(bytes: &[u8]) -> Option<DecryptHandle> {
562 if bytes.len() != 32 {
563 return None;
564 }
565
566 Some(DecryptHandle(
567 CompressedRistretto::from_slice(bytes).decompress()?,
568 ))
569 }
570}
571
572impl<'a, 'b> Add<&'b DecryptHandle> for &'a DecryptHandle {
573 type Output = DecryptHandle;
574
575 fn add(self, handle: &'b DecryptHandle) -> DecryptHandle {
576 DecryptHandle(&self.0 + &handle.0)
577 }
578}
579
580define_add_variants!(
581 LHS = DecryptHandle,
582 RHS = DecryptHandle,
583 Output = DecryptHandle
584);
585
586impl<'a, 'b> Sub<&'b DecryptHandle> for &'a DecryptHandle {
587 type Output = DecryptHandle;
588
589 fn sub(self, handle: &'b DecryptHandle) -> DecryptHandle {
590 DecryptHandle(&self.0 - &handle.0)
591 }
592}
593
594define_sub_variants!(
595 LHS = DecryptHandle,
596 RHS = DecryptHandle,
597 Output = DecryptHandle
598);
599
600impl<'a, 'b> Mul<&'b Scalar> for &'a DecryptHandle {
601 type Output = DecryptHandle;
602
603 fn mul(self, scalar: &'b Scalar) -> DecryptHandle {
604 DecryptHandle(&self.0 * scalar)
605 }
606}
607
608define_mul_variants!(LHS = DecryptHandle, RHS = Scalar, Output = DecryptHandle);
609
610impl<'a, 'b> Mul<&'b DecryptHandle> for &'a Scalar {
611 type Output = DecryptHandle;
612
613 fn mul(self, handle: &'b DecryptHandle) -> DecryptHandle {
614 DecryptHandle(self * &handle.0)
615 }
616}
617
618define_mul_variants!(LHS = Scalar, RHS = DecryptHandle, Output = DecryptHandle);
619
620#[cfg(test)]
621mod tests {
622 use {
623 super::*,
624 crate::encryption::pedersen::Pedersen,
625 solana_sdk::{signature::Keypair, signer::null_signer::NullSigner},
626 };
627
628 #[test]
629 fn test_encrypt_decrypt_correctness() {
630 let ElGamalKeypair { public, secret } = ElGamalKeypair::new_rand();
631 let amount: u32 = 57;
632 let ciphertext = ElGamal::encrypt(&public, amount);
633
634 let expected_instance = DiscreteLog::new(*G, Scalar::from(amount) * &(*G));
635
636 assert_eq!(expected_instance, ElGamal::decrypt(&secret, &ciphertext));
637 assert_eq!(57_u64, secret.decrypt_u32(&ciphertext).unwrap());
638 }
639
640 #[test]
641 fn test_encrypt_decrypt_correctness_multithreaded() {
642 let ElGamalKeypair { public, secret } = ElGamalKeypair::new_rand();
643 let amount: u32 = 57;
644 let ciphertext = ElGamal::encrypt(&public, amount);
645
646 let mut instance = ElGamal::decrypt(&secret, &ciphertext);
647 instance.num_threads(4).unwrap();
648 assert_eq!(57_u64, instance.decode_u32().unwrap());
649 }
650
651 #[test]
652 fn test_decrypt_handle() {
653 let ElGamalKeypair {
654 public: public_0,
655 secret: secret_0,
656 } = ElGamalKeypair::new_rand();
657 let ElGamalKeypair {
658 public: public_1,
659 secret: secret_1,
660 } = ElGamalKeypair::new_rand();
661
662 let amount: u32 = 77;
663 let (commitment, opening) = Pedersen::new(amount);
664
665 let handle_0 = public_0.decrypt_handle(&opening);
666 let handle_1 = public_1.decrypt_handle(&opening);
667
668 let ciphertext_0 = ElGamalCiphertext {
669 commitment,
670 handle: handle_0,
671 };
672 let ciphertext_1 = ElGamalCiphertext {
673 commitment,
674 handle: handle_1,
675 };
676
677 let expected_instance = DiscreteLog::new(*G, Scalar::from(amount) * &(*G));
678
679 assert_eq!(expected_instance, secret_0.decrypt(&ciphertext_0));
680 assert_eq!(expected_instance, secret_1.decrypt(&ciphertext_1));
681 }
682
683 #[test]
684 fn test_homomorphic_addition() {
685 let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand();
686 let amount_0: u64 = 57;
687 let amount_1: u64 = 77;
688
689 let opening_0 = PedersenOpening::new_rand();
691 let opening_1 = PedersenOpening::new_rand();
692
693 let ciphertext_0 = ElGamal::encrypt_with(amount_0, &public, &opening_0);
694 let ciphertext_1 = ElGamal::encrypt_with(amount_1, &public, &opening_1);
695
696 let ciphertext_sum =
697 ElGamal::encrypt_with(amount_0 + amount_1, &public, &(&opening_0 + &opening_1));
698
699 assert_eq!(ciphertext_sum, ciphertext_0 + ciphertext_1);
700
701 let opening = PedersenOpening::new_rand();
703 let ciphertext = ElGamal::encrypt_with(amount_0, &public, &opening);
704 let ciphertext_sum = ElGamal::encrypt_with(amount_0 + amount_1, &public, &opening);
705
706 assert_eq!(ciphertext_sum, ciphertext.add_amount(amount_1));
707 }
708
709 #[test]
710 fn test_homomorphic_subtraction() {
711 let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand();
712 let amount_0: u64 = 77;
713 let amount_1: u64 = 55;
714
715 let opening_0 = PedersenOpening::new_rand();
717 let opening_1 = PedersenOpening::new_rand();
718
719 let ciphertext_0 = ElGamal::encrypt_with(amount_0, &public, &opening_0);
720 let ciphertext_1 = ElGamal::encrypt_with(amount_1, &public, &opening_1);
721
722 let ciphertext_sub =
723 ElGamal::encrypt_with(amount_0 - amount_1, &public, &(&opening_0 - &opening_1));
724
725 assert_eq!(ciphertext_sub, ciphertext_0 - ciphertext_1);
726
727 let opening = PedersenOpening::new_rand();
729 let ciphertext = ElGamal::encrypt_with(amount_0, &public, &opening);
730 let ciphertext_sub = ElGamal::encrypt_with(amount_0 - amount_1, &public, &opening);
731
732 assert_eq!(ciphertext_sub, ciphertext.subtract_amount(amount_1));
733 }
734
735 #[test]
736 fn test_homomorphic_multiplication() {
737 let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand();
738 let amount_0: u64 = 57;
739 let amount_1: u64 = 77;
740
741 let opening = PedersenOpening::new_rand();
742
743 let ciphertext = ElGamal::encrypt_with(amount_0, &public, &opening);
744 let scalar = Scalar::from(amount_1);
745
746 let ciphertext_prod =
747 ElGamal::encrypt_with(amount_0 * amount_1, &public, &(&opening * scalar));
748
749 assert_eq!(ciphertext_prod, ciphertext * scalar);
750 assert_eq!(ciphertext_prod, scalar * ciphertext);
751 }
752
753 #[test]
754 fn test_serde_ciphertext() {
755 let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand();
756 let amount: u64 = 77;
757 let ciphertext = public.encrypt(amount);
758
759 let encoded = bincode::serialize(&ciphertext).unwrap();
760 let decoded: ElGamalCiphertext = bincode::deserialize(&encoded).unwrap();
761
762 assert_eq!(ciphertext, decoded);
763 }
764
765 #[test]
766 fn test_serde_pubkey() {
767 let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand();
768
769 let encoded = bincode::serialize(&public).unwrap();
770 let decoded: ElGamalPubkey = bincode::deserialize(&encoded).unwrap();
771
772 assert_eq!(public, decoded);
773 }
774
775 #[test]
776 fn test_serde_secretkey() {
777 let ElGamalKeypair { public: _, secret } = ElGamalKeypair::new_rand();
778
779 let encoded = bincode::serialize(&secret).unwrap();
780 let decoded: ElGamalSecretKey = bincode::deserialize(&encoded).unwrap();
781
782 assert_eq!(secret, decoded);
783 }
784
785 fn tmp_file_path(name: &str) -> String {
786 use std::env;
787 let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
788 let keypair = ElGamalKeypair::new_rand();
789 format!("{}/tmp/{}-{}", out_dir, name, keypair.public)
790 }
791
792 #[test]
793 fn test_write_keypair_file() {
794 let outfile = tmp_file_path("test_write_keypair_file.json");
795 let serialized_keypair = ElGamalKeypair::new_rand()
796 .write_json_file(&outfile)
797 .unwrap();
798 let keypair_vec: Vec<u8> = serde_json::from_str(&serialized_keypair).unwrap();
799 assert!(Path::new(&outfile).exists());
800 assert_eq!(
801 keypair_vec,
802 ElGamalKeypair::read_json_file(&outfile)
803 .unwrap()
804 .to_bytes()
805 .to_vec()
806 );
807
808 #[cfg(unix)]
809 {
810 use std::os::unix::fs::PermissionsExt;
811 assert_eq!(
812 File::open(&outfile)
813 .expect("open")
814 .metadata()
815 .expect("metadata")
816 .permissions()
817 .mode()
818 & 0o777,
819 0o600
820 );
821 }
822 fs::remove_file(&outfile).unwrap();
823 }
824
825 #[test]
826 fn test_write_keypair_file_overwrite_ok() {
827 let outfile = tmp_file_path("test_write_keypair_file_overwrite_ok.json");
828
829 ElGamalKeypair::new_rand()
830 .write_json_file(&outfile)
831 .unwrap();
832 ElGamalKeypair::new_rand()
833 .write_json_file(&outfile)
834 .unwrap();
835 }
836
837 #[test]
838 fn test_write_keypair_file_truncate() {
839 let outfile = tmp_file_path("test_write_keypair_file_truncate.json");
840
841 ElGamalKeypair::new_rand()
842 .write_json_file(&outfile)
843 .unwrap();
844 ElGamalKeypair::read_json_file(&outfile).unwrap();
845
846 {
848 let mut f = File::create(&outfile).unwrap();
849 f.write_all(String::from_utf8([b'a'; 2048].to_vec()).unwrap().as_bytes())
850 .unwrap();
851 }
852 ElGamalKeypair::new_rand()
853 .write_json_file(&outfile)
854 .unwrap();
855 ElGamalKeypair::read_json_file(&outfile).unwrap();
856 }
857
858 #[test]
859 fn test_secret_key_new() {
860 let keypair1 = Keypair::new();
861 let keypair2 = Keypair::new();
862
863 assert_ne!(
864 ElGamalSecretKey::new(&keypair1, &Pubkey::default())
865 .unwrap()
866 .0,
867 ElGamalSecretKey::new(&keypair2, &Pubkey::default())
868 .unwrap()
869 .0,
870 );
871
872 let null_signer = NullSigner::new(&Pubkey::default());
873 assert!(ElGamalSecretKey::new(&null_signer, &Pubkey::default()).is_err());
874 }
875
876 #[test]
877 fn test_decrypt_handle_bytes() {
878 let handle = DecryptHandle(RistrettoPoint::default());
879
880 let encoded = handle.to_bytes();
881 let decoded = DecryptHandle::from_bytes(&encoded).unwrap();
882
883 assert_eq!(handle, decoded);
884 }
885
886 #[test]
887 fn test_serde_decrypt_handle() {
888 let handle = DecryptHandle(RistrettoPoint::default());
889
890 let encoded = bincode::serialize(&handle).unwrap();
891 let decoded: DecryptHandle = bincode::deserialize(&encoded).unwrap();
892
893 assert_eq!(handle, decoded);
894 }
895}