Skip to content

Commit

Permalink
Add ElGamal cipher
Browse files Browse the repository at this point in the history
  • Loading branch information
xevisalle committed Apr 29, 2024
1 parent 03446b7 commit 39f9646
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 14 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ exclude = [".github/workflows/dusk-ci.yml", ".gitignore"]
[dependencies]
rand_core = { version = "0.6", default-features = false }
dusk-bytes = "0.1"
dusk-plonk = { version = "0.19", default-features = false, features = ["rkyv-impl", "alloc"] }
dusk-bls12_381 = { version = "0.13", default-features = false }
bls12_381-bls = { version = "0.3", default-features = false }
dusk-jubjub = { version = "0.14", default-features = false, features = ["zeroize"] }
Expand Down
File renamed without changes.
59 changes: 59 additions & 0 deletions src/encryption/elgamal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) DUSK NETWORK. All rights reserved.

use dusk_jubjub::{JubJubExtended, JubJubScalar, GENERATOR};
use dusk_plonk::prelude::*;

/// Encrypts a JubJubExtended plaintext given a public key and a fresh random
/// number, returning a ciphertext (JubJubExtended, JubJubExtended)
pub fn encrypt(
public_key: &JubJubExtended,
plaintext: &JubJubExtended,
r: &JubJubScalar,
) -> (JubJubExtended, JubJubExtended) {
let S = public_key * r;

let ciphertext_1 = GENERATOR * r;
let ciphertext_2 = plaintext + S;

(ciphertext_1, ciphertext_2)
}

/// Decrypts a ciphertext given a secret key,
/// returning a JubJubExtended plaintext
pub fn decrypt(
secret_key: &JubJubScalar,
ciphertext_1: &JubJubExtended,
ciphertext_2: &JubJubExtended,
) -> JubJubExtended {
ciphertext_2 - ciphertext_1 * secret_key
}

/// Encrypt in-circuit a plaintext
pub fn zk_encrypt(
composer: &mut Composer,
public_key: &JubJubAffine,
plaintext: &JubJubAffine,
r: &JubJubScalar,
ciphertext_1: &JubJubAffine,
ciphertext_2: &JubJubAffine,
) -> Result<(), Error> {
// IMPORT INPUTS
let public_key = composer.append_point(*public_key);
let plaintext = composer.append_point(*plaintext);
let r = composer.append_witness(*r);

// ENCRYPT
let S = composer.component_mul_point(r, public_key);
let ciphertext_1_p = composer.component_mul_generator(r, GENERATOR)?;
let ciphertext_2_p = composer.component_add_point(plaintext, S);

// ASSERT RESULT MAKING THE CIPHERTEXT PUBLIC
composer.assert_equal_public_point(ciphertext_1_p, *ciphertext_1);
composer.assert_equal_public_point(ciphertext_2_p, *ciphertext_2);

Ok(())
}
11 changes: 11 additions & 0 deletions src/encryption/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) DUSK NETWORK. All rights reserved.

/// AES symmetric cipher
pub mod aes;

/// ElGamal asymmetric cipher
pub mod elgamal;
8 changes: 5 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ pub mod note;
/// Phoenix Core Keys & Addresses
mod keys;

/// Encryption and decryption methods
/// Encryption algorithms
mod encryption;

/// Encryption and decryption methods
pub use encryption::{decrypt, encrypt, ENCRYPTION_EXTRA_SIZE};
/// AES symmetric cipher
pub use encryption::aes;
/// ElGamal asymmetric cipher
pub use encryption::elgamal;
/// Hash function
pub use keys::hash;
/// Public (Spend) Key
Expand Down
8 changes: 4 additions & 4 deletions src/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use dusk_jubjub::{
GENERATOR_NUMS_EXTENDED,
};

use crate::encryption::{decrypt, encrypt, ENCRYPTION_EXTRA_SIZE};
use crate::aes;

use dusk_poseidon::sponge::hash;
use ff::Field;
Expand All @@ -31,7 +31,7 @@ pub(crate) const PLAINTEXT_SIZE: usize = 40;

/// Size of the Phoenix notes encryption
pub(crate) const ENCRYPTION_SIZE: usize =
PLAINTEXT_SIZE + ENCRYPTION_EXTRA_SIZE;
PLAINTEXT_SIZE + aes::ENCRYPTION_EXTRA_SIZE;

