Browse Source

research/rlnv2: SMT registration

x 10 months ago
parent
commit
c8e21a7b5a
3 changed files with 254 additions and 97 deletions
  1. 22 0
      script/research/rlnv2/register.zk
  2. 71 97
      script/research/rlnv2/src/main.rs
  3. 161 0
      script/research/rlnv2/src/old.rs

+ 22 - 0
script/research/rlnv2/register.zk

@@ -0,0 +1,22 @@
+k = 13;
+field = "pallas";
+
+constant "RlnRegister" {}
+
+witness "RlnRegister" {
+	Base identity_nullifier,
+	Base identity_trapdoor,
+	Base user_message_limit,
+	Base message_limit,
+}
+
+circuit "RlnRegister" {
+	identity_secret = poseidon_hash(identity_nullifier, identity_trapdoor);
+	identity_secret_hash = poseidon_hash(identity_secret, user_message_limit);
+	identity_commitment = poseidon_hash(identity_secret_hash);
+
+	less_than_strict(user_message_limit, message_limit);
+
+	constrain_instance(identity_commitment);
+	constrain_instance(message_limit);
+}

+ 71 - 97
script/research/rlnv2/src/main.rs

@@ -16,53 +16,43 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::{
-    collections::BTreeMap,
-    time::{Instant, UNIX_EPOCH},
-};
+use std::time::Instant;
 
 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},
+    crypto::{
+        pasta_prelude::Field,
+        poseidon_hash,
+        smt::{MemoryStorageFp, PoseidonFp, SmtMemoryFp, EMPTY_NODES_FP},
+    },
     pasta::{group::ff::FromUniformBytes, pallas},
 };
 use rand::rngs::OsRng;
 
-struct Account {
+struct Identity {
     identity_nullifier: pallas::Base,
     identity_trapdoor: pallas::Base,
-    identity_leaf_pos: Position,
     user_message_limit: pallas::Base,
 }
 
-impl Account {
-    fn register(
-        membership_tree: &mut MerkleTree,
-        membership_map: &mut BTreeMap<pallas::Base, Position>,
-    ) -> 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 user_message_limit = pallas::Base::from(100);
-        let identity_commitment = poseidon_hash([identity_secret_hash, user_message_limit]);
-
-        membership_tree.append(MerkleNode::from(identity_commitment));
-        let identity_leaf_pos = membership_tree.mark().unwrap();
-        membership_map.insert(identity_commitment, identity_leaf_pos);
-
+impl Identity {
+    fn new(user_message_limit: pallas::Base) -> Self {
         Self {
-            identity_nullifier,
-            identity_trapdoor,
-            identity_leaf_pos,
-            // message id < user_message_limit
+            identity_nullifier: pallas::Base::random(&mut OsRng),
+            identity_trapdoor: pallas::Base::random(&mut OsRng),
             user_message_limit,
         }
     }
+
+    fn commitment(&self) -> pallas::Base {
+        let identity_secret = poseidon_hash([self.identity_nullifier, self.identity_trapdoor]);
+        let identity_secret_hash = poseidon_hash([identity_secret, self.user_message_limit]);
+
+        poseidon_hash([identity_secret_hash])
+    }
 }
 
 /// Hash message modulo Fp
@@ -76,86 +66,70 @@ fn hash_message(msg: &str) -> pallas::Base {
 }
 
 fn main() {
-    // There exists a Merkle tree of identity commitments that serves
-    // as the user registry.
-    let mut membership_tree = MerkleTree::new(1);
-    // Since bridgetree is append-only, we'll maintain a BTreeMap of all the
-    // identity commitments and their indexes. Whenever some idenity is banned
-    // we'll zero out that leaf and rebuild the bridgetree from the BTreeMap.
-    let mut membership_map = BTreeMap::new();
+    // There exists a Sparse Merkle Tree of identity commitments that
+    // serves as the user registry. If a leaf is NULL, it should mean
+    // that the identity is non-existent and thus should not be accepted.
+    let hasher = PoseidonFp::new();
+    let store = MemoryStorageFp::new();
+    let mut identity_tree = SmtMemoryFp::new(store, hasher.clone(), &EMPTY_NODES_FP);
 
     // 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);
+    // Create three accounts
+    let id0 = Identity::new(pallas::Base::from(5));
+    let id1 = Identity::new(pallas::Base::from(2));
+    let id2 = Identity::new(pallas::Base::from(1));
 
-    // Register account
-    let account0 = Account::register(&mut membership_tree, &mut membership_map);
-
-    // ==========
-    // Signalling
-    // ==========
-    let signal_zkbin = include_bytes!("../signal.zk.bin");
-    let signal_zkbin = ZkBinary::decode(signal_zkbin, false).unwrap();
-    let signal_empty_circuit =
-        ZkCircuit::new(empty_witnesses(&signal_zkbin).unwrap(), &signal_zkbin);
+    // ============
+    // Registration
+    // ============
+    let register_zkbin = include_bytes!("../register.zk.bin");
+    let register_zkbin = ZkBinary::decode(register_zkbin).unwrap();
+    let register_empty_circuit =
+        ZkCircuit::new(empty_witnesses(&register_zkbin).unwrap(), &register_zkbin);
 
-    print!("[Signal] Building Proving key... ");
+    print!("[Register] Building Proving key... ");
     let now = Instant::now();
-    let signal_pk = ProvingKey::build(signal_zkbin.k, &signal_empty_circuit);
+    let register_pk = ProvingKey::build(register_zkbin.k, &register_empty_circuit);
     println!("[{:?}]", now.elapsed());
 
-    print!("[Signal] Building Verifying key... ");
+    print!("[Register] Building Verifying key... ");
     let now = Instant::now();
-    let signal_vk = VerifyingKey::build(signal_zkbin.k, &signal_empty_circuit);
+    let register_vk = VerifyingKey::build(register_zkbin.k, &register_empty_circuit);
     println!("[{:?}]", now.elapsed());
 
-    // =========================
-    // Account 0 sends a message
-    // =========================
-
-    // 1. Construct share:
-    let message_id = pallas::Base::from(1);
-    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, message_id]);
-    let x = hash_message("hello i wanna spam");
-    let y = a_0 + x * a_1;
-
-    let internal_nullifier = poseidon_hash([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(external_nullifier)),
-        Witness::Base(Value::known(message_id)),
-        Witness::Base(Value::known(account0.user_message_limit)),
-        Witness::Base(Value::known(epoch)),
-    ];
-
-    let public_inputs =
-        vec![epoch, external_nullifier, x, y, internal_nullifier, 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());
+    for (i, id) in [id0, id1, id2].iter().enumerate() {
+        // Create ZK proof
+        // This 6 message limit is arbitrary and should likely be based on stake.
+        let witnesses = vec![
+            Witness::Base(Value::known(id.identity_nullifier)),
+            Witness::Base(Value::known(id.identity_trapdoor)),
+            Witness::Base(Value::known(id.user_message_limit)),
+            Witness::Base(Value::known(pallas::Base::from(6))),
+        ];
+        let public_inputs = vec![id.commitment(), pallas::Base::from(6)];
+
+        print!("[Register] Creating ZK proof for id{i}... ");
+        let now = Instant::now();
+        let register_circuit = ZkCircuit::new(witnesses, &register_zkbin);
+        let proof =
+            Proof::create(&register_pk, &[register_circuit], &public_inputs, &mut OsRng).unwrap();
+        println!("[{:?}]", now.elapsed());
+
+        // Verify ZK proof
+        print!("[Register] Verifying ZK proof for id{i}... ");
+        assert!(proof.verify(&register_vk, &public_inputs).is_ok());
+        println!("[{:?}]", now.elapsed());
+
+        let leaf = vec![id.commitment()];
+        let leaf: Vec<_> = leaf.into_iter().map(|l| (l, l)).collect();
+        // TODO: Should verify that identity doesn't exist yet before insert.
+        identity_tree.insert_batch(leaf.clone()).unwrap(); // leaf == pos
+        assert_eq!(leaf[0].0, id.commitment());
+        assert_eq!(leaf[0].1, id.commitment());
+    }
 
-    // ============
-    // Verification
-    // ============
-    print!("[Signal] Verifying ZK proof... ");
-    let now = Instant::now();
-    assert!(proof.verify(&signal_vk, &public_inputs).is_ok());
-    println!("[{:?}]", now.elapsed());
+    // At this point we have 3 identities registered. They also all have
+    // different message limits per epoch.
 }

