src/puzzle/types.rs
Rama main · réplica de lectura del repositorio original.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
/// Argon2id parameters for memory-hard PoW. Matches the upstream
/// `argon2::Params` shape; values are stamped on each challenge so the
/// client knows exactly which hash to compute and the server can tune
/// without redeploying clients.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Argon2idParams {
/// Memory cost in KiB. Default 8192 (8 MiB) — small enough to fit on
/// mobile, large enough to defeat trivial GPU parallelisation.
pub m_cost: u32,
/// Iteration count. Default 2.
pub t_cost: u32,
/// Parallelism / lanes. Default 1 (single-thread worker).
pub p_cost: u32,
}
impl Default for Argon2idParams {
fn default() -> Self {
Self {
m_cost: 8_192,
t_cost: 2,
p_cost: 1,
}
}
}
/// PoW algorithm. Chosen per server (via `PUZZLE_ALGORITHM`) and stamped on
/// each challenge so the client picks the right hash function.
///
/// Wire format is backward-compatible: `Sha256` serialises as the bare
/// string `"sha256"` (matching the original API), while
/// `Argon2id(params)` serialises as `{"argon2id": {...}}`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Algorithm {
Sha256,
Argon2id(Argon2idParams),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Challenge {
pub id: Uuid,
pub site_key: Uuid,
pub algorithm: Algorithm,
pub prefix: String,
pub difficulty: u32,
pub created_at: DateTime<Utc>,
/// Anchor for the verify-time dwell calculation. Equals `created_at` for a
/// first issuance, and is *inherited* when this challenge was minted to
/// replace one about to expire (see `refresh_of` on `GET /v1/puzzle`).
///
/// Separate from `created_at` because the two answer different questions
/// and only one of them may move: `created_at` anchors `expires_at` and
/// the decision log, while this anchors "how long has the visitor had this
/// form open". Without it, the widget's pre-expiry refresh silently reset
/// dwell to zero, so a visitor who spent five minutes on a form and
/// submitted just after a background refresh was scored — and logged — as
/// having submitted in 400ms.
pub dwell_since: DateTime<Utc>,
pub expires_at: DateTime<Utc>,
/// Failed PoW verify attempts against this challenge. A wrong nonce
/// deliberately leaves the challenge live for retry, so without a cap one
/// challenge could absorb unlimited (potentially memory-hard) verify
/// attempts. The store evicts the challenge once this reaches
/// `VERIFY_MAX_ATTEMPTS`.
#[serde(default)]
pub attempts: u32,
/// The client address this challenge was issued to (reverse-proxy
/// resolved, full address), so `/v1/verify` can check an
/// integrator-forwarded `remote_ip` against it. Held in memory for the
/// challenge's TTL only and never logged. `None` means unknown (a
/// challenge minted before this field existed), which disables the check
/// rather than failing it.
#[serde(default)]
pub issued_to: Option<std::net::IpAddr>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Solution {
pub challenge_id: Uuid,
pub nonce: u64,
}
#[derive(Debug, Clone)]
pub struct PuzzleConfig {
pub algorithm: Algorithm,
pub default_difficulty: u32,
pub ttl_secs: u64,
}
impl Default for PuzzleConfig {
fn default() -> Self {
Self {
algorithm: Algorithm::Sha256,
default_difficulty: 20,
ttl_secs: 300,
}
}
}