mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-09 14:48:08 -05:00
research/rln: Full POC
This commit is contained in:
3
script/research/rln/.gitignore
vendored
3
script/research/rln/.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
Cargo.lock
|
||||
target/
|
||||
target/
|
||||
*.bin
|
||||
|
||||
@@ -9,4 +9,5 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
darkfi-sdk = {path = "../../../src/sdk"}
|
||||
darkfi = {path = "../../../", features = ["zk"]}
|
||||
rand = "0.8.5"
|
||||
|
||||
34
script/research/rln/signal.zk
Normal file
34
script/research/rln/signal.zk
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
shares
|
||||
}
|
||||
|
||||
fn sss_recover(shares: &[ShamirPoint]) -> pallas::Base {
|
||||
assert!(shares.len() > 1);
|
||||
|
||||
let mut secret = pallas::Base::zero();
|
||||
|
||||
for (j, share_j) in shares.iter().enumerate() {
|
||||
let mut prod = pallas::Base::one();
|
||||
|
||||
for (i, share_i) in shares.iter().enumerate() {
|
||||
if i != j {
|
||||
prod *= share_i.x * (share_i.x - share_j.x).invert().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
prod *= share_j.y;
|
||||
secret += prod;
|
||||
}
|
||||
|
||||
secret
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut membership_tree = MerkleTree::new(100);
|
||||
// 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);
|
||||
|
||||
let epoch = pallas::Base::from(1674495551);
|
||||
let external_nullifier = poseidon_hash([epoch, rln_identifier]);
|
||||
|
||||
// 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()]);
|
||||
let alice_secret_key = pallas::Base::random(&mut OsRng);
|
||||
let alice_identity_commitment = poseidon_hash([key_derivation_path, alice_secret_key]);
|
||||
|
||||
// The user registers, and their identity commitment gets added into the
|
||||
// membership tree:
|
||||
// ============
|
||||
// Registration
|
||||
// ============
|
||||
let mut membership_tree = MerkleTree::new(100);
|
||||
membership_tree.append(&MerkleNode::from(alice_identity_commitment));
|
||||
let alice_identity_leaf_pos = membership_tree.witness().unwrap();
|
||||
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 alice_shares.iter().enumerate() {
|
||||
let mut prod = pallas::Base::one();
|
||||
for (i, share_i) in alice_shares.iter().enumerate() {
|
||||
if i != j {
|
||||
prod *= share_i.0 * (share_i.0 - share_j.0).invert().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
prod *= share_j.1;
|
||||
secret += prod;
|
||||
}
|
||||
|
||||
assert_eq!(secret, alice_secret_key);
|
||||
println!("u banned");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user