face_verification_core 0.2.0

Cross-platform on-device face liveness and verification core.
Documentation
use rand::Rng;
use serde::{Deserialize, Serialize};

/// One step in a liveness challenge.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ChallengeStep {
    FaceCentered,
    Smile,
    TurnLeftOnScreen,
    TurnRightOnScreen,
    ShowFingers { count: u8 },
    TouchTarget { target: TouchTarget },
}

/// Facial target used by hand-to-face liveness checks.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum TouchTarget {
    Nose,
    Cheek,
    Ear,
}

impl TouchTarget {
    fn random(rng: &mut impl Rng) -> Self {
        match rng.gen_range(0..3) {
            0 => Self::Nose,
            1 => Self::Cheek,
            _ => Self::Ear,
        }
    }

    pub(crate) fn touch_message(self) -> &'static str {
        match self {
            TouchTarget::Nose => "Acerca el dedo a la nariz.",
            TouchTarget::Cheek => "Acerca el dedo a la mejilla.",
            TouchTarget::Ear => "Acerca el dedo a la oreja.",
        }
    }
}

/// A complete liveness challenge.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct LivenessChallenge {
    pub steps: Vec<ChallengeStep>,
}

impl LivenessChallenge {
    /// Naru's current six-step challenge, expressed without any app-specific
    /// backend or UI contract.
    pub fn six_step(count: u8, target: TouchTarget) -> Self {
        Self {
            steps: vec![
                ChallengeStep::FaceCentered,
                ChallengeStep::Smile,
                ChallengeStep::TurnLeftOnScreen,
                ChallengeStep::TurnRightOnScreen,
                ChallengeStep::ShowFingers { count },
                ChallengeStep::TouchTarget { target },
            ],
        }
    }

    /// Randomized six-step challenge following the original web flow:
    /// front, smile, opposite screen turns, fingers, and a hand-to-face target.
    pub fn random(rng: &mut impl Rng) -> Self {
        let first_left = rng.gen_bool(0.5);
        let fingers = rng.gen_range(1..=5);
        let target = TouchTarget::random(rng);
        let first = if first_left {
            ChallengeStep::TurnLeftOnScreen
        } else {
            ChallengeStep::TurnRightOnScreen
        };
        let second = if first_left {
            ChallengeStep::TurnRightOnScreen
        } else {
            ChallengeStep::TurnLeftOnScreen
        };
        Self {
            steps: vec![
                ChallengeStep::FaceCentered,
                ChallengeStep::Smile,
                first,
                second,
                ChallengeStep::ShowFingers { count: fingers },
                ChallengeStep::TouchTarget { target },
            ],
        }
    }
}