Răsfoiți Sursa

research/rln: Add RLN-V2 POC

parazyd 2 ani în urmă
părinte
comite
86bb7bbd3f

+ 2 - 0
script/research/rln/rlnv2/.gitignore

@@ -0,0 +1,2 @@
+Cargo.lock
+target/

+ 19 - 0
script/research/rln/rlnv2/Cargo.toml

@@ -0,0 +1,19 @@
+[package]
+name = "rlnv2"
+version = "0.4.1"
+authors = ["Dyne.org foundation <foundation@dyne.org>"]
+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"}

+ 26 - 0
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

+ 44 - 0
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);
+}

+ 18 - 0
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);
+}

+ 171 - 0
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 <https://www.gnu.org/licenses/>.
+ */
+
+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<Position, pallas::Base>,
+    ) -> 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());
+}