rln.rs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. //! Rate-Limit Nullifier (RLN-V2, Diff variant) integration for the
  19. //! Event Graph.
  20. //!
  21. //! RLN lets anonymous users post to the DAG at a configurable rate.
  22. //! If a user exceeds their rate limit (by reusing a `message_id`
  23. //! within the same epoch), their shares reveal their `identity_secret_hash`
  24. //! via Shamir's Secret Sharing, and anyone can produce a slashing
  25. //! proof to remove them from the identity tree.
  26. use std::{
  27. collections::{BTreeMap, BTreeSet, VecDeque},
  28. io::Cursor,
  29. };
  30. use darkfi_sdk::{
  31. crypto::{
  32. pasta_prelude::{FromUniformBytes, PrimeField},
  33. poseidon_hash,
  34. smt::{MemoryStorageFp, PoseidonFp, SmtMemoryFp, EMPTY_NODES_FP, SMT_FP_DEPTH},
  35. },
  36. pasta::pallas,
  37. };
  38. use darkfi_serial::{async_trait, FutAsyncWriteExt, SerialDecodable, SerialEncodable};
  39. use halo2_proofs::{arithmetic::Field, circuit::Value};
  40. use rand::rngs::OsRng;
  41. use sled_overlay::sled;
  42. use tracing::info;
  43. use super::Event;
  44. use crate::{
  45. zk::{empty_witnesses, Proof, ProvingKey, VerifyingKey, Witness, ZkCircuit},
  46. zkas::ZkBinary,
  47. Error, Result,
  48. };
  49. pub const RLN2_REGISTER_ZKBIN: &[u8] = include_bytes!("proof/rlnv2-diff-register.zk.bin");
  50. pub const RLN2_SIGNAL_ZKBIN: &[u8] = include_bytes!("proof/rlnv2-diff-signal.zk.bin");
  51. pub const RLN2_SLASH_ZKBIN: &[u8] = include_bytes!("proof/rlnv2-diff-slash.zk.bin");
  52. pub const GENESIS_BLOB_GUARD: &[u8] = b"darkfi-rln-genesis-v1";
  53. pub const GENESIS_USER_MSG_LIMIT: u64 = MAX_MSG_LIMIT;
  54. /// RLN epoch genesis in millis.
  55. /// Used as the time-zero reference for epoch numbering.
  56. pub const RLN_GENESIS: u64 = 1_738_688_400_000;
  57. /// Duration of one RLN epoch in millis (10 minutes).
  58. pub const RLN_EPOCH_LEN: u64 = 600_000;
  59. /// Network-wide cap on `user_message_limit`. Registrations must
  60. /// pass this as the `max_message_limit` public input to the register
  61. /// circuit, and verifiers must reject anything else.
  62. pub const MAX_MSG_LIMIT: u64 = 100;
  63. /// How many consecutive epochs of share metadata to keep around for
  64. /// reuse detection. Must comfortably exceed [`crate::event_graph::EVENT_TIME_DRIFT`]
  65. /// divided by [`RLN_EPOCH_LEN`] so that an honest event arriving late
  66. /// across an epoch boundary still finds its sibling shares.
  67. const METADATA_RETAIN_EPOCHS: u64 = 2;
  68. /// Number of recent SMT roots to keep for signal proof verification.
  69. /// Allows valid proofs created against a slightly stale tree to
  70. /// still verify while registrations propagate across the network.
  71. const ROOT_HISTORY_SIZE: usize = 16;
  72. /// Wrapper for an RLN application identifier.
  73. ///
  74. /// Per RLN-V1 Technical overview, this is a "random finite field
  75. /// value unique per RLN app", used to prevent cross-app secret
  76. /// correlation when the same identity credentials are reused across
  77. /// different applications. It is mixed into the external nullifier
  78. /// alongside the epoch.
  79. ///
  80. /// In a multi-app deployment this should be derived from the app's
  81. /// genesis (e.g. `poseidon_hash(genesis_contents_field)`), or
  82. /// configured per app at startup. We expose it as a typed value so
  83. /// it cannot be confused with an arbitrary field element.
  84. #[derive(Copy, Clone, Debug, PartialEq, Eq)]
  85. pub struct RlnAppId(pub pallas::Base);
  86. impl RlnAppId {
  87. /// Derive a stable app identifier from the EventGraph's genesis
  88. /// contents. This way, two deployments using the same generic
  89. /// circuit but different genesis can never produce overlapping
  90. /// internal nullifiers.
  91. pub fn from_genesis(genesis_contents: &[u8]) -> Self {
  92. let mut buf = [0u8; 64];
  93. let h = blake3::hash(genesis_contents);
  94. buf[..32].copy_from_slice(h.as_bytes());
  95. Self(pallas::Base::from_uniform_bytes(&buf))
  96. }
  97. /// Construct from any field element. Useful for tests; in
  98. /// production prefer [`Self::from_genesis`] so the derivation
  99. /// is deterministic from the application's identity.
  100. pub fn from_field(v: pallas::Base) -> Self {
  101. Self(v)
  102. }
  103. pub fn as_field(&self) -> pallas::Base {
  104. self.0
  105. }
  106. }
  107. /// Versioned attestation accompanying a registration proof.
  108. ///
  109. /// Runtime admission currently accepts only app-configured pregenerated
  110. /// commitments paired with [`GENESIS_BLOB_GUARD`]. No serialized
  111. /// [`RegistrationBlob`] is accepted from the network today; the enum is
  112. /// retained as scaffolding for the future staked tier, where the
  113. /// `Staked` variant must carry a DarkFi smart-contract attestation that
  114. /// event graph can verify before admitting a new identity.
  115. #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
  116. pub enum RegistrationAttestation {
  117. /// Dormant proof-path scaffold for the currently disabled
  118. /// non-pregenerated registration flow. This is not an active public
  119. /// free tier; live admission bypasses `RegistrationBlob` entirely and
  120. /// accepts only pregenerated guard registrations.
  121. SPECIAL,
  122. /// Reserved for the future staking integration. The bytes are opaque
  123. /// until the DarkFi contract attestation format and verifier exist.
  124. Staked(Vec<u8>),
  125. }
  126. impl RegistrationAttestation {
  127. /// Message limit used by the dormant SPECIAL proof scaffold.
  128. pub const SPECIAL_TIER_LIMIT: u64 = 100;
  129. /// Validate the attestation against a claimed limit in the dormant
  130. /// registration-proof path.
  131. ///
  132. /// This helper is not sufficient for live admission. The live path
  133. /// rejects all serialized registration blobs until staked admission is
  134. /// backed by a verifiable DarkFi contract attestation.
  135. pub fn permits(&self, user_message_limit: u64) -> bool {
  136. match self {
  137. Self::SPECIAL => user_message_limit <= Self::SPECIAL_TIER_LIMIT,
  138. Self::Staked(_) => false,
  139. }
  140. }
  141. }
  142. /// The complete blob for the disabled registration-proof path.
  143. ///
  144. /// The proof's public inputs commit to the
  145. /// `(commitment, user_message_limit, max_message_limit)` tuple, and
  146. /// `attestation` is reserved for the future staking proof. Runtime
  147. /// verification intentionally rejects these blobs today because an
  148. /// unstaked public registration tier is a sybil attack surface.
  149. ///
  150. /// Current admission accepts only app-configured pregenerated
  151. /// commitments paired with [`GENESIS_BLOB_GUARD`]. Future staked
  152. /// admission must deserialize this type only after it can verify the
  153. /// DarkFi contract attestation carried by [`RegistrationAttestation::Staked`].
  154. #[derive(Clone, SerialEncodable, SerialDecodable)]
  155. pub struct RegistrationBlob {
  156. pub proof: Proof,
  157. pub user_message_limit: u64,
  158. pub max_message_limit: u64,
  159. pub attestation: RegistrationAttestation,
  160. }
  161. /// The complete blob attached to a slashing `StaticPut`.
  162. #[derive(Clone, SerialEncodable, SerialDecodable)]
  163. pub struct SlashBlob {
  164. pub proof: Proof,
  165. /// The recovered identity_secret_hash.
  166. pub identity_secret_hash: pallas::Base,
  167. /// The SMT root the slash proof was constructed against.
  168. pub merkle_root: pallas::Base,
  169. }
  170. /// Ephemeral data attached to an [`EventPut`] when RLN is active.
  171. #[derive(Clone, SerialEncodable, SerialDecodable)]
  172. pub struct Blob {
  173. /// The RLN signal proof.
  174. pub proof: Proof,
  175. /// The `y` share value: `y = a_0 + x * a_1`.
  176. pub y: pallas::Base,
  177. /// Nullifier derived from `(identity, epoch, message_id)`.
  178. pub internal_nullifier: pallas::Base,
  179. /// The user's per-registration message limit.
  180. /// Now bound cryptographically as a public input to the signal
  181. /// proof, so the verifier can trust this value.
  182. pub user_msg_limit: u64,
  183. /// The SMT root the sender proved membership against.
  184. pub merkle_root: pallas::Base,
  185. }
  186. /// Private witness and public inputs for a signal proof.
  187. ///
  188. /// This type is serializable so the local prover boundary can later be
  189. /// implemented by a trusted remote prover. It contains identity secrets and
  190. /// must never be sent to an untrusted service.
  191. #[derive(Clone, SerialEncodable, SerialDecodable)]
  192. pub struct SignalProvingRequest {
  193. pub nullifier: pallas::Base,
  194. pub trapdoor: pallas::Base,
  195. pub message_id: pallas::Base,
  196. pub merkle_path: [pallas::Base; SMT_FP_DEPTH],
  197. pub x: pallas::Base,
  198. pub user_message_limit: u64,
  199. pub app_id: pallas::Base,
  200. pub epoch: pallas::Base,
  201. pub merkle_root: pallas::Base,
  202. pub external_nullifier: pallas::Base,
  203. pub y: pallas::Base,
  204. pub internal_nullifier: pallas::Base,
  205. }
  206. /// Output of a signal proving request.
  207. #[derive(Clone, SerialEncodable, SerialDecodable)]
  208. pub struct SignalProvingResponse {
  209. pub proof: Proof,
  210. }
  211. /// Private witness and public inputs for a slash proof.
  212. ///
  213. /// This type is serializable for the same trusted-prover boundary as
  214. /// [`SignalProvingRequest`].
  215. #[derive(Clone, SerialEncodable, SerialDecodable)]
  216. pub struct SlashProvingRequest {
  217. pub identity_secret_hash: pallas::Base,
  218. pub merkle_path: [pallas::Base; SMT_FP_DEPTH],
  219. pub merkle_root: pallas::Base,
  220. }
  221. /// Output of a slash proving request.
  222. #[derive(Clone, SerialEncodable, SerialDecodable)]
  223. pub struct SlashProvingResponse {
  224. pub proof: Proof,
  225. }
  226. /// An entry in the static DAG representing an identity event.
  227. #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
  228. pub enum RLNNode {
  229. /// A new identity commitment being registered.
  230. Registration(pallas::Base),
  231. /// An identity commitment being slashed (removed).
  232. Slashing(pallas::Base),
  233. }
  234. const REGISTER_VK_KEY: &str = "rlnv2-diff-register-vk";
  235. const REGISTER_PK_KEY: &str = "rlnv2-diff-register-pk";
  236. const SIGNAL_VK_KEY: &str = "rlnv2-diff-signal-vk";
  237. const SIGNAL_PK_KEY: &str = "rlnv2-diff-signal-pk";
  238. const SLASH_VK_KEY: &str = "rlnv2-diff-slash-vk";
  239. const SLASH_PK_KEY: &str = "rlnv2-diff-slash-pk";
  240. /// ZK key cache.
  241. pub struct ZkKeys {
  242. /// Verifying key for identity registration proofs.
  243. pub register_vk: VerifyingKey,
  244. /// Verifying key for signal (rate-limit) proofs.
  245. pub signal_vk: VerifyingKey,
  246. /// Verifying key for slash proofs.
  247. pub slash_vk: VerifyingKey,
  248. /// Reference to the sled DB for proving-key access during local proving.
  249. sled_db: sled::Db,
  250. }
  251. impl ZkKeys {
  252. /// Ensure all required keys exist and load only verifying keys into memory.
  253. pub fn build_and_load(sled_db: &sled::Db) -> Result<Self> {
  254. Self::ensure_keys(sled_db)?;
  255. Self::load_verifying_keys(sled_db)
  256. }
  257. /// Build missing proving and verifying keys into persistent storage.
  258. fn ensure_keys(sled_db: &sled::Db) -> Result<()> {
  259. ensure_key(sled_db, REGISTER_VK_KEY, RLN2_REGISTER_ZKBIN, KeyKind::Vk)?;
  260. ensure_key(sled_db, REGISTER_PK_KEY, RLN2_REGISTER_ZKBIN, KeyKind::Pk)?;
  261. ensure_key(sled_db, SIGNAL_VK_KEY, RLN2_SIGNAL_ZKBIN, KeyKind::Vk)?;
  262. ensure_key(sled_db, SIGNAL_PK_KEY, RLN2_SIGNAL_ZKBIN, KeyKind::Pk)?;
  263. ensure_key(sled_db, SLASH_PK_KEY, RLN2_SLASH_ZKBIN, KeyKind::Pk)?;
  264. ensure_key(sled_db, SLASH_VK_KEY, RLN2_SLASH_ZKBIN, KeyKind::Vk)?;
  265. Ok(())
  266. }
  267. /// Load the verifying keys needed by validation paths.
  268. fn load_verifying_keys(sled_db: &sled::Db) -> Result<Self> {
  269. Ok(Self {
  270. register_vk: read_vk(sled_db, REGISTER_VK_KEY, RLN2_REGISTER_ZKBIN)?,
  271. signal_vk: read_vk(sled_db, SIGNAL_VK_KEY, RLN2_SIGNAL_ZKBIN)?,
  272. slash_vk: read_vk(sled_db, SLASH_VK_KEY, RLN2_SLASH_ZKBIN)?,
  273. sled_db: sled_db.clone(),
  274. })
  275. }
  276. /// Load the slash proving key from sled.
  277. pub fn load_slash_pk(&self) -> Result<ProvingKey> {
  278. read_pk(&self.sled_db, SLASH_PK_KEY, RLN2_SLASH_ZKBIN)
  279. }
  280. /// Load the register proving key from sled.
  281. pub fn load_register_pk(&self) -> Result<ProvingKey> {
  282. read_pk(&self.sled_db, REGISTER_PK_KEY, RLN2_REGISTER_ZKBIN)
  283. }
  284. /// Load the signal proving key from sled.
  285. pub fn load_signal_pk(&self) -> Result<ProvingKey> {
  286. read_pk(&self.sled_db, SIGNAL_PK_KEY, RLN2_SIGNAL_ZKBIN)
  287. }
  288. }
  289. /// Proving backend for RLN private proofs.
  290. ///
  291. /// The local implementation is backed by [`ZkKeys`]. A future trusted remote
  292. /// prover can implement this trait using the serializable request/response
  293. /// types without changing callers.
  294. #[async_trait]
  295. pub trait RlnProver: Send + Sync {
  296. /// Create a signal proof from a fully prepared request.
  297. async fn prove_signal(&self, request: SignalProvingRequest) -> Result<SignalProvingResponse>;
  298. /// Create a slash proof from a fully prepared request.
  299. async fn prove_slash(&self, request: SlashProvingRequest) -> Result<SlashProvingResponse>;
  300. }
  301. #[async_trait]
  302. impl RlnProver for ZkKeys {
  303. async fn prove_signal(&self, request: SignalProvingRequest) -> Result<SignalProvingResponse> {
  304. let pk = self.load_signal_pk()?;
  305. create_signal_proof(&request, &pk)
  306. }
  307. async fn prove_slash(&self, request: SlashProvingRequest) -> Result<SlashProvingResponse> {
  308. let pk = self.load_slash_pk()?;
  309. create_slash_proof_from_request(&request, &pk)
  310. }
  311. }
  312. /// Create a local signal proof from a prover request.
  313. pub fn create_signal_proof(
  314. request: &SignalProvingRequest,
  315. signal_pk: &ProvingKey,
  316. ) -> Result<SignalProvingResponse> {
  317. let zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN, false)?;
  318. let witnesses = vec![
  319. Witness::Base(Value::known(request.nullifier)),
  320. Witness::Base(Value::known(request.trapdoor)),
  321. Witness::Base(Value::known(request.message_id)),
  322. Witness::SparseMerklePath(Value::known(request.merkle_path)),
  323. Witness::Base(Value::known(request.x)),
  324. Witness::Base(Value::known(pallas::Base::from(request.user_message_limit))),
  325. Witness::Base(Value::known(request.app_id)),
  326. Witness::Base(Value::known(request.epoch)),
  327. ];
  328. let pi = vec![
  329. request.merkle_root,
  330. request.external_nullifier,
  331. pallas::Base::from(request.user_message_limit),
  332. request.x,
  333. request.y,
  334. request.internal_nullifier,
  335. ];
  336. let circuit = ZkCircuit::new(witnesses, &zkbin);
  337. let proof = Proof::create(signal_pk, &[circuit], &pi, &mut OsRng)
  338. .map_err(|e| Error::Custom(format!("Signal proof creation failed: {e}")))?;
  339. Ok(SignalProvingResponse { proof })
  340. }
  341. /// Create a local slash proof from a prover request.
  342. pub fn create_slash_proof_from_request(
  343. request: &SlashProvingRequest,
  344. slash_pk: &ProvingKey,
  345. ) -> Result<SlashProvingResponse> {
  346. let witnesses = vec![
  347. Witness::Base(Value::known(request.identity_secret_hash)),
  348. Witness::SparseMerklePath(Value::known(request.merkle_path)),
  349. ];
  350. let pi = vec![request.identity_secret_hash, request.merkle_root];
  351. let zkbin = ZkBinary::decode(RLN2_SLASH_ZKBIN, false)?;
  352. let circuit = ZkCircuit::new(witnesses, &zkbin);
  353. let proof = Proof::create(slash_pk, &[circuit], &pi, &mut OsRng)
  354. .map_err(|e| Error::Custom(format!("Slash proof creation failed: {e}")))?;
  355. Ok(SlashProvingResponse { proof })
  356. }
  357. /// Build the slash proving request from the local identity tree.
  358. pub fn prepare_slash_proof_request(
  359. identity_secret_hash: pallas::Base,
  360. identity_state: &IdentityState,
  361. ) -> SlashProvingRequest {
  362. let commitment = poseidon_hash([identity_secret_hash]);
  363. let merkle_root = identity_state.root();
  364. let merkle_path = identity_state.prove_membership(&commitment).path;
  365. SlashProvingRequest { identity_secret_hash, merkle_path, merkle_root }
  366. }
  367. /// Mutable RLN state shared across all protocol instances via
  368. /// `EventGraph::rln_state`. Each peer connection's protocol handler
  369. /// accesses this through a write lock so that duplicate/reuse
  370. /// detection works regardless of which peer relayed the event.
  371. pub struct RlnState {
  372. /// Per-nullifier share tracking, keyed first by epoch so we can
  373. /// prune by age rather than wiping on every epoch transition.
  374. pub metadata: MessageMetadata,
  375. }
  376. impl RlnState {
  377. pub fn new() -> Self {
  378. Self { metadata: MessageMetadata::new() }
  379. }
  380. }
  381. impl Default for RlnState {
  382. fn default() -> Self {
  383. Self::new()
  384. }
  385. }
  386. /// Outcome of [`EventGraph::rln_verify_signal`].
  387. #[derive(Debug)]
  388. pub enum SignalCheck {
  389. /// Proof valid, no conflict; the share has been recorded.
  390. Accepted,
  391. /// Drop silently. Covers: malformed blob, out-of-range
  392. /// `user_msg_limit`, unknown root, invalid proof, exact
  393. /// duplicate.
  394. Rejected,
  395. /// Different `(x, y)` for the same internal nullifier in this
  396. /// epoch - by SSS these expose `identity_secret_hash`. The
  397. /// caller should construct and broadcast a slash.
  398. ///
  399. /// When this variant is returned, the metadata table is *not*
  400. /// mutated. The conflicting share is included in the returned
  401. /// vector but not persisted, since the slash itself will remove
  402. /// the offending identity.
  403. Slashable(Vec<(pallas::Base, pallas::Base)>),
  404. }
  405. /// Outcome of [`EventGraph::rln_verify_static_event`].
  406. ///
  407. /// Distinguishes "drop silently" (e.g. propagation race, unknown
  408. /// root) from "the sender is malicious" (e.g. attestation didn't
  409. /// permit the claimed limit, slash references the wrong commitment).
  410. /// The protocol-layer wrapper translates `Malicious` into a strike
  411. /// against the peer; tests can assert on the discriminator directly.
  412. #[derive(Debug)]
  413. pub enum StaticEventCheck {
  414. /// Registration verified; commitment should be inserted.
  415. AcceptedRegistration(pallas::Base),
  416. /// Slash verified; commitment should be removed.
  417. AcceptedSlash(pallas::Base),
  418. /// Drop silently (malformed blob, unknown root, duplicate
  419. /// commitment, invalid proof). Not strikable on its own
  420. /// because a peer might legitimately be relaying a stale
  421. /// event.
  422. Rejected,
  423. /// Sender is misbehaving and should be striked. Covers:
  424. /// out-of-range limits in registration, attestation that
  425. /// doesn't permit the claimed limit, slash whose recovered
  426. /// commitment doesn't match the claimed one.
  427. Malicious,
  428. }
  429. /// The set of currently registered RLN identities, stored as a Sparse
  430. /// Merkle Tree (SMT).
  431. ///
  432. /// Persistence model: leaf commitments are stored in a dedicated sled
  433. /// tree (`rln-identity-leaves`). The in-memory SMT is rebuilt from
  434. /// these leaves on startup.
  435. pub struct IdentityState {
  436. smt: SmtMemoryFp,
  437. leaves: sled::Tree,
  438. slashed: sled::Tree,
  439. recent_roots: VecDeque<pallas::Base>,
  440. }
  441. impl IdentityState {
  442. pub fn new(sled_db: &sled::Db) -> Result<Self> {
  443. let hasher = PoseidonFp::new();
  444. let store = MemoryStorageFp::new();
  445. let mut smt = SmtMemoryFp::new(store, hasher, &EMPTY_NODES_FP);
  446. let leaves = sled_db.open_tree("rln-identity-leaves")?;
  447. let slashed = sled_db.open_tree("rln-slashed-identity-leaves")?;
  448. let mut batch = vec![];
  449. for item in leaves.iter() {
  450. let (key, val) = item?;
  451. if key.len() != 32 || val.len() != 32 || key.as_ref() != val.as_ref() {
  452. continue
  453. }
  454. let mut repr = [0u8; 32];
  455. repr.copy_from_slice(&val);
  456. if slashed.contains_key(repr)? {
  457. continue
  458. }
  459. if let Some(c) = pallas::Base::from_repr(repr).into() {
  460. batch.push((c, c));
  461. }
  462. }
  463. if !batch.is_empty() {
  464. info!(
  465. target: "event_graph::rln",
  466. "[RLN] Restoring {} identities from sled", batch.len(),
  467. );
  468. smt.insert_batch(batch)?;
  469. }
  470. let mut recent_roots = VecDeque::with_capacity(ROOT_HISTORY_SIZE);
  471. recent_roots.push_back(smt.root());
  472. Ok(Self { smt, leaves, slashed, recent_roots })
  473. }
  474. /// Returns true if the commitment is already a leaf in the tree.
  475. pub fn contains(&self, commitment: &pallas::Base) -> bool {
  476. self.leaves.contains_key(commitment.to_repr()).unwrap_or(false)
  477. }
  478. /// Returns true if the commitment has been permanently slashed.
  479. pub fn is_slashed(&self, commitment: &pallas::Base) -> bool {
  480. self.slashed.contains_key(commitment.to_repr()).unwrap_or(false)
  481. }
  482. /// Register a new identity.
  483. ///
  484. /// Returns an error if the commitment is already active or has been
  485. /// slashed before. Callers should treat this as a soft failure (drop the
  486. /// message), not as protocol-level malice. Concurrent honest registrations
  487. /// of the same commitment can race during P2P propagation.
  488. pub fn register(&mut self, commitment: pallas::Base) -> Result<()> {
  489. if self.is_slashed(&commitment) {
  490. return Err(Error::Custom("RLN: slashed identity commitment".into()))
  491. }
  492. if self.contains(&commitment) {
  493. return Err(Error::Custom("RLN: duplicate identity commitment".into()))
  494. }
  495. self.leaves.insert(commitment.to_repr(), commitment.to_repr().as_ref())?;
  496. self.smt.insert_batch(vec![(commitment, commitment)])?;
  497. self.push_root();
  498. Ok(())
  499. }
  500. /// Slash (remove) an identity and permanently tombstone its commitment.
  501. ///
  502. /// Removing a non-present commitment remains idempotent: the slash is
  503. /// still recorded as a tombstone, but the SMT root is unchanged.
  504. pub fn slash(&mut self, commitment: pallas::Base) -> Result<()> {
  505. let repr = commitment.to_repr();
  506. self.slashed.insert(repr, repr.as_ref())?;
  507. if !self.contains(&commitment) {
  508. return Ok(())
  509. }
  510. self.leaves.remove(repr)?;
  511. self.smt.remove_leaves(vec![(commitment, commitment)])?;
  512. self.push_root();
  513. Ok(())
  514. }
  515. pub fn root(&self) -> pallas::Base {
  516. self.smt.root()
  517. }
  518. /// Return the commitments currently persisted in the identity leaf tree.
  519. ///
  520. /// Startup recovery compares this set against the set derived by replaying
  521. /// the static DAG. Counting leaves is not enough because a crash or old
  522. /// code path can leave the right number of leaves with the wrong
  523. /// commitments.
  524. pub(crate) fn commitment_reprs(&self) -> Result<BTreeSet<[u8; 32]>> {
  525. let mut commitments = BTreeSet::new();
  526. for item in self.leaves.iter() {
  527. let (key, val) = item?;
  528. if key.len() != 32 || val.len() != 32 {
  529. return Err(Error::Custom(format!(
  530. "RLN identity leaf key/value must be 32 bytes, got {}/{}",
  531. key.len(),
  532. val.len()
  533. )))
  534. }
  535. if key.as_ref() != val.as_ref() {
  536. return Err(Error::Custom("RLN identity leaf key/value mismatch".into()))
  537. }
  538. let mut repr = [0u8; 32];
  539. repr.copy_from_slice(&val);
  540. commitments.insert(repr);
  541. }
  542. Ok(commitments)
  543. }
  544. /// Return the commitments persisted in the slashed-identity tombstone tree.
  545. pub(crate) fn slashed_commitment_reprs(&self) -> Result<BTreeSet<[u8; 32]>> {
  546. let mut commitments = BTreeSet::new();
  547. for item in self.slashed.iter() {
  548. let (key, val) = item?;
  549. if key.len() != 32 || val.len() != 32 {
  550. return Err(Error::Custom(format!(
  551. "RLN slashed identity key/value must be 32 bytes, got {}/{}",
  552. key.len(),
  553. val.len()
  554. )))
  555. }
  556. if key.as_ref() != val.as_ref() {
  557. return Err(Error::Custom("RLN slashed identity key/value mismatch".into()))
  558. }
  559. let mut repr = [0u8; 32];
  560. repr.copy_from_slice(&val);
  561. commitments.insert(repr);
  562. }
  563. Ok(commitments)
  564. }
  565. /// Check whether `root` matches the current SMT root.
  566. pub fn is_current_root(&self, root: &pallas::Base) -> bool {
  567. self.smt.root() == *root
  568. }
  569. /// Check whether `root` matches the current root or any recent
  570. /// historical root kept in the count-based cache.
  571. ///
  572. /// This is only an introspection/cache helper. Verifier paths must
  573. /// still apply timestamp validity to non-current roots.
  574. pub fn is_known_root(&self, root: &pallas::Base) -> bool {
  575. self.recent_roots.contains(root)
  576. }
  577. pub fn prove_membership(&self, commitment: &pallas::Base) -> darkfi_sdk::crypto::smt::PathFp {
  578. self.smt.prove_membership(commitment)
  579. }
  580. fn push_root(&mut self) {
  581. let root = self.smt.root();
  582. if self.recent_roots.len() >= ROOT_HISTORY_SIZE {
  583. self.recent_roots.pop_front();
  584. }
  585. self.recent_roots.push_back(root);
  586. }
  587. /// Reset the in-memory SMT and the persistent leaves tree to an
  588. /// empty state, in preparation for replaying the canonical
  589. /// static-DAG history.
  590. pub fn clear_for_rebuild(&mut self) -> Result<()> {
  591. // Drop every derived identity state from sled.
  592. self.leaves.clear()?;
  593. self.slashed.clear()?;
  594. // Replace the in-memory SMT with a fresh empty one.
  595. let hasher = PoseidonFp::new();
  596. let store = MemoryStorageFp::new();
  597. self.smt = SmtMemoryFp::new(store, hasher, &EMPTY_NODES_FP);
  598. // Reset the recent-roots cache. The empty SMT root is the
  599. // current state.
  600. self.recent_roots.clear();
  601. self.recent_roots.push_back(self.smt.root());
  602. Ok(())
  603. }
  604. }
  605. /// Hash an event's header ID into a field element suitable for use
  606. /// as the `x` coordinate in the RLN polynomial evaluation.
  607. pub fn hash_event(event: &Event) -> pallas::Base {
  608. let mut buf = [0u8; 64];
  609. buf[..blake3::OUT_LEN].copy_from_slice(event.header.id().as_bytes());
  610. pallas::Base::from_uniform_bytes(&buf)
  611. }
  612. /// Map a UNIX-millis timestamp to its enclosing RLN epoch number.
  613. ///
  614. /// Returns 0 if the timestamp predates [`RLN_GENESIS`], avoiding
  615. /// underflow on malicious timestamps.
  616. ///
  617. /// **Floor, not round.** The function returns the index of the
  618. /// epoch that *contains* the given timestamp:
  619. ///
  620. /// ```text
  621. /// epoch N = [GENESIS + N * EPOCH_LEN, GENESIS + (N+1) * EPOCH_LEN)
  622. /// ```
  623. ///
  624. /// Floor-based assignment is essential for two reasons:
  625. ///
  626. /// 1. **No ambiguity at boundaries.** A wall-clock instant always
  627. /// belongs to exactly one epoch, regardless of the direction it
  628. /// was approached from.
  629. /// 2. **No partial overlap with the time-drift window.** The event
  630. /// layer's `EVENT_TIME_DRIFT` is symmetric around `now`; epoch
  631. /// rounding would create a window in which an honest message
  632. /// could be "from the wrong epoch" relative to other peers.
  633. ///
  634. /// Returns the epoch *number* (0, 1, 2, ...), not a timestamp, so
  635. /// it is unambiguous and compact.
  636. ///
  637. /// Use [`current_epoch`] when you want the epoch number at the
  638. /// current wall-clock instant.
  639. pub fn epoch_of(timestamp_millis: u64) -> u64 {
  640. let Some(diff) = timestamp_millis.checked_sub(RLN_GENESIS) else { return 0 };
  641. diff / RLN_EPOCH_LEN
  642. }
  643. /// The epoch number at the current wall-clock instant.
  644. ///
  645. /// Convenience wrapper around [`epoch_of`] for the most common
  646. /// call site. Use [`epoch_of`] explicitly when you need the epoch
  647. /// for a *specific* timestamp (e.g. the timestamp of an event being
  648. /// validated).
  649. pub fn current_epoch() -> u64 {
  650. epoch_of(std::time::UNIX_EPOCH.elapsed().map(|d| d.as_millis() as u64).unwrap_or(0))
  651. }
  652. /// The wall-clock millis at the start of a given epoch number.
  653. /// Inverse of [`epoch_of`].
  654. pub fn epoch_start_millis(epoch: u64) -> u64 {
  655. RLN_GENESIS.saturating_add(epoch.saturating_mul(RLN_EPOCH_LEN))
  656. }
  657. #[derive(Debug, Clone, Default)]
  658. struct ShareData {
  659. /// Collected `(x, y)` share pairs for a single internal nullifier.
  660. shares: Vec<(pallas::Base, pallas::Base)>,
  661. }
  662. /// Per-epoch tracking of RLN shares, keyed first by epoch number,
  663. /// then by `internal_nullifier`.
  664. ///
  665. /// We use `(epoch, internal_nullifier)` rather than
  666. /// `(external_nullifier, internal_nullifier)`: the external nullifier
  667. /// is `poseidon(epoch, app_id)` and the app_id is constant across all
  668. /// shares we care about, so keying by epoch directly is equivalent
  669. /// while making age-based pruning trivial.
  670. ///
  671. /// This allows detecting:
  672. /// * **Duplicates** - the exact same `(x, y)` pair arriving twice
  673. /// (the event is just dropped).
  674. /// * **Slot reuse** - a different `(x, y)` for the same internal
  675. /// nullifier (the user reused a `message_id`, which by SSS reveals
  676. /// their secret).
  677. #[derive(Debug, Default)]
  678. pub struct MessageMetadata {
  679. by_epoch: BTreeMap<u64, BTreeMap<pallas::Base, ShareData>>,
  680. }
  681. impl MessageMetadata {
  682. pub fn new() -> Self {
  683. Self::default()
  684. }
  685. /// Drop epochs older than `current_epoch - METADATA_RETAIN_EPOCHS`.
  686. /// Called opportunistically before/after each insert.
  687. pub fn prune_old(&mut self, current_epoch: u64) {
  688. let cutoff = current_epoch.saturating_sub(METADATA_RETAIN_EPOCHS);
  689. // BTreeMap::split_off keeps everything >= cutoff; everything
  690. // before cutoff is discarded.
  691. let keep = self.by_epoch.split_off(&cutoff);
  692. self.by_epoch = keep;
  693. }
  694. /// Record a new share.
  695. pub fn add_share(
  696. &mut self,
  697. epoch: u64,
  698. int_null: pallas::Base,
  699. x: pallas::Base,
  700. y: pallas::Base,
  701. ) {
  702. self.by_epoch.entry(epoch).or_default().entry(int_null).or_default().shares.push((x, y));
  703. }
  704. /// Retrieve all shares for a given (epoch, internal_nullifier).
  705. pub fn get_shares(
  706. &self,
  707. epoch: u64,
  708. int_null: &pallas::Base,
  709. ) -> Vec<(pallas::Base, pallas::Base)> {
  710. self.by_epoch
  711. .get(&epoch)
  712. .and_then(|m| m.get(int_null))
  713. .map(|sd| sd.shares.clone())
  714. .unwrap_or_default()
  715. }
  716. /// Check whether the exact `(x, y)` pair is already recorded.
  717. pub fn is_duplicate(
  718. &self,
  719. epoch: u64,
  720. int_null: &pallas::Base,
  721. x: &pallas::Base,
  722. y: &pallas::Base,
  723. ) -> bool {
  724. self.by_epoch
  725. .get(&epoch)
  726. .and_then(|m| m.get(int_null))
  727. .map(|sd| sd.shares.iter().any(|(sx, sy)| sx == x && sy == y))
  728. .unwrap_or(false)
  729. }
  730. /// Check whether any share has been recorded for this nullifier
  731. /// in the given epoch.
  732. ///
  733. /// In RLN-V2, each `message_id` produces a unique
  734. /// `internal_nullifier`. A repeated internal_nullifier therefore
  735. /// means the user reused the same `message_id` slot in the
  736. /// epoch, which is the V2 violation condition.
  737. ///
  738. /// (Note: the V1 spec phrased this in terms of "more than `limit`
  739. /// shares", but V2 changed the model - the rate limit itself is
  740. /// enforced inside the circuit by `message_id < user_message_limit`,
  741. /// so any repeat is by definition a violation.)
  742. pub fn is_reused(&self, epoch: u64, int_null: &pallas::Base) -> bool {
  743. self.by_epoch.get(&epoch).map(|m| m.contains_key(int_null)).unwrap_or(false)
  744. }
  745. }
  746. /// Create a ZK proof that a user's identity_secret_hash has been
  747. /// recovered (via SSS) and they should be slashed from the identity tree.
  748. ///
  749. /// `identity_secret_hash` here is the value the spec calls
  750. /// "identity_secret_hash" - i.e. `poseidon(identity_secret, user_message_limit)`.
  751. /// It is what SSS recovery actually returns from the updated signal
  752. /// circuit, and from it the commitment is computable as
  753. /// `poseidon(identity_secret_hash)` directly.
  754. pub fn create_slash_proof(
  755. identity_secret_hash: pallas::Base,
  756. identity_state: &mut IdentityState,
  757. slash_pk: &ProvingKey,
  758. ) -> Result<(Proof, pallas::Base)> {
  759. let request = prepare_slash_proof_request(identity_secret_hash, identity_state);
  760. let merkle_root = request.merkle_root;
  761. let response = create_slash_proof_from_request(&request, slash_pk)?;
  762. Ok((response.proof, merkle_root))
  763. }
  764. /// Recover the secret from two or more `(x, y)` Shamir shares using
  765. /// Lagrange interpolation.
  766. ///
  767. /// What gets recovered is the constant term `a_0` of the polynomial.
  768. /// In our updated signal circuit, that's the user's
  769. /// `identity_secret_hash` (NOT the raw nullifier+trapdoor pair).
  770. ///
  771. /// Returns an error if fewer than 2 shares are provided or if any two
  772. /// shares have the same x-coordinate (which would cause a zero
  773. /// division).
  774. pub fn sss_recover(shares: &[(pallas::Base, pallas::Base)]) -> Result<pallas::Base> {
  775. if shares.len() < 2 {
  776. return Err(Error::Custom("Need >1 share for SSS recovery".into()))
  777. }
  778. // Guard against duplicate x-coordinates
  779. for i in 0..shares.len() {
  780. for j in (i + 1)..shares.len() {
  781. if shares[i].0 == shares[j].0 {
  782. return Err(Error::Custom("Duplicate x-coordinates in SSS shares".into()))
  783. }
  784. }
  785. }
  786. let mut secret = pallas::Base::zero();
  787. for (j, sj) in shares.iter().enumerate() {
  788. let mut basis = pallas::Base::one();
  789. for (i, si) in shares.iter().enumerate() {
  790. if i != j {
  791. let Some(denominator) = Option::<pallas::Base>::from((si.0 - sj.0).invert()) else {
  792. return Err(Error::Custom("Duplicate x-coordinates in SSS shares".into()))
  793. };
  794. basis *= si.0 * denominator;
  795. }
  796. }
  797. secret += basis * sj.1;
  798. }
  799. Ok(secret)
  800. }
  801. enum KeyKind {
  802. Pk,
  803. Vk,
  804. }
  805. /// Build a key into sled if it doesn't already exist.
  806. fn ensure_key(sled_db: &sled::Db, key: &str, zkbin_bytes: &[u8], kind: KeyKind) -> Result<()> {
  807. if sled_db.contains_key(key)? {
  808. return Ok(())
  809. }
  810. let zkbin = ZkBinary::decode(zkbin_bytes, false)?;
  811. let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin);
  812. info!(target: "event_graph::rln", "[RLN] Building {key}");
  813. let mut buf = vec![];
  814. match kind {
  815. KeyKind::Pk => {
  816. let pk = ProvingKey::build(zkbin.k, &circuit);
  817. pk.write(&mut buf)?;
  818. }
  819. KeyKind::Vk => {
  820. let vk = VerifyingKey::build(zkbin.k, &circuit);
  821. vk.write(&mut buf)?;
  822. }
  823. }
  824. sled_db.insert(key, buf)?;
  825. Ok(())
  826. }
  827. fn read_vk(sled_db: &sled::Db, key: &str, zkbin_bytes: &[u8]) -> Result<VerifyingKey> {
  828. let bytes = sled_db.get(key)?.ok_or_else(|| Error::Custom(format!("{key} not found")))?;
  829. let zkbin = ZkBinary::decode(zkbin_bytes, false)?;
  830. let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin);
  831. Ok(VerifyingKey::read(&mut Cursor::new(bytes), circuit)?)
  832. }
  833. fn read_pk(sled_db: &sled::Db, key: &str, zkbin_bytes: &[u8]) -> Result<ProvingKey> {
  834. let bytes = sled_db.get(key)?.ok_or_else(|| Error::Custom(format!("{key} not found")))?;
  835. let zkbin = ZkBinary::decode(zkbin_bytes, false)?;
  836. let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin);
  837. Ok(ProvingKey::read(&mut Cursor::new(bytes), circuit)?)
  838. }