crtx-ledger 0.1.1

Append-only event log, hash chain, trace assembly, and audit records.
Documentation
//! SHA-256 wrapper module.
//!
//! Thin wrapper around [`sha2::Sha256`] that localizes the call sites used
//! by [`crate::external_sink::anchor_text_sha256`] and downstream consumers.
//! The five NIST CAVP / FIPS 180-2 published test vectors below are kept
//! verbatim as known-answer regression guards so a future RustCrypto
//! regression would surface at build time.

use sha2::{Digest, Sha256};

/// Length of a SHA-256 digest in bytes.
pub const SHA256_DIGEST_LEN: usize = 32;

/// Length of a lowercase hex-encoded SHA-256 digest.
pub const SHA256_HEX_LEN: usize = SHA256_DIGEST_LEN * 2;

/// Compute the SHA-256 digest of `input`.
#[must_use]
pub fn sha256(input: &[u8]) -> [u8; SHA256_DIGEST_LEN] {
    Sha256::digest(input).into()
}

/// Compute the SHA-256 digest of `input`, returning lowercase hex.
#[must_use]
pub fn sha256_hex(input: &[u8]) -> String {
    let bytes = sha256(input);
    let mut hex = String::with_capacity(SHA256_HEX_LEN);
    for byte in bytes {
        hex.push(nibble_to_hex(byte >> 4));
        hex.push(nibble_to_hex(byte & 0x0F));
    }
    hex
}

#[inline]
fn nibble_to_hex(n: u8) -> char {
    match n {
        0..=9 => (b'0' + n) as char,
        10..=15 => (b'a' + n - 10) as char,
        _ => unreachable!("nibble is 4 bits"),
    }
}

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

    // NIST CAVP / FIPS 180-2 published test vectors. These exercise
    // `sha2::Sha256` through `sha256_hex`; if a future RustCrypto release
    // ever regressed SHA-256, the regression would surface here at build
    // time rather than at first ledger anchor mismatch.

    #[test]
    fn empty_input_matches_published_vector() {
        assert_eq!(
            sha256_hex(b""),
            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
        );
    }

    #[test]
    fn abc_matches_published_vector() {
        assert_eq!(
            sha256_hex(b"abc"),
            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
        );
    }

    #[test]
    fn fifty_six_byte_vector_matches() {
        let input = b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
        assert_eq!(
            sha256_hex(input),
            "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
        );
    }

    #[test]
    fn longer_vector_crosses_block_boundary() {
        // FIPS 180-2 long test vector: 112-byte message.
        let input =
            b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu";
        assert_eq!(
            sha256_hex(input),
            "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1"
        );
    }

    #[test]
    fn million_a_characters_matches() {
        let input = vec![b'a'; 1_000_000];
        assert_eq!(
            sha256_hex(&input),
            "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"
        );
    }
}