Selaa lähdekoodia

event_graph: Add RLN prover boundary

x 1 kuukausi sitten
vanhempi
sitoutus
a00aa8b274

+ 6 - 0
bin/darkirc/darkirc_config.toml

@@ -26,6 +26,12 @@
 ## Sled cache capacity for the DarkIRC/EventGraph datastore, in MiB
 #sled_cache_mb = 64
 
+## Datastore path for RLN proving and verifying keys
+#zk_key_datastore = "~/.local/share/darkfi/darkirc_zk_keys"
+
+## Sled cache capacity for the RLN key datastore, in MiB
+#zk_key_sled_cache_mb = 16
+
 ## Sets DB logs replay datastore path
 #replay_datastore = "~/.local/share/darkfi/darkirc/replayed_darkirc_db"
 

+ 18 - 30
bin/darkirc/src/crypto/rln.rs

@@ -18,20 +18,18 @@
 
 use darkfi::{
     event_graph::{
-        rln::{epoch_of, hash_event, Blob, RegistrationAttestation, RLN2_SIGNAL_ZKBIN},
+        rln::{
+            epoch_of, hash_event, Blob, RegistrationAttestation, RlnProver, SignalProvingRequest,
+        },
         Event, EventGraphPtr,
     },
     util::memory::log_memory,
-    zk::{
-        halo2::{Field, Value},
-        Proof, Witness, ZkCircuit,
-    },
-    zkas::ZkBinary,
+    zk::halo2::Field,
     Result,
 };
 use darkfi_sdk::{crypto::poseidon_hash, pasta::pallas};
 use darkfi_serial::{async_trait, SerialDecodable, SerialEncodable};
-use rand::{rngs::OsRng, CryptoRng, RngCore};
+use rand::{CryptoRng, RngCore};
 use tracing::info;
 
 /// Domain-separation tags for credential generation.
@@ -199,38 +197,28 @@ impl RlnIdentity {
         // Canonical membership path via the EG.
         let (root, path) = eg.rln_membership_path(&self.commitment()).await;
 
-        let zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN, false)?;
-
-        // Witness order MUST match `witness` declarations in
-        let witnesses = vec![
-            Witness::Base(Value::known(self.nullifier)),
-            Witness::Base(Value::known(self.trapdoor)),
-            Witness::Base(Value::known(mid)),
-            Witness::SparseMerklePath(Value::known(path.path)),
-            Witness::Base(Value::known(x)),
-            Witness::Base(Value::known(pallas::Base::from(self.user_message_limit))),
-            Witness::Base(Value::known(app_id)),
-            Witness::Base(Value::known(epoch)),
-        ];
-        // PI order MUST match `constrain_instance` in the .zk file.
-        let pi = vec![
-            root,
-            ext_null,
-            pallas::Base::from(self.user_message_limit),
+        let request = SignalProvingRequest {
+            nullifier: self.nullifier,
+            trapdoor: self.trapdoor,
+            message_id: mid,
+            merkle_path: path.path,
             x,
+            user_message_limit: self.user_message_limit,
+            app_id,
+            epoch,
+            merkle_root: root,
+            external_nullifier: ext_null,
             y,
             internal_nullifier,
-        ];
-        let circuit = ZkCircuit::new(witnesses, &zkbin);
-        log_memory("before local signal proving");
-        let pk = eg.zk_keys.load_signal_pk()?;
+        };
 
+        log_memory("before local signal proving");
         info!(
             target: "darkirc::crypto::rln",
             "[RLN] Creating signal proof for event {}",
             event.id(),
         );