+ 161 - 0
script/research/rlnv2/src/old.rs

@@ -0,0 +1,161 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 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,
+    user_message_limit: pallas::Base,
+}
+
+impl Account {
+    fn register(
+        membership_tree: &mut MerkleTree,
+        membership_map: &mut BTreeMap<pallas::Base, Position>,
+    ) -> 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 user_message_limit = pallas::Base::from(100);
+        let identity_commitment = poseidon_hash([identity_secret_hash, user_message_limit]);
+
+        membership_tree.append(MerkleNode::from(identity_commitment));
+        let identity_leaf_pos = membership_tree.mark().unwrap();
+        membership_map.insert(identity_commitment, identity_leaf_pos);
+
+        Self {
+            identity_nullifier,
+            identity_trapdoor,
+            identity_leaf_pos,
+            // message id < user_message_limit
+            user_message_limit,
+        }
+    }
+}
+
+/// 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(1);
+    // Since bridgetree is append-only, we'll maintain a BTreeMap of all the
+    // identity commitments and their indexes. Whenever some idenity is banned
+    // we'll zero out that leaf and rebuild the bridgetree from the BTreeMap.
+    let mut membership_map = BTreeMap::new();
+
+    // 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 account
+    let account0 = Account::register(&mut membership_tree, &mut membership_map);
+
+    // ==========
+    // 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 message_id = pallas::Base::from(1);
+    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, message_id]);
+    let x = hash_message("hello i wanna spam");
+    let y = a_0 + x * a_1;
+
+    let internal_nullifier = poseidon_hash([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(external_nullifier)),
+        Witness::Base(Value::known(message_id)),
+        Witness::Base(Value::known(account0.user_message_limit)),
+        Witness::Base(Value::known(epoch)),
+    ];
+
+    let public_inputs =
+        vec![epoch, external_nullifier, x, y, internal_nullifier, 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());
+}