1use ark_bn254::Bn254;
5use ark_ff::PrimeField;
6use ark_groth16::Proof;
7use eyre::Context;
8use groth16_material::circom::CircomGroth16Material;
9use serde::Serialize;
10
11use taceo_oprf::{
12 client::{Connector, VerifiableOprfOutput},
13 core::oprf::BlindingFactor,
14 types::ShareEpoch,
15};
16
17use world_id_primitives::{
18 FieldElement, ProofRequest, SessionFieldElement, TREE_DEPTH,
19 circuit_inputs::QueryProofCircuitInput,
20 oprf::{CredentialBlindingFactorOprfRequestAuthV1, NullifierOprfRequestAuthV1, OprfModule},
21};
22
23use crate::{
24 AuthenticatorProofInput,
25 proof::{OPRF_PROOF_DS, ProofError, errors},
26};
27
28#[expect(unused_imports, reason = "used for docs")]
29use world_id_primitives::SessionNullifier;
30
31pub struct OprfEntrypoint<'a> {
34 services: &'a [String],
36 threshold: usize,
39 query_material: &'a CircomGroth16Material,
41 authenticator_input: &'a AuthenticatorProofInput,
43 connector: &'a Connector,
45}
46
47#[derive(Debug, Clone)]
49pub struct FullOprfOutput {
50 pub query_proof_input: QueryProofCircuitInput<TREE_DEPTH>,
52 pub verifiable_oprf_output: VerifiableOprfOutput,
54}
55
56impl<'a> OprfEntrypoint<'a> {
57 pub fn new(
58 services: &'a [String],
59 threshold: usize,
60 query_material: &'a CircomGroth16Material,
61 authenticator_input: &'a AuthenticatorProofInput,
62 connector: &'a Connector,
63 ) -> Self {
64 Self {
65 services,
66 threshold,
67 query_material,
68 authenticator_input,
69 connector,
70 }
71 }
72
73 pub async fn gen_credential_blinding_factor<R: rand::CryptoRng + rand::RngCore>(
89 &self,
90 rng: &mut R,
91 issuer_schema_id: u64,
92 ) -> Result<(FieldElement, ShareEpoch), ProofError> {
93 let action = FieldElement::ZERO;
95
96 let result = Self::generate_query_proof(
97 self.query_material,
98 self.authenticator_input,
99 action,
100 FieldElement::ZERO,
101 issuer_schema_id.into(),
102 rng,
103 )?;
104
105 let auth = CredentialBlindingFactorOprfRequestAuthV1 {
106 proof: result.proof.into(),
107 action: *action,
108 nonce: *FieldElement::ZERO,
109 merkle_root: *self.authenticator_input.inclusion_proof.root,
110 issuer_schema_id,
111 };
112
113 let verifiable_oprf_output = Self::execute_distributed_oprf(
114 self.services,
115 self.threshold,
116 result.query_hash,
117 result.blinding_factor,
118 auth,
119 OprfModule::CredentialBlindingFactor,
120 self.connector.clone(),
121 )
122 .await?;
123 Ok((
124 verifiable_oprf_output.output.into(),
125 verifiable_oprf_output.epoch,
126 ))
127 }
128
129 pub async fn gen_nullifier<R: rand::CryptoRng + rand::RngCore>(
144 &self,
145 rng: &mut R,
146 proof_request: &ProofRequest,
147 ) -> Result<FullOprfOutput, ProofError> {
148 let action = if proof_request.is_session_proof() {
149 FieldElement::random_for_session(rng)
152 } else {
153 proof_request.action.unwrap_or(FieldElement::ZERO)
155 };
156
157 let result = Self::generate_query_proof(
158 self.query_material,
159 self.authenticator_input,
160 action,
161 proof_request.nonce,
162 proof_request.rp_id.into(),
163 rng,
164 )?;
165
166 let auth = NullifierOprfRequestAuthV1 {
167 proof: result.proof.into(),
168 action: *action,
169 nonce: *proof_request.nonce,
170 merkle_root: *self.authenticator_input.inclusion_proof.root,
171 current_time_stamp: proof_request.created_at,
172 expiration_timestamp: proof_request.expires_at,
173 signature: proof_request.signature,
174 rp_id: proof_request.rp_id,
175 };
176
177 let verifiable_oprf_output = Self::execute_distributed_oprf(
178 self.services,
179 self.threshold,
180 result.query_hash,
181 result.blinding_factor,
182 auth,
183 OprfModule::Nullifier,
184 self.connector.clone(),
185 )
186 .await?;
187
188 Ok(FullOprfOutput {
189 query_proof_input: result.query_proof_input,
190 verifiable_oprf_output,
191 })
192 }
193}
194
195impl<'a> OprfEntrypoint<'a> {
196 fn generate_query_proof<R: rand::CryptoRng + rand::RngCore>(
200 query_material: &CircomGroth16Material,
201 authenticator_input: &AuthenticatorProofInput,
202 action: FieldElement,
203 nonce: FieldElement,
204 scope: FieldElement,
205 rng: &mut R,
206 ) -> Result<QueryProofResult, ProofError> {
207 let blinding_factor = BlindingFactor::rand(rng);
208
209 let siblings: [ark_babyjubjub::Fq; TREE_DEPTH] =
210 authenticator_input.inclusion_proof.siblings.map(|s| *s);
211
212 let query_hash = world_id_primitives::authenticator::oprf_query_digest(
213 authenticator_input.inclusion_proof.leaf_index,
214 action,
215 scope,
216 );
217 let signature = authenticator_input.private_key.sign(*query_hash);
218
219 let query_proof_input = QueryProofCircuitInput::<TREE_DEPTH> {
220 pk: authenticator_input.key_set.as_affine_array(),
221 pk_index: authenticator_input.key_index.into(),
222 s: signature.s,
223 r: signature.r,
224 merkle_root: *authenticator_input.inclusion_proof.root,
225 depth: ark_babyjubjub::Fq::from(TREE_DEPTH as u64),
226 mt_index: authenticator_input.inclusion_proof.leaf_index.into(),
227 siblings,
228 beta: blinding_factor.beta(),
229 rp_id: *scope,
230 action: *action,
231 nonce: *nonce,
232 };
233 let _ = errors::check_query_input_validity(&query_proof_input)?;
234
235 tracing::debug!("generating query proof");
236 let (proof, public_inputs) = query_material.generate_proof(&query_proof_input, rng)?;
237 query_material.verify_proof(&proof, &public_inputs)?;
238 tracing::debug!("generated query proof");
239
240 Ok(QueryProofResult {
241 query_proof_input,
242 proof,
243 query_hash: *query_hash,
244 blinding_factor,
245 })
246 }
247
248 async fn execute_distributed_oprf<A: Clone + Serialize + Send + 'static>(
251 services: &[String],
252 threshold: usize,
253 query_hash: ark_babyjubjub::Fq,
254 blinding_factor: BlindingFactor,
255 auth: A,
256 oprf_module: OprfModule,
257 connector: Connector,
258 ) -> Result<VerifiableOprfOutput, ProofError> {
259 tracing::debug!("executing distributed OPRF");
260
261 let service_uris = taceo_oprf::client::to_oprf_uri_many(services, oprf_module)
262 .context("while building service URI")?;
263
264 let verifiable_oprf_output = taceo_oprf::client::distributed_oprf(
265 &service_uris,
266 threshold,
267 query_hash,
268 blinding_factor,
269 ark_babyjubjub::Fq::from_be_bytes_mod_order(OPRF_PROOF_DS),
270 auth,
271 connector,
272 )
273 .await?;
274
275 Ok(verifiable_oprf_output)
276 }
277}
278
279struct QueryProofResult {
281 query_proof_input: QueryProofCircuitInput<TREE_DEPTH>,
282 proof: Proof<Bn254>,
283 query_hash: ark_babyjubjub::Fq,
284 blinding_factor: BlindingFactor,
285}