Skip to main content

r402_extensions/siwx/
mod.rs

1//! Sign-In with X (`sign-in-with-x`) extension.
2//!
3//! Challenge advertisement, origin binding, paid-address/nonce storage, and
4//! CAIP-122 verification. HTTP `GrantAccess` lives in `r402-http`.
5//! Client proof is the `SIGN-IN-WITH-X` header, not `PaymentPayload.extensions`.
6
7pub mod advertise;
8pub mod client;
9pub mod origin;
10pub mod paid;
11pub mod proof;
12
13pub use advertise::{SiwxChain, SiwxExtension};
14pub use client::{SiwxClientExtension, SiwxSigner};
15pub use origin::{SiwxOrigin, SiwxOriginError};
16pub use paid::{InMemoryPaidAddressStore, PaidAddressStore};
17pub use proof::{SiwxProof, SiwxProofError};
18pub use siwx_evm::EvmVerifier;
19
20/// Wire key for the Sign-In with X extension.
21pub const SIWX_KEY: &str = "sign-in-with-x";
22
23/// HTTP request header carrying a SIWX proof. Never emit as a response header.
24pub const SIWX_HTTP_HEADER: &str = "SIGN-IN-WITH-X";
25
26/// Default `issuedAt` age accepted during field validation.
27pub const DEFAULT_MAX_ISSUED_AGE: time::Duration = time::Duration::minutes(5);
28
29/// Default challenge `expirationTime` offset from `issuedAt`.
30pub const DEFAULT_CHALLENGE_TTL: time::Duration = time::Duration::minutes(5);
31
32/// Default CAIP-122 `statement` advertised on SIWX challenges.
33pub const DEFAULT_STATEMENT: &str = "Sign in to access this resource";
34
35/// Spec `invalid_siwx_*` failure codes.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
37#[non_exhaustive]
38pub enum SiwxError {
39    /// `domain` does not match the configured public origin host.
40    #[error("invalid_siwx_domain_mismatch")]
41    DomainMismatch,
42    /// `uri` origin does not match the configured public origin.
43    #[error("invalid_siwx_uri_mismatch")]
44    UriMismatch,
45    /// `issuedAt` is not a valid timestamp.
46    #[error("invalid_siwx_issued_at")]
47    IssuedAt,
48    /// `issuedAt` exceeds the maximum age.
49    #[error("invalid_siwx_issued_at_too_old")]
50    IssuedAtTooOld,
51    /// `issuedAt` is in the future.
52    #[error("invalid_siwx_issued_at_in_future")]
53    IssuedAtInFuture,
54    /// `expirationTime` is not a valid timestamp.
55    #[error("invalid_siwx_expiration_time")]
56    ExpirationTime,
57    /// `expirationTime` is in the past.
58    #[error("invalid_siwx_expired")]
59    Expired,
60    /// `notBefore` is not a valid timestamp.
61    #[error("invalid_siwx_not_before")]
62    NotBefore,
63    /// `notBefore` is in the future.
64    #[error("invalid_siwx_not_yet_valid")]
65    NotYetValid,
66    /// Nonce failed uniqueness or format validation.
67    #[error("invalid_siwx_nonce")]
68    Nonce,
69    /// Cryptographic signature verification failed.
70    #[error("invalid_siwx_signature")]
71    Signature,
72    /// `chainId` is not a valid CAIP-2 identifier.
73    #[error("invalid_siwx_chain_id")]
74    ChainId,
75    /// `chainId` namespace is not supported.
76    #[error("invalid_siwx_unsupported_chain")]
77    UnsupportedChain,
78    /// Signature or address encoding is invalid.
79    #[error("invalid_siwx_malformed_signature")]
80    MalformedSignature,
81    /// Verifier threw (for example RPC unavailable during EIP-1271).
82    #[error("invalid_siwx_verifier_error")]
83    VerifierError,
84}
85
86impl SiwxError {
87    /// Spec machine-readable code.
88    #[must_use]
89    pub const fn as_str(self) -> &'static str {
90        match self {
91            Self::DomainMismatch => "invalid_siwx_domain_mismatch",
92            Self::UriMismatch => "invalid_siwx_uri_mismatch",
93            Self::IssuedAt => "invalid_siwx_issued_at",
94            Self::IssuedAtTooOld => "invalid_siwx_issued_at_too_old",
95            Self::IssuedAtInFuture => "invalid_siwx_issued_at_in_future",
96            Self::ExpirationTime => "invalid_siwx_expiration_time",
97            Self::Expired => "invalid_siwx_expired",
98            Self::NotBefore => "invalid_siwx_not_before",
99            Self::NotYetValid => "invalid_siwx_not_yet_valid",
100            Self::Nonce => "invalid_siwx_nonce",
101            Self::Signature => "invalid_siwx_signature",
102            Self::ChainId => "invalid_siwx_chain_id",
103            Self::UnsupportedChain => "invalid_siwx_unsupported_chain",
104            Self::MalformedSignature => "invalid_siwx_malformed_signature",
105            Self::VerifierError => "invalid_siwx_verifier_error",
106        }
107    }
108}