polyc-crypto 2026.9.6

Provenance signatures (commonware-cryptography ed25519) for polychrome tool calls.
//! Credential digest and bearer formatting helpers.

use sha2::{Digest as _, Sha256};

/// Computes `hex(sha256(salt || secret))` from the UTF-8 input bytes.
#[must_use]
pub fn salted_secret_digest(salt: &str, secret: &str) -> String {
    let mut hasher = Sha256::new();
    hasher.update(salt.as_bytes());
    hasher.update(secret.as_bytes());
    crate::hex::lower(&hasher.finalize())
}

/// Formats a bearer as `pc_<edge_id>_<secret>`.
#[must_use]
pub fn bearer_token(edge_id: &str, secret: &str) -> String {
    format!("pc_{edge_id}_{secret}")
}

/// Splits `pc_<edge_id>_<secret>` into the credential id and secret.
///
/// The secret can contain underscores. Returns `None` when the prefix or
/// either value is missing.
#[must_use]
pub fn parse_bearer_token(token: &str) -> Option<(&str, &str)> {
    let rest = token.strip_prefix("pc_")?;
    let (edge_id, secret) = rest.split_once('_')?;
    if edge_id.is_empty() || secret.is_empty() {
        return None;
    }
    Some((edge_id, secret))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn salted_secret_digest_matches_fixed_vector() {
        assert_eq!(
            salted_secret_digest("salty", "s3cr3t"),
            "2f0444c13a1f60d9cd0354be4a229d3ec8dcd47c1cb9bc96eb8c4635f94c280a"
        );
    }

    #[test]
    fn bearer_token_formats_the_credential_id_and_secret() {
        let bearer = bearer_token("edge-a", "test-secret");
        assert!(
            bearer == "pc_edge-a_test-secret",
            "bearer formatter returned an unexpected value"
        );
    }

    #[test]
    fn bearer_parser_splits_only_after_the_credential_id() {
        assert_eq!(
            parse_bearer_token("pc_edge-a_secret_with_underscores"),
            Some(("edge-a", "secret_with_underscores"))
        );
        assert_eq!(parse_bearer_token("not-a-bearer"), None);
        assert_eq!(parse_bearer_token("pc__secret"), None);
        assert_eq!(parse_bearer_token("pc_edge-a_"), None);
    }
}