diff --git a/circuits/CHANGELOG.md b/circuits/CHANGELOG.md index f3fc5c5..59d67c0 100644 --- a/circuits/CHANGELOG.md +++ b/circuits/CHANGELOG.md @@ -21,6 +21,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Rename `skeleton_hash` to `payload_hash` [#188] - Make `TxCircuit` to use the Recipient gadget +### Removed + +- Remove `RecipientParameters` +- Remove `elgamal::encrypt` and `elgamal::decrypt` + ## [0.1.0] - 2024-05-22 ### Added diff --git a/circuits/src/encryption/elgamal.rs b/circuits/src/encryption/elgamal.rs index 2db5fdc..c94ef1f 100644 --- a/circuits/src/encryption/elgamal.rs +++ b/circuits/src/encryption/elgamal.rs @@ -9,38 +9,9 @@ //! //! Reference: https://link.springer.com/chapter/10.1007/3-540-39568-7_2 -use dusk_jubjub::{JubJubExtended, JubJubScalar, GENERATOR}; +use dusk_jubjub::{JubJubScalar, GENERATOR}; use dusk_plonk::prelude::*; -/// Encrypts a JubJubExtended plaintext given a public key and a fresh random -/// number 'r'. -/// -/// ## Return -/// Returns a ciphertext (JubJubExtended, JubJubExtended). -pub fn encrypt( - public_key: &JubJubExtended, - plaintext: &JubJubExtended, - r: &JubJubScalar, -) -> (JubJubExtended, JubJubExtended) { - let ciphertext_1 = GENERATOR * r; - let ciphertext_2 = plaintext + public_key * r; - - (ciphertext_1, ciphertext_2) -} - -/// Decrypts a ciphertext given a secret key. -/// -/// ## Return -/// Returns a JubJubExtended plaintext. -pub fn decrypt( - secret_key: &JubJubScalar, - ciphertext_1: &JubJubExtended, - ciphertext_2: &JubJubExtended, -) -> JubJubExtended { - // return the plaintext - ciphertext_2 - ciphertext_1 * secret_key -} - /// Encrypt in-circuit a plaintext WitnessPoint. /// /// ## Return diff --git a/circuits/src/lib.rs b/circuits/src/lib.rs index 524311b..45859db 100644 --- a/circuits/src/lib.rs +++ b/circuits/src/lib.rs @@ -16,8 +16,5 @@ mod recipient; /// Transaction structs, and circuit pub mod transaction; -/// Recipient Parameters -pub use recipient::RecipientParameters; - /// ElGamal asymmetric cipher pub use encryption::elgamal; diff --git a/circuits/src/recipient.rs b/circuits/src/recipient.rs index 36e895e..8a04726 100644 --- a/circuits/src/recipient.rs +++ b/circuits/src/recipient.rs @@ -6,123 +6,11 @@ #![allow(non_snake_case)] -use dusk_jubjub::JubJubScalar; use dusk_plonk::prelude::*; -use ff::Field; -use jubjub_schnorr::{gadgets, SecretKey as SchnorrSecretKey, Signature}; -use rand::{CryptoRng, RngCore}; +use jubjub_schnorr::gadgets; +use phoenix_core::RecipientParameters; use crate::elgamal; -use phoenix_core::{PublicKey, SecretKey}; - -const TX_OUTPUT_NOTES: usize = 2; - -/// Parameters needed to prove a recipient in-circuit -#[derive(Debug, Clone, Copy)] -pub struct RecipientParameters { - /// Public key of the transaction sender - pub sender_pk: PublicKey, - /// Note public keys of each note recipient - pub output_npk: [JubJubAffine; TX_OUTPUT_NOTES], - /// Signatures of 'payload_hash' verifiable using 'pk_A' and 'pk_B' - pub sig: [Signature; TX_OUTPUT_NOTES], - /// Asymmetric encryption of 'pk_A' using both recipients 'npk' - pub enc_A: [(JubJubExtended, JubJubExtended); TX_OUTPUT_NOTES], - /// Asymmetric encryption of 'pk_B' using both recipients 'npk' - pub enc_B: [(JubJubExtended, JubJubExtended); TX_OUTPUT_NOTES], - /// Randomness needed to encrypt/decrypt 'pk_A' - pub r_A: [JubJubScalar; TX_OUTPUT_NOTES], - /// Randomness needed to encrypt/decrypt 'pk_B' - pub r_B: [JubJubScalar; TX_OUTPUT_NOTES], -} - -impl Default for RecipientParameters { - fn default() -> Self { - let sk = - SecretKey::new(JubJubScalar::default(), JubJubScalar::default()); - let sender_pk = PublicKey::from(&sk); - - Self { - sender_pk, - output_npk: [JubJubAffine::default(), JubJubAffine::default()], - sig: [Signature::default(), Signature::default()], - enc_A: [(JubJubExtended::default(), JubJubExtended::default()); - TX_OUTPUT_NOTES], - enc_B: [(JubJubExtended::default(), JubJubExtended::default()); - TX_OUTPUT_NOTES], - r_A: [JubJubScalar::default(); TX_OUTPUT_NOTES], - r_B: [JubJubScalar::default(); TX_OUTPUT_NOTES], - } - } -} - -impl RecipientParameters { - /// Create the recipient parameter - pub fn new( - rng: &mut (impl RngCore + CryptoRng), - sender_sk: &SecretKey, - output_npk: [JubJubAffine; TX_OUTPUT_NOTES], - payload_hash: BlsScalar, - ) -> Self { - // Encrypt the public key of the sender. We need to encrypt - // both 'A' and 'B', using both tx output note public keys. - let sender_pk = PublicKey::from(sender_sk); - - let r_A = [ - JubJubScalar::random(&mut *rng), - JubJubScalar::random(&mut *rng), - ]; - let r_B = [ - JubJubScalar::random(&mut *rng), - JubJubScalar::random(&mut *rng), - ]; - - let (A_enc_1_c1, A_enc_1_c2) = elgamal::encrypt( - &output_npk[0].into(), // note_pk_1.as_ref(), - sender_pk.A(), - &r_A[0], - ); - - let (B_enc_1_c1, B_enc_1_c2) = elgamal::encrypt( - &output_npk[0].into(), // note_pk_1.as_ref(), - sender_pk.B(), - &r_B[0], - ); - let (A_enc_2_c1, A_enc_2_c2) = elgamal::encrypt( - &output_npk[1].into(), // note_pk_2.as_ref(), - sender_pk.A(), - &r_A[1], - ); - - let (B_enc_2_c1, B_enc_2_c2) = elgamal::encrypt( - &output_npk[1].into(), // note_pk_2.as_ref(), - sender_pk.B(), - &r_B[1], - ); - - let enc_A = [(A_enc_1_c1, A_enc_1_c2), (A_enc_2_c1, A_enc_2_c2)]; - let enc_B = [(B_enc_1_c1, B_enc_1_c2), (B_enc_2_c1, B_enc_2_c2)]; - - // Sign the payload hash using both 'a' and 'b' - let schnorr_sk_a = SchnorrSecretKey::from(sender_sk.a()); - let sig_A = schnorr_sk_a.sign(rng, payload_hash); - - let schnorr_sk_b = SchnorrSecretKey::from(sender_sk.b()); - let sig_B = schnorr_sk_b.sign(rng, payload_hash); - - let sig = [sig_A, sig_B]; - - RecipientParameters { - sender_pk, - output_npk, - sig, - enc_A, - enc_B, - r_A, - r_B, - } - } -} /// Gadget to prove a valid origin for a given transaction. pub(crate) fn gadget( diff --git a/circuits/src/transaction.rs b/circuits/src/transaction.rs index d8cac6f..b4b40f6 100644 --- a/circuits/src/transaction.rs +++ b/circuits/src/transaction.rs @@ -19,9 +19,11 @@ use rand::{CryptoRng, RngCore, SeedableRng}; extern crate alloc; use alloc::vec::Vec; -use phoenix_core::{Error as PhoenixError, Note, SecretKey, ViewKey}; +use phoenix_core::{ + Error as PhoenixError, Note, RecipientParameters, SecretKey, ViewKey, +}; -use crate::{recipient, recipient::RecipientParameters}; +use crate::recipient; const TX_OUTPUT_NOTES: usize = 2; diff --git a/circuits/tests/elgamal.rs b/circuits/tests/elgamal.rs index b3817e8..ca3350b 100644 --- a/circuits/tests/elgamal.rs +++ b/circuits/tests/elgamal.rs @@ -7,8 +7,8 @@ use dusk_jubjub::{JubJubAffine, JubJubScalar, GENERATOR_EXTENDED}; use dusk_plonk::prelude::*; use ff::Field; -use phoenix_circuits::elgamal; -use phoenix_core::{PublicKey, SecretKey}; +use phoenix_circuits::elgamal::{decrypt_gadget, encrypt_gadget}; +use phoenix_core::{elgamal, PublicKey, SecretKey}; use rand::rngs::StdRng; use rand::SeedableRng; @@ -77,19 +77,15 @@ impl Circuit for ElGamalCircuit { // ENCRYPT let (ciphertext_1, ciphertext_2) = - elgamal::encrypt_gadget(composer, public_key, plaintext, r)?; + encrypt_gadget(composer, public_key, plaintext, r)?; // ASSERT RESULT MAKING THE CIPHERTEXT PUBLIC composer.assert_equal_public_point(ciphertext_1, self.ciphertext_1); composer.assert_equal_public_point(ciphertext_2, self.ciphertext_2); // DECRYPT - let dec_plaintext = elgamal::decrypt_gadget( - composer, - secret_key, - ciphertext_1, - ciphertext_2, - ); + let dec_plaintext = + decrypt_gadget(composer, secret_key, ciphertext_1, ciphertext_2); // ASSERT RESULTING PLAINTEXT composer.assert_equal_point(dec_plaintext, plaintext); diff --git a/circuits/tests/transaction.rs b/circuits/tests/transaction.rs index ccc6fdd..07e1e5a 100644 --- a/circuits/tests/transaction.rs +++ b/circuits/tests/transaction.rs @@ -13,11 +13,10 @@ use dusk_plonk::prelude::*; use ff::Field; use poseidon_merkle::{Item, Tree}; -use phoenix_circuits::{ - transaction::{TxCircuit, TxInputNote, TxOutputNote}, - RecipientParameters, +use phoenix_circuits::transaction::{TxCircuit, TxInputNote, TxOutputNote}; +use phoenix_core::{ + value_commitment, Note, PublicKey, RecipientParameters, SecretKey, }; -use phoenix_core::{value_commitment, Note, PublicKey, SecretKey}; #[macro_use] extern crate lazy_static; @@ -151,10 +150,6 @@ fn test_transfer_circuit_1_2() { TxOutputNote::new(value1, commitment1, blinder1), TxOutputNote::new(value2, commitment2, blinder2), ]; - // let tx_output_notes = [ - // TxOutputNote::new(10, JubJubScalar::random(&mut rng)), - // TxOutputNote::new(5, JubJubScalar::random(&mut rng)), - // ]; let (proof, public_inputs) = prover .prove( diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index dd3c363..e8035e4 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add `RecipientParameters` +- Add `elgamal::encrypt` and `elgamal::decrypt` - Add `stealth_address` and `sync_address` functions directly to note [#208] - Add a light sync method in the `ViewKey` [#199] - Add function `value_commitment` [#201] @@ -16,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Move `OUTPUT_NOTES` to crate root - Change `owns` and `owns_unchecked` to take `&Note` [#208] - Change `gen_note_sk` to take `&StealthAddress` [#208] - Rename `crossover` to `deposit` [#190] diff --git a/core/src/encryption/elgamal.rs b/core/src/encryption/elgamal.rs new file mode 100644 index 0000000..b9fcb08 --- /dev/null +++ b/core/src/encryption/elgamal.rs @@ -0,0 +1,41 @@ +// 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. + +//! This module implements the ElGamal asymmetric cipher. It allows to +//! encrypt and decrypt. +//! +//! Reference: https://link.springer.com/chapter/10.1007/3-540-39568-7_2 + +use dusk_jubjub::{JubJubExtended, JubJubScalar, GENERATOR}; + +/// Encrypts a JubJubExtended plaintext given a public key and a fresh random +/// number 'r'. +/// +/// ## Return +/// Returns a ciphertext (JubJubExtended, JubJubExtended). +pub fn encrypt( + public_key: &JubJubExtended, + plaintext: &JubJubExtended, + r: &JubJubScalar, +) -> (JubJubExtended, JubJubExtended) { + let ciphertext_1 = GENERATOR * r; + let ciphertext_2 = plaintext + public_key * r; + + (ciphertext_1, ciphertext_2) +} + +/// Decrypts a ciphertext given a secret key. +/// +/// ## Return +/// Returns a JubJubExtended plaintext. +pub fn decrypt( + secret_key: &JubJubScalar, + ciphertext_1: &JubJubExtended, + ciphertext_2: &JubJubExtended, +) -> JubJubExtended { + // return the plaintext + ciphertext_2 - ciphertext_1 * secret_key +} diff --git a/core/src/encryption/mod.rs b/core/src/encryption/mod.rs index f0fa278..40f5525 100644 --- a/core/src/encryption/mod.rs +++ b/core/src/encryption/mod.rs @@ -6,3 +6,5 @@ /// AES symmetric cipher pub mod aes; +/// ElGamal asymmetric encryption +pub mod elgamal; diff --git a/core/src/lib.rs b/core/src/lib.rs index a3fa08c..842b8d0 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -15,19 +15,24 @@ mod encryption; mod error; mod keys; mod note; +mod recipient; #[cfg(feature = "alloc")] mod transaction; +/// The number of output notes in a transaction +pub const OUTPUT_NOTES: usize = 2; + pub use addresses::stealth::StealthAddress; pub use addresses::sync::SyncAddress; -pub use encryption::aes; +pub use encryption::{aes, elgamal}; pub use error::Error; pub use keys::hash; pub use keys::public::PublicKey; pub use keys::secret::SecretKey; pub use keys::view::ViewKey; pub use note::{Note, NoteType, ENCRYPTION_SIZE as NOTE_ENCRYPTION_SIZE}; +pub use recipient::RecipientParameters; #[cfg(feature = "alloc")] /// Transaction Skeleton used by the phoenix transaction model diff --git a/core/src/recipient.rs b/core/src/recipient.rs new file mode 100644 index 0000000..376930c --- /dev/null +++ b/core/src/recipient.rs @@ -0,0 +1,122 @@ +// 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. + +#![allow(non_snake_case)] + +use dusk_bls12_381::BlsScalar; +use dusk_jubjub::{JubJubAffine, JubJubExtended, JubJubScalar}; +use ff::Field; +use jubjub_schnorr::{SecretKey as SchnorrSecretKey, Signature}; +use rand::{CryptoRng, RngCore}; + +use crate::{encryption::elgamal, PublicKey, SecretKey, OUTPUT_NOTES}; + +/// Parameters needed to prove a recipient in-circuit +#[derive(Debug, Clone, Copy)] +pub struct RecipientParameters { + /// Public key of the transaction sender + pub sender_pk: PublicKey, + /// Note public keys of each note recipient + pub output_npk: [JubJubAffine; OUTPUT_NOTES], + /// Signatures of 'payload_hash' verifiable using 'pk_A' and 'pk_B' + pub sig: [Signature; OUTPUT_NOTES], + /// Asymmetric encryption of 'pk_A' using both recipients 'npk' + pub enc_A: [(JubJubExtended, JubJubExtended); OUTPUT_NOTES], + /// Asymmetric encryption of 'pk_B' using both recipients 'npk' + pub enc_B: [(JubJubExtended, JubJubExtended); OUTPUT_NOTES], + /// Randomness needed to encrypt/decrypt 'pk_A' + pub r_A: [JubJubScalar; OUTPUT_NOTES], + /// Randomness needed to encrypt/decrypt 'pk_B' + pub r_B: [JubJubScalar; OUTPUT_NOTES], +} + +impl Default for RecipientParameters { + fn default() -> Self { + let sk = + SecretKey::new(JubJubScalar::default(), JubJubScalar::default()); + let sender_pk = PublicKey::from(&sk); + + Self { + sender_pk, + output_npk: [JubJubAffine::default(), JubJubAffine::default()], + sig: [Signature::default(), Signature::default()], + enc_A: [(JubJubExtended::default(), JubJubExtended::default()); + OUTPUT_NOTES], + enc_B: [(JubJubExtended::default(), JubJubExtended::default()); + OUTPUT_NOTES], + r_A: [JubJubScalar::default(); OUTPUT_NOTES], + r_B: [JubJubScalar::default(); OUTPUT_NOTES], + } + } +} + +impl RecipientParameters { + /// Create the recipient parameter + pub fn new( + rng: &mut (impl RngCore + CryptoRng), + sender_sk: &SecretKey, + output_npk: [JubJubAffine; OUTPUT_NOTES], + payload_hash: BlsScalar, + ) -> Self { + // Encrypt the public key of the sender. We need to encrypt + // both 'A' and 'B', using both tx output note public keys. + let sender_pk = PublicKey::from(sender_sk); + + let r_A = [ + JubJubScalar::random(&mut *rng), + JubJubScalar::random(&mut *rng), + ]; + let r_B = [ + JubJubScalar::random(&mut *rng), + JubJubScalar::random(&mut *rng), + ]; + + let (A_enc_1_c1, A_enc_1_c2) = elgamal::encrypt( + &output_npk[0].into(), // note_pk_1.as_ref(), + sender_pk.A(), + &r_A[0], + ); + + let (B_enc_1_c1, B_enc_1_c2) = elgamal::encrypt( + &output_npk[0].into(), // note_pk_1.as_ref(), + sender_pk.B(), + &r_B[0], + ); + let (A_enc_2_c1, A_enc_2_c2) = elgamal::encrypt( + &output_npk[1].into(), // note_pk_2.as_ref(), + sender_pk.A(), + &r_A[1], + ); + + let (B_enc_2_c1, B_enc_2_c2) = elgamal::encrypt( + &output_npk[1].into(), // note_pk_2.as_ref(), + sender_pk.B(), + &r_B[1], + ); + + let enc_A = [(A_enc_1_c1, A_enc_1_c2), (A_enc_2_c1, A_enc_2_c2)]; + let enc_B = [(B_enc_1_c1, B_enc_1_c2), (B_enc_2_c1, B_enc_2_c2)]; + + // Sign the payload hash using both 'a' and 'b' + let schnorr_sk_a = SchnorrSecretKey::from(sender_sk.a()); + let sig_A = schnorr_sk_a.sign(rng, payload_hash); + + let schnorr_sk_b = SchnorrSecretKey::from(sender_sk.b()); + let sig_B = schnorr_sk_b.sign(rng, payload_hash); + + let sig = [sig_A, sig_B]; + + RecipientParameters { + sender_pk, + output_npk, + sig, + enc_A, + enc_B, + r_A, + r_B, + } + } +} diff --git a/core/src/transaction.rs b/core/src/transaction.rs index 56e6e3b..5978fce 100644 --- a/core/src/transaction.rs +++ b/core/src/transaction.rs @@ -16,9 +16,7 @@ use rkyv::{Archive, Deserialize, Serialize}; use dusk_bls12_381::BlsScalar; use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable}; -use crate::Note; - -pub const OUTPUT_NOTES: usize = 2; +use crate::{Note, OUTPUT_NOTES}; /// A phoenix transaction, referred to as tx-skeleton in the specs. #[derive(Debug, Clone, PartialEq, Eq)]