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