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