Przeglądaj źródła

research/rln: Full POC

parazyd 3 lat temu
rodzic
commit
7bcb6d004c

+ 2 - 1
script/research/rln/.gitignore

@@ -1,2 +1,3 @@
 Cargo.lock
-target/
+target/
+*.bin

+ 1 - 0
script/research/rln/Cargo.toml

@@ -9,4 +9,5 @@ edition = "2021"
 
 [dependencies]
 darkfi-sdk = {path = "../../../src/sdk"}
+darkfi = {path = "../../../", features = ["zk"]}
 rand = "0.8.5"

+ 34 - 0
script/research/rln/signal.zk

@@ -0,0 +1,34 @@
+constant "RlnSignal" {}
+
+contract "RlnSignal" {
+	Base secret_key,
+	MerklePath identity_path,
+	Uint32 identity_leaf_pos,
+
+	# These are public so have to be properly constructed
+	Base message_hash, # x
+	Base epoch,
+	Base rln_identifier,
+}
+
+circuit "RlnSignal" {
+	constrain_instance(epoch);
+	constrain_instance(rln_identifier);
+	constrain_instance(message_hash);
+
+	# This has to be the same constant used outside
+	key_derivation_path = witness_base(0);
+	nf_derivation_path = witness_base(1);
+	identity_commit = poseidon_hash(key_derivation_path, secret_key);
+	root = merkle_root(identity_leaf_pos, identity_path, identity_commit);
+	constrain_instance(root);
+
+	external_nullifier = poseidon_hash(epoch, rln_identifier);
+	a1 = poseidon_hash(secret_key, external_nullifier);
+	internal_nullifier = poseidon_hash(nf_derivation_path, a1);
+	constrain_instance(internal_nullifier);
+
+	y_a = base_mul(a1, message_hash);
+	y = base_add(y_a, secret_key);
+	constrain_instance(y);
+}

+ 144 - 59
script/research/rln/src/main.rs

@@ -35,80 +35,165 @@
 //! registered users, therefore disabling their ability to send future
 //! messages and requiring them to register with a new key.
 
+use darkfi::{
+    zk::{
+        empty_witnesses, halo2::Value, proof::VerifyingKey, Proof, ProvingKey, Witness, ZkCircuit,
+    },
+    zkas::ZkBinary,
+};
 use darkfi_sdk::{
-    crypto::{pasta_prelude::*, poseidon_hash, MerkleNode, MerkleTree, SecretKey},
+    crypto::{pasta_prelude::*, poseidon_hash, MerkleNode, MerkleTree},
     incrementalmerkletree::Tree,
-    pasta::pallas,
+    pasta::{arithmetic::CurveExt, pallas},
 };
 use rand::rngs::OsRng;
 
