From 9d79b4486a9d1668bb321588aa3506a7f482bc6e Mon Sep 17 00:00:00 2001 From: xevisalle Date: Mon, 18 Nov 2024 18:48:42 +0100 Subject: [PATCH 01/10] Restructure the code for easier apps integration --- CHANGELOG.md | 5 + Cargo.toml | 3 +- benches/citadel.rs | 184 ------------------------- benches/license_circuit.rs | 131 ++++++++++++++++++ src/error.rs | 28 ++++ src/gadgets.rs | 173 ++++++++++++++++++++--- src/lib.rs | 10 +- src/license.rs | 271 +------------------------------------ src/request.rs | 54 ++++++++ src/session.rs | 101 ++++++++++++++ tests/citadel.rs | 237 ++++++++++---------------------- 11 files changed, 562 insertions(+), 635 deletions(-) delete mode 100644 benches/citadel.rs create mode 100644 benches/license_circuit.rs create mode 100644 src/error.rs create mode 100644 src/request.rs create mode 100644 src/session.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f82858d..f994403 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Refactor the crate [#98] + ## [0.14.0] - 2024-08-14 ### Changed @@ -167,6 +171,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add workflows for the Github Actions +[#98]: https://github.com/dusk-network/citadel/issues/98 [#113]: https://github.com/dusk-network/citadel/issues/113 [#111]: https://github.com/dusk-network/citadel/issues/111 [#109]: https://github.com/dusk-network/citadel/issues/109 diff --git a/Cargo.toml b/Cargo.toml index 5e88c8f..6715b4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,10 +24,9 @@ bytecheck = { version = "0.6", default-features = false } [dev-dependencies] criterion = "0.5" -lazy_static = "1.4" [[bench]] -name = "citadel" +name = "license_circuit" harness = false [features] diff --git a/benches/citadel.rs b/benches/citadel.rs deleted file mode 100644 index 69fd646..0000000 --- a/benches/citadel.rs +++ /dev/null @@ -1,184 +0,0 @@ -// 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::{JubJubAffine, JubJubScalar, GENERATOR_EXTENDED}; -use dusk_plonk::prelude::*; -use dusk_poseidon::{Domain, Hash}; -use ff::Field; -use phoenix_core::{PublicKey, SecretKey}; -use poseidon_merkle::{Item, Opening, Tree}; - -use zk_citadel::gadgets; -use zk_citadel::license::{CitadelProverParameters, License, Request, SessionCookie}; - -use criterion::{criterion_group, criterion_main, Criterion}; -use rand_core::OsRng; - -static mut CONSTRAINTS_CITADEL: usize = 0; - -static LABEL: &[u8; 12] = b"dusk-network"; - -const CAPACITY: usize = 15; // capacity required for the setup -const DEPTH: usize = 17; // depth of the n-ary Merkle tree - -// Example values -const ATTRIBUTE_DATA: u64 = 112233445566778899u64; -const CHALLENGE: u64 = 20221126u64; - -#[macro_use] -extern crate lazy_static; - -fn compute_random_license( - rng: &mut OsRng, - sk: &SecretKey, - sk_lp: &SecretKey, - pk_lp: &PublicKey, -) -> (License, Opening<(), DEPTH>) { - let pk = PublicKey::from(sk); - - // First, the user computes these values and requests a License - let lsa = pk.gen_stealth_address(&JubJubScalar::random(&mut *rng)); - let lsk = sk.gen_note_sk(&lsa); - let k_lic = JubJubAffine::from( - GENERATOR_EXTENDED * Hash::digest_truncated(Domain::Other, &[(*lsk.as_ref()).into()])[0], - ); - let req = Request::new(pk_lp, &lsa, &k_lic, rng).expect("Request correctly computed."); - - // Second, the LP computes these values and grants the License - let attr_data = JubJubScalar::from(ATTRIBUTE_DATA); - let lic = License::new(&attr_data, sk_lp, &req, rng).expect("License correctly computed."); - - let mut tree = Tree::<(), DEPTH>::new(); - let lpk = JubJubAffine::from(lic.lsa.note_pk().as_ref()); - - let item = Item { - hash: Hash::digest(Domain::Other, &[lpk.get_u(), lpk.get_v()])[0], - data: (), - }; - - let pos = 0; - tree.insert(pos, item); - - let merkle_proof = tree.opening(pos).expect("Tree was read successfully"); - - (lic, merkle_proof) -} - -fn compute_citadel_parameters( - rng: &mut OsRng, - sk: &SecretKey, - pk_lp: &PublicKey, - lic: &License, - merkle_proof: Opening<(), DEPTH>, -) -> (CitadelProverParameters, SessionCookie) { - let c = JubJubScalar::from(CHALLENGE); - let (cpp, sc) = - CitadelProverParameters::compute_parameters(sk, lic, pk_lp, pk_lp, &c, rng, merkle_proof) - .expect("Parameters correctly computed."); - (cpp, sc) -} - -struct Keys { - sk: SecretKey, - - sk_lp: SecretKey, - pk_lp: PublicKey, - - citadel_prover: Prover, - citadel_verifier: Verifier, -} - -lazy_static! { - static ref TEST_KEYS: Keys = { - // These are the keys of the user - let sk = SecretKey::random(&mut OsRng); - - // These are the keys of the LP - let sk_lp = SecretKey::random(&mut OsRng); - let pk_lp = PublicKey::from(&sk_lp); - - // Now we generate the ProverKey and VerifierKey for Citadel - let pp = PublicParameters::setup(1 << CAPACITY, &mut OsRng).unwrap(); - - let (citadel_prover, citadel_verifier) = - Compiler::compile::(&pp, LABEL).expect("failed to compile circuit"); - - Keys { sk, sk_lp, pk_lp, citadel_prover, citadel_verifier } - }; -} - -#[derive(Default, Debug)] -pub struct Citadel { - cpp: CitadelProverParameters, - sc: SessionCookie, -} - -impl Citadel { - pub fn new(cpp: &CitadelProverParameters, sc: &SessionCookie) -> Self { - Self { cpp: *cpp, sc: *sc } - } -} - -impl Circuit for Citadel { - fn circuit(&self, composer: &mut Composer) -> Result<(), Error> { - gadgets::use_license_citadel(composer, &self.cpp, &self.sc)?; - unsafe { - CONSTRAINTS_CITADEL = composer.constraints(); - } - Ok(()) - } -} - -fn citadel_benchmark(crit: &mut Criterion) { - let (lic, merkle_proof) = compute_random_license( - &mut OsRng, - &TEST_KEYS.sk, - &TEST_KEYS.sk_lp, - &TEST_KEYS.pk_lp, - ); - - let (cpp, sc) = compute_citadel_parameters( - &mut OsRng, - &TEST_KEYS.sk, - &TEST_KEYS.pk_lp, - &lic, - merkle_proof, - ); - - unsafe { - let log = &format!("Citadel Prover ({} constraints)", CONSTRAINTS_CITADEL); - crit.bench_function(log, |b| { - b.iter(|| { - TEST_KEYS - .citadel_prover - .prove(&mut OsRng, &Citadel::new(&cpp, &sc)) - .expect("failed to prove") - }) - }); - - // Benchmark the verifier - let (proof, public_inputs) = TEST_KEYS - .citadel_prover - .prove(&mut OsRng, &Citadel::new(&cpp, &sc)) - .expect("failed to prove"); - let log = &format!("Citadel Verifier ({} constraints)", CONSTRAINTS_CITADEL); - crit.bench_function(log, |b| { - b.iter(|| { - TEST_KEYS - .citadel_verifier - .verify(&proof, &public_inputs) - .expect("failed to verify proof") - }) - }); - } -} - -criterion_group! { - name = citadel; - config = Criterion::default().sample_size(10); - targets = citadel_benchmark -} -criterion_main!(citadel); diff --git a/benches/license_circuit.rs b/benches/license_circuit.rs new file mode 100644 index 0000000..9c3f458 --- /dev/null +++ b/benches/license_circuit.rs @@ -0,0 +1,131 @@ +// 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::{JubJubAffine, JubJubScalar, GENERATOR_EXTENDED}; +use dusk_plonk::prelude::*; +use dusk_poseidon::{Domain, Hash}; +use ff::Field; +use phoenix_core::{PublicKey, SecretKey}; +use poseidon_merkle::{Item, Tree}; + +use zk_citadel::{gadgets, License, Request, SessionCookie}; + +use criterion::{criterion_group, criterion_main, Criterion}; +use rand_core::OsRng; + +static mut CONSTRAINTS: usize = 0; + +static LABEL: &[u8; 12] = b"dusk-network"; + +const CAPACITY: usize = 15; // capacity required for the setup +const DEPTH: usize = 16; // depth of the n-ary Merkle tree + +// Example values +const ATTRIBUTE_DATA: u64 = 112233445566778899u64; +const CHALLENGE: u64 = 20221126u64; + +#[derive(Default, Debug)] +pub struct LicenseCircuit { + gp: gadgets::GadgetParameters, + sc: SessionCookie, +} + +impl LicenseCircuit { + pub fn new(gp: &gadgets::GadgetParameters, sc: &SessionCookie) -> Self { + Self { gp: *gp, sc: *sc } + } +} + +impl Circuit for LicenseCircuit { + fn circuit(&self, composer: &mut Composer) -> Result<(), Error> { + gadgets::use_license(composer, &self.gp, &self.sc)?; + unsafe { + CONSTRAINTS = composer.constraints(); + } + Ok(()) + } +} + +fn license_circuit_benchmark(crit: &mut Criterion) { + // Compute gadget parameters + let sk = SecretKey::random(&mut OsRng); + let pk = PublicKey::from(&sk); + let sk_lp = SecretKey::random(&mut OsRng); + let pk_lp = PublicKey::from(&sk_lp); + let pp = PublicParameters::setup(1 << CAPACITY, &mut OsRng).unwrap(); + let (prover, verifier) = + Compiler::compile::(&pp, LABEL).expect("failed to compile circuit"); + + let lsa = pk.gen_stealth_address(&JubJubScalar::random(&mut OsRng)); + let lsk = sk.gen_note_sk(&lsa); + let k_lic = JubJubAffine::from( + GENERATOR_EXTENDED * Hash::digest_truncated(Domain::Other, &[(*lsk.as_ref()).into()])[0], + ); + let req = Request::new(&pk_lp, &lsa, &k_lic, &mut OsRng).expect("Request correctly computed."); + + let attr_data = JubJubScalar::from(ATTRIBUTE_DATA); + let lic = + License::new(&attr_data, &sk_lp, &req, &mut OsRng).expect("License correctly computed."); + + let mut tree = Tree::<(), DEPTH>::new(); + let lpk = JubJubAffine::from(lic.lsa.note_pk().as_ref()); + + let item = Item { + hash: Hash::digest(Domain::Other, &[lpk.get_u(), lpk.get_v()])[0], + data: (), + }; + + let pos = 0; + tree.insert(pos, item); + + let merkle_proof = tree.opening(pos).expect("Tree was read successfully"); + + let c = JubJubScalar::from(CHALLENGE); + let pk_sp = pk_lp; // LP = SP + let (gp, sc) = gadgets::GadgetParameters::compute_parameters( + &sk, + &lic, + &pk_lp, + &pk_sp, + &c, + &mut OsRng, + merkle_proof, + ) + .expect("Parameters correctly computed."); + + // Perform the actual benchmarks + unsafe { + // Benchmark the prover + let log = &format!("License Circuit Prover ({} constraints)", CONSTRAINTS); + crit.bench_function(log, |b| { + b.iter(|| { + prover + .prove(&mut OsRng, &LicenseCircuit::new(&gp, &sc)) + .expect("failed to prove") + }) + }); + + // Benchmark the verifier + let (proof, public_inputs) = prover + .prove(&mut OsRng, &LicenseCircuit::new(&gp, &sc)) + .expect("failed to prove"); + let log = &format!("License Circuit Verifier ({} constraints)", CONSTRAINTS); + crit.bench_function(log, |b| { + b.iter(|| { + verifier + .verify(&proof, &public_inputs) + .expect("failed to verify proof") + }) + }); + } +} + +criterion_group! { + name = license_circuit; + config = Criterion::default().sample_size(10); + targets = license_circuit_benchmark +} +criterion_main!(license_circuit); diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..1148368 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,28 @@ +// 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 core::fmt; + +/// All possible errors for Citadel +#[allow(missing_docs)] +#[allow(clippy::enum_variant_names)] +#[derive(Debug, Clone)] +pub enum Error { + /// The commitment to the public key of the License Provider is incorrect + WrongLicenseProviderComm, + /// The commitment to the attribute data is incorrect + WrongAttributeDataComm, + /// The commitment to the challenge is incorrect + WrongChallengeComm, + /// The result of the session hash is incorrect + WrongSessionHash, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Citadel Error: {:?}", &self) + } +} diff --git a/src/gadgets.rs b/src/gadgets.rs index f1c67fc..d25faac 100644 --- a/src/gadgets.rs +++ b/src/gadgets.rs @@ -4,14 +4,25 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. +use dusk_bytes::Serializable; use dusk_jubjub::{GENERATOR, GENERATOR_NUMS}; +use dusk_jubjub::{GENERATOR_EXTENDED, GENERATOR_NUMS_EXTENDED}; use dusk_plonk::prelude::*; +use dusk_poseidon::Hash; use dusk_poseidon::{Domain, HashGadget}; +use ff::Field; use jubjub_schnorr::gadgets; +use jubjub_schnorr::{Signature, SignatureDouble}; +use phoenix_core::{aes::decrypt, PublicKey, SecretKey}; +use poseidon_merkle::{Item, Opening, Tree}; +use rand_core::{CryptoRng, RngCore}; -use poseidon_merkle::zk::opening_gadget; +#[cfg(feature = "rkyv-impl")] +use rkyv::{Archive, Deserialize, Serialize}; + +use crate::{license::LIC_PLAINTEXT_SIZE, License, SessionCookie}; -use crate::license::{CitadelProverParameters, SessionCookie}; +use poseidon_merkle::zk::opening_gadget; // out of this circuit, the generated public inputs vector collects // these values in that particular order: @@ -25,14 +36,14 @@ use crate::license::{CitadelProverParameters, SessionCookie}; // public_inputs[6]: com_2.y // public_inputs[7]: root -pub fn use_license_citadel( +pub fn use_license( composer: &mut Composer, - cpp: &CitadelProverParameters, + gp: &GadgetParameters, sc: &SessionCookie, ) -> Result<(), Error> { // APPEND THE LICENSE PUBLIC KEYS OF THE USER - let lpk = composer.append_point(cpp.lpk); - let lpk_p = composer.append_point(cpp.lpk_p); + let lpk = composer.append_point(gp.lpk); + let lpk_p = composer.append_point(gp.lpk_p); // COMPUTE THE SESSION ID let c = composer.append_witness(sc.c); @@ -42,8 +53,8 @@ pub fn use_license_citadel( composer.assert_equal(session_id[0], session_id_pi); // VERIFY THE LICENSE SIGNATURE - let sig_lic_u = composer.append_witness(*cpp.sig_lic.u()); - let sig_lic_r = composer.append_point(cpp.sig_lic.R()); + let sig_lic_u = composer.append_witness(*gp.sig_lic.u()); + let sig_lic_r = composer.append_point(gp.sig_lic.R()); let pk_lp = composer.append_point(sc.pk_lp); let attr_data = composer.append_witness(sc.attr_data); @@ -51,10 +62,10 @@ pub fn use_license_citadel( gadgets::verify_signature(composer, sig_lic_u, sig_lic_r, pk_lp, message[0])?; // VERIFY THE SESSION HASH SIGNATURE - let sig_session_hash_u = composer.append_witness(*cpp.sig_session_hash.u()); - let sig_session_hash_r = composer.append_point(cpp.sig_session_hash.R()); - let sig_session_hash_r_p = composer.append_point(cpp.sig_session_hash.R_prime()); - let session_hash = composer.append_public(cpp.session_hash); + let sig_session_hash_u = composer.append_witness(*gp.sig_session_hash.u()); + let sig_session_hash_r = composer.append_point(gp.sig_session_hash.R()); + let sig_session_hash_r_p = composer.append_point(gp.sig_session_hash.R_prime()); + let session_hash = composer.append_public(gp.session_hash); gadgets::verify_signature_double( composer, @@ -68,7 +79,7 @@ pub fn use_license_citadel( // COMMIT TO THE PK_LP USING A HASH FUNCTION let s_0 = composer.append_witness(sc.s_0); - let com_0_pi = composer.append_public(cpp.com_0); + let com_0_pi = composer.append_public(gp.com_0); let com_0 = HashGadget::digest(composer, Domain::Other, &[*pk_lp.x(), *pk_lp.y(), s_0]); composer.assert_equal(com_0[0], com_0_pi); @@ -79,7 +90,7 @@ pub fn use_license_citadel( let pc_1_2 = composer.component_mul_generator(s_1, GENERATOR_NUMS); let com_1 = composer.component_add_point(pc_1_1.unwrap(), pc_1_2.unwrap()); - composer.assert_equal_public_point(com_1, cpp.com_1); + composer.assert_equal_public_point(com_1, gp.com_1); // COMMIT TO THE CHALLENGE let s_2 = composer.append_witness(sc.s_2); @@ -87,15 +98,143 @@ pub fn use_license_citadel( let pc_2_2 = composer.component_mul_generator(s_2, GENERATOR_NUMS); let com_2 = composer.component_add_point(pc_2_1.unwrap(), pc_2_2.unwrap()); - composer.assert_equal_public_point(com_2, cpp.com_2); + composer.assert_equal_public_point(com_2, gp.com_2); // COMPUTE THE HASH OF THE LICENSE let license_hash = HashGadget::digest(composer, Domain::Other, &[*lpk.x(), *lpk.y()]); // VERIFY THE MERKLE PROOF - let root_pi = composer.append_public(cpp.merkle_proof.root().hash); - let root = opening_gadget(composer, &cpp.merkle_proof, license_hash[0]); + let root_pi = composer.append_public(gp.merkle_proof.root().hash); + let root = opening_gadget(composer, &gp.merkle_proof, license_hash[0]); composer.assert_equal(root, root_pi); Ok(()) } + +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +#[derive(Debug, Clone, Copy)] +pub struct GadgetParameters { + pub lpk: JubJubAffine, // license public key + pub lpk_p: JubJubAffine, // license public key prime + pub sig_lic: Signature, // signature of the license + + pub com_0: BlsScalar, // Hash commitment 0 + pub com_1: JubJubExtended, // Pedersen Commitment 1 + pub com_2: JubJubExtended, // Pedersen Commitment 2 + + pub session_hash: BlsScalar, // hash of the session + pub sig_session_hash: SignatureDouble, // signature of the session_hash + pub merkle_proof: Opening<(), DEPTH>, // Merkle proof for the Proof of Validity +} + +impl Default for GadgetParameters { + fn default() -> Self { + let mut tree = Tree::new(); + let item = Item { + hash: BlsScalar::zero(), + data: (), + }; + tree.insert(0, item); + let merkle_proof = tree.opening(0).expect("There is a leaf at position 0"); + Self { + lpk: JubJubAffine::default(), + lpk_p: JubJubAffine::default(), + sig_lic: Signature::default(), + + com_0: BlsScalar::default(), + com_1: JubJubExtended::default(), + com_2: JubJubExtended::default(), + + session_hash: BlsScalar::default(), + sig_session_hash: SignatureDouble::default(), + merkle_proof, + } + } +} + +impl GadgetParameters { + #[allow(clippy::too_many_arguments)] + pub fn compute_parameters( + sk: &SecretKey, + lic: &License, + pk_lp: &PublicKey, + pk_sp: &PublicKey, + c: &JubJubScalar, + mut rng: &mut R, + merkle_proof: Opening<(), DEPTH>, + ) -> Result<(Self, SessionCookie), phoenix_core::Error> { + let lsk = sk.gen_note_sk(&lic.lsa); + let k_lic = JubJubAffine::from( + GENERATOR_EXTENDED + * Hash::digest_truncated(Domain::Other, &[(*lsk.as_ref()).into()])[0], + ); + + let dec: [u8; LIC_PLAINTEXT_SIZE] = decrypt(&k_lic, &lic.enc)?; + + let mut sig_lic_bytes = [0u8; Signature::SIZE]; + sig_lic_bytes.copy_from_slice(&dec[..Signature::SIZE]); + let sig_lic = Signature::from_bytes(&sig_lic_bytes).expect("Deserialization was correct."); + + let mut attr_data_bytes = [0u8; JubJubScalar::SIZE]; + attr_data_bytes.copy_from_slice(&dec[Signature::SIZE..]); + let attr_data = + JubJubScalar::from_bytes(&attr_data_bytes).expect("Deserialization was correct."); + + let lpk = JubJubAffine::from(*lic.lsa.note_pk().as_ref()); + + let lsk = sk.gen_note_sk(&lic.lsa); + let lpk_p = JubJubAffine::from(GENERATOR_NUMS_EXTENDED * lsk.as_ref()); + + let s_0 = BlsScalar::random(&mut rng); + let s_1 = JubJubScalar::random(&mut rng); + let s_2 = JubJubScalar::random(&mut rng); + + let pk_sp = JubJubAffine::from(*pk_sp.A()); + let r = BlsScalar::random(&mut rng); + + let session_hash = Hash::digest(Domain::Other, &[pk_sp.get_u(), pk_sp.get_v(), r])[0]; + let sig_session_hash = lsk.sign_double(rng, session_hash); + + let mut session_id = Hash::new(Domain::Other); + let binding = &[lpk_p.get_u(), lpk_p.get_v(), BlsScalar::from(*c)]; + session_id.update(binding); + let session_id = session_id.finalize()[0]; + + let pk_lp = JubJubAffine::from(*pk_lp.A()); + + let com_0 = Hash::digest(Domain::Other, &[pk_lp.get_u(), pk_lp.get_v(), s_0])[0]; + let com_1 = (GENERATOR_EXTENDED * attr_data) + (GENERATOR_NUMS_EXTENDED * s_1); + let com_2 = (GENERATOR_EXTENDED * c) + (GENERATOR_NUMS_EXTENDED * s_2); + + Ok(( + Self { + lpk, + lpk_p, + sig_lic, + + com_0, + com_1, + com_2, + + session_hash, + sig_session_hash, + merkle_proof, + }, + SessionCookie { + pk_sp, + r, + session_id, + pk_lp, + attr_data, + c: *c, + s_0, + s_1, + s_2, + }, + )) + } +} diff --git a/src/lib.rs b/src/lib.rs index f74eea9..6b6d1c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,5 +4,13 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. +mod error; +mod license; +mod request; +mod session; + pub mod gadgets; -pub mod license; + +pub use license::License; +pub use request::Request; +pub use session::{Session, SessionCookie}; diff --git a/src/license.rs b/src/license.rs index bb9d978..ad4270b 100644 --- a/src/license.rs +++ b/src/license.rs @@ -5,15 +5,14 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use dusk_bytes::Serializable; -use dusk_jubjub::{dhke, GENERATOR_EXTENDED, GENERATOR_NUMS_EXTENDED}; +use dusk_jubjub::dhke; use dusk_poseidon::{Domain, Hash}; -use ff::Field; -use jubjub_schnorr::{SecretKey as NoteSecretKey, Signature, SignatureDouble}; +use jubjub_schnorr::{SecretKey as NoteSecretKey, Signature}; use phoenix_core::{ aes::{decrypt, encrypt, ENCRYPTION_EXTRA_SIZE}, - Error, PublicKey, SecretKey, StealthAddress, + Error, SecretKey, StealthAddress, }; -use poseidon_merkle::{Item, Opening, Tree}; + use rand_core::{CryptoRng, RngCore}; #[cfg(feature = "rkyv-impl")] @@ -21,141 +20,11 @@ use rkyv::{Archive, Deserialize, Serialize}; use dusk_plonk::prelude::*; -const REQ_PLAINTEXT_SIZE: usize = StealthAddress::SIZE + JubJubAffine::SIZE; -const REQ_ENCRYPTION_SIZE: usize = REQ_PLAINTEXT_SIZE + ENCRYPTION_EXTRA_SIZE; +use crate::request::{Request, REQ_PLAINTEXT_SIZE}; -const LIC_PLAINTEXT_SIZE: usize = Signature::SIZE + JubJubScalar::SIZE; +pub(crate) const LIC_PLAINTEXT_SIZE: usize = Signature::SIZE + JubJubScalar::SIZE; const LIC_ENCRYPTION_SIZE: usize = LIC_PLAINTEXT_SIZE + ENCRYPTION_EXTRA_SIZE; -#[cfg_attr( - feature = "rkyv-impl", - derive(Archive, Serialize, Deserialize), - archive_attr(derive(bytecheck::CheckBytes)) -)] -#[derive(Debug)] -pub struct Request { - pub rsa: StealthAddress, // request stealth address - pub enc: [u8; REQ_ENCRYPTION_SIZE], // encryption of the license stealth address and k_lic -} - -impl Request { - pub fn new( - pk_lp: &PublicKey, - lsa: &StealthAddress, - k_lic: &JubJubAffine, - rng: &mut R, - ) -> Result { - let r_dh = JubJubScalar::random(&mut *rng); - let rsa = pk_lp.gen_stealth_address(&r_dh); - let k_dh = dhke(&r_dh, pk_lp.A()); - - let mut plaintext = lsa.to_bytes().to_vec(); - plaintext.append(&mut k_lic.to_bytes().to_vec()); - - let enc = encrypt(&k_dh, &plaintext, rng)?; - - Ok(Self { rsa, enc }) - } -} - -#[cfg_attr( - feature = "rkyv-impl", - derive(Archive, Serialize, Deserialize), - archive_attr(derive(bytecheck::CheckBytes)) -)] -#[derive(Debug)] -pub struct Session { - pub session_hash: BlsScalar, - pub session_id: BlsScalar, - - pub com_0: BlsScalar, // Hash commitment 0 - pub com_1: JubJubExtended, // Pedersen Commitment 1 - pub com_2: JubJubExtended, // Pedersen Commitment 2 -} - -impl Session { - pub fn from(public_inputs: &[BlsScalar]) -> Self { - // public inputs are in negated form, we negate them again to assert correctly - let session_id = public_inputs[0]; - let session_hash = public_inputs[1]; - - let com_0 = public_inputs[2]; - let com_1 = JubJubExtended::from(JubJubAffine::from_raw_unchecked( - public_inputs[3], - public_inputs[4], - )); - let com_2 = JubJubExtended::from(JubJubAffine::from_raw_unchecked( - public_inputs[5], - public_inputs[6], - )); - - Self { - session_hash, - session_id, - - com_0, - com_1, - com_2, - } - } - - pub fn verify(&self, sc: SessionCookie, pk_lp: JubJubAffine, pk_sp: JubJubAffine) { - assert!(self.verifies_ok(sc, pk_lp, pk_sp)); - } - - pub fn verifies_ok(&self, sc: SessionCookie, pk_lp: JubJubAffine, pk_sp: JubJubAffine) -> bool { - if pk_lp != sc.pk_lp { - return false; - } - - if pk_sp != sc.pk_sp { - return false; - } - - let session_hash = - Hash::digest(Domain::Other, &[sc.pk_sp.get_u(), sc.pk_sp.get_v(), sc.r])[0]; - if session_hash != self.session_hash { - return false; - } - - let com_0 = Hash::digest(Domain::Other, &[pk_lp.get_u(), pk_lp.get_v(), sc.s_0])[0]; - if com_0 != self.com_0 { - return false; - } - - let com_1 = (GENERATOR_EXTENDED * sc.attr_data) + (GENERATOR_NUMS_EXTENDED * sc.s_1); - if com_1 != self.com_1 { - return false; - } - - let com_2 = (GENERATOR_EXTENDED * sc.c) + (GENERATOR_NUMS_EXTENDED * sc.s_2); - if com_2 != self.com_2 { - return false; - } - true - } -} - -#[cfg_attr( - feature = "rkyv-impl", - derive(Archive, Serialize, Deserialize), - archive_attr(derive(bytecheck::CheckBytes)) -)] -#[derive(Default, Debug, Clone, Copy)] -pub struct SessionCookie { - pub pk_sp: JubJubAffine, // public key of the SP - pub r: BlsScalar, // randomness for session_hash - pub session_id: BlsScalar, - - pub pk_lp: JubJubAffine, // public key of the LP - pub attr_data: JubJubScalar, // attribute data of the license - pub c: JubJubScalar, // challenge value - - pub s_0: BlsScalar, // randomness for com_0 - pub s_1: JubJubScalar, // randomness for com_1 - pub s_2: JubJubScalar, // randomness for com_2 -} - #[cfg_attr( feature = "rkyv-impl", derive(Archive, Serialize, Deserialize), @@ -203,131 +72,3 @@ impl License { Ok(Self { lsa, enc }) } } - -#[cfg_attr( - feature = "rkyv-impl", - derive(Archive, Serialize, Deserialize), - archive_attr(derive(bytecheck::CheckBytes)) -)] -#[derive(Debug, Clone, Copy)] -pub struct CitadelProverParameters { - pub lpk: JubJubAffine, // license public key - pub lpk_p: JubJubAffine, // license public key prime - pub sig_lic: Signature, // signature of the license - - pub com_0: BlsScalar, // Hash commitment 0 - pub com_1: JubJubExtended, // Pedersen Commitment 1 - pub com_2: JubJubExtended, // Pedersen Commitment 2 - - pub session_hash: BlsScalar, // hash of the session - pub sig_session_hash: SignatureDouble, // signature of the session_hash - pub merkle_proof: Opening<(), DEPTH>, // Merkle proof for the Proof of Validity -} - -impl Default for CitadelProverParameters { - fn default() -> Self { - let mut tree = Tree::new(); - let item = Item { - hash: BlsScalar::zero(), - data: (), - }; - tree.insert(0, item); - let merkle_proof = tree.opening(0).expect("There is a leaf at position 0"); - Self { - lpk: JubJubAffine::default(), - lpk_p: JubJubAffine::default(), - sig_lic: Signature::default(), - - com_0: BlsScalar::default(), - com_1: JubJubExtended::default(), - com_2: JubJubExtended::default(), - - session_hash: BlsScalar::default(), - sig_session_hash: SignatureDouble::default(), - merkle_proof, - } - } -} - -impl CitadelProverParameters { - #[allow(clippy::too_many_arguments)] - pub fn compute_parameters( - sk: &SecretKey, - lic: &License, - pk_lp: &PublicKey, - pk_sp: &PublicKey, - c: &JubJubScalar, - mut rng: &mut R, - merkle_proof: Opening<(), DEPTH>, - ) -> Result<(Self, SessionCookie), Error> { - let lsk = sk.gen_note_sk(&lic.lsa); - let k_lic = JubJubAffine::from( - GENERATOR_EXTENDED - * Hash::digest_truncated(Domain::Other, &[(*lsk.as_ref()).into()])[0], - ); - - let dec: [u8; LIC_PLAINTEXT_SIZE] = decrypt(&k_lic, &lic.enc)?; - - let mut sig_lic_bytes = [0u8; Signature::SIZE]; - sig_lic_bytes.copy_from_slice(&dec[..Signature::SIZE]); - let sig_lic = Signature::from_bytes(&sig_lic_bytes).expect("Deserialization was correct."); - - let mut attr_data_bytes = [0u8; JubJubScalar::SIZE]; - attr_data_bytes.copy_from_slice(&dec[Signature::SIZE..]); - let attr_data = - JubJubScalar::from_bytes(&attr_data_bytes).expect("Deserialization was correct."); - - let lpk = JubJubAffine::from(*lic.lsa.note_pk().as_ref()); - - let lsk = sk.gen_note_sk(&lic.lsa); - let lpk_p = JubJubAffine::from(GENERATOR_NUMS_EXTENDED * lsk.as_ref()); - - let s_0 = BlsScalar::random(&mut rng); - let s_1 = JubJubScalar::random(&mut rng); - let s_2 = JubJubScalar::random(&mut rng); - - let pk_sp = JubJubAffine::from(*pk_sp.A()); - let r = BlsScalar::random(&mut rng); - - let session_hash = Hash::digest(Domain::Other, &[pk_sp.get_u(), pk_sp.get_v(), r])[0]; - let sig_session_hash = lsk.sign_double(rng, session_hash); - - let mut session_id = Hash::new(Domain::Other); - let binding = &[lpk_p.get_u(), lpk_p.get_v(), BlsScalar::from(*c)]; - session_id.update(binding); - let session_id = session_id.finalize()[0]; - - let pk_lp = JubJubAffine::from(*pk_lp.A()); - - let com_0 = Hash::digest(Domain::Other, &[pk_lp.get_u(), pk_lp.get_v(), s_0])[0]; - let com_1 = (GENERATOR_EXTENDED * attr_data) + (GENERATOR_NUMS_EXTENDED * s_1); - let com_2 = (GENERATOR_EXTENDED * c) + (GENERATOR_NUMS_EXTENDED * s_2); - - Ok(( - Self { - lpk, - lpk_p, - sig_lic, - - com_0, - com_1, - com_2, - - session_hash, - sig_session_hash, - merkle_proof, - }, - SessionCookie { - pk_sp, - r, - session_id, - pk_lp, - attr_data, - c: *c, - s_0, - s_1, - s_2, - }, - )) - } -} diff --git a/src/request.rs b/src/request.rs new file mode 100644 index 0000000..f57aee7 --- /dev/null +++ b/src/request.rs @@ -0,0 +1,54 @@ +// 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_bytes::Serializable; +use dusk_jubjub::dhke; +use ff::Field; +use phoenix_core::{ + aes::{encrypt, ENCRYPTION_EXTRA_SIZE}, + Error, PublicKey, StealthAddress, +}; + +use rand_core::{CryptoRng, RngCore}; + +#[cfg(feature = "rkyv-impl")] +use rkyv::{Archive, Deserialize, Serialize}; + +use dusk_plonk::prelude::*; + +pub(crate) const REQ_PLAINTEXT_SIZE: usize = StealthAddress::SIZE + JubJubAffine::SIZE; +const REQ_ENCRYPTION_SIZE: usize = REQ_PLAINTEXT_SIZE + ENCRYPTION_EXTRA_SIZE; + +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +#[derive(Debug)] +pub struct Request { + pub rsa: StealthAddress, // request stealth address + pub enc: [u8; REQ_ENCRYPTION_SIZE], // encryption of the license stealth address and k_lic +} + +impl Request { + pub fn new( + pk_lp: &PublicKey, + lsa: &StealthAddress, + k_lic: &JubJubAffine, + rng: &mut R, + ) -> Result { + let r_dh = JubJubScalar::random(&mut *rng); + let rsa = pk_lp.gen_stealth_address(&r_dh); + let k_dh = dhke(&r_dh, pk_lp.A()); + + let mut plaintext = lsa.to_bytes().to_vec(); + plaintext.append(&mut k_lic.to_bytes().to_vec()); + + let enc = encrypt(&k_dh, &plaintext, rng)?; + + Ok(Self { rsa, enc }) + } +} diff --git a/src/session.rs b/src/session.rs new file mode 100644 index 0000000..82431ca --- /dev/null +++ b/src/session.rs @@ -0,0 +1,101 @@ +// 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::{GENERATOR_EXTENDED, GENERATOR_NUMS_EXTENDED}; +use dusk_poseidon::{Domain, Hash}; + +use crate::error::Error; + +#[cfg(feature = "rkyv-impl")] +use rkyv::{Archive, Deserialize, Serialize}; + +use dusk_plonk::prelude::*; + +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +#[derive(Debug)] +pub struct Session { + pub session_hash: BlsScalar, + pub session_id: BlsScalar, + + pub com_0: BlsScalar, // Hash commitment 0 + pub com_1: JubJubExtended, // Pedersen Commitment 1 + pub com_2: JubJubExtended, // Pedersen Commitment 2 +} + +impl Session { + pub fn from(public_inputs: &[BlsScalar]) -> Self { + let session_id = public_inputs[0]; + let session_hash = public_inputs[1]; + + let com_0 = public_inputs[2]; + let com_1 = JubJubExtended::from(JubJubAffine::from_raw_unchecked( + public_inputs[3], + public_inputs[4], + )); + let com_2 = JubJubExtended::from(JubJubAffine::from_raw_unchecked( + public_inputs[5], + public_inputs[6], + )); + + Self { + session_hash, + session_id, + + com_0, + com_1, + com_2, + } + } + + pub fn verify(&self, sc: SessionCookie) -> Result<(), Error> { + let session_hash = + Hash::digest(Domain::Other, &[sc.pk_sp.get_u(), sc.pk_sp.get_v(), sc.r])[0]; + if session_hash != self.session_hash { + return Err(Error::WrongSessionHash); + } + + let com_0 = Hash::digest(Domain::Other, &[sc.pk_lp.get_u(), sc.pk_lp.get_v(), sc.s_0])[0]; + if com_0 != self.com_0 { + return Err(Error::WrongLicenseProviderComm); + } + + let com_1 = (GENERATOR_EXTENDED * sc.attr_data) + (GENERATOR_NUMS_EXTENDED * sc.s_1); + if com_1 != self.com_1 { + return Err(Error::WrongAttributeDataComm); + } + + let com_2 = (GENERATOR_EXTENDED * sc.c) + (GENERATOR_NUMS_EXTENDED * sc.s_2); + if com_2 != self.com_2 { + return Err(Error::WrongChallengeComm); + } + + Ok(()) + } +} + +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +#[derive(Default, Debug, Clone, Copy)] +pub struct SessionCookie { + pub pk_sp: JubJubAffine, // public key of the SP + pub r: BlsScalar, // randomness for session_hash + pub session_id: BlsScalar, + + pub pk_lp: JubJubAffine, // public key of the LP + pub attr_data: JubJubScalar, // attribute data of the license + pub c: JubJubScalar, // challenge value + + pub s_0: BlsScalar, // randomness for com_0 + pub s_1: JubJubScalar, // randomness for com_1 + pub s_2: JubJubScalar, // randomness for com_2 +} diff --git a/tests/citadel.rs b/tests/citadel.rs index 2d4ce01..03a9225 100644 --- a/tests/citadel.rs +++ b/tests/citadel.rs @@ -9,43 +9,66 @@ use dusk_plonk::prelude::*; use dusk_poseidon::{Domain, Hash}; use ff::Field; use phoenix_core::{PublicKey, SecretKey}; -use poseidon_merkle::{Item, Opening, Tree}; +use poseidon_merkle::{Item, Tree}; use rand_core::OsRng; -use zk_citadel::gadgets; -use zk_citadel::license::{CitadelProverParameters, License, Request, Session, SessionCookie}; +use zk_citadel::{gadgets, License, Request, Session, SessionCookie}; static LABEL: &[u8; 12] = b"dusk-network"; const CAPACITY: usize = 15; // capacity required for the setup -const DEPTH: usize = 9; // depth of the n-ary Merkle tree +const DEPTH: usize = 16; // depth of the n-ary Merkle tree // Example values const ATTRIBUTE_DATA: u64 = 112233445566778899u64; const CHALLENGE: u64 = 20221126u64; -#[macro_use] -extern crate lazy_static; +#[derive(Default, Debug)] +pub struct LicenseCircuit { + gp: gadgets::GadgetParameters, + sc: SessionCookie, +} + +impl LicenseCircuit { + pub fn new(gp: &gadgets::GadgetParameters, sc: &SessionCookie) -> Self { + Self { gp: *gp, sc: *sc } + } +} + +impl Circuit for LicenseCircuit { + fn circuit(&self, composer: &mut Composer) -> Result<(), Error> { + gadgets::use_license(composer, &self.gp, &self.sc)?; + Ok(()) + } +} + +#[test] +fn test_full_citadel() { + // These are the keys of the user + let sk = SecretKey::random(&mut OsRng); + let pk = PublicKey::from(&sk); + + // These are the keys of the LP + let sk_lp = SecretKey::random(&mut OsRng); + let pk_lp = PublicKey::from(&sk_lp); -fn compute_random_license( - rng: &mut OsRng, - sk: &SecretKey, - sk_lp: &SecretKey, - pk_lp: &PublicKey, -) -> (License, Opening<(), DEPTH>) { - let pk = PublicKey::from(sk); + // Now we generate the ProverKey and VerifierKey for the license circuit + let pp = PublicParameters::setup(1 << CAPACITY, &mut OsRng).unwrap(); + let (prover, verifier) = + Compiler::compile::(&pp, LABEL).expect("failed to compile circuit"); - // First, the user computes these values and requests a License - let lsa = pk.gen_stealth_address(&JubJubScalar::random(&mut *rng)); + // To use Citadel, the user first computes these values and requests a License + let lsa = pk.gen_stealth_address(&JubJubScalar::random(&mut OsRng)); let lsk = sk.gen_note_sk(&lsa); let k_lic = JubJubAffine::from( GENERATOR_EXTENDED * Hash::digest_truncated(Domain::Other, &[(*lsk.as_ref()).into()])[0], ); - let req = Request::new(pk_lp, &lsa, &k_lic, rng).expect("Request correctly computed."); + let req = Request::new(&pk_lp, &lsa, &k_lic, &mut OsRng).expect("Request correctly computed."); - // Second, the LP computes these values and grants the License + // Second, the LP computes these values and grants the License on-chain let attr_data = JubJubScalar::from(ATTRIBUTE_DATA); - let lic = License::new(&attr_data, sk_lp, &req, rng).expect("License correctly computed."); + let lic = + License::new(&attr_data, &sk_lp, &req, &mut OsRng).expect("License correctly computed."); let mut tree = Tree::<(), DEPTH>::new(); let lpk = JubJubAffine::from(lic.lsa.note_pk().as_ref()); @@ -58,166 +81,50 @@ fn compute_random_license( let pos = 0; tree.insert(pos, item); + // Now, the user can use the license + let pk_sp = pk_lp; // in this case, LP = SP let merkle_proof = tree.opening(pos).expect("Tree was read successfully"); - (lic, merkle_proof) -} - -fn compute_citadel_parameters( - rng: &mut OsRng, - sk: &SecretKey, - pk_lp: &PublicKey, - lic: &License, - merkle_proof: Opening<(), DEPTH>, -) -> (CitadelProverParameters, SessionCookie) { let c = JubJubScalar::from(CHALLENGE); - let (cpp, sc) = - CitadelProverParameters::compute_parameters(sk, lic, pk_lp, pk_lp, &c, rng, merkle_proof) - .expect("Parameters computed correctly."); - (cpp, sc) -} - -struct Keys { - sk: SecretKey, - - sk_lp: SecretKey, - pk_lp: PublicKey, - - citadel_prover: Prover, - citadel_verifier: Verifier, -} - -lazy_static! { - static ref TEST_KEYS: Keys = { - // These are the keys of the user - let sk = SecretKey::random(&mut OsRng); - - // These are the keys of the LP - let sk_lp = SecretKey::random(&mut OsRng); - let pk_lp = PublicKey::from(&sk_lp); - - // Now we generate the ProverKey and VerifierKey for Citadel - let pp = PublicParameters::setup(1 << CAPACITY, &mut OsRng).unwrap(); - - let (citadel_prover, citadel_verifier) = - Compiler::compile::(&pp, LABEL).expect("failed to compile circuit"); - - Keys { sk, sk_lp, pk_lp, citadel_prover, citadel_verifier } - }; -} - -#[derive(Default, Debug)] -pub struct Citadel { - cpp: CitadelProverParameters, - sc: SessionCookie, -} - -impl Citadel { - pub fn new(cpp: &CitadelProverParameters, sc: &SessionCookie) -> Self { - Self { cpp: *cpp, sc: *sc } - } -} - -impl Circuit for Citadel { - fn circuit(&self, composer: &mut Composer) -> Result<(), Error> { - gadgets::use_license_citadel(composer, &self.cpp, &self.sc)?; - Ok(()) - } -} - -#[test] -fn test_full_citadel() { - let (lic, merkle_proof) = compute_random_license( - &mut OsRng, - &TEST_KEYS.sk, - &TEST_KEYS.sk_lp, - &TEST_KEYS.pk_lp, - ); - - let (cpp, sc) = compute_citadel_parameters( - &mut OsRng, - &TEST_KEYS.sk, - &TEST_KEYS.pk_lp, + let (gp, sc) = gadgets::GadgetParameters::compute_parameters( + &sk, &lic, + &pk_lp, + &pk_sp, + &c, + &mut OsRng, merkle_proof, - ); + ) + .expect("Parameters computed correctly."); - // Then, the user generates the proof - let (proof, public_inputs) = TEST_KEYS - .citadel_prover - .prove(&mut OsRng, &Citadel::new(&cpp, &sc)) + let (proof, public_inputs) = prover + .prove(&mut OsRng, &LicenseCircuit::new(&gp, &sc)) .expect("failed to prove"); - // After receiving the proof, the network verifies it - TEST_KEYS - .citadel_verifier + // The network verifies the proof received from the user + verifier .verify(&proof, &public_inputs) .expect("failed to verify proof"); - // Finally, the SP can verify a session - let pk_lp = sc.pk_lp; - let pk_sp = sc.pk_sp; - let session = Session::from(&public_inputs); - session.verify(sc, pk_lp, pk_sp); -} - -#[test] -#[should_panic] -fn test_citadel_false_public_input() { - let (lic, merkle_proof) = compute_random_license( - &mut OsRng, - &TEST_KEYS.sk, - &TEST_KEYS.sk_lp, - &TEST_KEYS.pk_lp, - ); - - let (cpp, sc) = compute_citadel_parameters( - &mut OsRng, - &TEST_KEYS.sk, - &TEST_KEYS.pk_lp, - &lic, - merkle_proof, - ); - - let (proof, public_inputs) = TEST_KEYS - .citadel_prover - .prove(&mut OsRng, &Citadel::new(&cpp, &sc)) - .expect("failed to prove"); - - // set a false public input - let mut false_public_inputs = public_inputs; + // We also test verifying the proof with a false public input + let mut false_public_inputs = public_inputs.clone(); false_public_inputs[0] = BlsScalar::random(&mut OsRng); - TEST_KEYS - .citadel_verifier - .verify(&proof, &false_public_inputs) - .expect("failed to verify proof"); -} + // So, this should fail + assert!(verifier.verify(&proof, &false_public_inputs).is_err()); -#[test] -#[should_panic] -fn test_citadel_false_session_cookie() { - let (lic, merkle_proof) = compute_random_license( - &mut OsRng, - &TEST_KEYS.sk, - &TEST_KEYS.sk_lp, - &TEST_KEYS.pk_lp, - ); + // Now, the SP can verify a received session cookie + assert_eq!(JubJubAffine::from(pk_lp.A()), sc.pk_lp); + assert_eq!(JubJubAffine::from(pk_sp.A()), sc.pk_sp); + assert_eq!(c, sc.c); + assert_eq!(attr_data, sc.attr_data); - let (cpp, sc) = compute_citadel_parameters( - &mut OsRng, - &TEST_KEYS.sk, - &TEST_KEYS.pk_lp, - &lic, - merkle_proof, - ); - - let (_proof, public_inputs) = TEST_KEYS - .citadel_prover - .prove(&mut OsRng, &Citadel::new(&cpp, &sc)) - .expect("failed to prove"); + // Finally, the SP can verify a session, related by the session_id + let session = Session::from(&public_inputs); + assert_eq!(session.session_id, sc.session_id); + session.verify(sc).expect("Session verified correctly."); - // set a false session cookie + // We also test setting a false session cookie let sc_false = SessionCookie { pk_sp: sc.pk_sp, r: sc.r, @@ -230,8 +137,6 @@ fn test_citadel_false_session_cookie() { s_2: sc.s_2, }; - let pk_lp = sc.pk_lp; - let pk_sp = sc.pk_sp; - let session = Session::from(&public_inputs); - session.verify(sc_false, pk_lp, pk_sp); + // So, this should be an error + assert!(session.verify(sc_false).is_err()); } From 2698bc263ab053ac4b2eb32fea746385f8f336f1 Mon Sep 17 00:00:00 2001 From: xevisalle Date: Mon, 25 Nov 2024 18:15:45 +0100 Subject: [PATCH 02/10] Restructure workspace --- .gitignore | 3 +- Cargo.toml | 40 +++----------------- README.md | 24 ++++-------- contract/Cargo.toml | 6 +++ contract/README.md | 9 +++++ contract/src/main.rs | 9 +++++ CHANGELOG.md => core/CHANGELOG.md | 0 core/Cargo.toml | 34 +++++++++++++++++ core/README.md | 24 ++++++++++++ {benches => core/benches}/license_circuit.rs | 0 {src => core/src}/error.rs | 0 {src => core/src}/gadgets.rs | 0 {src => core/src}/lib.rs | 0 {src => core/src}/license.rs | 0 {src => core/src}/request.rs | 0 {src => core/src}/session.rs | 0 {tests => core/tests}/citadel.rs | 0 17 files changed, 96 insertions(+), 53 deletions(-) create mode 100644 contract/Cargo.toml create mode 100644 contract/README.md create mode 100644 contract/src/main.rs rename CHANGELOG.md => core/CHANGELOG.md (100%) create mode 100644 core/Cargo.toml create mode 100644 core/README.md rename {benches => core/benches}/license_circuit.rs (100%) rename {src => core/src}/error.rs (100%) rename {src => core/src}/gadgets.rs (100%) rename {src => core/src}/lib.rs (100%) rename {src => core/src}/license.rs (100%) rename {src => core/src}/request.rs (100%) rename {src => core/src}/session.rs (100%) rename {tests => core/tests}/citadel.rs (100%) diff --git a/.gitignore b/.gitignore index 80ef275..17ff44b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ -/target/ -/setup/ +**/target/ Cargo.lock **/*.rs.bk .DS_Store diff --git a/Cargo.toml b/Cargo.toml index 6715b4d..86e2c28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,34 +1,6 @@ -[package] -name = "zk-citadel" -version = "0.14.0" -repository = "https://github.com/dusk-network/citadel" -description = "Implementation of Citadel, a SSI system integrated in Dusk Network." -categories = ["cryptography", "authentication", "mathematics", "science"] -keywords = ["cryptography", "self-sovereign", "identity", "zk-snarks", "zero-knowledge"] -edition = "2021" -license = "MPL-2.0" - -[dependencies] -dusk-bytes = "0.1" -dusk-poseidon = { version = "0.40", features = ["zk"] } -poseidon-merkle = { version = "0.7", features = ["rkyv-impl", "zk", "size_32"] } -dusk-plonk = { version = "0.20", default-features = false, features = ["rkyv-impl", "alloc"] } -dusk-bls12_381 = { version = "0.13", default-features = false, features = ["rkyv-impl", "alloc"] } -dusk-jubjub = { version = "0.14", default-features = false, features = ["rkyv-impl", "alloc"] } -ff = { version = "0.13", default-features = false } -jubjub-schnorr = { version = "0.5", features = ["zk", "rkyv-impl", "alloc"] } -phoenix-core = { version = "0.32", features = ["rkyv-impl", "alloc"] } -rand_core = { version = "0.6", default-features=false, features = ["getrandom"] } -rkyv = { version = "0.7", default-features = false } -bytecheck = { version = "0.6", default-features = false } - -[dev-dependencies] -criterion = "0.5" - -[[bench]] -name = "license_circuit" -harness = false - -[features] -rkyv-impl = [] -default=["rkyv-impl"] +[workspace] +members = [ + "core", + "contract", +] +resolver = "2" diff --git a/README.md b/README.md index 0b2af05..e47e6c6 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,14 @@ # Citadel: Self-Sovereign Identities on Dusk -![Build Status](https://github.com/dusk-network/citadel/workflows/Continuous%20integration/badge.svg) +![Build Status](https://github.com/dusk-network/citadel/actions/workflows/dusk_ci.yml/badge.svg) [![Repository](https://img.shields.io/badge/github-citadel-blueviolet?logo=github)](https://github.com/dusk-network/citadel) -This repository contains the implementation of Citadel, a protocol that integrates a self-sovereign identity system into the Dusk blockchain. A document with all the details about the protocol can be found [here](https://github.com/dusk-network/citadel/tree/main/docs/specs.pdf). +This repository contains the implementation of Citadel, a protocol that integrates a self-sovereign identity system into the Dusk blockchain. Our implementation is based on the original idea from this [paper](https://arxiv.org/pdf/2301.09378). -**DISCLAIMER**: this library **has not gone through an exhaustive security analysis**, so it is not intended to be used in a production environment, only for academic purposes. +This repository is structured as follows: -## Tests +- :computer: [**Core**](core): the core Citadel protocol implementation, containing all the involved data types, the protocol workflows, and the license circuit. +- :pencil: [**License Contract**](contract): The license contract, along with all the required code to test and deploy it. +- :scroll: [**Docs**](docs): A folder where you can find the documentation concerning the our Citadel specific implementation. -The library can be tested by running: - -``` -cargo t --release -``` - -## Benchmarks - -The library can be benchmarked by running: - -``` -cargo bench -``` +**DISCLAIMER**: the code in this repository **has not gone through an exhaustive security analysis**, so it is not intended to be used in a production environment, only for academic purposes. diff --git a/contract/Cargo.toml b/contract/Cargo.toml new file mode 100644 index 0000000..55ec65b --- /dev/null +++ b/contract/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "license-contract" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/contract/README.md b/contract/README.md new file mode 100644 index 0000000..fd6a816 --- /dev/null +++ b/contract/README.md @@ -0,0 +1,9 @@ +# (WIP) Citadel Contract + +![Build Status](https://github.com/dusk-network/citadel/actions/workflows/dusk_ci.yml/badge.svg) +[![Repository](https://img.shields.io/badge/github-citadel-blueviolet?logo=github)](https://github.com/dusk-network/citadel) + +This package contains the Citadel contract. + +**DISCLAIMER**: this contract **has not gone through an exhaustive security analysis**, so it is not intended to be used in a production environment, only for academic purposes. + diff --git a/contract/src/main.rs b/contract/src/main.rs new file mode 100644 index 0000000..52a6975 --- /dev/null +++ b/contract/src/main.rs @@ -0,0 +1,9 @@ +// 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. + +fn main() { + println!("Hello, world!"); +} diff --git a/CHANGELOG.md b/core/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to core/CHANGELOG.md diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000..ea9f561 --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "zk-citadel" +version = "0.14.0" +repository = "https://github.com/dusk-network/citadel/core" +description = "Implementation of Citadel, a SSI system integrated in Dusk Network." +categories = ["cryptography", "authentication", "mathematics", "science"] +keywords = ["cryptography", "self-sovereign", "identity", "zk-snarks", "zero-knowledge"] +edition = "2021" +license = "MPL-2.0" + +[dependencies] +dusk-bytes = "0.1" +dusk-poseidon = { version = "0.40", features = ["zk"] } +poseidon-merkle = { version = "0.7", features = ["rkyv-impl", "zk", "size_32"] } +dusk-plonk = { version = "0.20", default-features = false, features = ["rkyv-impl", "alloc"] } +dusk-bls12_381 = { version = "0.13", default-features = false, features = ["rkyv-impl", "alloc"] } +dusk-jubjub = { version = "0.14", default-features = false, features = ["rkyv-impl", "alloc"] } +ff = { version = "0.13", default-features = false } +jubjub-schnorr = { version = "0.5", features = ["zk", "rkyv-impl", "alloc"] } +phoenix-core = { version = "0.32", features = ["rkyv-impl", "alloc"] } +rand_core = { version = "0.6", default-features=false, features = ["getrandom"] } +rkyv = { version = "0.7", default-features = false } +bytecheck = { version = "0.6", default-features = false } + +[dev-dependencies] +criterion = "0.5" + +[[bench]] +name = "license_circuit" +harness = false + +[features] +rkyv-impl = [] +default=["rkyv-impl"] diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000..0b971c9 --- /dev/null +++ b/core/README.md @@ -0,0 +1,24 @@ +# Citadel Core + +![Build Status](https://github.com/dusk-network/citadel/actions/workflows/dusk_ci.yml/badge.svg) +[![Repository](https://img.shields.io/badge/github-citadel-blueviolet?logo=github)](https://github.com/dusk-network/citadel) + +This package contains the core implementation of Citadel. + +**DISCLAIMER**: this package **has not gone through an exhaustive security analysis**, so it is not intended to be used in a production environment, only for academic purposes. + +## Tests + +The package can be tested by running: + +``` +cargo t --release +``` + +## Benchmarks + +The package can be benchmarked by running: + +``` +cargo bench +``` diff --git a/benches/license_circuit.rs b/core/benches/license_circuit.rs similarity index 100% rename from benches/license_circuit.rs rename to core/benches/license_circuit.rs diff --git a/src/error.rs b/core/src/error.rs similarity index 100% rename from src/error.rs rename to core/src/error.rs diff --git a/src/gadgets.rs b/core/src/gadgets.rs similarity index 100% rename from src/gadgets.rs rename to core/src/gadgets.rs diff --git a/src/lib.rs b/core/src/lib.rs similarity index 100% rename from src/lib.rs rename to core/src/lib.rs diff --git a/src/license.rs b/core/src/license.rs similarity index 100% rename from src/license.rs rename to core/src/license.rs diff --git a/src/request.rs b/core/src/request.rs similarity index 100% rename from src/request.rs rename to core/src/request.rs diff --git a/src/session.rs b/core/src/session.rs similarity index 100% rename from src/session.rs rename to core/src/session.rs diff --git a/tests/citadel.rs b/core/tests/citadel.rs similarity index 100% rename from tests/citadel.rs rename to core/tests/citadel.rs From 4bb6a042f5816fa2b5081e6055bcbe95fabe45d0 Mon Sep 17 00:00:00 2001 From: xevisalle Date: Mon, 25 Nov 2024 20:49:16 +0100 Subject: [PATCH 03/10] core: Add circuit module --- core/CHANGELOG.md | 4 ++++ core/benches/license_circuit.rs | 14 +++++--------- core/src/circuit.rs | 31 ++++++++++++++++++++++++++++++ core/src/lib.rs | 1 + core/tests/citadel.rs | 34 ++++++--------------------------- 5 files changed, 47 insertions(+), 37 deletions(-) create mode 100644 core/src/circuit.rs diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index f994403..9e3df72 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `circuit` module + ### Changed - Refactor the crate [#98] diff --git a/core/benches/license_circuit.rs b/core/benches/license_circuit.rs index 9c3f458..d3c4bac 100644 --- a/core/benches/license_circuit.rs +++ b/core/benches/license_circuit.rs @@ -11,30 +11,26 @@ use ff::Field; use phoenix_core::{PublicKey, SecretKey}; use poseidon_merkle::{Item, Tree}; -use zk_citadel::{gadgets, License, Request, SessionCookie}; +use zk_citadel::{circuit, gadgets, License, Request, SessionCookie}; use criterion::{criterion_group, criterion_main, Criterion}; use rand_core::OsRng; static mut CONSTRAINTS: usize = 0; - static LABEL: &[u8; 12] = b"dusk-network"; -const CAPACITY: usize = 15; // capacity required for the setup -const DEPTH: usize = 16; // depth of the n-ary Merkle tree - // Example values const ATTRIBUTE_DATA: u64 = 112233445566778899u64; const CHALLENGE: u64 = 20221126u64; #[derive(Default, Debug)] pub struct LicenseCircuit { - gp: gadgets::GadgetParameters, + gp: gadgets::GadgetParameters<{ circuit::DEPTH }>, sc: SessionCookie, } impl LicenseCircuit { - pub fn new(gp: &gadgets::GadgetParameters, sc: &SessionCookie) -> Self { + pub fn new(gp: &gadgets::GadgetParameters<{ circuit::DEPTH }>, sc: &SessionCookie) -> Self { Self { gp: *gp, sc: *sc } } } @@ -55,7 +51,7 @@ fn license_circuit_benchmark(crit: &mut Criterion) { let pk = PublicKey::from(&sk); let sk_lp = SecretKey::random(&mut OsRng); let pk_lp = PublicKey::from(&sk_lp); - let pp = PublicParameters::setup(1 << CAPACITY, &mut OsRng).unwrap(); + let pp = PublicParameters::setup(1 << circuit::CAPACITY, &mut OsRng).unwrap(); let (prover, verifier) = Compiler::compile::(&pp, LABEL).expect("failed to compile circuit"); @@ -70,7 +66,7 @@ fn license_circuit_benchmark(crit: &mut Criterion) { let lic = License::new(&attr_data, &sk_lp, &req, &mut OsRng).expect("License correctly computed."); - let mut tree = Tree::<(), DEPTH>::new(); + let mut tree = Tree::<(), { circuit::DEPTH }>::new(); let lpk = JubJubAffine::from(lic.lsa.note_pk().as_ref()); let item = Item { diff --git a/core/src/circuit.rs b/core/src/circuit.rs new file mode 100644 index 0000000..7bda089 --- /dev/null +++ b/core/src/circuit.rs @@ -0,0 +1,31 @@ +// 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 crate::{gadgets, SessionCookie}; +use dusk_plonk::prelude::*; + +#[allow(dead_code)] +pub const CAPACITY: usize = 15; // capacity required for the setup +pub const DEPTH: usize = 16; // depth of the n-ary Merkle tree + +#[derive(Default, Debug)] +pub struct LicenseCircuit { + gp: gadgets::GadgetParameters, + sc: SessionCookie, +} + +impl LicenseCircuit { + pub fn new(gp: &gadgets::GadgetParameters, sc: &SessionCookie) -> Self { + Self { gp: *gp, sc: *sc } + } +} + +impl Circuit for LicenseCircuit { + fn circuit(&self, composer: &mut Composer) -> Result<(), Error> { + gadgets::use_license(composer, &self.gp, &self.sc)?; + Ok(()) + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 6b6d1c1..63c19c2 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -9,6 +9,7 @@ mod license; mod request; mod session; +pub mod circuit; pub mod gadgets; pub use license::License; diff --git a/core/tests/citadel.rs b/core/tests/citadel.rs index 03a9225..af33c83 100644 --- a/core/tests/citadel.rs +++ b/core/tests/citadel.rs @@ -12,36 +12,14 @@ use phoenix_core::{PublicKey, SecretKey}; use poseidon_merkle::{Item, Tree}; use rand_core::OsRng; -use zk_citadel::{gadgets, License, Request, Session, SessionCookie}; +use zk_citadel::{circuit, gadgets, License, Request, Session, SessionCookie}; static LABEL: &[u8; 12] = b"dusk-network"; -const CAPACITY: usize = 15; // capacity required for the setup -const DEPTH: usize = 16; // depth of the n-ary Merkle tree - // Example values const ATTRIBUTE_DATA: u64 = 112233445566778899u64; const CHALLENGE: u64 = 20221126u64; -#[derive(Default, Debug)] -pub struct LicenseCircuit { - gp: gadgets::GadgetParameters, - sc: SessionCookie, -} - -impl LicenseCircuit { - pub fn new(gp: &gadgets::GadgetParameters, sc: &SessionCookie) -> Self { - Self { gp: *gp, sc: *sc } - } -} - -impl Circuit for LicenseCircuit { - fn circuit(&self, composer: &mut Composer) -> Result<(), Error> { - gadgets::use_license(composer, &self.gp, &self.sc)?; - Ok(()) - } -} - #[test] fn test_full_citadel() { // These are the keys of the user @@ -53,9 +31,9 @@ fn test_full_citadel() { let pk_lp = PublicKey::from(&sk_lp); // Now we generate the ProverKey and VerifierKey for the license circuit - let pp = PublicParameters::setup(1 << CAPACITY, &mut OsRng).unwrap(); - let (prover, verifier) = - Compiler::compile::(&pp, LABEL).expect("failed to compile circuit"); + let pp = PublicParameters::setup(1 << circuit::CAPACITY, &mut OsRng).unwrap(); + let (prover, verifier) = Compiler::compile::(&pp, LABEL) + .expect("failed to compile circuit"); // To use Citadel, the user first computes these values and requests a License let lsa = pk.gen_stealth_address(&JubJubScalar::random(&mut OsRng)); @@ -70,7 +48,7 @@ fn test_full_citadel() { let lic = License::new(&attr_data, &sk_lp, &req, &mut OsRng).expect("License correctly computed."); - let mut tree = Tree::<(), DEPTH>::new(); + let mut tree = Tree::<(), { circuit::DEPTH }>::new(); let lpk = JubJubAffine::from(lic.lsa.note_pk().as_ref()); let item = Item { @@ -98,7 +76,7 @@ fn test_full_citadel() { .expect("Parameters computed correctly."); let (proof, public_inputs) = prover - .prove(&mut OsRng, &LicenseCircuit::new(&gp, &sc)) + .prove(&mut OsRng, &circuit::LicenseCircuit::new(&gp, &sc)) .expect("failed to prove"); // The network verifies the proof received from the user From 316c2636b763efb7cf8aac7cacf4054936440d26 Mon Sep 17 00:00:00 2001 From: xevisalle Date: Tue, 26 Nov 2024 00:35:00 +0100 Subject: [PATCH 04/10] Override build and set optimization --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 86e2c28..ff64056 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,6 @@ members = [ "contract", ] resolver = "2" + +[profile.release.build-override] +opt-level = 3 \ No newline at end of file From 036cc5843cef7530ca9bac7b3420642fc3b395e5 Mon Sep 17 00:00:00 2001 From: xevisalle Date: Tue, 26 Nov 2024 00:35:46 +0100 Subject: [PATCH 05/10] contract: Add build script --- contract/Cargo.toml | 9 ++++++ contract/build.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 contract/build.rs diff --git a/contract/Cargo.toml b/contract/Cargo.toml index 55ec65b..58763b3 100644 --- a/contract/Cargo.toml +++ b/contract/Cargo.toml @@ -4,3 +4,12 @@ version = "0.1.0" edition = "2021" [dependencies] + +[build-dependencies] +zk-citadel = { path = "../core"} +dusk-plonk = { version = "0.20", default-features = false, features = ["rkyv-impl", "alloc"] } +rand_core = { version = "0.6", default-features=false, features = ["getrandom"] } +reqwest = "0.12" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +build-print = "0.1.1" +sha2 = { version = "0.10.8", default-features = false } diff --git a/contract/build.rs b/contract/build.rs new file mode 100644 index 0000000..95fa6dc --- /dev/null +++ b/contract/build.rs @@ -0,0 +1,68 @@ +// 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 build_print::*; +use dusk_plonk::prelude::*; +use rand_core::OsRng; +use sha2::{Digest, Sha256}; +use std::fs::File; +use std::io::prelude::*; +use std::path::Path; +use zk_citadel::circuit; + +static LABEL: &[u8; 12] = b"dusk-network"; + +const CRS_URL: &str = "https://nodes.dusk.network/trusted-setup"; +const CRS_17_HASH: &str = "6161605616b62356cf09fa28252c672ef53b2c8489ad5f81d87af26e105f6059"; +const VERIFIER_PATH: &str = "../target/verifier"; + +#[tokio::main] +async fn main() { + // If verifier key already exists, no need to download again + if !(Path::new(VERIFIER_PATH).exists()) { + let response = reqwest::get(CRS_URL).await; + + match response { + Ok(pp_bytes) => { + // If verifier key didn't exist, we download again from server + let pp_bytes = pp_bytes.bytes().await.unwrap(); + let mut hasher = Sha256::new(); + hasher.update(pp_bytes.clone()); + let hash = format!("{:x}", hasher.finalize()); + + // We check the file integrity + assert_eq!(hash, CRS_17_HASH); + + let pp = PublicParameters::from_slice(pp_bytes.to_vec().as_slice()) + .expect("Creating PublicParameters from slice failed."); + + // Compile the license circuit + let (_prover, verifier) = Compiler::compile::(&pp, LABEL) + .expect("failed to compile circuit"); + + // Write verifier key to disk + let mut file = File::create(VERIFIER_PATH).unwrap(); + file.write_all(&verifier.to_bytes()).unwrap(); + + info!("Local trusted setup not found, a new one was downloaded."); + } + Err(_e) => { + // If download fails, we create a setup from scratch + let pp = PublicParameters::setup(1 << circuit::CAPACITY, &mut OsRng).unwrap(); + + // Compile the license circuit + let (_prover, verifier) = Compiler::compile::(&pp, LABEL) + .expect("failed to compile circuit"); + + // Write verifier key to disk + let mut file = File::create(VERIFIER_PATH).unwrap(); + file.write_all(&verifier.to_bytes()).unwrap(); + + warn!("Download of trusted setup from server failed. A new one was generated from scratch. USE AT YOUR OWN RISK."); + } + } + } +} From e9c9e2e084aed004263a1c16ffb47056f32d40fe Mon Sep 17 00:00:00 2001 From: xevisalle Date: Tue, 26 Nov 2024 01:55:48 +0100 Subject: [PATCH 06/10] Update toolchain --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e4ada99..0075965 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] channel = "nightly-2023-11-10" -components = ["rustfmt", "clippy"] +components = ["rust-src", "rustfmt", "cargo", "clippy"] From 87c4a56ab52264ebf58ea7c12dd552745dc4fadf Mon Sep 17 00:00:00 2001 From: xevisalle Date: Tue, 26 Nov 2024 02:48:50 +0100 Subject: [PATCH 07/10] contract: Port contract from Rusk Co-authored-by: Milosz Muszynski --- contract/CHANGELOG.md | 39 +++ contract/Cargo.toml | 18 ++ contract/README.md | 21 +- contract/build.rs | 20 +- contract/src/collection.rs | 99 +++++++ contract/src/{main.rs => error.rs} | 14 +- contract/src/lib.rs | 80 ++++++ contract/src/license_types.rs | 42 +++ contract/src/state.rs | 149 ++++++++++ contract/tests/license_contract.rs | 434 +++++++++++++++++++++++++++++ 10 files changed, 908 insertions(+), 8 deletions(-) create mode 100644 contract/CHANGELOG.md create mode 100644 contract/src/collection.rs rename contract/src/{main.rs => error.rs} (52%) create mode 100644 contract/src/lib.rs create mode 100644 contract/src/license_types.rs create mode 100644 contract/src/state.rs create mode 100644 contract/tests/license_contract.rs diff --git a/contract/CHANGELOG.md b/contract/CHANGELOG.md new file mode 100644 index 0000000..537e287 --- /dev/null +++ b/contract/CHANGELOG.md @@ -0,0 +1,39 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +### Added + +- Renamed method 'noop' to 'request_license' [#1151] +- Added method 'get_info' [#1052] + +### Changed + +- Change dependencies declarations enforce bytecheck [#1371] +- Changed 'get_licenses' to use feeder for passing return values [#1054] +- Changed 'use_license' to check if license already nullified [#1051] +- Changed 'get_licenses' to return values by adding 'pos' to every license returned [#1040] +- Changed 'issue_license' by removing the 'pos' argument and self position determination [#1039] +- Fixed `license` tests error when creating `PublicParameters` from a slice +- Moved the `license-contract` to the `Citadel` repository [#122] + +## [0.1.0] - 2023-07-13 + +### Added + +- Add `license` contract to Rusk [#960] + +[#122]: https://github.com/dusk-network/citadel/issues/122 +[#1371]: https://github.com/dusk-network/rusk/issues/1371 +[#1151]: https://github.com/dusk-network/rusk/issues/1151 +[#1054]: https://github.com/dusk-network/rusk/issues/1054 +[#1052]: https://github.com/dusk-network/rusk/issues/1052 +[#1051]: https://github.com/dusk-network/rusk/issues/1051 +[#1040]: https://github.com/dusk-network/rusk/issues/1040 +[#1039]: https://github.com/dusk-network/rusk/issues/1039 +[#960]: https://github.com/dusk-network/rusk/issues/960 \ No newline at end of file diff --git a/contract/Cargo.toml b/contract/Cargo.toml index 58763b3..73d7887 100644 --- a/contract/Cargo.toml +++ b/contract/Cargo.toml @@ -3,7 +3,18 @@ name = "license-contract" version = "0.1.0" edition = "2021" +[lib] +crate-type = ["cdylib", "rlib"] + [dependencies] +execution-core = { version = "0.1.0", git = "https://github.com/dusk-network/rusk/", branch = "master", features = ["zk"] } +dusk-bytes = "=0.1.7" +rkyv = { version = "=0.7.39", default-features = false, features = ["size_32"] } +bytecheck = { version = "=0.6.12", default-features = false } +poseidon-merkle = { version = "=0.7.0", features = ["rkyv-impl"] } + +[target.'cfg(target_family = "wasm")'.dependencies] +rusk-abi = { version = "0.13.0-rc.0", git = "https://github.com/dusk-network/rusk/", branch = "master", features = ["abi", "dlmalloc"] } [build-dependencies] zk-citadel = { path = "../core"} @@ -13,3 +24,10 @@ reqwest = "0.12" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } build-print = "0.1.1" sha2 = { version = "0.10.8", default-features = false } + +[dev-dependencies] +rusk-abi = { version = "0.13.0-rc.0", git = "https://github.com/dusk-network/rusk/", branch = "master", default-features = false, features = ["host"] } +zk-citadel = { path = "../core"} +ff = { version = "=0.13.0", default-features = false } +rand = { version = "=0.8.5", default-features = false } +dusk-poseidon = "=0.40.0" diff --git a/contract/README.md b/contract/README.md index fd6a816..d440db0 100644 --- a/contract/README.md +++ b/contract/README.md @@ -1,4 +1,4 @@ -# (WIP) Citadel Contract +# Citadel Contract ![Build Status](https://github.com/dusk-network/citadel/actions/workflows/dusk_ci.yml/badge.svg) [![Repository](https://img.shields.io/badge/github-citadel-blueviolet?logo=github)](https://github.com/dusk-network/citadel) @@ -7,3 +7,22 @@ This package contains the Citadel contract. **DISCLAIMER**: this contract **has not gone through an exhaustive security analysis**, so it is not intended to be used in a production environment, only for academic purposes. +## Usage + +First, compile the license circuit (`/target/prover` and `/target/verifier`) as follows: + +``` +cargo b --release +``` + +Then, compile the license contract: + +``` +cargo b --target wasm32-unknown-unknown --release +``` + +Finally, execute the tests: + +``` +cargo t --release +``` diff --git a/contract/build.rs b/contract/build.rs index 95fa6dc..0a1857a 100644 --- a/contract/build.rs +++ b/contract/build.rs @@ -17,17 +17,19 @@ static LABEL: &[u8; 12] = b"dusk-network"; const CRS_URL: &str = "https://nodes.dusk.network/trusted-setup"; const CRS_17_HASH: &str = "6161605616b62356cf09fa28252c672ef53b2c8489ad5f81d87af26e105f6059"; + +const PROVER_PATH: &str = "../target/prover"; const VERIFIER_PATH: &str = "../target/verifier"; #[tokio::main] async fn main() { - // If verifier key already exists, no need to download again - if !(Path::new(VERIFIER_PATH).exists()) { + // If keys already exist locally, no need to download again + if !(Path::new(PROVER_PATH).exists()) || !(Path::new(VERIFIER_PATH).exists()) { let response = reqwest::get(CRS_URL).await; match response { Ok(pp_bytes) => { - // If verifier key didn't exist, we download again from server + // If setup didn't exist locally, we download the setup again from server let pp_bytes = pp_bytes.bytes().await.unwrap(); let mut hasher = Sha256::new(); hasher.update(pp_bytes.clone()); @@ -40,9 +42,13 @@ async fn main() { .expect("Creating PublicParameters from slice failed."); // Compile the license circuit - let (_prover, verifier) = Compiler::compile::(&pp, LABEL) + let (prover, verifier) = Compiler::compile::(&pp, LABEL) .expect("failed to compile circuit"); + // Write prover key to disk + let mut file = File::create(PROVER_PATH).unwrap(); + file.write_all(&prover.to_bytes()).unwrap(); + // Write verifier key to disk let mut file = File::create(VERIFIER_PATH).unwrap(); file.write_all(&verifier.to_bytes()).unwrap(); @@ -54,9 +60,13 @@ async fn main() { let pp = PublicParameters::setup(1 << circuit::CAPACITY, &mut OsRng).unwrap(); // Compile the license circuit - let (_prover, verifier) = Compiler::compile::(&pp, LABEL) + let (prover, verifier) = Compiler::compile::(&pp, LABEL) .expect("failed to compile circuit"); + // Write prover key to disk + let mut file = File::create(PROVER_PATH).unwrap(); + file.write_all(&prover.to_bytes()).unwrap(); + // Write verifier key to disk let mut file = File::create(VERIFIER_PATH).unwrap(); file.write_all(&verifier.to_bytes()).unwrap(); diff --git a/contract/src/collection.rs b/contract/src/collection.rs new file mode 100644 index 0000000..43d61a2 --- /dev/null +++ b/contract/src/collection.rs @@ -0,0 +1,99 @@ +// 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 alloc::vec::Vec; + +#[derive(Debug, Clone)] +pub struct Map { + data: Vec<(K, V)>, +} + +#[allow(dead_code)] +impl Map { + pub const fn new() -> Self { + Self { data: Vec::new() } + } + + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn get(&self, key: &K) -> Option<&V> { + self.data.iter().find_map(|(k, v)| (k == key).then_some(v)) + } + + pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { + self.data + .iter_mut() + .find_map(|(k, v)| (k == key).then_some(v)) + } + + pub fn insert(&mut self, key: K, value: V) { + if let Some(pos) = self.data.iter().position(|(k, _)| k == &key) { + self.data[pos] = (key, value) + } else { + self.data.push((key, value)) + } + } + + pub fn remove(&mut self, key: &K) { + self.data.retain(|(k, _)| k != key); + } + + pub fn find(&self, f: F) -> Option<&V> + where + F: Fn(&V) -> bool, + { + self.data.iter().find_map(|(_, v)| f(v).then_some(v)) + } + + pub fn filter(&self, f: F) -> impl Iterator + where + F: Fn(&V) -> bool, + { + self.data.iter().filter_map(move |(_, v)| f(v).then_some(v)) + } + + pub fn entries_filter(&self, f: F) -> impl Iterator + where + F: Fn((&K, &V)) -> bool, + { + self.data.iter().filter(move |(k, v)| f((k, v))) + } +} + +impl Default for Map { + fn default() -> Self { + Self { data: Vec::new() } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_map() { + let mut data = Map::::default(); + + assert!(data.get(&1).is_none()); + assert!(data.get(&12).is_none()); + + data.insert(12, 0); + + assert!(data.get(&1).is_none()); + assert!(data.get(&12).is_some()); + + data.remove(&12); + + assert!(data.get(&1).is_none()); + assert!(data.get(&12).is_none()); + } +} diff --git a/contract/src/main.rs b/contract/src/error.rs similarity index 52% rename from contract/src/main.rs rename to contract/src/error.rs index 52a6975..e8a5c44 100644 --- a/contract/src/main.rs +++ b/contract/src/error.rs @@ -4,6 +4,16 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -fn main() { - println!("Hello, world!"); +use core::fmt; + +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub enum Error { + ProofVerification, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", &self) + } } diff --git a/contract/src/lib.rs b/contract/src/lib.rs new file mode 100644 index 0000000..287a02a --- /dev/null +++ b/contract/src/lib.rs @@ -0,0 +1,80 @@ +// 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. + +#![cfg_attr(target_family = "wasm", no_std)] +#![cfg(target_family = "wasm")] +#![feature(arbitrary_self_types)] +#![deny(unused_crate_dependencies)] +#![deny(unused_extern_crates)] + +extern crate alloc; + +pub(crate) mod collection; +mod error; +mod license_types; +#[cfg(target_family = "wasm")] +mod state; + +pub use license_types::{LicenseSession, LicenseSessionId, UseLicenseArg}; + +const VD_LICENSE_CIRCUIT: &[u8] = include_bytes!("../../target/verifier"); + +/// Verifier data for the `License` circuit. +#[allow(dead_code)] +pub const fn verifier_data_license_circuit() -> &'static [u8] { + VD_LICENSE_CIRCUIT +} + +#[cfg(target_family = "wasm")] +#[path = ""] +mod wasm { + use super::*; + + use state::LicenseContractState; + + static mut STATE: LicenseContractState = LicenseContractState::new(); + + #[no_mangle] + unsafe fn issue_license(arg_len: u32) -> u32 { + rusk_abi::wrap_call(arg_len, |(license, hash)| { + STATE.issue_license(license, hash) + }) + } + + #[no_mangle] + unsafe fn get_licenses(arg_len: u32) -> u32 { + rusk_abi::wrap_call(arg_len, |user_public_key| { + STATE.get_licenses(user_public_key) + }) + } + + #[no_mangle] + unsafe fn get_merkle_opening(arg_len: u32) -> u32 { + rusk_abi::wrap_call(arg_len, |position| STATE.get_merkle_opening(position)) + } + + #[no_mangle] + unsafe fn use_license(arg_len: u32) -> u32 { + rusk_abi::wrap_call(arg_len, |use_license_arg| { + STATE.use_license(use_license_arg) + }) + } + + #[no_mangle] + unsafe fn get_session(arg_len: u32) -> u32 { + rusk_abi::wrap_call(arg_len, |nullifier| STATE.get_session(nullifier)) + } + + #[no_mangle] + unsafe fn request_license(arg_len: u32) -> u32 { + rusk_abi::wrap_call(arg_len, |_: ()| STATE.request_license()) + } + + #[no_mangle] + unsafe fn get_info(arg_len: u32) -> u32 { + rusk_abi::wrap_call(arg_len, |_: ()| STATE.get_info()) + } +} diff --git a/contract/src/license_types.rs b/contract/src/license_types.rs new file mode 100644 index 0000000..e152441 --- /dev/null +++ b/contract/src/license_types.rs @@ -0,0 +1,42 @@ +// 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 alloc::vec::Vec; + +use bytecheck::CheckBytes; +use rkyv::{Archive, Deserialize, Serialize}; + +use execution_core::{plonk::Proof, BlsScalar}; + +/// Use License Argument. +#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub struct UseLicenseArg { + pub proof: Proof, + pub public_inputs: Vec, +} + +/// License Session Id +#[derive(Debug, Clone, Copy, PartialEq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub struct LicenseSessionId { + pub id: BlsScalar, +} + +/// License Session +#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub struct LicenseSession { + pub public_inputs: Vec, +} + +impl LicenseSession { + pub fn session_id(&self) -> LicenseSessionId { + LicenseSessionId { + id: self.public_inputs[0], + } + } +} diff --git a/contract/src/state.rs b/contract/src/state.rs new file mode 100644 index 0000000..c577db4 --- /dev/null +++ b/contract/src/state.rs @@ -0,0 +1,149 @@ +// 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 core::ops::Range; + +use alloc::vec::Vec; + +use dusk_bytes::Serializable; +use execution_core::BlsScalar; + +use crate::collection::Map; +use crate::error::Error; +use crate::license_types::{LicenseSession, LicenseSessionId, UseLicenseArg}; +use crate::verifier_data_license_circuit; + +const DEPTH: usize = 16; // the depth of LicenseCircuit's Merkle tree + +pub type LicenseTree = poseidon_merkle::Tree<(), DEPTH>; +pub type LicenseOpening = poseidon_merkle::Opening<(), DEPTH>; +pub type LicenseTreeItem = poseidon_merkle::Item<()>; + +#[derive(Debug, Clone)] +pub struct RequestEntry { + pub block_height: u64, + pub request: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct LicenseEntry { + pub block_height: u64, + pub license: Vec, +} + +#[derive(Debug, Clone)] +pub struct LicenseContractState { + pub sessions: Map, + pub licenses: Map, + pub tree: LicenseTree, +} + +#[allow(dead_code)] +impl LicenseContractState { + pub const fn new() -> Self { + Self { + sessions: Map::new(), + licenses: Map::new(), + tree: LicenseTree::new(), + } + } + + pub fn identifier() -> &'static [u8; 7] { + b"license" + } +} + +#[allow(dead_code)] +impl LicenseContractState { + /// Inserts a license into the collection of licenses. + /// Method intended to be called by the License Provider. + pub fn issue_license(&mut self, license: Vec, hash: BlsScalar) { + let item = LicenseTreeItem { hash, data: () }; + let mut pos = self.tree.len(); + while self.tree.contains(pos) { + pos += 1; + } + self.tree.insert(pos, item); + let block_height = rusk_abi::block_height(); + self.licenses.insert( + pos, + LicenseEntry { + block_height, + license, + }, + ); + } + + /// Returns licenses for a given range of block-heights. + /// Method intended to be called by the user. + pub fn get_licenses(&mut self, block_heights: Range) { + for pos_license_pair in self + .licenses + .entries_filter(|(_, le)| block_heights.contains(&le.block_height)) + .map(|(pos, le)| (*pos, le.license.clone())) + { + rusk_abi::feed(pos_license_pair); + } + } + + /// Returns merkle opening for a given position in the merkle tree of + /// license hashes. Returns none if the given position slot in the tree is + /// empty. Method intended to be called by the user. + pub fn get_merkle_opening(&mut self, position: u64) -> Option { + self.tree.opening(position) + } + + /// Verifies the proof of a given license, if successful, + /// creates a session with the corresponding session id. + /// Method intended to be called by the user. + pub fn use_license(&mut self, use_license_arg: UseLicenseArg) { + Self::assert_proof( + verifier_data_license_circuit(), + use_license_arg.proof.to_bytes().to_vec(), + use_license_arg.public_inputs.clone(), + ) + .expect("Provided proof verification should succeed!"); + + // after a successful proof verification we can add a session to a + // shared list of sessions + let license_session = LicenseSession { + public_inputs: use_license_arg.public_inputs, + }; + let session_id = license_session.session_id(); + if self.sessions.get(&session_id).is_some() { + panic!("License already nullified"); + } + self.sessions.insert(session_id, license_session); + } + + /// Returns session with a given session id. + /// Method intended to be called by the Service Provider. + pub fn get_session(&self, session_id: LicenseSessionId) -> Option { + self.sessions.get(&session_id).cloned() + } + + /// Method needed for inserting payloads into blockchain + pub fn request_license(&self) {} + + fn assert_proof( + verifier_data: &[u8], + proof: Vec, + public_inputs: Vec, + ) -> Result<(), Error> { + rusk_abi::verify_plonk(verifier_data.to_vec(), proof, public_inputs) + .then_some(()) + .ok_or(Error::ProofVerification) + } + + /// Info about contract state + pub fn get_info(&self) -> (u32, u32, u32) { + ( + self.licenses.len() as u32, + self.tree.len() as u32, + self.sessions.len() as u32, + ) + } +} diff --git a/contract/tests/license_contract.rs b/contract/tests/license_contract.rs new file mode 100644 index 0000000..f857796 --- /dev/null +++ b/contract/tests/license_contract.rs @@ -0,0 +1,434 @@ +// 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. + +extern crate alloc; + +use std::ops::Range; +use std::sync::mpsc; + +use dusk_poseidon::{Domain, Hash}; +use execution_core::plonk::{Prover, Verifier}; +use ff::Field; +use rand::rngs::StdRng; +use rand::{CryptoRng, RngCore, SeedableRng}; +use rkyv::{check_archived_root, Deserialize, Infallible}; +use zk_citadel::{circuit, gadgets, License, Request, SessionCookie}; + +const PROVER_PATH: &str = "../target/prover"; +const VERIFIER_PATH: &str = "../target/verifier"; + +pub type LicenseOpening = poseidon_merkle::Opening<(), { circuit::DEPTH }>; + +use execution_core::{ + transfer::phoenix::{PublicKey, SecretKey, StealthAddress, ViewKey}, + BlsScalar, ContractId, JubJubAffine, JubJubScalar, GENERATOR_EXTENDED, +}; +use rusk_abi::{ContractData, Session}; + +use std::fs::File; +use std::io::Read; + +#[path = "../src/license_types.rs"] +mod license_types; +use license_types::*; + +const LICENSE_CONTRACT_ID: ContractId = { + let mut bytes = [0u8; 32]; + bytes[0] = 0xf8; + ContractId::from_bytes(bytes) +}; + +const POINT_LIMIT: u64 = 0x10000000; +const TEST_OWNER: [u8; 32] = [0; 32]; +const CHAIN_ID: u8 = 0xFA; +const USER_ATTRIBUTES: u64 = 545072475273; + +fn create_test_license( + attr: &JubJubScalar, + sk_lp: &SecretKey, + pk_lp: &PublicKey, + sa_user: &StealthAddress, + k_lic: &JubJubAffine, + rng: &mut R, +) -> License { + let request = Request::new(pk_lp, sa_user, k_lic, rng).unwrap(); + License::new(attr, sk_lp, &request, rng).unwrap() +} + +fn initialize() -> Session { + let vm = rusk_abi::new_ephemeral_vm().expect("Creating a VM should succeed"); + + let bytecode = + include_bytes!("../../target/wasm32-unknown-unknown/release/license_contract.wasm"); + + let mut session = rusk_abi::new_genesis_session(&vm, CHAIN_ID); + + session + .deploy( + bytecode, + ContractData::builder() + .owner(TEST_OWNER) + .contract_id(LICENSE_CONTRACT_ID), + POINT_LIMIT, + ) + .expect("Deploying the license contract should succeed"); + + session +} + +/// Deserializes license, panics if deserialization fails. +fn deserialise_license(v: &Vec) -> License { + let response_data = + check_archived_root::(v.as_slice()).expect("License should deserialize correctly"); + let license: License = response_data + .deserialize(&mut Infallible) + .expect("Infallible"); + license +} + +/// Finds owned license in a collection of licenses. +/// It searches in a reverse order to return a newest license. +fn find_owned_license( + sk_user: &SecretKey, + licenses: &Vec<(u64, Vec)>, +) -> Option<(u64, License)> { + for (pos, license) in licenses.iter().rev() { + let license = deserialise_license(&license); + if ViewKey::from(sk_user).owns(&license.lsa) { + return Some((pos.clone(), license)); + } + } + None +} + +/// Creates the Citadel request object +fn create_request( + sk_user: &SecretKey, + pk_lp: &PublicKey, + rng: &mut R, +) -> Request { + let pk = PublicKey::from(sk_user); + let lsa = pk.gen_stealth_address(&JubJubScalar::random(&mut *rng)); + let lsk = sk_user.gen_note_sk(&lsa); + let k_lic = JubJubAffine::from( + GENERATOR_EXTENDED * Hash::digest_truncated(Domain::Other, &[(*lsk.as_ref()).into()])[0], + ); + Request::new(pk_lp, &lsa, &k_lic, rng).unwrap() +} + +fn compute_citadel_parameters( + rng: &mut StdRng, + sk: &SecretKey, + pk_lp: &PublicKey, + lic: &License, + merkle_proof: LicenseOpening, +) -> (gadgets::GadgetParameters<{ circuit::DEPTH }>, SessionCookie) { + const CHALLENGE: u64 = 20221126u64; + let c = JubJubScalar::from(CHALLENGE); + let (gp, sc) = + gadgets::GadgetParameters::compute_parameters(sk, lic, pk_lp, pk_lp, &c, rng, merkle_proof) + .expect("Parameters computed correctly."); + (gp, sc) +} + +#[test] +fn license_issue_get_merkle() { + let rng = &mut StdRng::seed_from_u64(0xcafe); + let mut session = initialize(); + + // user + let sk_user = SecretKey::random(rng); + let pk_user = PublicKey::from(&sk_user); + let sa_user = pk_user.gen_stealth_address(&JubJubScalar::random(&mut *rng)); + + // license provider + let sk_lp = SecretKey::random(rng); + let pk_lp = PublicKey::from(&sk_lp); + let k_lic = JubJubAffine::from(GENERATOR_EXTENDED * JubJubScalar::random(&mut *rng)); + + let attr = JubJubScalar::from(USER_ATTRIBUTES); + + let license = create_test_license(&attr, &sk_lp, &pk_lp, &sa_user, &k_lic, rng); + let license_blob = rkyv::to_bytes::<_, 4096>(&license) + .expect("Request should serialize correctly") + .to_vec(); + + let lpk = JubJubAffine::from(license.lsa.note_pk().as_ref()); + let license_hash = Hash::digest(Domain::Other, &[lpk.get_u(), lpk.get_v()])[0]; + + session + .call::<(Vec, BlsScalar), ()>( + LICENSE_CONTRACT_ID, + "issue_license", + &(license_blob, license_hash), + POINT_LIMIT, + ) + .expect("Issuing license should succeed"); + + let bh_range = 0..10000u64; + let (feeder, receiver) = mpsc::channel(); + session + .feeder_call::, ()>( + LICENSE_CONTRACT_ID, + "get_licenses", + &bh_range, + u64::MAX, + feeder, + ) + .expect("Querying of the licenses should succeed") + .data; + + let pos_license_pairs: Vec<(u64, Vec)> = receiver + .iter() + .map(|bytes| rkyv::from_bytes(&bytes).expect("Should return licenses")) + .collect(); + + assert!( + !pos_license_pairs.is_empty(), + "Call to getting a license request should return some licenses" + ); + + let owned_license = find_owned_license(&sk_user, &pos_license_pairs); + assert!( + owned_license.is_some(), + "Some license should be owned by the user" + ); + let (pos, _) = owned_license.unwrap(); + + let _merkle_opening = session + .call::(LICENSE_CONTRACT_ID, "get_merkle_opening", &pos, POINT_LIMIT) + .expect("Querying the merkle opening should succeed") + .data; +} + +#[test] +fn multiple_licenses_issue_get_merkle() { + let rng = &mut StdRng::seed_from_u64(0xcafe); + let mut session = initialize(); + + // user + let sk_user = SecretKey::random(rng); + let pk_user = PublicKey::from(&sk_user); + let sa_user = pk_user.gen_stealth_address(&JubJubScalar::random(&mut *rng)); + + // license provider + let sk_lp = SecretKey::random(rng); + let pk_lp = PublicKey::from(&sk_lp); + + let attr = JubJubScalar::from(USER_ATTRIBUTES); + + const NUM_LICENSES: usize = 4 + 1; + for _ in 0..NUM_LICENSES { + let k_lic = JubJubAffine::from(GENERATOR_EXTENDED * JubJubScalar::random(&mut *rng)); + let license = create_test_license(&attr, &sk_lp, &pk_lp, &sa_user, &k_lic, rng); + let license_blob = rkyv::to_bytes::<_, 4096>(&license) + .expect("Request should serialize correctly") + .to_vec(); + + let lpk = JubJubAffine::from(license.lsa.note_pk().as_ref()); + let license_hash = Hash::digest(Domain::Other, &[lpk.get_u(), lpk.get_v()])[0]; + session + .call::<(Vec, BlsScalar), ()>( + LICENSE_CONTRACT_ID, + "issue_license", + &(license_blob, license_hash), + POINT_LIMIT, + ) + .expect("Issuing license should succeed"); + } + + let (feeder, receiver) = mpsc::channel(); + let bh_range = 0..NUM_LICENSES as u64; + session + .feeder_call::, ()>( + LICENSE_CONTRACT_ID, + "get_licenses", + &bh_range, + u64::MAX, + feeder, + ) + .expect("Querying of the licenses should succeed") + .data; + + let pos_license_pairs: Vec<(u64, Vec)> = receiver + .iter() + .map(|bytes| rkyv::from_bytes(&bytes).expect("Should return licenses")) + .collect(); + + assert_eq!( + pos_license_pairs.len(), + NUM_LICENSES, + "Call to getting license requests should return licenses" + ); + + let owned_license = find_owned_license(&sk_user, &pos_license_pairs); + assert!( + owned_license.is_some(), + "Some license should be owned by the user" + ); + let (pos, _) = owned_license.unwrap(); + + let _merkle_opening = session + .call::(LICENSE_CONTRACT_ID, "get_merkle_opening", &pos, POINT_LIMIT) + .expect("Querying the merkle opening should succeed") + .data; +} + +#[test] +fn session_not_found() { + const SESSION_ID: u64 = 7u64; + let mut session = initialize(); + let session_id = LicenseSessionId { + id: BlsScalar::from(SESSION_ID), + }; + + let license_session = session + .call::>( + LICENSE_CONTRACT_ID, + "get_session", + &session_id, + POINT_LIMIT, + ) + .expect("Querying the session should succeed") + .data; + + assert_eq!(None::, license_session); +} + +#[test] +fn use_license_get_session() { + let mut session = initialize(); + + // NOTE: it is important that the seed is the same as in the recovery + // PUB_PARAMS initialization code + let rng = &mut StdRng::seed_from_u64(0xbeef); + + let mut f = File::open(PROVER_PATH).expect("Failed to open file."); + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer).expect("Failed to read file."); + + let prover = + Prover::try_from_bytes(buffer.as_slice()).expect("Prover failed to be created from slice."); + + let mut f = File::open(VERIFIER_PATH).expect("Failed to open file."); + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer).expect("Failed to read file."); + + let verifier = Verifier::try_from_bytes(buffer.as_slice()) + .expect("Verifier failed to be created from slice."); + + // user + let sk_user = SecretKey::random(rng); + + // license provider + let sk_lp = SecretKey::random(rng); + let pk_lp = PublicKey::from(&sk_lp); + + let request = create_request(&sk_user, &pk_lp, rng); + let attr = JubJubScalar::from(USER_ATTRIBUTES); + let license = License::new(&attr, &sk_lp, &request, rng).unwrap(); + + let license_blob = rkyv::to_bytes::<_, 4096>(&license) + .expect("Request should serialize correctly") + .to_vec(); + + let lpk = JubJubAffine::from(license.lsa.note_pk().as_ref()); + let license_hash = Hash::digest(Domain::Other, &[lpk.get_u(), lpk.get_v()])[0]; + + session + .call::<(Vec, BlsScalar), ()>( + LICENSE_CONTRACT_ID, + "issue_license", + &(license_blob, license_hash), + POINT_LIMIT, + ) + .expect("Issuing license should succeed"); + + let (feeder, receiver) = mpsc::channel(); + let bh_range = 0..10000u64; + session + .feeder_call::, ()>( + LICENSE_CONTRACT_ID, + "get_licenses", + &bh_range, + u64::MAX, + feeder, + ) + .expect("Querying the license should succeed") + .data; + + let pos_license_pairs: Vec<(u64, Vec)> = receiver + .iter() + .map(|bytes| rkyv::from_bytes(&bytes).expect("Should return licenses")) + .collect(); + + assert!( + !pos_license_pairs.is_empty(), + "Call to getting license requests should return licenses" + ); + + let owned_license = find_owned_license(&sk_user, &pos_license_pairs); + assert!( + owned_license.is_some(), + "Some license should be owned by the user" + ); + let (pos, owned_license) = owned_license.unwrap(); + + let merkle_opening = session + .call::(LICENSE_CONTRACT_ID, "get_merkle_opening", &pos, POINT_LIMIT) + .expect("Querying the merkle opening should succeed") + .data; + + let (gp, sc) = + compute_citadel_parameters(rng, &sk_user, &pk_lp, &owned_license, merkle_opening); + let circuit = circuit::LicenseCircuit::new(&gp, &sc); + + let (proof, public_inputs) = prover.prove(rng, &circuit).expect("Proving should succeed"); + + let session_id = LicenseSessionId { + id: public_inputs[0], + }; + + verifier + .verify(&proof, &public_inputs) + .expect("Verifying the circuit should succeed"); + + let use_license_arg = UseLicenseArg { + proof, + public_inputs, + }; + + session + .call::( + LICENSE_CONTRACT_ID, + "use_license", + &use_license_arg, + POINT_LIMIT, + ) + .expect("Use license should succeed"); + + assert!( + session + .call::>( + LICENSE_CONTRACT_ID, + "get_session", + &session_id, + POINT_LIMIT + ) + .expect("Get session should succeed") + .data + .is_some(), + "Call to get session should return a session" + ); +} + +#[test] +fn test_request_license() { + let mut session = initialize(); + session + .call::<(), ()>(LICENSE_CONTRACT_ID, "request_license", &(), POINT_LIMIT) + .expect("Request license should succeed"); +} From 6b11de0fa6d749847f0b1e8a007170b61caf1e35 Mon Sep 17 00:00:00 2001 From: xevisalle Date: Tue, 26 Nov 2024 03:54:08 +0100 Subject: [PATCH 08/10] Update workflows --- .github/workflows/dusk_ci.yml | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dusk_ci.yml b/.github/workflows/dusk_ci.yml index bb0b283..c3e704e 100644 --- a/.github/workflows/dusk_ci.yml +++ b/.github/workflows/dusk_ci.yml @@ -25,11 +25,22 @@ jobs: test_std: name: Stable toolchain tests - uses: dusk-network/.github/.github/workflows/run-tests.yml@main + runs-on: core + steps: + - uses: actions/checkout@v4 + - uses: dsherret/rust-toolchain-file@v1 + - run: cargo b --release + - run: rustup target add wasm32-unknown-unknown + - run: cd contract && cargo b --target wasm32-unknown-unknown --release + - run: cargo t --release test_no_std: name: Stable toolchain no_std tests - uses: dusk-network/.github/.github/workflows/run-tests.yml@main - with: - test_flags: --no-default-features - + runs-on: core + steps: + - uses: actions/checkout@v4 + - uses: dsherret/rust-toolchain-file@v1 + - run: cargo b --release + - run: rustup target add wasm32-unknown-unknown + - run: cd contract && cargo b --target wasm32-unknown-unknown --release + - run: cargo t --release --no-default-features From 398846b6436ad3baa4a1a6c7f9285585a631bd59 Mon Sep 17 00:00:00 2001 From: xevisalle Date: Tue, 26 Nov 2024 12:00:40 +0100 Subject: [PATCH 09/10] contract: Remove execution-core dependency --- contract/Cargo.toml | 7 ++++--- contract/src/license_types.rs | 4 ++-- contract/src/state.rs | 5 ++--- contract/tests/license_contract.rs | 3 ++- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/contract/Cargo.toml b/contract/Cargo.toml index 73d7887..8ca2fb2 100644 --- a/contract/Cargo.toml +++ b/contract/Cargo.toml @@ -7,8 +7,7 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -execution-core = { version = "0.1.0", git = "https://github.com/dusk-network/rusk/", branch = "master", features = ["zk"] } -dusk-bytes = "=0.1.7" +dusk-bls12_381 = { version = "=0.13.0", default-features = false } rkyv = { version = "=0.7.39", default-features = false, features = ["size_32"] } bytecheck = { version = "=0.6.12", default-features = false } poseidon-merkle = { version = "=0.7.0", features = ["rkyv-impl"] } @@ -21,13 +20,15 @@ zk-citadel = { path = "../core"} dusk-plonk = { version = "0.20", default-features = false, features = ["rkyv-impl", "alloc"] } rand_core = { version = "0.6", default-features=false, features = ["getrandom"] } reqwest = "0.12" -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread"] } build-print = "0.1.1" sha2 = { version = "0.10.8", default-features = false } [dev-dependencies] +execution-core = { version = "0.1.0", git = "https://github.com/dusk-network/rusk/", branch = "master", features = ["zk"] } rusk-abi = { version = "0.13.0-rc.0", git = "https://github.com/dusk-network/rusk/", branch = "master", default-features = false, features = ["host"] } zk-citadel = { path = "../core"} ff = { version = "=0.13.0", default-features = false } rand = { version = "=0.8.5", default-features = false } dusk-poseidon = "=0.40.0" +dusk-bytes = "=0.1.7" diff --git a/contract/src/license_types.rs b/contract/src/license_types.rs index e152441..80d13ad 100644 --- a/contract/src/license_types.rs +++ b/contract/src/license_types.rs @@ -9,13 +9,13 @@ use alloc::vec::Vec; use bytecheck::CheckBytes; use rkyv::{Archive, Deserialize, Serialize}; -use execution_core::{plonk::Proof, BlsScalar}; +use dusk_bls12_381::BlsScalar; /// Use License Argument. #[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] #[archive_attr(derive(CheckBytes))] pub struct UseLicenseArg { - pub proof: Proof, + pub proof: Vec, pub public_inputs: Vec, } diff --git a/contract/src/state.rs b/contract/src/state.rs index c577db4..14c5cb2 100644 --- a/contract/src/state.rs +++ b/contract/src/state.rs @@ -8,8 +8,7 @@ use core::ops::Range; use alloc::vec::Vec; -use dusk_bytes::Serializable; -use execution_core::BlsScalar; +use dusk_bls12_381::BlsScalar; use crate::collection::Map; use crate::error::Error; @@ -102,7 +101,7 @@ impl LicenseContractState { pub fn use_license(&mut self, use_license_arg: UseLicenseArg) { Self::assert_proof( verifier_data_license_circuit(), - use_license_arg.proof.to_bytes().to_vec(), + use_license_arg.proof, use_license_arg.public_inputs.clone(), ) .expect("Provided proof verification should succeed!"); diff --git a/contract/tests/license_contract.rs b/contract/tests/license_contract.rs index f857796..fadc362 100644 --- a/contract/tests/license_contract.rs +++ b/contract/tests/license_contract.rs @@ -9,6 +9,7 @@ extern crate alloc; use std::ops::Range; use std::sync::mpsc; +use dusk_bytes::Serializable; use dusk_poseidon::{Domain, Hash}; use execution_core::plonk::{Prover, Verifier}; use ff::Field; @@ -397,7 +398,7 @@ fn use_license_get_session() { .expect("Verifying the circuit should succeed"); let use_license_arg = UseLicenseArg { - proof, + proof: proof.to_bytes().to_vec(), public_inputs, }; From c1d492d4b62cd979ce5ce40f8682ddcc65106825 Mon Sep 17 00:00:00 2001 From: xevisalle Date: Tue, 26 Nov 2024 14:20:30 +0100 Subject: [PATCH 10/10] contract: Include keys as bytes --- contract/tests/license_contract.rs | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/contract/tests/license_contract.rs b/contract/tests/license_contract.rs index fadc362..27ec510 100644 --- a/contract/tests/license_contract.rs +++ b/contract/tests/license_contract.rs @@ -18,8 +18,12 @@ use rand::{CryptoRng, RngCore, SeedableRng}; use rkyv::{check_archived_root, Deserialize, Infallible}; use zk_citadel::{circuit, gadgets, License, Request, SessionCookie}; -const PROVER_PATH: &str = "../target/prover"; -const VERIFIER_PATH: &str = "../target/verifier"; +const PROVER_BYTES: &[u8] = include_bytes!("../../target/prover"); + +const VERIFIER_BYTES: &[u8] = include_bytes!("../../target/verifier"); + +const LICENSE_CONTRACT_BYTECODE: &[u8] = + include_bytes!("../../target/wasm32-unknown-unknown/release/license_contract.wasm"); pub type LicenseOpening = poseidon_merkle::Opening<(), { circuit::DEPTH }>; @@ -29,9 +33,6 @@ use execution_core::{ }; use rusk_abi::{ContractData, Session}; -use std::fs::File; -use std::io::Read; - #[path = "../src/license_types.rs"] mod license_types; use license_types::*; @@ -61,15 +62,11 @@ fn create_test_license( fn initialize() -> Session { let vm = rusk_abi::new_ephemeral_vm().expect("Creating a VM should succeed"); - - let bytecode = - include_bytes!("../../target/wasm32-unknown-unknown/release/license_contract.wasm"); - let mut session = rusk_abi::new_genesis_session(&vm, CHAIN_ID); session .deploy( - bytecode, + LICENSE_CONTRACT_BYTECODE, ContractData::builder() .owner(TEST_OWNER) .contract_id(LICENSE_CONTRACT_ID), @@ -307,18 +304,10 @@ fn use_license_get_session() { // PUB_PARAMS initialization code let rng = &mut StdRng::seed_from_u64(0xbeef); - let mut f = File::open(PROVER_PATH).expect("Failed to open file."); - let mut buffer = Vec::new(); - f.read_to_end(&mut buffer).expect("Failed to read file."); - let prover = - Prover::try_from_bytes(buffer.as_slice()).expect("Prover failed to be created from slice."); - - let mut f = File::open(VERIFIER_PATH).expect("Failed to open file."); - let mut buffer = Vec::new(); - f.read_to_end(&mut buffer).expect("Failed to read file."); + Prover::try_from_bytes(PROVER_BYTES).expect("Prover failed to be created from slice."); - let verifier = Verifier::try_from_bytes(buffer.as_slice()) + let verifier = Verifier::try_from_bytes(VERIFIER_BYTES) .expect("Verifier failed to be created from slice."); // user