-        let proof = Proof::create(&pk, &[circuit], &pi, &mut OsRng)?;
+        let proof = eg.zk_keys.prove_signal(request).await?.proof;
         log_memory("after local signal proving");
 
         Ok(Blob {

+ 12 - 17
bin/darkirc/src/irc/services/nickserv.rs

@@ -60,7 +60,7 @@ use std::{str::SplitAsciiWhitespace, sync::Arc};
 
 use darkfi::{
     event_graph::{
-        rln::{create_slash_proof, RLNNode, SlashBlob, GENESIS_USER_MSG_LIMIT},
+        rln::{prepare_slash_proof_request, RLNNode, RlnProver, SlashBlob, GENESIS_USER_MSG_LIMIT},
         Event,
     },
     util::memory::log_memory,
@@ -788,25 +788,20 @@ impl NickServ {
             ))
         }
 
-        // Build the slash proof. `create_slash_proof` takes
+        // Build the slash proof. The request contains
         // identity_secret_hash (NOT the raw nullifier+trapdoor pair)
-        // because that's what SSS would have recovered in the
-        // misbehavior path; the slash circuit's public input is
-        // identity_secret_hash + root, and the witness is
-        // (identity_secret_hash, merkle_path).
-        //
-        // The ProvingKey comes from the EG's shared zk_keys cache.
-        // The IdentityState write lock is held only for the duration
-        // of the proof construction (root read + path computation);
-        // the actual proof generation does not need the lock, but
-        // the API takes &mut so we hold it for the whole call.
+        // because that's what SSS would recover in the misbehavior path.
+        // The expensive proof work runs through the RLN prover boundary so a
+        // future trusted remote prover can implement the same API.
         let identity_secret_hash = identity.identity_secret_hash();
-        log_memory("before slash proving");
-        let slash_pk = evgr.zk_keys.load_slash_pk()?;
-        let (proof, root) = {
-            let mut id_state = evgr.identity_state.write().await;
-            create_slash_proof(identity_secret_hash, &mut id_state, &slash_pk)?
+        let request = {
+            let id_state = evgr.identity_state.read().await;
+            prepare_slash_proof_request(identity_secret_hash, &id_state)
         };
+        let root = request.merkle_root;
+
+        log_memory("before slash proving");
+        let proof = evgr.zk_keys.prove_slash(request).await?.proof;
         log_memory("after slash proving");
 
         let slash_blob = SlashBlob { proof, identity_secret_hash, merkle_root: root };

+ 48 - 7
bin/darkirc/src/main.rs

@@ -92,14 +92,14 @@ fn generated_rln_identity_user_msg_limit() -> u64 {
     GENESIS_USER_MSG_LIMIT
 }
 
-fn sled_cache_capacity_bytes(cache_mb: u64) -> Result<u64> {
+fn sled_cache_capacity_bytes(name: &str, cache_mb: u64) -> Result<u64> {
     if cache_mb == 0 {
-        return Err(Error::Custom("sled_cache_mb must be greater than 0".to_string()))
+        return Err(Error::Custom(format!("{name} must be greater than 0")))
     }
 
     cache_mb
         .checked_mul(BYTES_PER_MIB)
-        .ok_or_else(|| Error::Custom("sled_cache_mb overflows bytes".to_string()))
+        .ok_or_else(|| Error::Custom(format!("{name} overflows bytes")))
 }
 
 fn validate_history_window(dags_count: usize, history_retention_dags: usize) -> Result<()> {
@@ -173,6 +173,14 @@ struct Args {
     /// Sled cache capacity for the datastore, in MiB
     sled_cache_mb: u64,
 
+    #[structopt(long, default_value = "~/.local/share/darkfi/darkirc_zk_keys")]
+    /// Datastore path for RLN proving and verifying keys
+    zk_key_datastore: String,
+
+    #[structopt(long, default_value = "16")]
+    /// Sled cache capacity for the RLN key datastore, in MiB
+    zk_key_sled_cache_mb: u64,
+
     #[structopt(short, long, default_value = "~/.local/share/darkfi/replayed_darkirc_db")]
     /// Replay logs (DB) path
     replay_datastore: String,
@@ -449,6 +457,18 @@ pub const DARKIRC_GENESIS_COMMITMENTS_REPR: &[[u8; 32]] = &[
         return Err(e.into());
     }
 
+    let zk_key_datastore = match expand_path(&args.zk_key_datastore) {
+        Ok(v) => v,
+        Err(e) => {
+            error!("Bad RLN key datastore path `{}`: {e}", args.zk_key_datastore);
+            return Err(e);
+        }
+    };
+    if let Err(e) = fs::create_dir_all(&zk_key_datastore).await {
+        error!("Failed to create RLN key datastore path `{zk_key_datastore:?}`: {e}");
+        return Err(e.into());
+    }
+
     let replay_datastore = match expand_path(&args.replay_datastore) {
         Ok(v) => v,
         Err(e) => {
@@ -464,7 +484,7 @@ pub const DARKIRC_GENESIS_COMMITMENTS_REPR: &[[u8; 32]] = &[
         args.history_retention_dags, args.dags_count,
     );
 
-    let sled_cache_capacity = sled_cache_capacity_bytes(args.sled_cache_mb)?;
+    let sled_cache_capacity = sled_cache_capacity_bytes("sled_cache_mb", args.sled_cache_mb)?;
     info!("Instantiating event DAG with {} MiB sled cache", args.sled_cache_mb);
     let sled_db = match sled::Config::new()
         .path(datastore.clone())
@@ -478,6 +498,22 @@ pub const DARKIRC_GENESIS_COMMITMENTS_REPR: &[[u8; 32]] = &[
         }
     };
     log_memory("after sled open");
+
+    let zk_key_sled_cache_capacity =
+        sled_cache_capacity_bytes("zk_key_sled_cache_mb", args.zk_key_sled_cache_mb)?;
+    info!("Opening RLN key datastore with {} MiB sled cache", args.zk_key_sled_cache_mb);
+    let zk_key_db = match sled::Config::new()
+        .path(zk_key_datastore.clone())
+        .cache_capacity(zk_key_sled_cache_capacity)
+        .open()
+    {
+        Ok(v) => v,
+        Err(e) => {
+            error!("Failed to open RLN key datastore `{zk_key_datastore:?}`: {e}");
+            return Err(e.into());
+        }
+    };
+
     let p2p_settings: darkfi::net::Settings =
         (env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"), args.net).try_into()?;
     let p2p = match P2p::new(p2p_settings, ex.clone()).await {
@@ -495,9 +531,10 @@ pub const DARKIRC_GENESIS_COMMITMENTS_REPR: &[[u8; 32]] = &[
         pregenerated_identity_commitments: genesis_commits::pregenerated_identity_commitments(),
         max_dags: Some(args.history_retention_dags),
     };
-    let event_graph = match EventGraph::new(
+    let event_graph = match EventGraph::new_with_zk_key_db(
         p2p.clone(),
         sled_db.clone(),
+        zk_key_db.clone(),
         replay_datastore.clone(),
         replay_mode,
         eg_config,
@@ -766,6 +803,10 @@ pub const DARKIRC_GENESIS_COMMITMENTS_REPR: &[[u8; 32]] = &[
     let flushed_bytes = sled_db.flush_async().await?;
     info!("Flushed {flushed_bytes} bytes");
 
+    info!("Flushing RLN key sled database...");
+    let flushed_key_bytes = zk_key_db.flush_async().await?;
+    info!("Flushed {flushed_key_bytes} RLN key bytes");
+
     info!("Shut down successfully");
     Ok(())
 }
@@ -916,12 +957,12 @@ mod tests {
 
     #[test]
     fn sled_cache_capacity_rejects_zero() {
-        assert!(sled_cache_capacity_bytes(0).is_err());
+        assert!(sled_cache_capacity_bytes("test_cache_mb", 0).is_err());
     }
 
     #[test]
     fn sled_cache_capacity_converts_mib() {
-        assert_eq!(sled_cache_capacity_bytes(64).unwrap(), 64 * BYTES_PER_MIB);
+        assert_eq!(sled_cache_capacity_bytes("test_cache_mb", 64).unwrap(), 64 * BYTES_PER_MIB);
     }
 
     #[test]

+ 18 - 4
src/event_graph/mod.rs

@@ -761,18 +761,32 @@ impl EventGraph {
         replay_mode: bool,
         config: EventGraphConfig,
         ex: Arc<Executor<'_>>,
+    ) -> Result<EventGraphPtr> {
+        let zk_key_db = sled_db.clone();
+        Self::new_with_zk_key_db(p2p, sled_db, zk_key_db, datastore, replay_mode, config, ex).await
+    }
+
+    /// Create a new Event Graph using a separate DB for RLN key material.
+    pub async fn new_with_zk_key_db(
+        p2p: P2pPtr,
+        sled_db: sled::Db,
+        zk_key_db: sled::Db,
+        datastore: PathBuf,
+        replay_mode: bool,
+        config: EventGraphConfig,
+        ex: Arc<Executor<'_>>,
     ) -> Result<EventGraphPtr> {
         config.validate()?;
-        let zk_keys = Arc::new(ZkKeys::build_and_load(&sled_db)?);
+        let zk_keys = Arc::new(ZkKeys::build_and_load(&zk_key_db)?);
         log_memory("after RLN key initialization");
         Self::with_zk_keys(p2p, sled_db, datastore, replay_mode, config, zk_keys, ex).await
     }
 
     /// Same as [`Self::new`] but accepts a pre-built [`ZkKeys`].
     ///
-    /// Production always wants `Self::new`, which builds keys once
-    /// against its own sled DB. Tests use this variant to share a
-    /// single [`Arc<ZkKeys>`] across many `EventGraph` instances -
+    /// Production should use [`Self::new`] or [`Self::new_with_zk_key_db`].
+    /// Tests use this variant to share a single [`Arc<ZkKeys>`] across
+    /// many `EventGraph` instances -
     /// proving keys are large (hundreds of MB each) and copying
     /// them per-test would blow out RAM and `/dev/shm`.
     pub async fn with_zk_keys(

+ 10 - 16
src/event_graph/proto.rs

@@ -42,7 +42,7 @@ use tracing::{error, warn};
 use super::{
     event::Header,
     filter_requested_event_rep,
-    rln::{self, create_slash_proof, sss_recover, RLNNode, SlashBlob},
+    rln::{self, prepare_slash_proof_request, sss_recover, RLNNode, RlnProver, SlashBlob},
     Event, EventGraphPtr, LayerUTips, NULL_ID, NULL_PARENTS,
 };
 use crate::{
@@ -754,27 +754,21 @@ impl ProtocolEventGraph {
             }
         }
 
-        let slash_pk = match self.event_graph.zk_keys.load_slash_pk() {
-            Ok(pk) => pk,
-            Err(e) => {
-                error!(target: "event_graph::protocol", "[RLN] Failed to load slash PK: {e}");
-                return
-            }
+        let request = {
+            let id = self.event_graph.identity_state.read().await;
+            prepare_slash_proof_request(identity_secret_hash, &id)
         };
-
-        let mut id = self.event_graph.identity_state.write().await;
-        let (proof, root) = match create_slash_proof(identity_secret_hash, &mut id, &slash_pk) {
-            Ok(v) => v,
+        let root = request.merkle_root;
+        let proof = match self.event_graph.zk_keys.prove_slash(request).await {
+            Ok(response) => response.proof,
             Err(e) => {
                 error!(target: "event_graph::protocol", "[RLN] Slash proof creation failed: {e}");
                 return
             }
         };
-        // Note: create_slash_proof itself does NOT mutate the SMT -
-        // it only reads the membership path. The actual removal happens
-        // when commit_verified_static_event applies the verified slashing
-        // event, the same state transition remote slashes use.
-        drop(id);
+        // The proving request captures only the membership path and root. The
+        // actual removal happens when commit_verified_static_event applies the
+        // verified slashing event, the same state transition remote slashes use.
 
         let slash_blob = SlashBlob { proof, identity_secret_hash, merkle_root: root };
         let blob = serialize_async(&slash_blob).await;

+ 135 - 15
src/event_graph/rln.rs

@@ -34,7 +34,7 @@ use darkfi_sdk::{
     crypto::{
         pasta_prelude::{FromUniformBytes, PrimeField},
         poseidon_hash,
-        smt::{MemoryStorageFp, PoseidonFp, SmtMemoryFp, EMPTY_NODES_FP},
+        smt::{MemoryStorageFp, PoseidonFp, SmtMemoryFp, EMPTY_NODES_FP, SMT_FP_DEPTH},
     },
     pasta::pallas,
 };
@@ -205,6 +205,50 @@ pub struct Blob {
     pub merkle_root: pallas::Base,
 }
 
+/// Private witness and public inputs for a signal proof.
+///
+/// This type is serializable so the local prover boundary can later be
+/// implemented by a trusted remote prover. It contains identity secrets and
+/// must never be sent to an untrusted service.
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct SignalProvingRequest {
+    pub nullifier: pallas::Base,
+    pub trapdoor: pallas::Base,
+    pub message_id: pallas::Base,
+    pub merkle_path: [pallas::Base; SMT_FP_DEPTH],
+    pub x: pallas::Base,
+    pub user_message_limit: u64,
+    pub app_id: pallas::Base,
+    pub epoch: pallas::Base,
+    pub merkle_root: pallas::Base,
+    pub external_nullifier: pallas::Base,
+    pub y: pallas::Base,
+    pub internal_nullifier: pallas::Base,
+}
+
+/// Output of a signal proving request.
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct SignalProvingResponse {
+    pub proof: Proof,
+}
+
+/// Private witness and public inputs for a slash proof.
+///
+/// This type is serializable for the same trusted-prover boundary as
+/// [`SignalProvingRequest`].
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct SlashProvingRequest {
+    pub identity_secret_hash: pallas::Base,
+    pub merkle_path: [pallas::Base; SMT_FP_DEPTH],
+    pub merkle_root: pallas::Base,
+}
+
+/// Output of a slash proving request.
+#[derive(Clone, SerialEncodable, SerialDecodable)]
+pub struct SlashProvingResponse {
+    pub proof: Proof,
+}
+
 /// An entry in the static DAG representing an identity event.
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub enum RLNNode {
@@ -277,6 +321,92 @@ impl ZkKeys {
     }
 }
 
+/// Proving backend for RLN private proofs.
+///
+/// The local implementation is backed by [`ZkKeys`]. A future trusted remote
+/// prover can implement this trait using the serializable request/response
+/// types without changing callers.
+#[async_trait]
+pub trait RlnProver: Send + Sync {
+    /// Create a signal proof from a fully prepared request.
+    async fn prove_signal(&self, request: SignalProvingRequest) -> Result<SignalProvingResponse>;
+
+    /// Create a slash proof from a fully prepared request.
+    async fn prove_slash(&self, request: SlashProvingRequest) -> Result<SlashProvingResponse>;
+}
+
+#[async_trait]
+impl RlnProver for ZkKeys {
+    async fn prove_signal(&self, request: SignalProvingRequest) -> Result<SignalProvingResponse> {
+        let pk = self.load_signal_pk()?;
+        create_signal_proof(&request, &pk)
+    }
+
+    async fn prove_slash(&self, request: SlashProvingRequest) -> Result<SlashProvingResponse> {
+        let pk = self.load_slash_pk()?;
+        create_slash_proof_from_request(&request, &pk)
+    }
+}
+
+/// Create a local signal proof from a prover request.
+pub fn create_signal_proof(
+    request: &SignalProvingRequest,
+    signal_pk: &ProvingKey,
+) -> Result<SignalProvingResponse> {
+    let zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN, false)?;
+    let witnesses = vec![
+        Witness::Base(Value::known(request.nullifier)),
+        Witness::Base(Value::known(request.trapdoor)),
+        Witness::Base(Value::known(request.message_id)),
+        Witness::SparseMerklePath(Value::known(request.merkle_path)),
+        Witness::Base(Value::known(request.x)),
+        Witness::Base(Value::known(pallas::Base::from(request.user_message_limit))),
+        Witness::Base(Value::known(request.app_id)),
+        Witness::Base(Value::known(request.epoch)),
+    ];
+    let pi = vec![
+        request.merkle_root,
+        request.external_nullifier,
+        pallas::Base::from(request.user_message_limit),
+        request.x,
+        request.y,
+        request.internal_nullifier,
+    ];
+    let circuit = ZkCircuit::new(witnesses, &zkbin);
+    let proof = Proof::create(signal_pk, &[circuit], &pi, &mut OsRng)
+        .map_err(|e| Error::Custom(format!("Signal proof creation failed: {e}")))?;
+    Ok(SignalProvingResponse { proof })
+}
+
+/// Create a local slash proof from a prover request.
+pub fn create_slash_proof_from_request(
+    request: &SlashProvingRequest,
+    slash_pk: &ProvingKey,
+) -> Result<SlashProvingResponse> {
+    let witnesses = vec![
+        Witness::Base(Value::known(request.identity_secret_hash)),
+        Witness::SparseMerklePath(Value::known(request.merkle_path)),
+    ];
+    let pi = vec![request.identity_secret_hash, request.merkle_root];
+    let zkbin = ZkBinary::decode(RLN2_SLASH_ZKBIN, false)?;
+    let circuit = ZkCircuit::new(witnesses, &zkbin);
+    let proof = Proof::create(slash_pk, &[circuit], &pi, &mut OsRng)
+        .map_err(|e| Error::Custom(format!("Slash proof creation failed: {e}")))?;
+    Ok(SlashProvingResponse { proof })
+}
+
+/// Build the slash proving request from the local identity tree.
+pub fn prepare_slash_proof_request(
+    identity_secret_hash: pallas::Base,
+    identity_state: &IdentityState,
+) -> SlashProvingRequest {
+    let commitment = poseidon_hash([identity_secret_hash]);
+    let merkle_root = identity_state.root();
+    let merkle_path = identity_state.prove_membership(&commitment).path;
+
+    SlashProvingRequest { identity_secret_hash, merkle_path, merkle_root }
+}
+
 /// Mutable RLN state shared across all protocol instances via
 /// `EventGraph::rln_state`. Each peer connection's protocol handler
 /// accesses this through a write lock so that duplicate/reuse
@@ -708,20 +838,10 @@ pub fn create_slash_proof(
     identity_state: &mut IdentityState,
     slash_pk: &ProvingKey,
 ) -> Result<(Proof, pallas::Base)> {
-    let commitment = poseidon_hash([identity_secret_hash]);
-    let root = identity_state.root();
-    let path = identity_state.prove_membership(&commitment);
-
-    let witnesses = vec![
-        Witness::Base(Value::known(identity_secret_hash)),
-        Witness::SparseMerklePath(Value::known(path.path)),
-    ];
-    let pi = vec![identity_secret_hash, root];
-    let zkbin = ZkBinary::decode(RLN2_SLASH_ZKBIN, false)?;
-    let circuit = ZkCircuit::new(witnesses, &zkbin);
-    let proof = Proof::create(slash_pk, &[circuit], &pi, &mut OsRng)
-        .map_err(|e| Error::Custom(format!("Slash proof creation failed: {e}")))?;
-    Ok((proof, root))
+    let request = prepare_slash_proof_request(identity_secret_hash, identity_state);
+    let merkle_root = request.merkle_root;
+    let response = create_slash_proof_from_request(&request, slash_pk)?;
+    Ok((response.proof, merkle_root))
 }
 
 /// Recover the secret from two or more `(x, y)` Shamir shares using

+ 18 - 30
src/event_graph/test_helpers.rs

@@ -266,20 +266,15 @@ where
 
 mod test_identity {
     use darkfi_sdk::{crypto::poseidon_hash, pasta::pallas};
-    use halo2_proofs::circuit::Value;
-    use rand::rngs::OsRng;
 
     use super::*;
-    use crate::{
-        event_graph::{
-            event::Header,
-            rln::{
-                epoch_of, hash_event, Blob, RLNNode, RegistrationAttestation, RLN2_SIGNAL_ZKBIN,
-            },
-            NULL_PARENTS,
+    use crate::event_graph::{
+        event::Header,
+        rln::{
+            epoch_of, hash_event, Blob, RLNNode, RegistrationAttestation, RlnProver,
+            SignalProvingRequest,
         },
-        zk::{Proof, Witness, ZkCircuit},
-        zkas::ZkBinary,
+        NULL_PARENTS,
     };
 
     /// A test RLN identity with deterministic secrets and an
@@ -371,28 +366,21 @@ mod test_identity {
             let y = a_0 + x * a_1;
             let internal_nullifier = poseidon_hash([a_1]);
 
-            let witnesses = vec![
-                Witness::Base(Value::known(self.nullifier)),
-                Witness::Base(Value::known(self.trapdoor)),
-                Witness::Base(Value::known(pallas::Base::from(message_id))),
-                Witness::SparseMerklePath(Value::known(path.path)),
-                Witness::Base(Value::known(x)),
-                Witness::Base(Value::known(pallas::Base::from(self.user_message_limit))),
-                Witness::Base(Value::known(app_id)),
-                Witness::Base(Value::known(epoch_field)),
-            ];
-            let pi = vec![
-                root,
-                external_nullifier,
-                pallas::Base::from(self.user_message_limit),
+            let request = SignalProvingRequest {
+                nullifier: self.nullifier,
+                trapdoor: self.trapdoor,
+                message_id: pallas::Base::from(message_id),
+                merkle_path: path.path,
                 x,
+                user_message_limit: self.user_message_limit,
+                app_id,
+                epoch: epoch_field,
+                merkle_root: root,
+                external_nullifier,
                 y,
                 internal_nullifier,
-            ];
-            let zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN, false)?;
-            let circuit = ZkCircuit::new(witnesses, &zkbin);
-            let pk = eg.zk_keys.load_signal_pk()?;
-            let proof = Proof::create(&pk, &[circuit], &pi, &mut OsRng)?;
+            };
+            let proof = eg.zk_keys.prove_signal(request).await?.proof;
 
             Ok(Blob {
                 proof,

+ 22 - 22
src/event_graph/tests_rln.rs

@@ -32,9 +32,10 @@ use smol::Executor;
 use crate::{
     event_graph::{
         rln::{
-            epoch_of, epoch_start_millis, sss_recover, Blob, IdentityState, MessageMetadata,
-            RLNNode, RegistrationAttestation, RegistrationBlob, RlnAppId, SignalCheck, SlashBlob,
-            GENESIS_BLOB_GUARD, MAX_MSG_LIMIT, RLN_EPOCH_LEN, RLN_GENESIS,
+            epoch_of, epoch_start_millis, prepare_slash_proof_request, sss_recover, Blob,
+            IdentityState, MessageMetadata, RLNNode, RegistrationAttestation, RegistrationBlob,
+            RlnAppId, RlnProver, SignalCheck, SlashBlob, GENESIS_BLOB_GUARD, MAX_MSG_LIMIT,
+            RLN_EPOCH_LEN, RLN_GENESIS,
         },
         test_helpers::{
             make_eg, make_eg_with_config, make_eg_with_config_and_db, make_network,
@@ -915,7 +916,6 @@ fn rln_e2e_slot_reuse_is_slashable() {
 #[test]
 fn rln_e2e_slash_proof_round_trip() {
     // Recover identity_secret_hash, build a slash proof, verify it.
-    use crate::event_graph::rln::create_slash_proof;
     smol::block_on(async {
         let (eg, id) = fresh_identity_and_eg().await;
         id.register_directly(&eg).await.unwrap();
@@ -937,10 +937,12 @@ fn rln_e2e_slash_proof_round_trip() {
         let recovered = sss_recover(&[s1, s2]).unwrap();
         assert_eq!(recovered, a_0);
 
-        let slash_pk = eg.zk_keys.load_slash_pk().unwrap();
-        let (proof, root) =
-            create_slash_proof(recovered, &mut *eg.identity_state.write().await, &slash_pk)
-                .unwrap();
+        let request = {
+            let id_state = eg.identity_state.read().await;
+            prepare_slash_proof_request(recovered, &id_state)
+        };
+        let root = request.merkle_root;
+        let proof = eg.zk_keys.prove_slash(request).await.unwrap().proof;
 
         // The recovered commitment must verify against the slash VK.
         let pi = vec![recovered, root];
@@ -1000,13 +1002,12 @@ async fn concurrent_slashes(ex: Arc<Executor<'static>>) {
         ish: pallas::Base,
         commitment: pallas::Base,
     ) -> (Event, Vec<u8>) {
-        let slash_pk = eg.zk_keys.load_slash_pk().expect("pk");
-        let (proof, root) = crate::event_graph::rln::create_slash_proof(
-            ish,
-            &mut *eg.identity_state.write().await,
-            &slash_pk,
-        )
-        .expect("proof");
+        let request = {
+            let id_state = eg.identity_state.read().await;
+            prepare_slash_proof_request(ish, &id_state)
+        };
+        let root = request.merkle_root;
+        let proof = eg.zk_keys.prove_slash(request).await.expect("proof").proof;
         let blob = SlashBlob { proof, identity_secret_hash: ish, merkle_root: root };
         let event = Event::new_static(serialize_async(&RLNNode::Slashing(commitment)).await, eg)
             .await
@@ -1062,13 +1063,12 @@ fn rln_static_slashes_persist_and_tombstone_commitment() {
         eg.static_insert(&reg_event).await.unwrap();
         assert!(eg.rln_contains(&commitment).await);
 
-        let slash_pk = eg.zk_keys.load_slash_pk().unwrap();
-        let (proof, root) = crate::event_graph::rln::create_slash_proof(
-            id.identity_secret_hash(),
-            &mut *eg.identity_state.write().await,
-            &slash_pk,
-        )
-        .unwrap();
+        let request = {
+            let id_state = eg.identity_state.read().await;
+            prepare_slash_proof_request(id.identity_secret_hash(), &id_state)
+        };
+        let root = request.merkle_root;
+        let proof = eg.zk_keys.prove_slash(request).await.unwrap().proof;
         let slash_blob =
             SlashBlob { proof, identity_secret_hash: id.identity_secret_hash(), merkle_root: root };
         let blob = serialize_async(&slash_blob).await;