/* 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 .
*/
use std::time::UNIX_EPOCH;
use darkfi::{
event_graph::Event,
zk::{
halo2::{Field, Value},
Proof, ProvingKey, Witness, ZkCircuit,
},
zkas::ZkBinary,
Result,
};
use darkfi_sdk::{
bridgetree::Position,
crypto::{pasta_prelude::FromUniformBytes, poseidon_hash, MerkleTree},
pasta::pallas,
};
use rand::{rngs::OsRng, CryptoRng, RngCore};
use tracing::info;
pub const RLN_APP_IDENTIFIER: pallas::Base = pallas::Base::from_raw([4242, 0, 0, 0]);
pub const RLN_TRAPDOOR_DERIVATION_PATH: pallas::Base = pallas::Base::from_raw([4211, 0, 0, 0]);
pub const RLN_NULLIFIER_DERIVATION_PATH: pallas::Base = pallas::Base::from_raw([4212, 0, 0, 0]);
/// RLN epoch genesis
pub const RLN_GENESIS: u64 = 1738688400;
/// RLN epoch length in seconds
pub const RLN_EPOCH_LEN: u64 = 600; // 10 min
pub const RLN2_SIGNAL_ZKBIN: &[u8] = include_bytes!("../../proof/rlnv2-diff-signal.zk.bin");
pub const RLN2_SLASH_ZKBIN: &[u8] = include_bytes!("../../proof/rlnv2-diff-slash.zk.bin");
/// Find closest epoch to given timestamp
pub fn closest_epoch(timestamp: u64) -> u64 {
let time_diff = timestamp - RLN_GENESIS;
let epoch_idx = time_diff as f64 / RLN_EPOCH_LEN as f64;
let rounded = epoch_idx.round() as i64;
RLN_GENESIS + (rounded * RLN_EPOCH_LEN as i64) as u64
}
/// Hash message/event modulo `Fp`
pub fn hash_event(event: &Event) -> pallas::Base {
let mut buf = [0u8; 64];
buf[..blake3::OUT_LEN].copy_from_slice(event.id().as_bytes());
pallas::Base::from_uniform_bytes(&buf)
}
#[derive(Copy, Clone)]
pub struct RlnIdentity {
pub nullifier: pallas::Base,
pub trapdoor: pallas::Base,
pub user_message_limit: u64,
/// This should increment during a single epoch and reset on new epochs
pub message_id: u64,
/// Last known epoch
pub last_epoch: u64,
}
impl RlnIdentity {
pub fn new(mut rng: (impl CryptoRng + RngCore)) -> Self {
Self {
nullifier: poseidon_hash([
RLN_NULLIFIER_DERIVATION_PATH,
pallas::Base::random(&mut rng),
]),
trapdoor: poseidon_hash([RLN_TRAPDOOR_DERIVATION_PATH, pallas::Base::random(&mut rng)]),
user_message_limit: 100,
message_id: 1,
last_epoch: closest_epoch(UNIX_EPOCH.elapsed().unwrap().as_secs()),
}
}
pub fn commitment(&self) -> pallas::Base {
poseidon_hash([
poseidon_hash([self.nullifier, self.trapdoor]),
pallas::Base::from(self.user_message_limit),
])
}
pub fn create_signal_proof(
&self,
event: &Event,
identity_tree: &MerkleTree,
identity_pos: Position,
proving_key: &ProvingKey,
) -> Result<(Proof, Vec)> {
// 1. Construct share
let epoch = pallas::Base::from(closest_epoch(event.timestamp));
let message_id = pallas::Base::from(self.message_id);
let external_nullifier = poseidon_hash([epoch, RLN_APP_IDENTIFIER]);
let a_0 = poseidon_hash([self.nullifier, self.trapdoor]);
let a_1 = poseidon_hash([a_0, external_nullifier, message_id]);
let x = hash_event(event);
let y = a_0 + x * a_1;
let internal_nullifier = poseidon_hash([a_1]);
// 2. Create Merkle proof
let identity_root = identity_tree.root(0).unwrap();
let identity_path = identity_tree.witness(identity_pos, 0).unwrap();
// 3. Create ZK proof
let witnesses = vec![
Witness::Base(Value::known(self.nullifier)),
Witness::Base(Value::known(self.trapdoor)),
Witness::MerklePath(Value::known(identity_path.clone().try_into().unwrap())),
Witness::Uint32(Value::known(u64::from(identity_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(pallas::Base::from(self.user_message_limit))),
Witness::Base(Value::known(epoch)),
];
let public_inputs =
vec![epoch, external_nullifier, x, y, internal_nullifier, identity_root.inner()];
info!(target: "crypto::rln::create_proof", "[RLN] Creating proof for event {}", event.id());
let signal_zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN)?;
let signal_circuit = ZkCircuit::new(witnesses, &signal_zkbin);
let proof = Proof::create(proving_key, &[signal_circuit], &public_inputs, &mut OsRng)?;
Ok((proof, vec![y, internal_nullifier]))
}
}
/// Recover a secret from given secret shares
#[allow(dead_code)]
pub fn sss_recover(shares: &[(pallas::Base, pallas::Base)]) -> pallas::Base {
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.0 * (share_i.0 - share_j.0).invert().unwrap();
}
}
prod *= share_j.1;
secret += prod;
}
secret
}