/// The types of a Note
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
Expand Down Expand Up @@ -123,7 +123,7 @@ impl Note {
let mut plaintext = value.to_bytes().to_vec();
plaintext.append(&mut blinding_factor.to_bytes().to_vec());

encrypt(&shared_secret, &plaintext, rng)
aes::encrypt(&shared_secret, &plaintext, rng)
.expect("Encrypted correctly.")
}
};
Expand Down Expand Up @@ -200,7 +200,7 @@ impl Note {
let shared_secret = dhke(vk.a(), R);

let dec_plaintext: [u8; PLAINTEXT_SIZE] =
decrypt(&shared_secret, &self.encryption)?;
aes::decrypt(&shared_secret, &self.encryption)?;

let value = u64::from_slice(&dec_plaintext[..u64::SIZE])?;

Expand Down
106 changes: 99 additions & 7 deletions tests/encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,117 @@
//
// Copyright (c) DUSK NETWORK. All rights reserved.

use dusk_jubjub::{JubJubAffine, JubJubScalar, GENERATOR};
use dusk_jubjub::{JubJubAffine, JubJubScalar, GENERATOR, GENERATOR_EXTENDED};
use dusk_plonk::prelude::*;
use ff::Field;
use rand_core::OsRng;

use phoenix_core::{decrypt, encrypt, ENCRYPTION_EXTRA_SIZE};
use phoenix_core::{aes, elgamal, PublicKey, SecretKey};

static LABEL: &[u8; 12] = b"dusk-network";
const CAPACITY: usize = 12; // capacity required for the setup

#[derive(Default, Debug)]
pub struct ElGamalCircuit {
public_key: JubJubAffine,
plaintext: JubJubAffine,
r: JubJubScalar,
ciphertext_1: JubJubAffine,
ciphertext_2: JubJubAffine,
}

impl ElGamalCircuit {
pub fn new(
public_key: &JubJubExtended,
plaintext: &JubJubExtended,
r: &JubJubScalar,
ciphertext_1: &JubJubExtended,
ciphertext_2: &JubJubExtended,
) -> Self {
Self {
public_key: JubJubAffine::from(public_key),
plaintext: JubJubAffine::from(plaintext),
r: *r,
ciphertext_1: JubJubAffine::from(ciphertext_1),
ciphertext_2: JubJubAffine::from(ciphertext_2),
}
}
}

impl Circuit for ElGamalCircuit {
fn circuit(&self, composer: &mut Composer) -> Result<(), Error> {
elgamal::zk_encrypt(
composer,
&self.public_key,
&self.plaintext,
&self.r,
&self.ciphertext_1,
&self.ciphertext_2,
)?;
Ok(())
}
}

#[test]
fn test_encrypt_and_decrypt() {
fn test_aes_encrypt_and_decrypt() {
const PLAINTEXT_SIZE: usize = 20;
const ENCRYPTION_SIZE: usize = PLAINTEXT_SIZE + ENCRYPTION_EXTRA_SIZE;
const ENCRYPTION_SIZE: usize = PLAINTEXT_SIZE + aes::ENCRYPTION_EXTRA_SIZE;

let shared_secret_key =
JubJubAffine::from(GENERATOR * JubJubScalar::from(1234u64));

let plaintext = b"00112233445566778899";
let encryption: [u8; ENCRYPTION_SIZE] =
encrypt(&shared_secret_key, plaintext, &mut OsRng)
aes::encrypt(&shared_secret_key, plaintext, &mut OsRng)
.expect("Encrypted correctly.");
let dec_plaintext =
decrypt(&shared_secret_key, &encryption).expect("Decrypted correctly.");
let dec_plaintext = aes::decrypt(&shared_secret_key, &encryption)
.expect("Decrypted correctly.");

assert_eq!(&dec_plaintext, plaintext);
}

#[test]
fn test_elgamal_encrypt_and_decrypt() {
let sk = SecretKey::random(&mut OsRng);
let pk = PublicKey::from(&sk);

let message = GENERATOR_EXTENDED * JubJubScalar::from(1234u64);

// Encrypt using a fresh random value 'r'
let r = JubJubScalar::random(&mut OsRng);
let (c1, c2) = elgamal::encrypt(pk.A(), &message, &r);

// Assert decryption
let dec_message = elgamal::decrypt(sk.a(), &c1, &c2);
assert_eq!(message, dec_message);

// Assert decryption using an incorrect key
let dec_message_wrong = elgamal::decrypt(sk.b(), &c1, &c2);
assert_ne!(message, dec_message_wrong);
}

#[test]
fn test_elgamal_zk_encrypt() {
let sk = SecretKey::random(&mut OsRng);
let pk = PublicKey::from(&sk);

let message = GENERATOR_EXTENDED * JubJubScalar::from(1234u64);
let r = JubJubScalar::random(&mut OsRng);
let (c1, c2) = elgamal::encrypt(pk.A(), &message, &r);

let pp = PublicParameters::setup(1 << CAPACITY, &mut OsRng).unwrap();

let (prover, verifier) = Compiler::compile::<ElGamalCircuit>(&pp, LABEL)
.expect("failed to compile circuit");

let (proof, public_inputs) = prover
.prove(
&mut OsRng,
&ElGamalCircuit::new(&pk.A(), &message, &r, &c1, &c2),
)
.expect("failed to prove");

verifier
.verify(&proof, &public_inputs)
.expect("failed to verify proof");
}

0 comments on commit 39f9646

Please sign in to comment.