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