1#[cfg(target_arch = "wasm32")]
18use wasm_bindgen::prelude::*;
19use {
24 crate::{
25 encryption::{
26 discrete_log::DiscreteLog,
27 pedersen::{Pedersen, PedersenCommitment, PedersenOpening, G, H},
28 DECRYPT_HANDLE_LEN, ELGAMAL_CIPHERTEXT_LEN, ELGAMAL_KEYPAIR_LEN, ELGAMAL_PUBKEY_LEN,
29 ELGAMAL_SECRET_KEY_LEN, PEDERSEN_COMMITMENT_LEN,
30 },
31 errors::ElGamalError,
32 },
33 base64::{prelude::BASE64_STANDARD, Engine},
34 core::ops::{Add, Mul, Sub},
35 curve25519_dalek::{
36 ristretto::{CompressedRistretto, RistrettoPoint},
37 scalar::Scalar,
38 traits::Identity,
39 },
40 rand::rngs::OsRng,
41 serde::{Deserialize, Serialize},
42 sha3::Sha3_512,
43 std::{convert::TryInto, fmt},
44 subtle::{Choice, ConstantTimeEq},
45 zeroize::Zeroize,
46};
47#[cfg(not(target_arch = "wasm32"))]
48use {
49 sha3::Digest,
50 solana_derivation_path::DerivationPath,
51 solana_sdk::{
52 signature::Signature,
53 signer::{
54 keypair::generate_seed_from_seed_phrase_and_passphrase, EncodableKey, EncodableKeypair,
55 SeedDerivable, Signer, SignerError,
56 },
57 },
58 std::{
59 error,
60 io::{Read, Write},
61 path::Path,
62 },
63};
64
65pub struct ElGamal;
67impl ElGamal {
68 fn keygen() -> ElGamalKeypair {
72 let mut s = Scalar::random(&mut OsRng);
74 let keypair = Self::keygen_with_scalar(&s);
75
76 s.zeroize();
77 keypair
78 }
79
80 fn keygen_with_scalar(s: &Scalar) -> ElGamalKeypair {
84 let secret = ElGamalSecretKey(*s);
85 let public = ElGamalPubkey::new(&secret);
86
87 ElGamalKeypair { public, secret }
88 }
89
90 fn encrypt<T: Into<Scalar>>(public: &ElGamalPubkey, amount: T) -> ElGamalCiphertext {
95 let (commitment, opening) = Pedersen::new(amount);
96 let handle = public.decrypt_handle(&opening);
97
98 ElGamalCiphertext { commitment, handle }
99 }
100
101 fn encrypt_with<T: Into<Scalar>>(
104 amount: T,
105 public: &ElGamalPubkey,
106 opening: &PedersenOpening,
107 ) -> ElGamalCiphertext {
108 let commitment = Pedersen::with(amount, opening);
109 let handle = public.decrypt_handle(opening);
110
111 ElGamalCiphertext { commitment, handle }
112 }
113
114 pub fn encode<T: Into<Scalar>>(amount: T) -> ElGamalCiphertext {
118 let commitment = Pedersen::encode(amount);
119 let handle = DecryptHandle(RistrettoPoint::identity());
120
121 ElGamalCiphertext { commitment, handle }
122 }
123
124 fn decrypt(secret: &ElGamalSecretKey, ciphertext: &ElGamalCiphertext) -> DiscreteLog {
130 DiscreteLog::new(
131 *G,
132 ciphertext.commitment.get_point() - &(&secret.0 * &ciphertext.handle.0),
133 )
134 }
135
136 fn decrypt_u32(secret: &ElGamalSecretKey, ciphertext: &ElGamalCiphertext) -> Option<u64> {
142 let discrete_log_instance = Self::decrypt(secret, ciphertext);
143 discrete_log_instance.decode_u32()
144 }
145}
146
147#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
151#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Zeroize)]
152pub struct ElGamalKeypair {
153 public: ElGamalPubkey,
155 secret: ElGamalSecretKey,
157}
158
159#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
160impl ElGamalKeypair {
161 pub fn new_rand() -> Self {
165 ElGamal::keygen()
166 }
167
168 pub fn pubkey_owned(&self) -> ElGamalPubkey {
169 self.public
170 }
171}
172
173impl ElGamalKeypair {
174 pub fn pubkey(&self) -> &ElGamalPubkey {
175 &self.public
176 }
177
178 pub fn secret(&self) -> &ElGamalSecretKey {
179 &self.secret
180 }
181}
182
183#[cfg(not(target_arch = "wasm32"))]
184impl ElGamalKeypair {
185 pub fn new_for_tests(public: ElGamalPubkey, secret: ElGamalSecretKey) -> Self {
191 Self { public, secret }
192 }
193
194 pub fn new(secret: ElGamalSecretKey) -> Self {
196 let public = ElGamalPubkey::new(&secret);
197 Self { public, secret }
198 }
199
200 pub fn new_from_signer(
213 signer: &dyn Signer,
214 public_seed: &[u8],
215 ) -> Result<Self, Box<dyn error::Error>> {
216 let secret = ElGamalSecretKey::new_from_signer(signer, public_seed)?;
217 Ok(Self::new(secret))
218 }
219
220 pub fn new_from_signature(signature: &Signature) -> Result<Self, Box<dyn error::Error>> {
222 let secret = ElGamalSecretKey::new_from_signature(signature)?;
223 Ok(Self::new(secret))
224 }
225
226 pub fn read_json<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> {
228 let bytes: Vec<u8> = serde_json::from_reader(reader)?;
229 Self::try_from(bytes.as_slice()).ok().ok_or_else(|| {
230 std::io::Error::new(std::io::ErrorKind::Other, "Invalid ElGamalKeypair").into()
231 })
232 }
233
234 pub fn read_json_file<F: AsRef<Path>>(path: F) -> Result<Self, Box<dyn error::Error>> {
236 Self::read_from_file(path)
237 }
238
239 pub fn write_json<W: Write>(&self, writer: &mut W) -> Result<String, Box<dyn error::Error>> {
241 let json =
242 serde_json::to_string(&Into::<[u8; ELGAMAL_KEYPAIR_LEN]>::into(self).as_slice())?;
243 writer.write_all(&json.clone().into_bytes())?;
244 Ok(json)
245 }
246
247 pub fn write_json_file<F: AsRef<Path>>(
249 &self,
250 outfile: F,
251 ) -> Result<String, Box<dyn std::error::Error>> {
252 self.write_to_file(outfile)
253 }
254}
255
256#[cfg(not(target_arch = "wasm32"))]
257impl EncodableKey for ElGamalKeypair {
258 fn read<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> {
259 Self::read_json(reader)
260 }
261
262 fn write<W: Write>(&self, writer: &mut W) -> Result<String, Box<dyn error::Error>> {
263 self.write_json(writer)
264 }
265}
266
267impl TryFrom<&[u8]> for ElGamalKeypair {
268 type Error = ElGamalError;
269 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
270 if bytes.len() != ELGAMAL_KEYPAIR_LEN {
271 return Err(ElGamalError::KeypairDeserialization);
272 }
273
274 Ok(Self {
275 public: ElGamalPubkey::try_from(&bytes[..ELGAMAL_PUBKEY_LEN])?,
276 secret: ElGamalSecretKey::try_from(&bytes[ELGAMAL_PUBKEY_LEN..])?,
277 })
278 }
279}
280
281impl From<ElGamalKeypair> for [u8; ELGAMAL_KEYPAIR_LEN] {
282 fn from(keypair: ElGamalKeypair) -> Self {
283 let mut bytes = [0u8; ELGAMAL_KEYPAIR_LEN];
284 bytes[..ELGAMAL_PUBKEY_LEN]
285 .copy_from_slice(&Into::<[u8; ELGAMAL_PUBKEY_LEN]>::into(keypair.public));
286 bytes[ELGAMAL_PUBKEY_LEN..].copy_from_slice(keypair.secret.as_bytes());
287 bytes
288 }
289}
290
291impl From<&ElGamalKeypair> for [u8; ELGAMAL_KEYPAIR_LEN] {
292 fn from(keypair: &ElGamalKeypair) -> Self {
293 let mut bytes = [0u8; ELGAMAL_KEYPAIR_LEN];
294 bytes[..ELGAMAL_PUBKEY_LEN]
295 .copy_from_slice(&Into::<[u8; ELGAMAL_PUBKEY_LEN]>::into(keypair.public));
296 bytes[ELGAMAL_PUBKEY_LEN..].copy_from_slice(keypair.secret.as_bytes());
297 bytes
298 }
299}
300
301#[cfg(not(target_arch = "wasm32"))]
302impl SeedDerivable for ElGamalKeypair {
303 fn from_seed(seed: &[u8]) -> Result<Self, Box<dyn error::Error>> {
304 let secret = ElGamalSecretKey::from_seed(seed)?;
305 let public = ElGamalPubkey::new(&secret);
306 Ok(ElGamalKeypair { public, secret })
307 }
308
309 fn from_seed_and_derivation_path(
310 _seed: &[u8],
311 _derivation_path: Option<DerivationPath>,
312 ) -> Result<Self, Box<dyn error::Error>> {
313 Err(ElGamalError::DerivationMethodNotSupported.into())
314 }
315
316 fn from_seed_phrase_and_passphrase(
317 seed_phrase: &str,
318 passphrase: &str,
319 ) -> Result<Self, Box<dyn error::Error>> {
320 Self::from_seed(&generate_seed_from_seed_phrase_and_passphrase(
321 seed_phrase,
322 passphrase,
323 ))
324 }
325}
326
327#[cfg(not(target_arch = "wasm32"))]
328impl EncodableKeypair for ElGamalKeypair {
329 type Pubkey = ElGamalPubkey;
330
331 fn encodable_pubkey(&self) -> Self::Pubkey {
332 self.public
333 }
334}
335
336#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
338#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Zeroize)]
339pub struct ElGamalPubkey(RistrettoPoint);
340impl ElGamalPubkey {
341 pub fn new(secret: &ElGamalSecretKey) -> Self {
343 let s = &secret.0;
344 assert!(s != &Scalar::ZERO);
345
346 ElGamalPubkey(s.invert() * &(*H))
347 }
348
349 pub fn get_point(&self) -> &RistrettoPoint {
350 &self.0
351 }
352
353 pub fn encrypt<T: Into<Scalar>>(&self, amount: T) -> ElGamalCiphertext {
357 ElGamal::encrypt(self, amount)
358 }
359
360 pub fn encrypt_with<T: Into<Scalar>>(
362 &self,
363 amount: T,
364 opening: &PedersenOpening,
365 ) -> ElGamalCiphertext {
366 ElGamal::encrypt_with(amount, self, opening)
367 }
368
369 pub fn decrypt_handle(self, opening: &PedersenOpening) -> DecryptHandle {
372 DecryptHandle::new(&self, opening)
373 }
374}
375
376#[cfg(not(target_arch = "wasm32"))]
377impl EncodableKey for ElGamalPubkey {
378 fn read<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> {
379 let bytes: Vec<u8> = serde_json::from_reader(reader)?;
380 Self::try_from(bytes.as_slice()).ok().ok_or_else(|| {
381 std::io::Error::new(std::io::ErrorKind::Other, "Invalid ElGamalPubkey").into()
382 })
383 }
384
385 fn write<W: Write>(&self, writer: &mut W) -> Result<String, Box<dyn error::Error>> {
386 let bytes = Into::<[u8; ELGAMAL_PUBKEY_LEN]>::into(*self);
387 let json = serde_json::to_string(&bytes.to_vec())?;
388 writer.write_all(&json.clone().into_bytes())?;
389 Ok(json)
390 }
391}
392
393impl fmt::Display for ElGamalPubkey {
394 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
395 write!(
396 f,
397 "{}",
398 BASE64_STANDARD.encode(Into::<[u8; ELGAMAL_PUBKEY_LEN]>::into(*self))
399 )
400 }
401}
402
403impl TryFrom<&[u8]> for ElGamalPubkey {
404 type Error = ElGamalError;
405 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
406 if bytes.len() != ELGAMAL_PUBKEY_LEN {
407 return Err(ElGamalError::PubkeyDeserialization);
408 }
409 let Ok(compressed_ristretto) = CompressedRistretto::from_slice(bytes) else {
410 return Err(ElGamalError::PubkeyDeserialization);
411 };
412
413 Ok(ElGamalPubkey(
414 compressed_ristretto
415 .decompress()
416 .ok_or(ElGamalError::PubkeyDeserialization)?,
417 ))
418 }
419}
420
421impl From<ElGamalPubkey> for [u8; ELGAMAL_PUBKEY_LEN] {
422 fn from(pubkey: ElGamalPubkey) -> Self {
423 pubkey.0.compress().to_bytes()
424 }
425}
426
427impl From<&ElGamalPubkey> for [u8; ELGAMAL_PUBKEY_LEN] {
428 fn from(pubkey: &ElGamalPubkey) -> Self {
429 pubkey.0.compress().to_bytes()
430 }
431}
432
433#[derive(Clone, Debug, Deserialize, Serialize, Zeroize)]
437#[zeroize(drop)]
438pub struct ElGamalSecretKey(Scalar);
439impl ElGamalSecretKey {
440 pub fn new_rand() -> Self {
444 ElGamalSecretKey(Scalar::random(&mut OsRng))
445 }
446
447 pub fn from_seed(seed: &[u8]) -> Result<Self, ElGamalError> {
449 const MINIMUM_SEED_LEN: usize = ELGAMAL_SECRET_KEY_LEN;
450 const MAXIMUM_SEED_LEN: usize = 65535;
451
452 if seed.len() < MINIMUM_SEED_LEN {
453 return Err(ElGamalError::SeedLengthTooShort);
454 }
455 if seed.len() > MAXIMUM_SEED_LEN {
456 return Err(ElGamalError::SeedLengthTooLong);
457 }
458 Ok(ElGamalSecretKey(Scalar::hash_from_bytes::<Sha3_512>(seed)))
459 }
460
461 pub fn get_scalar(&self) -> &Scalar {
462 &self.0
463 }
464
465 pub fn as_bytes(&self) -> &[u8; ELGAMAL_SECRET_KEY_LEN] {
466 self.0.as_bytes()
467 }
468
469 pub fn decrypt(&self, ciphertext: &ElGamalCiphertext) -> DiscreteLog {
474 ElGamal::decrypt(self, ciphertext)
475 }
476
477 pub fn decrypt_u32(&self, ciphertext: &ElGamalCiphertext) -> Option<u64> {
479 ElGamal::decrypt_u32(self, ciphertext)
480 }
481}
482
483#[cfg(not(target_arch = "wasm32"))]
484impl ElGamalSecretKey {
485 pub fn new_from_signer(
489 signer: &dyn Signer,
490 public_seed: &[u8],
491 ) -> Result<Self, Box<dyn error::Error>> {
492 let seed = Self::seed_from_signer(signer, public_seed)?;
493 let key = Self::from_seed(&seed)?;
494 Ok(key)
495 }
496
497 pub fn seed_from_signer(
501 signer: &dyn Signer,
502 public_seed: &[u8],
503 ) -> Result<Vec<u8>, SignerError> {
504 let message = [b"ElGamalSecretKey", public_seed].concat();
505 let signature = signer.try_sign_message(&message)?;
506
507 if bool::from(signature.as_ref().ct_eq(Signature::default().as_ref())) {
510 return Err(SignerError::Custom("Rejecting default signatures".into()));
511 }
512
513 Ok(Self::seed_from_signature(&signature))
514 }
515
516 pub fn new_from_signature(signature: &Signature) -> Result<Self, Box<dyn error::Error>> {
518 let seed = Self::seed_from_signature(signature);
519 let key = Self::from_seed(&seed)?;
520 Ok(key)
521 }
522
523 pub fn seed_from_signature(signature: &Signature) -> Vec<u8> {
525 let mut hasher = Sha3_512::new();
526 hasher.update(signature.as_ref());
527 let result = hasher.finalize();
528
529 result.to_vec()
530 }
531}
532
533#[cfg(not(target_arch = "wasm32"))]
534impl EncodableKey for ElGamalSecretKey {
535 fn read<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> {
536 let bytes: Vec<u8> = serde_json::from_reader(reader)?;
537 Self::try_from(bytes.as_slice()).ok().ok_or_else(|| {
538 std::io::Error::new(std::io::ErrorKind::Other, "Invalid ElGamalSecretKey").into()
539 })
540 }
541
542 fn write<W: Write>(&self, writer: &mut W) -> Result<String, Box<dyn error::Error>> {
543 let bytes = Into::<[u8; ELGAMAL_SECRET_KEY_LEN]>::into(self);
544 let json = serde_json::to_string(&bytes.to_vec())?;
545 writer.write_all(&json.clone().into_bytes())?;
546 Ok(json)
547 }
548}
549
550#[cfg(not(target_arch = "wasm32"))]
551impl SeedDerivable for ElGamalSecretKey {
552 fn from_seed(seed: &[u8]) -> Result<Self, Box<dyn error::Error>> {
553 let key = Self::from_seed(seed)?;
554 Ok(key)
555 }
556
557 fn from_seed_and_derivation_path(
558 _seed: &[u8],
559 _derivation_path: Option<DerivationPath>,
560 ) -> Result<Self, Box<dyn error::Error>> {
561 Err(ElGamalError::DerivationMethodNotSupported.into())
562 }
563
564 fn from_seed_phrase_and_passphrase(
565 seed_phrase: &str,
566 passphrase: &str,
567 ) -> Result<Self, Box<dyn error::Error>> {
568 let key = Self::from_seed(&generate_seed_from_seed_phrase_and_passphrase(
569 seed_phrase,
570 passphrase,
571 ))?;
572 Ok(key)
573 }
574}
575
576impl From<Scalar> for ElGamalSecretKey {
577 fn from(scalar: Scalar) -> ElGamalSecretKey {
578 ElGamalSecretKey(scalar)
579 }
580}
581
582impl TryFrom<&[u8]> for ElGamalSecretKey {
583 type Error = ElGamalError;
584 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
585 match bytes.try_into() {
586 Ok(bytes) => Ok(ElGamalSecretKey::from(
587 Scalar::from_canonical_bytes(bytes)
588 .into_option()
589 .ok_or(ElGamalError::SecretKeyDeserialization)?,
590 )),
591 _ => Err(ElGamalError::SecretKeyDeserialization),
592 }
593 }
594}
595
596impl From<ElGamalSecretKey> for [u8; ELGAMAL_SECRET_KEY_LEN] {
597 fn from(secret_key: ElGamalSecretKey) -> Self {
598 secret_key.0.to_bytes()
599 }
600}
601
602impl From<&ElGamalSecretKey> for [u8; ELGAMAL_SECRET_KEY_LEN] {
603 fn from(secret_key: &ElGamalSecretKey) -> Self {
604 secret_key.0.to_bytes()
605 }
606}
607
608impl Eq for ElGamalSecretKey {}
609impl PartialEq for ElGamalSecretKey {
610 fn eq(&self, other: &Self) -> bool {
611 self.ct_eq(other).unwrap_u8() == 1u8
612 }
613}
614impl ConstantTimeEq for ElGamalSecretKey {
615 fn ct_eq(&self, other: &Self) -> Choice {
616 self.0.ct_eq(&other.0)
617 }
618}
619
620#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
622pub struct ElGamalCiphertext {
623 pub commitment: PedersenCommitment,
624 pub handle: DecryptHandle,
625}
626impl ElGamalCiphertext {
627 pub fn add_amount<T: Into<Scalar>>(&self, amount: T) -> Self {
628 let point = amount.into() * &(*G);
629 let commitment_to_add = PedersenCommitment::new(point);
630 ElGamalCiphertext {
631 commitment: &self.commitment + &commitment_to_add,
632 handle: self.handle,
633 }
634 }
635
636 pub fn subtract_amount<T: Into<Scalar>>(&self, amount: T) -> Self {
637 let point = amount.into() * &(*G);
638 let commitment_to_subtract = PedersenCommitment::new(point);
639 ElGamalCiphertext {
640 commitment: &self.commitment - &commitment_to_subtract,
641 handle: self.handle,
642 }
643 }
644
645 pub fn to_bytes(&self) -> [u8; ELGAMAL_CIPHERTEXT_LEN] {
646 let mut bytes = [0u8; ELGAMAL_CIPHERTEXT_LEN];
647 bytes[..PEDERSEN_COMMITMENT_LEN].copy_from_slice(&self.commitment.to_bytes());
648 bytes[PEDERSEN_COMMITMENT_LEN..].copy_from_slice(&self.handle.to_bytes());
649 bytes
650 }
651
652 pub fn from_bytes(bytes: &[u8]) -> Option<ElGamalCiphertext> {
653 if bytes.len() != ELGAMAL_CIPHERTEXT_LEN {
654 return None;
655 }
656
657 Some(ElGamalCiphertext {
658 commitment: PedersenCommitment::from_bytes(&bytes[..PEDERSEN_COMMITMENT_LEN])?,
659 handle: DecryptHandle::from_bytes(&bytes[PEDERSEN_COMMITMENT_LEN..])?,
660 })
661 }
662
663 pub fn decrypt(&self, secret: &ElGamalSecretKey) -> DiscreteLog {
668 ElGamal::decrypt(secret, self)
669 }
670
671 pub fn decrypt_u32(&self, secret: &ElGamalSecretKey) -> Option<u64> {
677 ElGamal::decrypt_u32(secret, self)
678 }
679}
680
681impl fmt::Display for ElGamalCiphertext {
682 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
683 write!(f, "{}", BASE64_STANDARD.encode(self.to_bytes()))
684 }
685}
686
687impl<'a, 'b> Add<&'b ElGamalCiphertext> for &'a ElGamalCiphertext {
688 type Output = ElGamalCiphertext;
689
690 fn add(self, ciphertext: &'b ElGamalCiphertext) -> ElGamalCiphertext {
691 ElGamalCiphertext {
692 commitment: &self.commitment + &ciphertext.commitment,
693 handle: &self.handle + &ciphertext.handle,
694 }
695 }
696}
697
698define_add_variants!(
699 LHS = ElGamalCiphertext,
700 RHS = ElGamalCiphertext,
701 Output = ElGamalCiphertext
702);
703
704impl<'a, 'b> Sub<&'b ElGamalCiphertext> for &'a ElGamalCiphertext {
705 type Output = ElGamalCiphertext;
706
707 fn sub(self, ciphertext: &'b ElGamalCiphertext) -> ElGamalCiphertext {
708 ElGamalCiphertext {
709 commitment: &self.commitment - &ciphertext.commitment,
710 handle: &self.handle - &ciphertext.handle,
711 }
712 }
713}
714
715define_sub_variants!(
716 LHS = ElGamalCiphertext,
717 RHS = ElGamalCiphertext,
718 Output = ElGamalCiphertext
719);
720
721impl<'a, 'b> Mul<&'b Scalar> for &'a ElGamalCiphertext {
722 type Output = ElGamalCiphertext;
723
724 fn mul(self, scalar: &'b Scalar) -> ElGamalCiphertext {
725 ElGamalCiphertext {
726 commitment: &self.commitment * scalar,
727 handle: &self.handle * scalar,
728 }
729 }
730}
731
732define_mul_variants!(
733 LHS = ElGamalCiphertext,
734 RHS = Scalar,
735 Output = ElGamalCiphertext
736);
737
738impl<'a, 'b> Mul<&'b ElGamalCiphertext> for &'a Scalar {
739 type Output = ElGamalCiphertext;
740
741 fn mul(self, ciphertext: &'b ElGamalCiphertext) -> ElGamalCiphertext {
742 ElGamalCiphertext {
743 commitment: self * &ciphertext.commitment,
744 handle: self * &ciphertext.handle,
745 }
746 }
747}
748
749define_mul_variants!(
750 LHS = Scalar,
751 RHS = ElGamalCiphertext,
752 Output = ElGamalCiphertext
753);
754
755#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
757pub struct DecryptHandle(RistrettoPoint);
758impl DecryptHandle {
759 pub fn new(public: &ElGamalPubkey, opening: &PedersenOpening) -> Self {
760 Self(&public.0 * opening.get_scalar())
761 }
762
763 pub fn get_point(&self) -> &RistrettoPoint {
764 &self.0
765 }
766
767 pub fn to_bytes(&self) -> [u8; DECRYPT_HANDLE_LEN] {
768 self.0.compress().to_bytes()
769 }
770
771 pub fn from_bytes(bytes: &[u8]) -> Option<DecryptHandle> {
772 if bytes.len() != DECRYPT_HANDLE_LEN {
773 return None;
774 }
775 let Ok(compressed_ristretto) = CompressedRistretto::from_slice(bytes) else {
776 return None;
777 };
778
779 compressed_ristretto.decompress().map(DecryptHandle)
780 }
781}
782
783impl<'a, 'b> Add<&'b DecryptHandle> for &'a DecryptHandle {
784 type Output = DecryptHandle;
785
786 fn add(self, handle: &'b DecryptHandle) -> DecryptHandle {
787 DecryptHandle(&self.0 + &handle.0)
788 }
789}
790
791define_add_variants!(
792 LHS = DecryptHandle,
793 RHS = DecryptHandle,
794 Output = DecryptHandle
795);
796
797impl<'a, 'b> Sub<&'b DecryptHandle> for &'a DecryptHandle {
798 type Output = DecryptHandle;
799
800 fn sub(self, handle: &'b DecryptHandle) -> DecryptHandle {
801 DecryptHandle(&self.0 - &handle.0)
802 }
803}
804
805define_sub_variants!(
806 LHS = DecryptHandle,
807 RHS = DecryptHandle,
808 Output = DecryptHandle
809);
810
811impl<'a, 'b> Mul<&'b Scalar> for &'a DecryptHandle {
812 type Output = DecryptHandle;
813
814 fn mul(self, scalar: &'b Scalar) -> DecryptHandle {
815 DecryptHandle(&self.0 * scalar)
816 }
817}
818
819define_mul_variants!(LHS = DecryptHandle, RHS = Scalar, Output = DecryptHandle);
820
821impl<'a, 'b> Mul<&'b DecryptHandle> for &'a Scalar {
822 type Output = DecryptHandle;
823
824 fn mul(self, handle: &'b DecryptHandle) -> DecryptHandle {
825 DecryptHandle(self * &handle.0)
826 }
827}
828
829define_mul_variants!(LHS = Scalar, RHS = DecryptHandle, Output = DecryptHandle);
830
831#[cfg(test)]
832mod tests {
833 use {
834 super::*,
835 crate::encryption::pedersen::Pedersen,
836 bip39::{Language, Mnemonic, MnemonicType, Seed},
837 solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::null_signer::NullSigner},
838 std::fs::{self, File},
839 };
840
841 #[test]
842 fn test_encrypt_decrypt_correctness() {
843 let ElGamalKeypair { public, secret } = ElGamalKeypair::new_rand();
844 let amount: u32 = 57;
845 let ciphertext = ElGamal::encrypt(&public, amount);
846
847 let expected_instance = DiscreteLog::new(*G, Scalar::from(amount) * &(*G));
848
849 assert_eq!(expected_instance, ElGamal::decrypt(&secret, &ciphertext));
850 assert_eq!(57_u64, secret.decrypt_u32(&ciphertext).unwrap());
851 }
852
853 #[cfg(not(target_arch = "wasm32"))]
854 #[test]
855 fn test_encrypt_decrypt_correctness_multithreaded() {
856 let ElGamalKeypair { public, secret } = ElGamalKeypair::new_rand();
857 let amount: u32 = 57;
858 let ciphertext = ElGamal::encrypt(&public, amount);
859
860 let mut instance = ElGamal::decrypt(&secret, &ciphertext);
861 instance.num_threads(4.try_into().unwrap()).unwrap();
862 assert_eq!(57_u64, instance.decode_u32().unwrap());
863 }
864
865 #[test]
866 fn test_decrypt_handle() {
867 let ElGamalKeypair {
868 public: public_0,
869 secret: secret_0,
870 } = ElGamalKeypair::new_rand();
871 let ElGamalKeypair {
872 public: public_1,
873 secret: secret_1,
874 } = ElGamalKeypair::new_rand();
875
876 let amount: u32 = 77;
877 let (commitment, opening) = Pedersen::new(amount);
878
879 let handle_0 = public_0.decrypt_handle(&opening);
880 let handle_1 = public_1.decrypt_handle(&opening);
881
882 let ciphertext_0 = ElGamalCiphertext {
883 commitment,
884 handle: handle_0,
885 };
886 let ciphertext_1 = ElGamalCiphertext {
887 commitment,
888 handle: handle_1,
889 };
890
891 let expected_instance = DiscreteLog::new(*G, Scalar::from(amount) * &(*G));
892
893 assert_eq!(expected_instance, secret_0.decrypt(&ciphertext_0));
894 assert_eq!(expected_instance, secret_1.decrypt(&ciphertext_1));
895 }
896
897 #[test]
898 fn test_homomorphic_addition() {
899 let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand();
900 let amount_0: u64 = 57;
901 let amount_1: u64 = 77;
902
903 let opening_0 = PedersenOpening::new_rand();
905 let opening_1 = PedersenOpening::new_rand();
906
907 let ciphertext_0 = ElGamal::encrypt_with(amount_0, &public, &opening_0);
908 let ciphertext_1 = ElGamal::encrypt_with(amount_1, &public, &opening_1);
909
910 let ciphertext_sum =
911 ElGamal::encrypt_with(amount_0 + amount_1, &public, &(&opening_0 + &opening_1));
912
913 assert_eq!(ciphertext_sum, ciphertext_0 + ciphertext_1);
914
915 let opening = PedersenOpening::new_rand();
917 let ciphertext = ElGamal::encrypt_with(amount_0, &public, &opening);
918 let ciphertext_sum = ElGamal::encrypt_with(amount_0 + amount_1, &public, &opening);
919
920 assert_eq!(ciphertext_sum, ciphertext.add_amount(amount_1));
921 }
922
923 #[test]
924 fn test_homomorphic_subtraction() {
925 let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand();
926 let amount_0: u64 = 77;
927 let amount_1: u64 = 55;
928
929 let opening_0 = PedersenOpening::new_rand();
931 let opening_1 = PedersenOpening::new_rand();
932
933 let ciphertext_0 = ElGamal::encrypt_with(amount_0, &public, &opening_0);
934 let ciphertext_1 = ElGamal::encrypt_with(amount_1, &public, &opening_1);
935
936 let ciphertext_sub =
937 ElGamal::encrypt_with(amount_0 - amount_1, &public, &(&opening_0 - &opening_1));
938
939 assert_eq!(ciphertext_sub, ciphertext_0 - ciphertext_1);
940
941 let opening = PedersenOpening::new_rand();
943 let ciphertext = ElGamal::encrypt_with(amount_0, &public, &opening);
944 let ciphertext_sub = ElGamal::encrypt_with(amount_0 - amount_1, &public, &opening);
945
946 assert_eq!(ciphertext_sub, ciphertext.subtract_amount(amount_1));
947 }
948
949 #[test]
950 fn test_homomorphic_multiplication() {
951 let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand();
952 let amount_0: u64 = 57;
953 let amount_1: u64 = 77;
954
955 let opening = PedersenOpening::new_rand();
956
957 let ciphertext = ElGamal::encrypt_with(amount_0, &public, &opening);
958 let scalar = Scalar::from(amount_1);
959
960 let ciphertext_prod =
961 ElGamal::encrypt_with(amount_0 * amount_1, &public, &(&opening * scalar));
962
963 assert_eq!(ciphertext_prod, ciphertext * scalar);
964 assert_eq!(ciphertext_prod, scalar * ciphertext);
965 }
966
967 #[test]
968 fn test_serde_ciphertext() {
969 let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand();
970 let amount: u64 = 77;
971 let ciphertext = public.encrypt(amount);
972
973 let encoded = bincode::serialize(&ciphertext).unwrap();
974 let decoded: ElGamalCiphertext = bincode::deserialize(&encoded).unwrap();
975
976 assert_eq!(ciphertext, decoded);
977 }
978
979 #[test]
980 fn test_serde_pubkey() {
981 let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand();
982
983 let encoded = bincode::serialize(&public).unwrap();
984 let decoded: ElGamalPubkey = bincode::deserialize(&encoded).unwrap();
985
986 assert_eq!(public, decoded);
987 }
988
989 #[test]
990 fn test_serde_secretkey() {
991 let ElGamalKeypair { public: _, secret } = ElGamalKeypair::new_rand();
992
993 let encoded = bincode::serialize(&secret).unwrap();
994 let decoded: ElGamalSecretKey = bincode::deserialize(&encoded).unwrap();
995
996 assert_eq!(secret, decoded);
997 }
998
999 fn tmp_file_path(name: &str) -> String {
1000 use std::env;
1001 let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
1002 let keypair = ElGamalKeypair::new_rand();
1003 format!("{}/tmp/{}-{}", out_dir, name, keypair.public)
1004 }
1005
1006 #[test]
1007 fn test_write_keypair_file() {
1008 let outfile = tmp_file_path("test_write_keypair_file.json");
1009 let serialized_keypair = ElGamalKeypair::new_rand()
1010 .write_json_file(&outfile)
1011 .unwrap();
1012 let keypair_vec: Vec<u8> = serde_json::from_str(&serialized_keypair).unwrap();
1013 assert!(Path::new(&outfile).exists());
1014 assert_eq!(
1015 keypair_vec,
1016 Into::<[u8; ELGAMAL_KEYPAIR_LEN]>::into(
1017 ElGamalKeypair::read_json_file(&outfile).unwrap()
1018 )
1019 .to_vec()
1020 );
1021
1022 #[cfg(unix)]
1023 {
1024 use std::os::unix::fs::PermissionsExt;
1025 assert_eq!(
1026 File::open(&outfile)
1027 .expect("open")
1028 .metadata()
1029 .expect("metadata")
1030 .permissions()
1031 .mode()
1032 & 0o777,
1033 0o600
1034 );
1035 }
1036 fs::remove_file(&outfile).unwrap();
1037 }
1038
1039 #[test]
1040 fn test_write_keypair_file_overwrite_ok() {
1041 let outfile = tmp_file_path("test_write_keypair_file_overwrite_ok.json");
1042
1043 ElGamalKeypair::new_rand()
1044 .write_json_file(&outfile)
1045 .unwrap();
1046 ElGamalKeypair::new_rand()
1047 .write_json_file(&outfile)
1048 .unwrap();
1049 }
1050
1051 #[test]
1052 fn test_write_keypair_file_truncate() {
1053 let outfile = tmp_file_path("test_write_keypair_file_truncate.json");
1054
1055 ElGamalKeypair::new_rand()
1056 .write_json_file(&outfile)
1057 .unwrap();
1058 ElGamalKeypair::read_json_file(&outfile).unwrap();
1059
1060 {
1062 let mut f = File::create(&outfile).unwrap();
1063 f.write_all(String::from_utf8([b'a'; 2048].to_vec()).unwrap().as_bytes())
1064 .unwrap();
1065 }
1066 ElGamalKeypair::new_rand()
1067 .write_json_file(&outfile)
1068 .unwrap();
1069 ElGamalKeypair::read_json_file(&outfile).unwrap();
1070 }
1071
1072 #[test]
1073 fn test_secret_key_new_from_signer() {
1074 let keypair1 = Keypair::new();
1075 let keypair2 = Keypair::new();
1076
1077 assert_ne!(
1078 ElGamalSecretKey::new_from_signer(&keypair1, Pubkey::default().as_ref())
1079 .unwrap()
1080 .0,
1081 ElGamalSecretKey::new_from_signer(&keypair2, Pubkey::default().as_ref())
1082 .unwrap()
1083 .0,
1084 );
1085
1086 let null_signer = NullSigner::new(&Pubkey::default());
1087 assert!(
1088 ElGamalSecretKey::new_from_signer(&null_signer, Pubkey::default().as_ref()).is_err()
1089 );
1090 }
1091
1092 #[test]
1093 fn test_keypair_from_seed() {
1094 let good_seed = vec![0; 32];
1095 assert!(ElGamalKeypair::from_seed(&good_seed).is_ok());
1096
1097 let too_short_seed = vec![0; 31];
1098 assert!(ElGamalKeypair::from_seed(&too_short_seed).is_err());
1099
1100 let too_long_seed = vec![0; 65536];
1101 assert!(ElGamalKeypair::from_seed(&too_long_seed).is_err());
1102 }
1103
1104 #[test]
1105 fn test_keypair_from_seed_phrase_and_passphrase() {
1106 let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English);
1107 let passphrase = "42";
1108 let seed = Seed::new(&mnemonic, passphrase);
1109 let expected_keypair = ElGamalKeypair::from_seed(seed.as_bytes()).unwrap();
1110 let keypair =
1111 ElGamalKeypair::from_seed_phrase_and_passphrase(mnemonic.phrase(), passphrase).unwrap();
1112 assert_eq!(keypair.public, expected_keypair.public);
1113 }
1114
1115 #[test]
1116 fn test_decrypt_handle_bytes() {
1117 let handle = DecryptHandle(RistrettoPoint::default());
1118
1119 let encoded = handle.to_bytes();
1120 let decoded = DecryptHandle::from_bytes(&encoded).unwrap();
1121
1122 assert_eq!(handle, decoded);
1123 }
1124
1125 #[test]
1126 fn test_serde_decrypt_handle() {
1127 let handle = DecryptHandle(RistrettoPoint::default());
1128
1129 let encoded = bincode::serialize(&handle).unwrap();
1130 let decoded: DecryptHandle = bincode::deserialize(&encoded).unwrap();
1131
1132 assert_eq!(handle, decoded);
1133 }
1134}