Skip to main content

world_id_primitives/
oprf.rs

1use crate::serde_utils;
2use alloy_primitives::U256;
3use ark_bn254::Bn254;
4use circom_types::groth16::Proof;
5use serde::{Deserialize, Serialize};
6use taceo_oprf::types::api::{CloseFrameMessage, OprfRequestAuthenticatorError};
7
8use crate::{FieldElement, rp::RpId};
9
10/// The most significant byte (MSB) of an OPRF input field element, which separates
11/// the domains in which the input may be used.
12///
13/// All three prefixes share one OPRF key and query structure, so the MSB is what keeps
14/// their outputs from colliding. The variants are exhaustive for the nullifier and
15/// session OPRF inputs.
16///
17/// These variants are the authoritative OPRF input domains.
18#[repr(u8)]
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum OprfPrefix {
21    /// The default domain: uniqueness actions, and any other OPRF input that is not
22    /// session-scoped.
23    ///
24    /// Unlike the session prefixes, `0x00` elements are not minted by the protocol —
25    /// uniqueness actions are chosen freely by the RP. Checking for this prefix
26    /// therefore only rules out session reuse; it implies nothing else about the value.
27    Uniqueness = 0x00,
28    /// The [`crate::SessionId::oprf_seed`].
29    SessionOprfSeed = 0x01,
30    /// The action used to compute the inner nullifier in a [`crate::SessionNullifier`].
31    SessionAction = 0x02,
32}
33
34/// Generation and validation of domain-prefixed OPRF inputs. See [`OprfPrefix`].
35pub trait OprfPrefixedFieldElement {
36    /// Generate a randomized field element carrying the given OPRF prefix.
37    fn random_with_prefix<R: rand::CryptoRng + rand::RngCore>(
38        rng: &mut R,
39        prefix: OprfPrefix,
40    ) -> FieldElement;
41
42    /// Returns whether the field element carries the given OPRF prefix.
43    fn has_prefix(&self, prefix: OprfPrefix) -> bool;
44}
45
46impl OprfPrefixedFieldElement for FieldElement {
47    fn random_with_prefix<R: rand::CryptoRng + rand::RngCore>(
48        rng: &mut R,
49        prefix: OprfPrefix,
50    ) -> FieldElement {
51        let mut bytes = [0u8; 32];
52        rng.fill_bytes(&mut bytes);
53        bytes[0] = prefix as u8;
54        Self::from_be_bytes(&bytes).expect(
55            "should always fit in the field because with 0x02 or lower as the MSB, the field element < babyjubjub modulus",
56        )
57    }
58
59    fn has_prefix(&self, prefix: OprfPrefix) -> bool {
60        self.to_be_bytes()[0] == prefix as u8
61    }
62}
63
64/// A module identifier for OPRF evaluations.
65#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
66pub enum OprfModule {
67    /// Oprf module for generating nullifiers
68    Nullifier,
69    /// Oprf module for generating credential blinding factors
70    CredentialBlindingFactor,
71    /// Oprf module for generating internal nullifiers for sessions proofs and the `session_id_r_seed`
72    Session,
73}
74
75/// Additional data needed to reconstruct the message covered by an RP signature.
76#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(rename_all = "snake_case")]
78pub enum RpSignatureVerification {
79    /// A uniqueness action covered by the RP signature.
80    ///
81    /// This is used on create-and-bind session-seed queries, whose OPRF action is the
82    /// session seed rather than the uniqueness action included in the signed message.
83    UniquenessAction {
84        /// The RP-signed uniqueness action (MSB `0x00`).
85        action: FieldElement,
86    },
87}
88
89impl std::fmt::Display for OprfModule {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        match self {
92            Self::Nullifier => write!(f, "nullifier"),
93            Self::CredentialBlindingFactor => write!(f, "credential_blinding_factor"),
94            Self::Session => write!(f, "session"),
95        }
96    }
97}
98
99/// A request sent by a client for OPRF nullifier authentication.
100#[derive(Clone, Serialize, Deserialize)]
101pub struct NullifierOprfRequestAuthV1 {
102    /// Zero-knowledge proof provided by the user.
103    pub proof: Proof<Bn254>,
104    /// The action
105    #[serde(with = "ark_serde_compat::field")]
106    pub action: ark_babyjubjub::Fq,
107    /// The nonce
108    #[serde(with = "ark_serde_compat::field")]
109    pub nonce: ark_babyjubjub::Fq,
110    /// The Merkle root associated with this request.
111    #[serde(with = "ark_serde_compat::field")]
112    pub merkle_root: ark_babyjubjub::Fq,
113    /// The current time stamp (unix secs)
114    #[serde(alias = "current_time_stamp")]
115    pub created_at: u64,
116    /// Expiration timestamp of the request (unix secs)
117    #[serde(alias = "expiration_timestamp")]
118    pub expires_at: u64,
119    /// The RP's signature on the request, see `compute_rp_signature_msg` for details.
120    ///
121    /// Can be `None` if the RP is a WIP101 conform contract.
122    #[serde(default, skip_serializing_if = "Option::is_none")]
123    pub signature: Option<alloy_primitives::Signature>,
124    /// The `rp_id`
125    pub rp_id: RpId,
126    /// Auxiliary data for WIP101 verification.
127    ///
128    /// Maximum length of this field is 1024 bytes. If the RP is not backed by a WIP101 signer contract, you can omit this value is it will be ignored by the OPRF-nodes anyways.
129    ///
130    /// If the RP signer is an WIP101 backed contract, this data is send verbatim to the contract without any form of validation (except size).
131    #[serde(
132        default,
133        skip_serializing_if = "Option::is_none",
134        with = "serde_utils::hex_bytes_opt"
135    )]
136    pub wip101_data: Option<Vec<u8>>,
137    /// Additional data needed to reconstruct the RP-signed message.
138    ///
139    /// Currently only valid on create-and-bind session-seed queries (see
140    /// [`OprfPrefix::SessionOprfSeed`]) from EOA-backed RPs.
141    #[serde(default, skip_serializing_if = "Option::is_none")]
142    pub rp_signature_verification: Option<RpSignatureVerification>,
143}
144
145/// A request sent by a client for OPRF credential blinding factor authentication.
146#[derive(Clone, Serialize, Deserialize)]
147pub struct CredentialBlindingFactorOprfRequestAuthV1 {
148    /// Zero-knowledge proof provided by the user.
149    pub proof: Proof<Bn254>,
150    /// The action
151    #[serde(with = "ark_serde_compat::field")]
152    pub action: ark_babyjubjub::Fq,
153    /// The nonce
154    #[serde(with = "ark_serde_compat::field")]
155    pub nonce: ark_babyjubjub::Fq,
156    /// The Merkle root associated with this request.
157    #[serde(with = "ark_serde_compat::field")]
158    pub merkle_root: ark_babyjubjub::Fq,
159    /// The `issuer_schema_id` in the `CredentialSchemaIssuerRegistry` contract
160    pub issuer_schema_id: u64,
161}
162
163/// Concrete error type returned by OPRF request authentication.
164///
165/// Variants map 1-to-1 with the numeric close-frame error codes in [`error_codes`], which are
166/// sent to the client over the WebSocket connection when authentication fails.
167#[derive(Copy, Clone, Debug, thiserror::Error)]
168#[non_exhaustive]
169pub enum WorldIdRequestAuthError {
170    /// Unknown RP. The RP is likely not registerd in the `RpRegistry`.
171    #[error("unknown_rp")]
172    UnknownRp,
173    /// Inactive RP. The RP was deactivated in the `RpRegistry`. Inactive RPs cannot
174    /// request proofs. If you are the RP, call `updateRp` to re-activate.
175    #[error("inactive_rp")]
176    InactiveRp,
177    /// **Only valid for Credential Blinding Factor generation**.
178    ///
179    /// The `issuerSchemaId` provided to generate a blinding factor is not valid. The
180    /// value is either incorrect or the `issuerSchemaId` is not correctly registered in
181    /// the `CredentialSchemaIssuerRegistry`.
182    #[error("unknown_schema_issuer_id")]
183    UnknownSchemaIssuerId,
184    /// The request timestamp is too old. If you are the RP please sign a request with
185    /// a fresh timestamp.
186    #[error("timestamp_too_old")]
187    CreatedAtTooOld,
188    /// The request timestamp is too far in the future. If you are the RP please sign a request with
189    /// a fresh timestamp.
190    #[error("timestamp_too_far_in_future")]
191    CreatedAtTooFarInFuture,
192    /// The expires_at timestamp is too far in the future. If you are the RP please sign a new request with
193    /// a new expires at.
194    #[error("expires_at_too_far_in_future")]
195    ExpiresAtTooFarInFuture,
196    /// The timestamp cannot be parsed as it was not a valid unix epoch timestamp.
197    #[error("invalid_timestamp")]
198    InvalidTimestamp,
199    /// The RP signature has expired. If you are the RP please sign a request
200    /// with a fresh timestamp.
201    #[error("rp_signature_expired")]
202    RpSignatureExpired,
203    /// The RP's signature on the request could not be verified. The signature may be
204    /// incorrect, the wrong public key used, or does not match the expected message.
205    #[error("invalid_rp_signature")]
206    InvalidRpSignature,
207    /// Requester did not provide a signature of the RP, but the RP's signer
208    /// is an EOA.
209    /// Empty signatures are only supported for WIP101 backed RPs.
210    #[error("rp_signature_missing")]
211    RpSignatureMissing,
212    /// RP signer is an EOA but request had auxiliary data.
213    #[error("wip101_aux_data_on_eoa")]
214    Wip101AuxDataOnEoa,
215    /// A duplicate nonce was detected. Duplicate nonces are not allowed to prevent
216    /// replay attacks. If you are the RP please generate a new nonce.
217    #[error("duplicate_nonce")]
218    DuplicateNonce,
219    /// The provided Merkle root is not valid for the `WorldIDRegistry`. This can happen
220    /// when the inclusion proof is too old. Please compute a new inclusion proof.
221    #[error("invalid_merkle_root")]
222    InvalidMerkleRoot,
223    /// The client Query Proof, used to authenticate the user did not verify correctly
224    /// for the provided inputs.
225    #[error("invalid_query_proof")]
226    InvalidQueryProof,
227    /// **Only valid for Credential Blinding Factor generation**.
228    ///
229    /// The provided action for the credential issuer blinding factor computation is not valid.
230    #[error("invalid_action_for_blinding_factor")]
231    InvalidActionSchemaIssuer,
232    /// The provided action for the nullifier computation is not valid. Nullifier actions must
233    /// start with `0x00` (MSB).
234    #[error("invalid_action_for_nullifier")]
235    InvalidActionNullifier,
236    /// **Only valid for Session Proofs**.
237    ///
238    /// The provided action for the Session Proof is invalid. See [`OprfPrefix`] for the valid action
239    /// prefixes.
240    #[error("invalid_action_for_session")]
241    InvalidActionSession,
242    /// The provided RP signature verification data is invalid or not allowed on this query.
243    ///
244    /// Verification data is only valid on create-and-bind session-seed queries from
245    /// EOA-backed RPs and must carry a uniqueness action (MSB `0x00`).
246    #[error("invalid_rp_signature_verification")]
247    InvalidRpSignatureVerification,
248    /// The RP signer is a contract but does not implement the WIP101 interface.
249    #[error("wip101_incompatible_rp_signer")]
250    Wip101IncompatibleRpSigner,
251    /// The WIP101 signer contract rejected the request.
252    ///
253    /// The contract may optionally return a rejection code (`U256`), which is captured in this error as `Some(code)`. If no additional code is provided, this will be `None`.
254    ///
255    /// When constructing this variant from just the `CloseFrame`'s `code`, the contract's additional code will be lost. The additional code, if any, is sent as `reason` in the `CloseFrame`.
256    #[error("wip101_verification_failed")]
257    Wip101VerificationFailed(Option<U256>),
258    /// Invalid custom revert for WIP101 contract.
259    ///
260    /// WIP101 specifies that contracts must revert with `error RpInvalidRequest(uint256 code)` but contract reverted with unknown error.
261    #[error("wip101_custom_revert")]
262    Wip101CustomRevert,
263    /// Provided auxiliary data is too large.
264    ///
265    /// WIP101 specifies that provided `data` must be smaller than 1024 bytes.
266    #[error("wip101_aux_data_too_large")]
267    Wip101AuxDataTooLarge,
268    /// WIP101 signature verification ran into timeout.
269    #[error("wip101_verification_timeout")]
270    Wip101VerificationTimeout,
271    /// Doing WIP101/ERC165 check on RP's signer ran into timeout.
272    #[error("wip101_account_check_timeout")]
273    Wip101AccountCheckTimeout,
274    /// Internal server error.
275    #[error("internal_server_error")]
276    Internal,
277    /// Unknown error code not mapped to a known variant.
278    #[error("unknown_error_{0}")]
279    Unknown(u16),
280}
281
282/// The actor where a provided OPRF error likely originated and with ability
283/// to fix it.
284#[derive(Debug, Clone, Copy, PartialEq, Eq)]
285pub enum ErrorActor {
286    /// The Relying Party requesting a Proof
287    Rp,
288    /// The Issuer of a Credential
289    Issuer,
290    /// The Authenticator of the user
291    Authenticator,
292    /// Error attributable to an OPRF node
293    OprfNode,
294}
295
296impl WorldIdRequestAuthError {
297    /// Return the [`ErrorActor`] associated for this error.
298    #[must_use]
299    pub const fn as_actor(&self) -> ErrorActor {
300        match self {
301            Self::UnknownRp
302            | Self::InactiveRp
303            | Self::CreatedAtTooOld
304            | Self::CreatedAtTooFarInFuture
305            | Self::ExpiresAtTooFarInFuture
306            | Self::InvalidTimestamp
307            | Self::RpSignatureExpired
308            | Self::InvalidRpSignature
309            | Self::DuplicateNonce
310            | Self::InvalidActionNullifier
311            | Self::Wip101IncompatibleRpSigner
312            | Self::Wip101VerificationFailed(_)
313            | Self::Wip101CustomRevert
314            | Self::Wip101VerificationTimeout
315            | Self::Wip101AuxDataOnEoa
316            | Self::Wip101AuxDataTooLarge
317            | Self::Wip101AccountCheckTimeout => ErrorActor::Rp,
318            Self::UnknownSchemaIssuerId => ErrorActor::Issuer,
319            Self::InvalidMerkleRoot
320            | Self::InvalidQueryProof
321            | Self::InvalidActionSchemaIssuer
322            | Self::InvalidActionSession
323            | Self::InvalidRpSignatureVerification
324            | Self::RpSignatureMissing => ErrorActor::Authenticator,
325            Self::Internal | Self::Unknown(_) => ErrorActor::OprfNode,
326        }
327    }
328}
329
330impl From<u16> for WorldIdRequestAuthError {
331    fn from(value: u16) -> Self {
332        match value {
333            error_codes::UNKNOWN_RP => Self::UnknownRp,
334            error_codes::INACTIVE_RP => Self::InactiveRp,
335            error_codes::CREATED_AT_TOO_OLD => Self::CreatedAtTooOld,
336            error_codes::INVALID_RP_SIGNATURE => Self::InvalidRpSignature,
337            error_codes::DUPLICATE_NONCE => Self::DuplicateNonce,
338            error_codes::INVALID_MERKLE_ROOT => Self::InvalidMerkleRoot,
339            error_codes::INVALID_QUERY_PROOF => Self::InvalidQueryProof,
340            error_codes::INVALID_ACTION_SCHEMA_ISSUER => Self::InvalidActionSchemaIssuer,
341            error_codes::UNKNOWN_SCHEMA_ISSUER => Self::UnknownSchemaIssuerId,
342            error_codes::INVALID_ACTION_NULLIFIER => Self::InvalidActionNullifier,
343            error_codes::INVALID_ACTION_SESSION => Self::InvalidActionSession,
344            error_codes::INVALID_RP_SIGNATURE_VERIFICATION => Self::InvalidRpSignatureVerification,
345            error_codes::RP_SIGNATURE_EXPIRED => Self::RpSignatureExpired,
346            error_codes::RP_SIGNATURE_MISSING => Self::RpSignatureMissing,
347            error_codes::INVALID_TIMESTAMP => Self::InvalidTimestamp,
348            error_codes::CREATED_AT_TOO_FAR_IN_FUTURE => Self::CreatedAtTooFarInFuture,
349            error_codes::EXPIRES_AT_TOO_FAR_IN_FUTURE => Self::ExpiresAtTooFarInFuture,
350            error_codes::WIP101_INCOMPATIBLE_RP_SIGNER => Self::Wip101IncompatibleRpSigner,
351            error_codes::WIP101_VERIFICATION_TIMEOUT => Self::Wip101VerificationTimeout,
352            error_codes::WIP101_ACCOUNT_CHECK_TIMEOUT => Self::Wip101AccountCheckTimeout,
353            // we lost the additional code when converting from just the u16
354            error_codes::WIP101_VERIFICATION_FAILED => Self::Wip101VerificationFailed(None),
355            error_codes::WIP101_CUSTOM_REVERT => Self::Wip101CustomRevert,
356            error_codes::INTERNAL => Self::Internal,
357            other => Self::Unknown(other),
358        }
359    }
360}
361
362impl From<WorldIdRequestAuthError> for u16 {
363    fn from(value: WorldIdRequestAuthError) -> Self {
364        match value {
365            WorldIdRequestAuthError::UnknownRp => error_codes::UNKNOWN_RP,
366            WorldIdRequestAuthError::InactiveRp => error_codes::INACTIVE_RP,
367            WorldIdRequestAuthError::CreatedAtTooOld => error_codes::CREATED_AT_TOO_OLD,
368            WorldIdRequestAuthError::ExpiresAtTooFarInFuture => {
369                error_codes::EXPIRES_AT_TOO_FAR_IN_FUTURE
370            }
371            WorldIdRequestAuthError::InvalidTimestamp => error_codes::INVALID_TIMESTAMP,
372            WorldIdRequestAuthError::InvalidRpSignature => error_codes::INVALID_RP_SIGNATURE,
373            WorldIdRequestAuthError::RpSignatureMissing => error_codes::RP_SIGNATURE_MISSING,
374            WorldIdRequestAuthError::DuplicateNonce => error_codes::DUPLICATE_NONCE,
375            WorldIdRequestAuthError::InvalidMerkleRoot => error_codes::INVALID_MERKLE_ROOT,
376            WorldIdRequestAuthError::InvalidQueryProof => error_codes::INVALID_QUERY_PROOF,
377            WorldIdRequestAuthError::InvalidActionSchemaIssuer => {
378                error_codes::INVALID_ACTION_SCHEMA_ISSUER
379            }
380            WorldIdRequestAuthError::UnknownSchemaIssuerId => error_codes::UNKNOWN_SCHEMA_ISSUER,
381            WorldIdRequestAuthError::InvalidActionNullifier => {
382                error_codes::INVALID_ACTION_NULLIFIER
383            }
384            WorldIdRequestAuthError::InvalidActionSession => error_codes::INVALID_ACTION_SESSION,
385            WorldIdRequestAuthError::InvalidRpSignatureVerification => {
386                error_codes::INVALID_RP_SIGNATURE_VERIFICATION
387            }
388            WorldIdRequestAuthError::RpSignatureExpired => error_codes::RP_SIGNATURE_EXPIRED,
389            WorldIdRequestAuthError::CreatedAtTooFarInFuture => {
390                error_codes::CREATED_AT_TOO_FAR_IN_FUTURE
391            }
392            WorldIdRequestAuthError::Wip101IncompatibleRpSigner => {
393                error_codes::WIP101_INCOMPATIBLE_RP_SIGNER
394            }
395            WorldIdRequestAuthError::Wip101VerificationFailed(_) => {
396                error_codes::WIP101_VERIFICATION_FAILED
397            }
398            WorldIdRequestAuthError::Wip101VerificationTimeout => {
399                error_codes::WIP101_VERIFICATION_TIMEOUT
400            }
401            WorldIdRequestAuthError::Wip101CustomRevert => error_codes::WIP101_CUSTOM_REVERT,
402            WorldIdRequestAuthError::Wip101AuxDataOnEoa => error_codes::WIP101_AUX_DATA_ON_EOA,
403            WorldIdRequestAuthError::Wip101AuxDataTooLarge => {
404                error_codes::WIP101_AUX_DATA_TOO_LARGE
405            }
406            WorldIdRequestAuthError::Wip101AccountCheckTimeout => {
407                error_codes::WIP101_ACCOUNT_CHECK_TIMEOUT
408            }
409            WorldIdRequestAuthError::Internal => error_codes::INTERNAL,
410            WorldIdRequestAuthError::Unknown(other) => other,
411        }
412    }
413}
414
415/// Numeric close-frame error codes sent to the client when [`WorldIdRequestAuthError`] occurs.
416pub mod error_codes {
417    /// Error code for [`super::WorldIdRequestAuthError::UnknownRp`].
418    pub const UNKNOWN_RP: u16 = 4500;
419    /// Error code for [`super::WorldIdRequestAuthError::CreatedAtTooOld`].
420    pub const CREATED_AT_TOO_OLD: u16 = 4501;
421    /// Error code for [`super::WorldIdRequestAuthError::InvalidRpSignature`].
422    pub const INVALID_RP_SIGNATURE: u16 = 4502;
423    /// Error code for [`super::WorldIdRequestAuthError::DuplicateNonce`].
424    pub const DUPLICATE_NONCE: u16 = 4503;
425    /// Error code for [`super::WorldIdRequestAuthError::InvalidMerkleRoot`].
426    pub const INVALID_MERKLE_ROOT: u16 = 4504;
427    /// Error code for [`super::WorldIdRequestAuthError::InvalidQueryProof`].
428    pub const INVALID_QUERY_PROOF: u16 = 4505;
429    /// Error code for [`super::WorldIdRequestAuthError::InvalidActionSchemaIssuer`].
430    pub const INVALID_ACTION_SCHEMA_ISSUER: u16 = 4506;
431    /// Error code for [`super::WorldIdRequestAuthError::UnknownSchemaIssuerId`].
432    pub const UNKNOWN_SCHEMA_ISSUER: u16 = 4507;
433    /// Error code for [`super::WorldIdRequestAuthError::InvalidActionNullifier`].
434    pub const INVALID_ACTION_NULLIFIER: u16 = 4508;
435    /// Error code for [`super::WorldIdRequestAuthError::InvalidActionSession`].
436    pub const INVALID_ACTION_SESSION: u16 = 4509;
437    /// Error code for [`super::WorldIdRequestAuthError::InactiveRp`].
438    pub const INACTIVE_RP: u16 = 4510;
439    /// Error code for [`super::WorldIdRequestAuthError::RpSignatureExpired`].
440    pub const RP_SIGNATURE_EXPIRED: u16 = 4511;
441    /// Error code for [`super::WorldIdRequestAuthError::InvalidTimestamp`].
442    pub const INVALID_TIMESTAMP: u16 = 4512;
443    /// Error code for [`super::WorldIdRequestAuthError::CreatedAtTooFarInFuture`].
444    pub const CREATED_AT_TOO_FAR_IN_FUTURE: u16 = 4513;
445    /// Error code for [`super::WorldIdRequestAuthError::Wip101IncompatibleRpSigner`].
446    pub const WIP101_INCOMPATIBLE_RP_SIGNER: u16 = 4514;
447    /// Error code for [`super::WorldIdRequestAuthError::Wip101VerificationFailed`].
448    pub const WIP101_VERIFICATION_FAILED: u16 = 4515;
449    /// Error code for [`super::WorldIdRequestAuthError::Wip101CustomRevert`].
450    pub const WIP101_CUSTOM_REVERT: u16 = 4516;
451    /// Error code for [`super::WorldIdRequestAuthError::Wip101AuxDataTooLarge`].
452    pub const WIP101_AUX_DATA_TOO_LARGE: u16 = 4517;
453    /// Error code for [`super::WorldIdRequestAuthError::RpSignatureMissing`]
454    pub const RP_SIGNATURE_MISSING: u16 = 4518;
455    /// Error code for [`super::WorldIdRequestAuthError::Wip101AuxDataOnEoa`]
456    pub const WIP101_AUX_DATA_ON_EOA: u16 = 4519;
457    /// Error code for [`super::WorldIdRequestAuthError::Wip101VerificationTimeout`]
458    pub const WIP101_VERIFICATION_TIMEOUT: u16 = 4520;
459    /// Error code for [`super::WorldIdRequestAuthError::Wip101AccountCheckTimeout`]
460    pub const WIP101_ACCOUNT_CHECK_TIMEOUT: u16 = 4521;
461    /// Error code for [`super::WorldIdRequestAuthError::ExpiresAtTooFarInFuture`].
462    pub const EXPIRES_AT_TOO_FAR_IN_FUTURE: u16 = 4523;
463    /// Error code for [`super::WorldIdRequestAuthError::InvalidRpSignatureVerification`].
464    pub const INVALID_RP_SIGNATURE_VERIFICATION: u16 = 4524;
465    /// Error code for [`super::WorldIdRequestAuthError::Internal`].
466    pub const INTERNAL: u16 = 1011;
467}
468
469impl From<WorldIdRequestAuthError> for OprfRequestAuthenticatorError {
470    fn from(value: WorldIdRequestAuthError) -> Self {
471        let code = u16::from(value);
472        let msg = match value {
473            WorldIdRequestAuthError::UnknownRp => {
474                taceo_oprf::types::close_frame_message!("unknown RP")
475            }
476            WorldIdRequestAuthError::CreatedAtTooOld => {
477                taceo_oprf::types::close_frame_message!("created_at too old")
478            }
479            WorldIdRequestAuthError::CreatedAtTooFarInFuture => {
480                taceo_oprf::types::close_frame_message!("created_at too far in future")
481            }
482            WorldIdRequestAuthError::ExpiresAtTooFarInFuture => {
483                taceo_oprf::types::close_frame_message!("expires_at too far in the future")
484            }
485            WorldIdRequestAuthError::InvalidRpSignature => {
486                taceo_oprf::types::close_frame_message!("signature from RP cannot be verified")
487            }
488            WorldIdRequestAuthError::RpSignatureMissing => {
489                taceo_oprf::types::close_frame_message!("RP signature missing but signer is an EOA")
490            }
491            WorldIdRequestAuthError::DuplicateNonce => {
492                taceo_oprf::types::close_frame_message!("signature nonce already used")
493            }
494            WorldIdRequestAuthError::InvalidMerkleRoot => {
495                taceo_oprf::types::close_frame_message!("invalid merkle root")
496            }
497            WorldIdRequestAuthError::InvalidQueryProof => {
498                taceo_oprf::types::close_frame_message!("cannot verify query proof")
499            }
500            WorldIdRequestAuthError::InvalidActionSchemaIssuer => {
501                taceo_oprf::types::close_frame_message!(
502                    "invalid action for credential sub blinding factor"
503                )
504            }
505            WorldIdRequestAuthError::UnknownSchemaIssuerId => {
506                taceo_oprf::types::close_frame_message!("unknown schema issuer id")
507            }
508            WorldIdRequestAuthError::InvalidActionNullifier => {
509                taceo_oprf::types::close_frame_message!("invalid action for nullifier")
510            }
511            WorldIdRequestAuthError::InvalidActionSession => {
512                taceo_oprf::types::close_frame_message!("invalid action for session proofs")
513            }
514            WorldIdRequestAuthError::InactiveRp => {
515                taceo_oprf::types::close_frame_message!("inactive RP")
516            }
517            WorldIdRequestAuthError::RpSignatureExpired => {
518                taceo_oprf::types::close_frame_message!("RP signature expired")
519            }
520            WorldIdRequestAuthError::InvalidTimestamp => {
521                taceo_oprf::types::close_frame_message!("cannot parse timestamp on request")
522            }
523            WorldIdRequestAuthError::Wip101IncompatibleRpSigner => {
524                taceo_oprf::types::close_frame_message!(
525                    "RP has a contract backed signer but doesn't conform to WIP101"
526                )
527            }
528            WorldIdRequestAuthError::Wip101CustomRevert => {
529                taceo_oprf::types::close_frame_message!(
530                    "RP signer contract reverted with custom error (and not error RpInvalidRequest(uint256 code);)"
531                )
532            }
533            WorldIdRequestAuthError::Wip101VerificationFailed(None) => {
534                // send empty message so that it is easier to parse the code in case there is any
535                taceo_oprf::types::close_frame_message!("")
536            }
537            WorldIdRequestAuthError::Wip101VerificationTimeout => {
538                taceo_oprf::types::close_frame_message!("WIP101 verification ran into timeout")
539            }
540            WorldIdRequestAuthError::Wip101VerificationFailed(Some(code)) => {
541                // this should never truncate as code is a U256 encoded as hex
542                CloseFrameMessage::new_truncate(format!("{:#x}", code))
543            }
544            WorldIdRequestAuthError::InvalidRpSignatureVerification => {
545                taceo_oprf::types::close_frame_message!("Invalid RP signature verification data")
546            }
547            WorldIdRequestAuthError::Wip101AuxDataOnEoa => taceo_oprf::types::close_frame_message!(
548                "Auxiliary data must be empty with EOA backed signer"
549            ),
550            WorldIdRequestAuthError::Wip101AuxDataTooLarge => {
551                taceo_oprf::types::close_frame_message!(
552                    "Auxiliary data for WIP101 contract too large - max 1024 bytes"
553                )
554            }
555            WorldIdRequestAuthError::Wip101AccountCheckTimeout => {
556                taceo_oprf::types::close_frame_message!(
557                    "Ran into timeout while doing WIP101/ERC165 check on RP's signer"
558                )
559            }
560            WorldIdRequestAuthError::Internal => {
561                taceo_oprf::types::close_frame_message!("internal server error")
562            }
563            WorldIdRequestAuthError::Unknown(_) => {
564                taceo_oprf::types::close_frame_message!("unknown")
565            }
566        };
567        Self::with_message(code, msg)
568    }
569}
570
571#[cfg(test)]
572mod tests {
573    use super::*;
574
575    const ALL_PREFIXES: [OprfPrefix; 3] = [
576        OprfPrefix::Uniqueness,
577        OprfPrefix::SessionOprfSeed,
578        OprfPrefix::SessionAction,
579    ];
580
581    #[test]
582    fn random_with_prefix_is_recognized_only_by_its_own_prefix() {
583        let mut rng = rand::rngs::OsRng;
584        for prefix in ALL_PREFIXES {
585            let field_element = FieldElement::random_with_prefix(&mut rng, prefix);
586            assert_eq!(field_element.to_be_bytes()[0], prefix as u8);
587            for other in ALL_PREFIXES {
588                assert_eq!(field_element.has_prefix(other), other == prefix);
589            }
590        }
591    }
592
593    /// A structurally valid Groth16 proof (BN254 generator points) for serde tests.
594    fn test_proof() -> Proof<Bn254> {
595        serde_json::from_value(serde_json::json!({
596            "pi_a": ["1", "2", "1"],
597            "pi_b": [
598                [
599                    "10857046999023057135944570762232829481370756359578518086990519993285655852781",
600                    "11559732032986387107991004021392285783925812861821192530917403151452391805634"
601                ],
602                [
603                    "8495653923123431417604973247489272438418190587263600148770280649306958101930",
604                    "4082367875863433681332203403145435568316851327593401208105741076214120093531"
605                ],
606                ["1", "0"]
607            ],
608            "pi_c": ["1", "2", "1"],
609            "protocol": "groth16",
610            "curve": "bn128"
611        }))
612        .expect("valid test proof")
613    }
614
615    fn test_auth(
616        rp_signature_verification: Option<RpSignatureVerification>,
617    ) -> NullifierOprfRequestAuthV1 {
618        NullifierOprfRequestAuthV1 {
619            proof: test_proof(),
620            action: ark_babyjubjub::Fq::from(1u64),
621            nonce: ark_babyjubjub::Fq::from(2u64),
622            merkle_root: ark_babyjubjub::Fq::from(3u64),
623            created_at: 4,
624            expires_at: 5,
625            signature: None,
626            rp_id: RpId::new(6),
627            wip101_data: None,
628            rp_signature_verification,
629        }
630    }
631
632    #[test]
633    fn nullifier_auth_rp_signature_verification_json_roundtrip() {
634        let verification = RpSignatureVerification::UniquenessAction {
635            action: FieldElement::from(42u64),
636        };
637        let auth = test_auth(Some(verification));
638        let json = serde_json::to_string(&auth).unwrap();
639        let parsed: NullifierOprfRequestAuthV1 = serde_json::from_str(&json).unwrap();
640        assert_eq!(parsed.rp_signature_verification, Some(verification));
641    }
642
643    #[test]
644    fn nullifier_auth_rp_signature_verification_cbor_roundtrip() {
645        let verification = RpSignatureVerification::UniquenessAction {
646            action: FieldElement::from(42u64),
647        };
648        let auth = test_auth(Some(verification));
649        let mut bytes = Vec::new();
650        ciborium::into_writer(&auth, &mut bytes).unwrap();
651        let parsed: NullifierOprfRequestAuthV1 = ciborium::from_reader(bytes.as_slice()).unwrap();
652        assert_eq!(parsed.rp_signature_verification, Some(verification));
653    }
654
655    #[test]
656    fn error_code_roundtrip() {
657        let codes: &[u16] = &[
658            error_codes::UNKNOWN_RP,
659            error_codes::CREATED_AT_TOO_OLD,
660            error_codes::CREATED_AT_TOO_FAR_IN_FUTURE,
661            error_codes::INVALID_RP_SIGNATURE,
662            error_codes::DUPLICATE_NONCE,
663            error_codes::INVALID_MERKLE_ROOT,
664            error_codes::INVALID_QUERY_PROOF,
665            error_codes::INVALID_ACTION_SCHEMA_ISSUER,
666            error_codes::UNKNOWN_SCHEMA_ISSUER,
667            error_codes::INVALID_ACTION_NULLIFIER,
668            error_codes::INVALID_ACTION_SESSION,
669            error_codes::INVALID_RP_SIGNATURE_VERIFICATION,
670            error_codes::INACTIVE_RP,
671            error_codes::RP_SIGNATURE_EXPIRED,
672            error_codes::INVALID_TIMESTAMP,
673            error_codes::WIP101_INCOMPATIBLE_RP_SIGNER,
674            error_codes::WIP101_VERIFICATION_FAILED,
675            error_codes::WIP101_CUSTOM_REVERT,
676            error_codes::WIP101_AUX_DATA_TOO_LARGE,
677            error_codes::RP_SIGNATURE_MISSING,
678            error_codes::WIP101_AUX_DATA_ON_EOA,
679            error_codes::WIP101_VERIFICATION_TIMEOUT,
680            error_codes::WIP101_ACCOUNT_CHECK_TIMEOUT,
681            error_codes::INTERNAL,
682        ];
683        for &code in codes {
684            let error = WorldIdRequestAuthError::from(code);
685            let back: u16 = error.into();
686            assert_eq!(code, back, "roundtrip failed for code {code}");
687        }
688    }
689}