Ctrl K
ComunidadGuías…

src/puzzle/types.rs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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,
        }
    }
}