snid 0.2.1

Polyglot sortable identifier protocol with UUID v7-compatible ordering and extended identifier families
Documentation
//! Access Key ID (AKID) dual-part credentials.

use crate::core::Snid;
use crate::encoding::decode_base58_value;
use crate::error::Error;
use crate::helpers::fnv1a;

#[derive(Clone, Debug)]
pub struct Akid {
    pub public: Snid,
    pub secret: String,
}

impl Akid {
    pub fn new_public() -> Self {
        Self {
            public: Snid::new_fast(),
            secret: String::new(),
        }
    }

    pub fn encode_secret(&self) -> Result<String, Error> {
        if self.secret.is_empty() {
            return Ok(String::new());
        }
        let hash = fnv1a(&self.secret);
        let checksum = (hash % 58) as u8;
        let alphabet = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
        let mut out = String::new();
        for byte in self.secret.as_bytes() {
            out.push(alphabet[(byte % 58) as usize] as char);
        }
        out.push(alphabet[checksum as usize] as char);
        Ok(out)
    }

    pub fn new_secret(secret: &str) -> Self {
        Self {
            public: Snid::new_fast(),
            secret: secret.to_string(),
        }
    }

    pub fn verify_secret_checksum(encoded: &str) -> Result<(), Error> {
        if encoded.is_empty() {
            return Ok(());
        }
        let bytes = encoded.as_bytes();
        let checksum_byte = bytes[bytes.len() - 1];
        let checksum_pos = decode_base58_value(checksum_byte).ok_or(Error::InvalidPayload)?;
        let mut hash: u32 = 2166136261;
        for &byte in &bytes[..bytes.len() - 1] {
            let pos = decode_base58_value(byte).ok_or(Error::InvalidPayload)? as u32;
            hash ^= pos;
            hash = hash.wrapping_mul(16777619);
        }
        if (hash % 58) as u8 != checksum_pos {
            return Err(Error::ChecksumMismatch);
        }
        Ok(())
    }

    pub fn parse(wire: &str) -> Result<(Self, String), Error> {
        let wire = wire.trim();
        if !wire.starts_with("KEY:") {
            return Err(Error::InvalidFormat);
        }
        let parts: Vec<&str> = wire.splitn(2, '_').collect();
        if parts.len() != 2 {
            return Err(Error::InvalidFormat);
        }
        let (public, atom) = Snid::parse_wire(parts[0])?;
        if atom != "KEY" {
            return Err(Error::InvalidAtom);
        }
        Self::verify_secret_checksum(parts[1])?;
        Ok((
            Self {
                public,
                secret: parts[1].to_string(),
            },
            atom,
        ))
    }

    pub fn format(public: Snid, secret: &str) -> String {
        format!(
            "KEY:{}_{}",
            public
                .to_wire("MAT")
                .unwrap()
                .split(':')
                .nth(1)
                .unwrap_or(""),
            secret.trim()
        )
    }
}