use crate::serde_utils;
use alloy_primitives::U256;
use ark_bn254::Bn254;
use circom_types::groth16::Proof;
use serde::{Deserialize, Serialize};
use taceo_oprf::types::api::{CloseFrameMessage, OprfRequestAuthenticatorError};
use crate::rp::RpId;
#[expect(unused_imports, reason = "used in doc comments")]
use crate::SessionFeType;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum OprfModule {
Nullifier,
CredentialBlindingFactor,
Session,
}
impl std::fmt::Display for OprfModule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Nullifier => write!(f, "nullifier"),
Self::CredentialBlindingFactor => write!(f, "credential_blinding_factor"),
Self::Session => write!(f, "session"),
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct NullifierOprfRequestAuthV1 {
pub proof: Proof<Bn254>,
#[serde(with = "ark_serde_compat::field")]
pub action: ark_babyjubjub::Fq,
#[serde(with = "ark_serde_compat::field")]
pub nonce: ark_babyjubjub::Fq,
#[serde(with = "ark_serde_compat::field")]
pub merkle_root: ark_babyjubjub::Fq,
pub current_time_stamp: u64,
pub expiration_timestamp: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signature: Option<alloy_primitives::Signature>,
pub rp_id: RpId,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "serde_utils::hex_bytes_opt"
)]
pub wip101_data: Option<Vec<u8>>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct CredentialBlindingFactorOprfRequestAuthV1 {
pub proof: Proof<Bn254>,
#[serde(with = "ark_serde_compat::field")]
pub action: ark_babyjubjub::Fq,
#[serde(with = "ark_serde_compat::field")]
pub nonce: ark_babyjubjub::Fq,
#[serde(with = "ark_serde_compat::field")]
pub merkle_root: ark_babyjubjub::Fq,
pub issuer_schema_id: u64,
}
#[derive(Copy, Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum WorldIdRequestAuthError {
#[error("unknown_rp")]
UnknownRp,
#[error("inactive_rp")]
InactiveRp,
#[error("unknown_schema_issuer_id")]
UnknownSchemaIssuerId,
#[error("timestamp_too_old")]
TimestampTooOld,
#[error("timestamp_too_far_in_future")]
TimestampTooFarInFuture,
#[error("invalid_timestamp")]
InvalidTimestamp,
#[error("rp_signature_expired")]
RpSignatureExpired,
#[error("invalid_rp_signature")]
InvalidRpSignature,
#[error("rp_signature_missing")]
RpSignatureMissing,
#[error("wip101_aux_data_on_eoa")]
Wip101AuxDataOnEoa,
#[error("duplicate_nonce")]
DuplicateNonce,
#[error("invalid_merkle_root")]
InvalidMerkleRoot,
#[error("invalid_query_proof")]
InvalidQueryProof,
#[error("invalid_action_for_blinding_factor")]
InvalidActionSchemaIssuer,
#[error("invalid_action_for_nullifier")]
InvalidActionNullifier,
#[error("invalid_action_for_session")]
InvalidActionSession,
#[error("wip101_incompatible_rp_signer")]
Wip101IncompatibleRpSigner,
#[error("wip101_verification_failed")]
Wip101VerificationFailed(Option<U256>),
#[error("wip101_custom_revert")]
Wip101CustomRevert,
#[error("wip101_aux_data_too_large")]
Wip101AuxDataTooLarge,
#[error("wip101_verification_timeout")]
Wip101VerificationTimeout,
#[error("wip101_account_check_timeout")]
Wip101AccountCheckTimeout,
#[error("internal_server_error")]
Internal,
#[error("unknown_error_{0}")]
Unknown(u16),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorActor {
Rp,
Issuer,
Authenticator,
OprfNode,
}
impl WorldIdRequestAuthError {
#[must_use]
pub const fn as_actor(&self) -> ErrorActor {
match self {
Self::UnknownRp
| Self::InactiveRp
| Self::TimestampTooOld
| Self::TimestampTooFarInFuture
| Self::InvalidTimestamp
| Self::RpSignatureExpired
| Self::InvalidRpSignature
| Self::DuplicateNonce
| Self::InvalidActionNullifier
| Self::Wip101IncompatibleRpSigner
| Self::Wip101VerificationFailed(_)
| Self::Wip101CustomRevert
| Self::Wip101VerificationTimeout
| Self::Wip101AuxDataOnEoa
| Self::Wip101AuxDataTooLarge
| Self::Wip101AccountCheckTimeout => ErrorActor::Rp,
Self::UnknownSchemaIssuerId => ErrorActor::Issuer,
Self::InvalidMerkleRoot
| Self::InvalidQueryProof
| Self::InvalidActionSchemaIssuer
| Self::InvalidActionSession
| Self::RpSignatureMissing => ErrorActor::Authenticator,
Self::Internal | Self::Unknown(_) => ErrorActor::OprfNode,
}
}
}
impl From<u16> for WorldIdRequestAuthError {
fn from(value: u16) -> Self {
match value {
error_codes::UNKNOWN_RP => Self::UnknownRp,
error_codes::INACTIVE_RP => Self::InactiveRp,
error_codes::TIMESTAMP_TOO_OLD => Self::TimestampTooOld,
error_codes::INVALID_RP_SIGNATURE => Self::InvalidRpSignature,
error_codes::DUPLICATE_NONCE => Self::DuplicateNonce,
error_codes::INVALID_MERKLE_ROOT => Self::InvalidMerkleRoot,
error_codes::INVALID_QUERY_PROOF => Self::InvalidQueryProof,
error_codes::INVALID_ACTION_SCHEMA_ISSUER => Self::InvalidActionSchemaIssuer,
error_codes::UNKNOWN_SCHEMA_ISSUER => Self::UnknownSchemaIssuerId,
error_codes::INVALID_ACTION_NULLIFIER => Self::InvalidActionNullifier,
error_codes::INVALID_ACTION_SESSION => Self::InvalidActionSession,
error_codes::RP_SIGNATURE_EXPIRED => Self::RpSignatureExpired,
error_codes::RP_SIGNATURE_MISSING => Self::RpSignatureMissing,
error_codes::INVALID_TIMESTAMP => Self::InvalidTimestamp,
error_codes::TIMESTAMP_TOO_FAR_IN_FUTURE => Self::TimestampTooFarInFuture,
error_codes::WIP101_INCOMPATIBLE_RP_SIGNER => Self::Wip101IncompatibleRpSigner,
error_codes::WIP101_VERIFICATION_TIMEOUT => Self::Wip101VerificationTimeout,
error_codes::WIP101_ACCOUNT_CHECK_TIMEOUT => Self::Wip101AccountCheckTimeout,
error_codes::WIP101_VERIFICATION_FAILED => Self::Wip101VerificationFailed(None),
error_codes::WIP101_CUSTOM_REVERT => Self::Wip101CustomRevert,
error_codes::INTERNAL => Self::Internal,
other => Self::Unknown(other),
}
}
}
impl From<WorldIdRequestAuthError> for u16 {
fn from(value: WorldIdRequestAuthError) -> Self {
match value {
WorldIdRequestAuthError::UnknownRp => error_codes::UNKNOWN_RP,
WorldIdRequestAuthError::InactiveRp => error_codes::INACTIVE_RP,
WorldIdRequestAuthError::TimestampTooOld => error_codes::TIMESTAMP_TOO_OLD,
WorldIdRequestAuthError::InvalidTimestamp => error_codes::INVALID_TIMESTAMP,
WorldIdRequestAuthError::InvalidRpSignature => error_codes::INVALID_RP_SIGNATURE,
WorldIdRequestAuthError::RpSignatureMissing => error_codes::RP_SIGNATURE_MISSING,
WorldIdRequestAuthError::DuplicateNonce => error_codes::DUPLICATE_NONCE,
WorldIdRequestAuthError::InvalidMerkleRoot => error_codes::INVALID_MERKLE_ROOT,
WorldIdRequestAuthError::InvalidQueryProof => error_codes::INVALID_QUERY_PROOF,
WorldIdRequestAuthError::InvalidActionSchemaIssuer => {
error_codes::INVALID_ACTION_SCHEMA_ISSUER
}
WorldIdRequestAuthError::UnknownSchemaIssuerId => error_codes::UNKNOWN_SCHEMA_ISSUER,
WorldIdRequestAuthError::InvalidActionNullifier => {
error_codes::INVALID_ACTION_NULLIFIER
}
WorldIdRequestAuthError::InvalidActionSession => error_codes::INVALID_ACTION_SESSION,
WorldIdRequestAuthError::RpSignatureExpired => error_codes::RP_SIGNATURE_EXPIRED,
WorldIdRequestAuthError::TimestampTooFarInFuture => {
error_codes::TIMESTAMP_TOO_FAR_IN_FUTURE
}
WorldIdRequestAuthError::Wip101IncompatibleRpSigner => {
error_codes::WIP101_INCOMPATIBLE_RP_SIGNER
}
WorldIdRequestAuthError::Wip101VerificationFailed(_) => {
error_codes::WIP101_VERIFICATION_FAILED
}
WorldIdRequestAuthError::Wip101VerificationTimeout => {
error_codes::WIP101_VERIFICATION_TIMEOUT
}
WorldIdRequestAuthError::Wip101CustomRevert => error_codes::WIP101_CUSTOM_REVERT,
WorldIdRequestAuthError::Wip101AuxDataOnEoa => error_codes::WIP101_AUX_DATA_ON_EOA,
WorldIdRequestAuthError::Wip101AuxDataTooLarge => {
error_codes::WIP101_AUX_DATA_TOO_LARGE
}
WorldIdRequestAuthError::Wip101AccountCheckTimeout => {
error_codes::WIP101_ACCOUNT_CHECK_TIMEOUT
}
WorldIdRequestAuthError::Internal => error_codes::INTERNAL,
WorldIdRequestAuthError::Unknown(other) => other,
}
}
}
pub mod error_codes {
pub const UNKNOWN_RP: u16 = 4500;
pub const TIMESTAMP_TOO_OLD: u16 = 4501;
pub const INVALID_RP_SIGNATURE: u16 = 4502;
pub const DUPLICATE_NONCE: u16 = 4503;
pub const INVALID_MERKLE_ROOT: u16 = 4504;
pub const INVALID_QUERY_PROOF: u16 = 4505;
pub const INVALID_ACTION_SCHEMA_ISSUER: u16 = 4506;
pub const UNKNOWN_SCHEMA_ISSUER: u16 = 4507;
pub const INVALID_ACTION_NULLIFIER: u16 = 4508;
pub const INVALID_ACTION_SESSION: u16 = 4509;
pub const INACTIVE_RP: u16 = 4510;
pub const RP_SIGNATURE_EXPIRED: u16 = 4511;
pub const INVALID_TIMESTAMP: u16 = 4512;
pub const TIMESTAMP_TOO_FAR_IN_FUTURE: u16 = 4513;
pub const WIP101_INCOMPATIBLE_RP_SIGNER: u16 = 4514;
pub const WIP101_VERIFICATION_FAILED: u16 = 4515;
pub const WIP101_CUSTOM_REVERT: u16 = 4516;
pub const WIP101_AUX_DATA_TOO_LARGE: u16 = 4517;
pub const RP_SIGNATURE_MISSING: u16 = 4518;
pub const WIP101_AUX_DATA_ON_EOA: u16 = 4519;
pub const WIP101_VERIFICATION_TIMEOUT: u16 = 4520;
pub const WIP101_ACCOUNT_CHECK_TIMEOUT: u16 = 4521;
pub const INTERNAL: u16 = 1011;
}
impl From<WorldIdRequestAuthError> for OprfRequestAuthenticatorError {
fn from(value: WorldIdRequestAuthError) -> Self {
let code = u16::from(value);
let msg = match value {
WorldIdRequestAuthError::UnknownRp => {
taceo_oprf::types::close_frame_message!("unknown RP")
}
WorldIdRequestAuthError::TimestampTooOld => {
taceo_oprf::types::close_frame_message!("timestamp in request too old")
}
WorldIdRequestAuthError::TimestampTooFarInFuture => {
taceo_oprf::types::close_frame_message!("timestamp too far in future")
}
WorldIdRequestAuthError::InvalidRpSignature => {
taceo_oprf::types::close_frame_message!("signature from RP cannot be verified")
}
WorldIdRequestAuthError::RpSignatureMissing => {
taceo_oprf::types::close_frame_message!("RP signature missing but signer is an EOA")
}
WorldIdRequestAuthError::DuplicateNonce => {
taceo_oprf::types::close_frame_message!("signature nonce already used")
}
WorldIdRequestAuthError::InvalidMerkleRoot => {
taceo_oprf::types::close_frame_message!("invalid merkle root")
}
WorldIdRequestAuthError::InvalidQueryProof => {
taceo_oprf::types::close_frame_message!("cannot verify query proof")
}
WorldIdRequestAuthError::InvalidActionSchemaIssuer => {
taceo_oprf::types::close_frame_message!(
"invalid action for credential sub blinding factor"
)
}
WorldIdRequestAuthError::UnknownSchemaIssuerId => {
taceo_oprf::types::close_frame_message!("unknown schema issuer id")
}
WorldIdRequestAuthError::InvalidActionNullifier => {
taceo_oprf::types::close_frame_message!("invalid action for nullifier")
}
WorldIdRequestAuthError::InvalidActionSession => {
taceo_oprf::types::close_frame_message!("invalid action for session proofs")
}
WorldIdRequestAuthError::InactiveRp => {
taceo_oprf::types::close_frame_message!("inactive RP")
}
WorldIdRequestAuthError::RpSignatureExpired => {
taceo_oprf::types::close_frame_message!("RP signature expired")
}
WorldIdRequestAuthError::InvalidTimestamp => {
taceo_oprf::types::close_frame_message!("cannot parse timestamp on request")
}
WorldIdRequestAuthError::Wip101IncompatibleRpSigner => {
taceo_oprf::types::close_frame_message!(
"RP has a contract backed signer but doesn't conform to WIP101"
)
}
WorldIdRequestAuthError::Wip101CustomRevert => {
taceo_oprf::types::close_frame_message!(
"RP signer contract reverted with custom error (and not error RpInvalidRequest(uint256 code);)"
)
}
WorldIdRequestAuthError::Wip101VerificationFailed(None) => {
taceo_oprf::types::close_frame_message!("")
}
WorldIdRequestAuthError::Wip101VerificationTimeout => {
taceo_oprf::types::close_frame_message!("WIP101 verification ran into timeout")
}
WorldIdRequestAuthError::Wip101VerificationFailed(Some(code)) => {
CloseFrameMessage::new_truncate(format!("{:#x}", code))
}
WorldIdRequestAuthError::Wip101AuxDataOnEoa => taceo_oprf::types::close_frame_message!(
"Auxiliary data must be empty with EOA backed signer"
),
WorldIdRequestAuthError::Wip101AuxDataTooLarge => {
taceo_oprf::types::close_frame_message!(
"Auxiliary data for WIP101 contract too large - max 1024 bytes"
)
}
WorldIdRequestAuthError::Wip101AccountCheckTimeout => {
taceo_oprf::types::close_frame_message!(
"Ran into timeout while doing WIP101/ERC165 check on RP's signer"
)
}
WorldIdRequestAuthError::Internal => {
taceo_oprf::types::close_frame_message!("internal server error")
}
WorldIdRequestAuthError::Unknown(_) => {
taceo_oprf::types::close_frame_message!("unknown")
}
};
Self::with_message(code, msg)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_code_roundtrip() {
let codes: &[u16] = &[
error_codes::UNKNOWN_RP,
error_codes::TIMESTAMP_TOO_OLD,
error_codes::TIMESTAMP_TOO_FAR_IN_FUTURE,
error_codes::INVALID_RP_SIGNATURE,
error_codes::DUPLICATE_NONCE,
error_codes::INVALID_MERKLE_ROOT,
error_codes::INVALID_QUERY_PROOF,
error_codes::INVALID_ACTION_SCHEMA_ISSUER,
error_codes::UNKNOWN_SCHEMA_ISSUER,
error_codes::INVALID_ACTION_NULLIFIER,
error_codes::INVALID_ACTION_SESSION,
error_codes::INACTIVE_RP,
error_codes::RP_SIGNATURE_EXPIRED,
error_codes::INVALID_TIMESTAMP,
error_codes::WIP101_INCOMPATIBLE_RP_SIGNER,
error_codes::WIP101_VERIFICATION_FAILED,
error_codes::WIP101_CUSTOM_REVERT,
error_codes::WIP101_AUX_DATA_TOO_LARGE,
error_codes::RP_SIGNATURE_MISSING,
error_codes::WIP101_AUX_DATA_ON_EOA,
error_codes::WIP101_VERIFICATION_TIMEOUT,
error_codes::WIP101_ACCOUNT_CHECK_TIMEOUT,
error_codes::INTERNAL,
];
for &code in codes {
let error = WorldIdRequestAuthError::from(code);
let back: u16 = error.into();
assert_eq!(code, back, "roundtrip failed for code {code}");
}
}
}