polyc-crypto 2026.8.0

Provenance signatures (commonware-cryptography ed25519) for polychrome tool calls.
Documentation
//! Salted-secret hashing shared by every bearer credential this workspace
//! verifies.
//!
//! `hex(sha256(salt || secret))` is the scheme the control plane's credential
//! store computes for both halves of its one credential model — the per-edge
//! transport bearer and the admin service credential — plus the CLI's
//! dev-credential fixture and each of their test fixtures. One copy here
//! instead of a hand-rolled `Sha256::new()`/`update`/`finalize` at each call
//! site.

use sha2::{Digest, Sha256};

/// Compute `hex(sha256(salt || secret))`.
///
/// The salt and secret are concatenated as raw UTF-8 bytes (no separator)
/// before hashing, and the digest is encoded as lowercase hex via
/// [`crate::hex::lower`]. Used both to mint a credential's stored
/// `secret_sha256` at provisioning time and to recompute it from a
/// presented secret for a constant-time comparison at verify time — see
/// `crate::edge_registry::EdgeRegistry::verify_bearer`, which is the
/// production caller.
#[must_use]
pub fn hash_salted_secret(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())
}

#[cfg(test)]
mod tests {
    #![allow(clippy::pedantic, clippy::nursery, missing_docs)]

    use super::*;

    #[test]
    fn matches_a_hand_computed_digest() {
        let mut hasher = Sha256::new();
        hasher.update(b"salty");
        hasher.update(b"s3cr3t");
        let expected = crate::hex::lower(&hasher.finalize());
        assert_eq!(hash_salted_secret("salty", "s3cr3t"), expected);
    }

    #[test]
    fn different_salts_yield_different_hashes() {
        assert_ne!(
            hash_salted_secret("salt-a", "s3cr3t"),
            hash_salted_secret("salt-b", "s3cr3t")
        );
    }

    #[test]
    fn is_deterministic() {
        assert_eq!(
            hash_salted_secret("salty", "s3cr3t"),
            hash_salted_secret("salty", "s3cr3t")
        );
    }
}