-const IDENTITY_COMMITMENT_PREFIX: u64 = 42;
-
-#[derive(Copy, Clone, Debug)]
-struct ShamirPoint {
-    pub x: pallas::Base,
-    pub y: pallas::Base,
-}
-
-fn sss_share(secret: pallas::Base, n_shares: usize, threshold: usize) -> Vec<ShamirPoint> {
-    assert!(threshold > 2 && n_shares > threshold);
-
-    let mut coefficients = vec![secret];
-    for _ in 0..threshold - 1 {
-        coefficients.push(pallas::Base::random(&mut OsRng));
-    }
-
-    let mut shares = Vec::with_capacity(n_shares);
-
-    for x in 1..n_shares + 1 {
-        let x = pallas::Base::from(x as u64);
-        let mut y = pallas::Base::zero();
-        for coeff in coefficients.iter().rev() {
-            y *= x;
-            y += coeff;
-        }
-
-        shares.push(ShamirPoint { x, y })
-    }
+fn main() {
+    // This should be unique constant per application
+    let rln_identifier = pallas::Base::from(42);
+    let key_derivation_path = pallas::Base::from(0);
+    let nf_derivation_path = pallas::Base::from(1);
 
-    shares
-}
+    let epoch = pallas::Base::from(1674495551);
+    let external_nullifier = poseidon_hash([epoch, rln_identifier]);
 
-fn sss_recover(shares: &[ShamirPoint]) -> pallas::Base {
-    assert!(shares.len() > 1);
+    // The identity commitment should be something that cannot be precalculated
+    // for usage in the future, and possibly also has to be some kind of puzzle
+    // that is costly to precalculate.
+    // Alternatively, it could be economic stake of funds which could then be
+    // lost if spam is detected and acted upon.
+    let alice_secret_key = pallas::Base::random(&mut OsRng);
+    let alice_identity_commitment = poseidon_hash([key_derivation_path, alice_secret_key]);
 
+    // ============
+    // Registration
+    // ============
+    let mut membership_tree = MerkleTree::new(100);
+    membership_tree.append(&MerkleNode::from(alice_identity_commitment));
+    let alice_identity_leafpos = membership_tree.witness().unwrap();
+
+    // ==========
+    // Signalling
+    // ==========
+
+    // Our secret-sharing polynomial will be A(x) = a_1*x + a_0, where:
+    // a_0 = secret_key,
+    // a_1 = Poseidon(a_0, external_nullifier)
+    // To send a message, the user has to come up with a share - an (x, y) on the polynomial.
+    // x = Poseidon(message), y = A(x)
+    // Thus, if the same epoch user sends more than one message, their secret can be recovered.
+
+    // TODO: I don't know a better way to do this:
+    let message = b"hello i wanna spam";
+    let hasher = pallas::Point::hash_to_curve("ircd_domain");
+    let message_point = hasher(message);
+    let message_coords = message_point.to_affine().coordinates().unwrap();
+    let x = poseidon_hash([*message_coords.x(), *message_coords.y()]);
+    let y = poseidon_hash([alice_secret_key, external_nullifier]) * x + alice_secret_key;
+
+    let internal_nullifier =
+        poseidon_hash([nf_derivation_path, poseidon_hash([alice_secret_key, external_nullifier])]);
+
+    let identity_root = membership_tree.root(0).unwrap();
+    let alice_identity_path =
+        membership_tree.authentication_path(alice_identity_leafpos, &identity_root).unwrap();
+
+    // NIZK stuff
+    let zkbin = include_bytes!("../signal.zk.bin");
+    let rln_zkbin = ZkBinary::decode(zkbin).unwrap();
+    let rln_empty_circuit = ZkCircuit::new(empty_witnesses(&rln_zkbin), rln_zkbin.clone());
+
+    println!("Building Proving key...");
+    let rln_pk = ProvingKey::build(13, &rln_empty_circuit);
+    println!("Building Verifying key...");
+    let rln_vk = VerifyingKey::build(13, &rln_empty_circuit);
+
+    // Alice has her witnesses and creates a NIZK proof
+    let prover_witnesses = vec![
+        Witness::Base(Value::known(alice_secret_key)),
+        Witness::MerklePath(Value::known(alice_identity_path.clone().try_into().unwrap())),
+        Witness::Uint32(Value::known(u64::from(alice_identity_leafpos).try_into().unwrap())),
+        Witness::Base(Value::known(x)),
+        Witness::Base(Value::known(epoch)),
+        Witness::Base(Value::known(rln_identifier)),
+    ];
+
+    let rln_circuit = ZkCircuit::new(prover_witnesses, rln_zkbin.clone());
+    let public_inputs = vec![
+        epoch,
+        rln_identifier,
+        x, // <-- message hash
+        identity_root.inner(),
+        internal_nullifier,
+        y,
+    ];
+
+    println!("Creating ZK proof...");
+    let proof = Proof::create(&rln_pk, &[rln_circuit], &public_inputs, &mut OsRng).unwrap();
+
+    // ============
+    // Verification
+    // ============
+    println!("Verifying ZK proof...");
+    assert!(proof.verify(&rln_vk, &public_inputs).is_ok());
+
+    let mut alice_shares = vec![(public_inputs[2], public_inputs[5])];
+
+    // Now if Alice sends another message in the same epoch, we should be able to
+    // get their secret key and ban them
+    let message = b"hello i'm spamming";
+    let hasher = pallas::Point::hash_to_curve("ircd_domain");
+    let message_point = hasher(message);
+    let message_coords = message_point.to_affine().coordinates().unwrap();
+    let x = poseidon_hash([*message_coords.x(), *message_coords.y()]);
+    let y = poseidon_hash([alice_secret_key, external_nullifier]) * x + alice_secret_key;
+
+    // Same epoch and account, different message
+    let prover_witnesses = vec![
+        Witness::Base(Value::known(alice_secret_key)),
+        Witness::MerklePath(Value::known(alice_identity_path.try_into().unwrap())),
+        Witness::Uint32(Value::known(u64::from(alice_identity_leafpos).try_into().unwrap())),
+        Witness::Base(Value::known(x)),
+        Witness::Base(Value::known(epoch)),
+        Witness::Base(Value::known(rln_identifier)),
+    ];
+
+    let rln_circuit = ZkCircuit::new(prover_witnesses, rln_zkbin);
+
+    let public_inputs = vec![
+        epoch,
+        rln_identifier,
+        x, // <-- message hash
+        identity_root.inner(),
+        internal_nullifier,
+        y,
+    ];
+
+    println!("Creating ZK proof...");
+    let proof = Proof::create(&rln_pk, &[rln_circuit], &public_inputs, &mut OsRng).unwrap();
+
+    println!("Verifying ZK proof...");
+    assert!(proof.verify(&rln_vk, &public_inputs).is_ok());
+    alice_shares.push((public_inputs[2], public_inputs[5]));
+
+    // ========
+    // Slashing
+    // ========
+    // We should be able to retrieve Alice's secret key because she sent two
+    // messages in the same epoch.
     let mut secret = pallas::Base::zero();
-
-    for (j, share_j) in shares.iter().enumerate() {
+    for (j, share_j) in alice_shares.iter().enumerate() {
         let mut prod = pallas::Base::one();
-
-        for (i, share_i) in shares.iter().enumerate() {
+        for (i, share_i) in alice_shares.iter().enumerate() {
             if i != j {
-                prod *= share_i.x * (share_i.x - share_j.x).invert().unwrap();
+                prod *= share_i.0 * (share_i.0 - share_j.0).invert().unwrap();
             }
         }
 
-        prod *= share_j.y;
+        prod *= share_j.1;
         secret += prod;
     }
 
-    secret
-}
-
-fn main() {
-    let mut membership_tree = MerkleTree::new(100);
-
-    // The identity commitment should be something that cannot be precalculated
-    // for usage in the future, and possibly also has to be some kind of puzzle
-    // that is costly to precalculate.
-    // Alternatively, it could be economic stake of funds which could then be
-    // lost if spam is detected and acted upon.
-    let alice_secret_key = SecretKey::random(&mut OsRng);
-    let alice_identity_commitment =
-        poseidon_hash([pallas::Base::from(IDENTITY_COMMITMENT_PREFIX), alice_secret_key.inner()]);
-
-    // The user registers, and their identity commitment gets added into the
-    // membership tree:
-    membership_tree.append(&MerkleNode::from(alice_identity_commitment));
-    let alice_identity_leaf_pos = membership_tree.witness().unwrap();
+    assert_eq!(secret, alice_secret_key);
+    println!("u banned");
 }