research/rln: Full POC

This commit is contained in:
parazyd
2023-01-23 19:42:27 +01:00
parent 06d5c79212
commit 7bcb6d004c
4 changed files with 184 additions and 63 deletions

View File

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

View File

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

View 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);
}

View File

@@ -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");
}