Skip to main content

world_id_proof/
nullifier.rs

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