From 86bb7bbd3f15eaa1d032cd564b2d11c29d2244b9 Mon Sep 17 00:00:00 2001 From: parazyd Date: Mon, 27 Nov 2023 12:04:12 +0100 Subject: [PATCH] research/rln: Add RLN-V2 POC --- script/research/rln/rlnv2/.gitignore | 2 + script/research/rln/rlnv2/Cargo.toml | 19 +++ script/research/rln/rlnv2/Makefile | 26 ++++ script/research/rln/rlnv2/signal.zk | 44 +++++++ script/research/rln/rlnv2/slash.zk | 18 +++ script/research/rln/rlnv2/src/main.rs | 171 ++++++++++++++++++++++++++ 6 files changed, 280 insertions(+) create mode 100644 script/research/rln/rlnv2/.gitignore create mode 100644 script/research/rln/rlnv2/Cargo.toml create mode 100644 script/research/rln/rlnv2/Makefile create mode 100644 script/research/rln/rlnv2/signal.zk create mode 100644 script/research/rln/rlnv2/slash.zk create mode 100644 script/research/rln/rlnv2/src/main.rs diff --git a/script/research/rln/rlnv2/.gitignore b/script/research/rln/rlnv2/.gitignore new file mode 100644 index 000000000..1e7caa9ea --- /dev/null +++ b/script/research/rln/rlnv2/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock +target/ diff --git a/script/research/rln/rlnv2/Cargo.toml b/script/research/rln/rlnv2/Cargo.toml new file mode 100644 index 000000000..be8ac88cc --- /dev/null +++ b/script/research/rln/rlnv2/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "rlnv2" +version = "0.4.1" +authors = ["Dyne.org foundation "] +license = "AGPL-3.0-only" +edition = "2021" + +[workspace] + +[dependencies] +darkfi-sdk = {path = "../../../../src/sdk"} +darkfi = {path = "../../../../", features = ["zk"]} +lazy_static = "1.4.0" +rand = "0.8.5" +blake3 = "1.5.0" + +[patch.crates-io] +halo2_proofs = {git="https://github.com/parazyd/halo2", branch="v4"} +halo2_gadgets = {git="https://github.com/parazyd/halo2", branch="v4"} diff --git a/script/research/rln/rlnv2/Makefile b/script/research/rln/rlnv2/Makefile new file mode 100644 index 000000000..fc8f84610 --- /dev/null +++ b/script/research/rln/rlnv2/Makefile @@ -0,0 +1,26 @@ +.POSIX: + +# Cargo binary +CARGO = cargo +nightly + +# Compile target +RUST_TARGET = $(shell rustc -Vv | grep '^host: ' | cut -d' ' -f2) + +PROOFS_SRC = signal.zk slash.zk +PROOFS_BIN = $(PROOFS_SRC:=.bin) + +ZKAS = ../../../../zkas + +all: $(PROOFS_BIN) + $(CARGO) run --target=$(RUST_TARGET) --release + +$(ZKAS): + $(MAKE) -C ../../../../zkas + +$(PROOFS_BIN): $(ZKAS) $(PROOFS_SRC) + $(ZKAS) $(basename $@) -o $@ + +clean: + rm -rf target $(PROOFS_BIN) Cargo.lock + +.PHONY: all clean diff --git a/script/research/rln/rlnv2/signal.zk b/script/research/rln/rlnv2/signal.zk new file mode 100644 index 000000000..52b3408a3 --- /dev/null +++ b/script/research/rln/rlnv2/signal.zk @@ -0,0 +1,44 @@ +k = 13; +field = "pallas"; + +constant "RlnSignal" {} + +witness "RlnSignal" { + Base identity_nullifier, + Base identity_trapdoor, + + MerklePath identity_path, + Uint32 identity_leaf_pos, + + Base x, # The message hash + Base message_id, + Base message_limit, + + # These are public + Base epoch, + Base rln_identifier, +} + +circuit "RlnSignal" { + constrain_instance(message_limit); + constrain_instance(epoch); + + external_nullifier = poseidon_hash(epoch, rln_identifier); + constrain_instance(external_nullifier); + + a_0 = poseidon_hash(identity_nullifier, identity_trapdoor); + a_1 = poseidon_hash(a_0, external_nullifier, message_id); + + internal_nullifier = poseidon_hash(a_1); + constrain_instance(internal_nullifier); + + # y = a_0 + x * a_1 + x_a_1 = base_mul(x, a_1); + y = base_add(a_0, x_a_1); + constrain_instance(x); + constrain_instance(y); + + identity_commitment = poseidon_hash(a_0); + root = merkle_root(identity_leaf_pos, identity_path, identity_commitment); + constrain_instance(root); +} diff --git a/script/research/rln/rlnv2/slash.zk b/script/research/rln/rlnv2/slash.zk new file mode 100644 index 000000000..19bc0f192 --- /dev/null +++ b/script/research/rln/rlnv2/slash.zk @@ -0,0 +1,18 @@ +k = 13; +field = "pallas"; + +constant "RlnSlash" {} + +witness "RlnSlash" { + Base secret_key, + MerklePath identity_path, + Uint32 identity_leaf_pos, +} + +circuit "RlnSlash" { + identity_derivation_path = witness_base(11); + + identity_commit = poseidon_hash(identity_derivation_path, secret_key); + root = merkle_root(identity_leaf_pos, identity_path, identity_commit); + constrain_instance(root); +} diff --git a/script/research/rln/rlnv2/src/main.rs b/script/research/rln/rlnv2/src/main.rs new file mode 100644 index 000000000..e2d54ef48 --- /dev/null +++ b/script/research/rln/rlnv2/src/main.rs @@ -0,0 +1,171 @@ +/* This file is part of DarkFi (https://dark.fi) + * + * Copyright (C) 2020-2023 Dyne.org foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::{ + collections::BTreeMap, + time::{Instant, UNIX_EPOCH}, +}; + +use darkfi::{ + zk::{empty_witnesses, halo2::Value, Proof, ProvingKey, VerifyingKey, Witness, ZkCircuit}, + zkas::ZkBinary, +}; +use darkfi_sdk::{ + bridgetree::Position, + crypto::{pasta_prelude::Field, poseidon_hash, MerkleNode, MerkleTree}, + pasta::{group::ff::FromUniformBytes, pallas}, +}; +use rand::rngs::OsRng; + +struct Account { + identity_nullifier: pallas::Base, + identity_trapdoor: pallas::Base, + identity_leaf_pos: Position, + msgid: pallas::Base, +} + +impl Account { + fn register( + membership_tree: &mut MerkleTree, + membership_map: &mut BTreeMap, + ) -> Self { + let identity_nullifier = pallas::Base::random(&mut OsRng); + let identity_trapdoor = pallas::Base::random(&mut OsRng); + + let identity_secret_hash = poseidon_hash([identity_nullifier, identity_trapdoor]); + let identity_commitment = poseidon_hash([identity_secret_hash]); + + membership_tree.append(MerkleNode::from(identity_commitment)); + let identity_leaf_pos = membership_tree.mark().unwrap(); + membership_map.insert(identity_leaf_pos, identity_commitment); + + Self { identity_nullifier, identity_trapdoor, identity_leaf_pos } + } +} + +/// Hash message modulo Fp +/// In DarkIRC/eventgraph this could be the event ID +fn hash_message(msg: &str) -> pallas::Base { + let message_hash = blake3::hash(msg.as_bytes()); + + let mut buf = [0u8; 64]; + buf[..blake3::OUT_LEN].copy_from_slice(message_hash.as_bytes()); + pallas::Base::from_uniform_bytes(&buf) +} + +fn main() { + // There exists a Merkle tree of identity commitments that serves + // as the user registry. + let mut membership_tree = MerkleTree::new(100); + // Since bridgetree is append-only, we'll maintain a BTreeMap of all the + // identity commitments in their indexes and whenever some idenity is banned + // we'll zero out that leaf and rebuild the bridgetree from the BTreeMap. + let mut membership_map = BTreeMap::new(); + + // The global message limit per-account per-epoch + let message_limit = pallas::Base::from(3); + + // Per-app identifier + let rln_identifier = pallas::Base::from(42); + + // Current epoch + let epoch = pallas::Base::from(UNIX_EPOCH.elapsed().unwrap().as_secs() as u64); + + // Register three accounts + let account0 = Account::register(&mut membership_tree, &mut membership_map); + let account0_msgid = pallas::Base::from(0); + + /* + let account1 = Account::register(&mut membership_tree, &mut membership_map); + let account1_msgid = pallas::Base::from(0); + + let account2 = Account::register(&mut membership_tree, &mut membership_map); + let account2_msgid = pallas::Base::from(0); + */ + + // ========== + // Signalling + // ========== + let signal_zkbin = include_bytes!("../signal.zk.bin"); + let signal_zkbin = ZkBinary::decode(signal_zkbin).unwrap(); + let signal_empty_circuit = + ZkCircuit::new(empty_witnesses(&signal_zkbin).unwrap(), &signal_zkbin); + + print!("[Signal] Building Proving key... "); + let now = Instant::now(); + let signal_pk = ProvingKey::build(signal_zkbin.k, &signal_empty_circuit); + println!("[{:?}]", now.elapsed()); + + print!("[Signal] Building Verifying key... "); + let now = Instant::now(); + let signal_vk = VerifyingKey::build(signal_zkbin.k, &signal_empty_circuit); + println!("[{:?}]", now.elapsed()); + + // ========================= + // Account 0 sends a message + // ========================= + + // 1. Construct share: + let external_nullifier = poseidon_hash([epoch, rln_identifier]); + let a_0 = poseidon_hash([account0.identity_nullifier, account0.identity_trapdoor]); + let a_1 = poseidon_hash([a_0, external_nullifier, account0_msgid]); + let internal_nullifier = poseidon_hash([a_1]); + let x = hash_message("hello i wanna spam"); + let y = a_0 + x * a_1; + + // 2. Create Merkle proof: + let identity_root = membership_tree.root(0).unwrap(); + let identity_path = membership_tree.witness(account0.identity_leaf_pos, 0).unwrap(); + + // 3. Create ZK proof: + let witnesses = vec![ + Witness::Base(Value::known(account0.identity_nullifier)), + Witness::Base(Value::known(account0.identity_trapdoor)), + Witness::MerklePath(Value::known(identity_path.clone().try_into().unwrap())), + Witness::Uint32(Value::known(u64::from(account0.identity_leaf_pos).try_into().unwrap())), + Witness::Base(Value::known(x)), + Witness::Base(Value::known(account0_msgid)), + Witness::Base(Value::known(message_limit)), + Witness::Base(Value::known(epoch)), + Witness::Base(Value::known(rln_identifier)), + ]; + + let public_inputs = vec![ + message_limit, + epoch, + external_nullifier, + internal_nullifier, + x, + y, + identity_root.inner(), + ]; + + print!("[Signal] Creating ZK proof for 0:0..."); + let now = Instant::now(); + let signal_circuit = ZkCircuit::new(witnesses, &signal_zkbin); + let proof = Proof::create(&signal_pk, &[signal_circuit], &public_inputs, &mut OsRng).unwrap(); + println!("[{:?}]", now.elapsed()); + + // ============ + // Verification + // ============ + print!("[Signal] Verifying ZK proof... "); + let now = Instant::now(); + assert!(proof.verify(&signal_vk, &public_inputs).is_ok()); + println!("[{:?}]", now.elapsed()); +}