Skip to main content

world_id_proof/
nullifier.rs

1//! Logic to generate nullifiers using the OPRF Nodes.
2
3use ark_ff::PrimeField;
4use groth16_material::circom::CircomGroth16Material;
5
6use taceo_oprf::{
7    client::{Connector, VerifiableOprfOutput},
8    core::oprf::BlindingFactor,
9};
10
11use world_id_primitives::{
12    FieldElement, ProofRequest, TREE_DEPTH,
13    circuit_inputs::QueryProofCircuitInput,
14    oprf::{NullifierOprfRequestAuthV1, OprfModule},
15};
16
17use crate::{
18    AuthenticatorProofInput,
19    proof::{OPRF_PROOF_DS, ProofError},
20};
21
22/// Nullifier computed using OPRF Nodes.
23#[derive(Debug, Clone)]
24pub struct OprfNullifier {
25    /// The raw inputs to the Query Proof circuit
26    pub query_proof_input: QueryProofCircuitInput<TREE_DEPTH>,
27    /// The result of the distributed OPRF protocol, including the final nullifier.
28    pub verifiable_oprf_output: VerifiableOprfOutput,
29}
30
31impl OprfNullifier {
32    /// Generates a nullifier through the provided OPRF nodes for
33    /// a specific proof request.
34    ///
35    /// This method will handle the signature from the Authenticator authorizing the
36    /// request for the OPRF nodes.
37    ///
38    /// # Arguments
39    /// - `services`: The list of endpoints of all OPRF nodes.
40    /// - `threshold`: The minimum number of OPRF nodes responses required to compute a valid nullifier. The
41    ///   source of truth for this value lives in the `OprfKeyRegistry` contract.
42    /// - `query_material`: The material for the query proof circuit.
43    /// - `authenticator_input`: See [`AuthenticatorProofInput`] for more details.
44    /// - `proof_request`: The proof request provided by the RP.
45    ///
46    /// # Errors
47    ///
48    /// Returns [`ProofError`] in the following cases:
49    /// * `PublicKeyNotFound` - the public key for the given authenticator private key is not found in the `key_set`.
50    /// * `InvalidDLogProof` – the `DLog` equality proof could not be verified.
51    /// * Other errors may propagate from network requests, proof generation, or Groth16 verification.
52    pub async fn generate(
53        services: &[String],
54        threshold: usize,
55        query_material: &CircomGroth16Material,
56        authenticator_input: AuthenticatorProofInput,
57        proof_request: &ProofRequest,
58        connector: Connector,
59    ) -> Result<Self, ProofError> {
60        let mut rng = rand::rngs::OsRng;
61
62        let query_blinding_factor = BlindingFactor::rand(&mut rng);
63
64        let siblings: [ark_babyjubjub::Fq; TREE_DEPTH] =
65            authenticator_input.inclusion_proof.siblings.map(|s| *s);
66
67        let action = *proof_request.computed_action(&mut rng);
68        let query_hash = world_id_primitives::authenticator::oprf_query_digest(
69            authenticator_input.inclusion_proof.leaf_index,
70            action.into(),
71            proof_request.rp_id.into(),
72        );
73        let signature = authenticator_input.private_key.sign(*query_hash);
74
75        let query_proof_input = QueryProofCircuitInput::<TREE_DEPTH> {
76            pk: authenticator_input.key_set.as_affine_array(),
77            pk_index: authenticator_input.key_index.into(),
78            s: signature.s,
79            r: signature.r,
80            merkle_root: *authenticator_input.inclusion_proof.root,
81            depth: ark_babyjubjub::Fq::from(TREE_DEPTH as u64),
82            mt_index: authenticator_input.inclusion_proof.leaf_index.into(),
83            siblings,
84            beta: query_blinding_factor.beta(),
85            rp_id: *FieldElement::from(proof_request.rp_id),
86            action,
87            nonce: *proof_request.nonce,
88        };
89
90        tracing::debug!("generating query proof");
91        let (proof, public_inputs) = query_material.generate_proof(&query_proof_input, &mut rng)?;
92        query_material.verify_proof(&proof, &public_inputs)?;
93        tracing::debug!("generated query proof");
94
95        let auth = NullifierOprfRequestAuthV1 {
96            proof: proof.into(),
97            action,
98            nonce: *proof_request.nonce,
99            merkle_root: *authenticator_input.inclusion_proof.root,
100            current_time_stamp: proof_request.created_at,
101            expiration_timestamp: proof_request.expires_at,
102            signature: proof_request.signature,
103            rp_id: proof_request.rp_id,
104        };
105
106        tracing::debug!("executing distributed OPRF");
107
108        let verifiable_oprf_output = taceo_oprf::client::distributed_oprf(
109            services,
110            OprfModule::Nullifier.to_string().as_str(),
111            threshold,
112            *query_hash,
113            query_blinding_factor,
114            ark_babyjubjub::Fq::from_be_bytes_mod_order(OPRF_PROOF_DS),
115            auth,
116            connector,
117        )
118        .await?;
119
120        Ok(Self {
121            query_proof_input,
122            verifiable_oprf_output,
123        })
124    }
125}