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