Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add recipient gadget #198

Merged
merged 2 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions circuits/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add Recipient gadget [#197]

### Changed

- Remove `ViewKey` from `TxOutputNote::new()` parameters [#191]
- Make `rng` the first param in `TxInputNote::new` [#189]
- Rename `crossover` to `deposit` [#190]
- Remove recomputation of `value_commitment` in `TxOutputNote::New()`
- Rename `skeleton_hash` to `payload_hash` [#188]
- Make `TxCircuit` to use the Recipient gadget

## [0.1.0] - 2024-05-22

Expand All @@ -29,6 +36,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update `poseidon-merkle` to v0.6 [#179]

<!-- ISSUES -->
[#197]: https://github.com/dusk-network/phoenix/issues/197
[#188]: https://github.com/dusk-network/phoenix/issues/188
[#191]: https://github.com/dusk-network/phoenix/issues/191
[#190]: https://github.com/dusk-network/phoenix/issues/190
[#189]: https://github.com/dusk-network/phoenix/issues/189
Expand Down
6 changes: 5 additions & 1 deletion circuits/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@
#![no_std]

mod encryption;
mod recipient;

/// Transaction structs, gadget, and circuit
/// Transaction structs, and circuit
pub mod transaction;

/// Recipient Parameters
pub use recipient::RecipientParameters;

/// ElGamal asymmetric cipher
pub use encryption::elgamal;
114 changes: 114 additions & 0 deletions circuits/src/recipient.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// 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::JubJubScalar;
use dusk_plonk::prelude::*;
use jubjub_schnorr::{gadgets, Signature};

use crate::elgamal;
use phoenix_core::PublicKey;

use rand::rngs::StdRng;
use rand::SeedableRng;

use phoenix_core::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 recipient_npk_vec: [JubJubAffine; TX_OUTPUT_NOTES],
/// Signatures of 'payload_hash' verifiable using 'pk_A' and 'pk_B'
pub sig_vec: [Signature; TX_OUTPUT_NOTES],
/// Asymmetric encryption of 'pk_A' using both recipients 'npk'
pub enc_A_vec: [(JubJubExtended, JubJubExtended); TX_OUTPUT_NOTES],
/// Asymmetric encryption of 'pk_B' using both recipients 'npk'
pub enc_B_vec: [(JubJubExtended, JubJubExtended); TX_OUTPUT_NOTES],
/// Randomness needed to encrypt/decrypt 'pk_A'
pub r_A_vec: [JubJubScalar; TX_OUTPUT_NOTES],
/// Randomness needed to encrypt/decrypt 'pk_B'
pub r_B_vec: [JubJubScalar; TX_OUTPUT_NOTES],
}

impl Default for RecipientParameters {
fn default() -> Self {
let mut rng = StdRng::seed_from_u64(0xbeef);

let sk = SecretKey::random(&mut rng);
let sender_pk = PublicKey::from(&sk);

Self {
sender_pk,
recipient_npk_vec: [
JubJubAffine::default(),
JubJubAffine::default(),
],
sig_vec: [Signature::default(), Signature::default()],
enc_A_vec: [(JubJubExtended::default(), JubJubExtended::default());
TX_OUTPUT_NOTES],
enc_B_vec: [(JubJubExtended::default(), JubJubExtended::default());
TX_OUTPUT_NOTES],
r_A_vec: [JubJubScalar::default(); TX_OUTPUT_NOTES],
r_B_vec: [JubJubScalar::default(); TX_OUTPUT_NOTES],
}
}
}

/// Gadget to prove a valid origin for a given transaction.
pub(crate) fn gadget(
composer: &mut Composer,
rp: &RecipientParameters,
payload_hash: Witness,
) -> Result<(), Error> {
// VERIFY A SIGNATURE FOR EACH KEY 'A' AND 'B'
let pk_A = composer.append_point(rp.sender_pk.A());
let pk_B = composer.append_point(rp.sender_pk.B());

let sig_A_u = composer.append_witness(*rp.sig_vec[0].u());
let sig_A_R = composer.append_point(rp.sig_vec[0].R());

let sig_B_u = composer.append_witness(*rp.sig_vec[1].u());
let sig_B_R = composer.append_point(rp.sig_vec[1].R());

gadgets::verify_signature(composer, sig_A_u, sig_A_R, pk_A, payload_hash)?;
gadgets::verify_signature(composer, sig_B_u, sig_B_R, pk_B, payload_hash)?;

// ENCRYPT EACH KEY 'A' and 'B' USING EACH OUTPUT 'NPK'
let note_pk_1 = composer.append_public_point(rp.recipient_npk_vec[0]);
let note_pk_2 = composer.append_public_point(rp.recipient_npk_vec[1]);

let r_A_1 = composer.append_witness(rp.r_A_vec[0]);
let r_A_2 = composer.append_witness(rp.r_A_vec[1]);

let r_B_1 = composer.append_witness(rp.r_B_vec[0]);
let r_B_2 = composer.append_witness(rp.r_B_vec[1]);

let (enc_A_1_c1, enc_A_1_c2) =
elgamal::encrypt_gadget(composer, note_pk_1, pk_A, r_A_1)?;
let (enc_A_2_c1, enc_A_2_c2) =
elgamal::encrypt_gadget(composer, note_pk_2, pk_A, r_A_2)?;

let (enc_B_1_c1, enc_B_1_c2) =
elgamal::encrypt_gadget(composer, note_pk_1, pk_B, r_B_1)?;
let (enc_B_2_c1, enc_B_2_c2) =
elgamal::encrypt_gadget(composer, note_pk_2, pk_B, r_B_2)?;

composer.assert_equal_public_point(enc_A_1_c1, rp.enc_A_vec[0].0);
composer.assert_equal_public_point(enc_A_1_c2, rp.enc_A_vec[0].1);
composer.assert_equal_public_point(enc_A_2_c1, rp.enc_A_vec[1].0);
composer.assert_equal_public_point(enc_A_2_c2, rp.enc_A_vec[1].1);

composer.assert_equal_public_point(enc_B_1_c1, rp.enc_B_vec[0].0);
composer.assert_equal_public_point(enc_B_1_c2, rp.enc_B_vec[0].1);
composer.assert_equal_public_point(enc_B_2_c1, rp.enc_B_vec[1].0);
composer.assert_equal_public_point(enc_B_2_c2, rp.enc_B_vec[1].1);

Ok(())
}
67 changes: 45 additions & 22 deletions circuits/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Copyright (c) DUSK NETWORK. All rights reserved.

use dusk_jubjub::{
JubJubAffine, JubJubScalar, GENERATOR, GENERATOR_EXTENDED, GENERATOR_NUMS,
JubJubAffine, JubJubScalar, GENERATOR, GENERATOR_NUMS,
GENERATOR_NUMS_EXTENDED,
};
use dusk_plonk::prelude::*;
Expand All @@ -21,6 +21,8 @@ use alloc::vec::Vec;

use phoenix_core::{Error as PhoenixError, Note, Ownable, SecretKey, ViewKey};

use crate::{recipient, recipient::RecipientParameters};

const TX_OUTPUT_NOTES: usize = 2;

/// Struct representing a note willing to be spent, in a way
Expand Down Expand Up @@ -57,7 +59,7 @@ impl<const H: usize> TxInputNote<H> {
note: &Note,
merkle_opening: poseidon_merkle::Opening<(), H>,
sk: &SecretKey,
skeleton_hash: BlsScalar,
payload_hash: BlsScalar,
) -> Result<crate::transaction::TxInputNote<H>, PhoenixError> {
let note_sk = sk.gen_note_sk(note);
let note_pk_p =
Expand All @@ -72,7 +74,7 @@ impl<const H: usize> TxInputNote<H> {
&[note_pk_p.get_u(), note_pk_p.get_v(), (*note.pos()).into()],
)[0];

let signature = note_sk.sign_double(rng, skeleton_hash);
let signature = note_sk.sign_double(rng, payload_hash);

Ok(crate::transaction::TxInputNote {
merkle_opening,
Expand Down Expand Up @@ -139,12 +141,11 @@ struct WitnessTxOutputNote {

impl TxOutputNote {
/// Crate a new `TxOutputNote`.
pub fn new(value: u64, blinding_factor: JubJubScalar) -> Self {
let value_commitment = JubJubAffine::from(
(GENERATOR_EXTENDED * JubJubScalar::from(value))
+ (GENERATOR_NUMS_EXTENDED * blinding_factor),
);

pub fn new(
value: u64,
value_commitment: JubJubAffine,
blinding_factor: JubJubScalar,
) -> Self {
Self {
value,
value_commitment,
Expand Down Expand Up @@ -184,22 +185,20 @@ impl TxOutputNote {
/// deposit, where a deposit refers to funds being transfered to a contract.
///
/// The gadget appends the following public input values to the circuit:
/// - `skeleton_hash`
/// - `root`
/// - `[nullifier; I]`
/// - `[output_value_commitment; 2]`
/// - `max_fee`
/// - `deposit`
pub fn gadget<const H: usize, const I: usize>(
fn nullify_gadget<const H: usize, const I: usize>(
composer: &mut Composer,
skeleton_hash: &BlsScalar,
payload_hash: &Witness,
root: &BlsScalar,
tx_input_notes: &[TxInputNote<H>; I],
tx_output_notes: &[TxOutputNote; TX_OUTPUT_NOTES],
max_fee: u64,
deposit: u64,
) -> Result<(), Error> {
let skeleton_hash_pi = composer.append_public(*skeleton_hash);
let root_pi = composer.append_public(*root);

let mut tx_input_notes_sum = Composer::ZERO;
Expand All @@ -217,7 +216,7 @@ pub fn gadget<const H: usize, const I: usize>(
w_tx_input_note.signature_r_p,
w_tx_input_note.note_pk,
w_tx_input_note.note_pk_p,
skeleton_hash_pi,
*payload_hash,
)?;

// COMPUTE AND ASSERT THE NULLIFIER
Expand Down Expand Up @@ -329,10 +328,11 @@ pub fn gadget<const H: usize, const I: usize>(
pub struct TxCircuit<const H: usize, const I: usize> {
tx_input_notes: [TxInputNote<H>; I],
tx_output_notes: [TxOutputNote; TX_OUTPUT_NOTES],
skeleton_hash: BlsScalar,
payload_hash: BlsScalar,
root: BlsScalar,
deposit: u64,
max_fee: u64,
rp: RecipientParameters,
}

impl<const H: usize, const I: usize> Default for TxCircuit<H, I> {
Expand All @@ -342,7 +342,7 @@ impl<const H: usize, const I: usize> Default for TxCircuit<H, I> {
let sk = SecretKey::random(&mut rng);

let mut tree = Tree::<(), H>::new();
let skeleton_hash = BlsScalar::default();
let payload_hash = BlsScalar::default();

let mut tx_input_notes = Vec::new();
let note = Note::empty();
Expand All @@ -359,7 +359,7 @@ impl<const H: usize, const I: usize> Default for TxCircuit<H, I> {
&note,
merkle_opening,
&sk,
skeleton_hash,
payload_hash,
)
.expect("Note created properly.");

Expand All @@ -379,13 +379,16 @@ impl<const H: usize, const I: usize> Default for TxCircuit<H, I> {
let deposit = u64::default();
let max_fee = u64::default();

let rp = RecipientParameters::default();

Self {
tx_input_notes: tx_input_notes.try_into().unwrap(),
tx_output_notes,
skeleton_hash,
payload_hash,
root,
deposit,
max_fee,
rp,
}
}
}
Expand All @@ -395,33 +398,53 @@ impl<const H: usize, const I: usize> TxCircuit<H, I> {
pub fn new(
tx_input_notes: [TxInputNote<H>; I],
tx_output_notes: [TxOutputNote; TX_OUTPUT_NOTES],
skeleton_hash: BlsScalar,
payload_hash: BlsScalar,
root: BlsScalar,
deposit: u64,
max_fee: u64,
rp: RecipientParameters,
) -> Self {
Self {
tx_input_notes,
tx_output_notes,
skeleton_hash,
payload_hash,
root,
deposit,
max_fee,
rp,
}
}
}

impl<const H: usize, const I: usize> Circuit for TxCircuit<H, I> {
/// The circuit has the following public inputs:
/// - `payload_hash`
/// - `root`
/// - `[nullifier; I]`
/// - `[output_value_commitment; 2]`
/// - `max_fee`
/// - `deposit`
/// - `(npk_1, npk_2)`
/// - `(enc_A_npk_1, enc_A_npk_2)`
/// - `(enc_B_npk_1, enc_B_npk_2)`
fn circuit(&self, composer: &mut Composer) -> Result<(), Error> {
gadget::<H, I>(
// Make the payload hash a public input of the circuit
let payload_hash = composer.append_public(self.payload_hash);

// Nullify all the tx input notes
nullify_gadget::<H, I>(
composer,
&self.skeleton_hash,
&payload_hash,
&self.root,
&self.tx_input_notes,
&self.tx_output_notes,
self.max_fee,
self.deposit,
)?;

// Prove correctness of the sender keys encryption
recipient::gadget(composer, &self.rp, payload_hash)?;

Ok(())
}
}
Loading