vyre 0.4.0

GPU compute intermediate representation with a standard operation library
Documentation
#![cfg(test)]

use crate::ops::hash::{
    argon2id, blake2b, blake2s, hkdf_expand, hkdf_extract, hmac_md5, hmac_sha1, hmac_sha256,
    pbkdf2_sha256, sha384, sha3_256, sha3_512, siphash13,
};
use crate::ops::hash::reference::{
    blake3,
    hex::{bytes_to_hex, u32_words_to_hex, u64_to_hex, u64_words_to_hex},
    kdf::Argon2idParams,
    md5, ripemd160, sha1, sha256, sha512, xxhash,
};

// Known-answer test vectors for every hash operation in this module.

#[test]
pub fn hash_kat_vectors() {
    let empty = b"";
    let one_zero = b"\0";
    let abc = b"abc";
    let one_kib_zero = vec![0u8; 1024];

    assert_eq!(
        u32_words_to_hex(&sha256::sha256_words(empty)),
        "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
    );
    assert_eq!(
        u32_words_to_hex(&sha256::sha256_words(one_zero)),
        "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"
    );
    assert_eq!(
        u32_words_to_hex(&sha256::sha256_words(abc)),
        "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
    );
    assert_eq!(
        u32_words_to_hex(&sha256::sha256_words(&one_kib_zero)),
        "5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
    );

    assert_eq!(
        u32_words_to_hex(&sha1::sha1_words(empty)),
        "da39a3ee5e6b4b0d3255bfef95601890afd80709"
    );
    assert_eq!(
        u32_words_to_hex(&sha1::sha1_words(one_zero)),
        "5ba93c9db0cff93f52b521d7420e43f6eda2784f"
    );
    assert_eq!(
        u32_words_to_hex(&sha1::sha1_words(abc)),
        "a9993e364706816aba3e25717850c26c9cd0d89d"
    );
    assert_eq!(
        u32_words_to_hex(&sha1::sha1_words(&one_kib_zero)),
        "60cacbf3d72e1e7834203da608037b1bf83b40e8"
    );

    assert_eq!(
        u32_words_to_hex(&md5::md5_words(empty)),
        "d41d8cd98f00b204e9800998ecf8427e"
    );
    assert_eq!(
        u32_words_to_hex(&md5::md5_words(one_zero)),
        "93b885adfe0da089cdf634904fd59f71"
    );
    assert_eq!(
        u32_words_to_hex(&md5::md5_words(abc)),
        "900150983cd24fb0d6963f7d28e17f72"
    );
    assert_eq!(
        u32_words_to_hex(&md5::md5_words(&one_kib_zero)),
        "0f343b0931126a20f133d67c2b018a3b"
    );

    assert_eq!(u64_to_hex(xxhash::xxhash64(empty)), "ef46db3751d8e999");
    assert_eq!(u64_to_hex(xxhash::xxhash64(b"a")), "d24ec4f1a98c6e5b");
    assert_eq!(u64_to_hex(xxhash::xxhash64(abc)), "44bc2cf5ad770999");
    assert_eq!(
        u64_to_hex(xxhash::xxhash64(&one_kib_zero)),
        "27742888f085accd"
    );

    assert_eq!(u64_to_hex(xxhash::xxhash3_64(empty)), "2d06800538d394c2");
    assert_eq!(u64_to_hex(xxhash::xxhash3_64(b"a")), "e6c632b61e964e1f");
    assert_eq!(u64_to_hex(xxhash::xxhash3_64(abc)), "78af5f94892f3950");
    assert_eq!(
        u64_to_hex(xxhash::xxhash3_64(&one_kib_zero)),
        "de5f15ab6daf7941"
    );

    assert_eq!(
        u32_words_to_hex(&hmac_sha256::hmac_sha256(empty, empty)),
        "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"
    );
    assert_eq!(
        u32_words_to_hex(&hmac_sha256::hmac_sha256(&[0x0b; 20], b"Hi There")),
        "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
    );
    assert_eq!(
        u32_words_to_hex(&hmac_sha256::hmac_sha256(
            b"Jefe",
            b"what do ya want for nothing?"
        )),
        "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
    );
    assert_eq!(
        u32_words_to_hex(&hmac_sha256::hmac_sha256(&one_kib_zero, &one_kib_zero)),
        "cf12883e11051a30615126e6fe9c00c4256c6d5709a55b94f975a56d64d77e1f"
    );

    // Audit hash finding #4: HMAC-SHA256 with 131-byte keys (RFC 4231
    // Test Cases 6 and 7) and the 64-byte boundary were previously
    // untested, leaving the `key.len() > block_size` and
    // `key.len() == block_size` branches unprotected.
    let key_131 = vec![0xaa; 131];
    assert_eq!(
        u32_words_to_hex(&hmac_sha256::hmac_sha256(
            &key_131,
            b"Test Using Larger Than Block-Size Key - Hash Key First",
        )),
        "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54",
        "RFC 4231 Test Case 6",
    );
    assert_eq!(
        u32_words_to_hex(&hmac_sha256::hmac_sha256(
            &key_131,
            b"This is a test using a larger than block-size key and \
              a larger than block-size data. The key needs to be hashed \
              before being used by the HMAC algorithm.",
        )),
        "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2",
        "RFC 4231 Test Case 7",
    );
    // 64-byte key hits the SHA-256 block boundary exactly; RFC 4231
    // does not define this case so the vector is supplementary, verified
    // against Python hmac and reproduced in the audit.
    let key_64 = vec![0x0b; 64];
    assert_eq!(
        u32_words_to_hex(&hmac_sha256::hmac_sha256(&key_64, b"Hi There")),
        "21cd586aeca0579d99a1c938127c92525a371f807bc5ba6eb78bc825bd4f2be3",
    );
}

#[test]
pub fn wave_b_hash_kat_vectors() {
    assert_eq!(
        u32_words_to_hex(&blake2s::blake2s(b"")),
        "69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9"
    );
    assert_eq!(
        u64_words_to_hex(&blake2b::blake2b(b"")),
        "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce"
    );
    assert_eq!(
        u32_words_to_hex(&blake3::blake3_words(b"")),
        "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262"
    );
    assert_eq!(
        u32_words_to_hex(&blake3::blake3_words(b"abc")),
        "6437b3ac38465133ffb63b75273a8db548c558465d79db03fd359c6cd5bd9d85"
    );
    assert_eq!(
        u32_words_to_hex(&blake3::blake3_words(&vec![0u8; 1024])),
        "d6fd9de5bccf223f523b316c9cd1cf9a9d87ea42473d68e011dad13f09bf8917"
    );
    assert_eq!(
        u32_words_to_hex(&blake3::blake3_words(&vec![b'a'; 1024 * 1024])),
        "b5358909f8bed53f55bf9324e290e9a5a585de8b0239d18040e9d3b0c7e8f9cf"
    );
    assert_eq!(
        u64_words_to_hex(&sha384::sha384(b"abc")),
        "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7"
    );
    assert_eq!(
        u64_words_to_hex(&sha512::sha512_words(b"abc")),
        "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
    );
    assert_eq!(
        u32_words_to_hex(&sha3_256::sha3_256(b"abc")),
        "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"
    );
    assert_eq!(
        u64_words_to_hex(&sha3_512::sha3_512(b"abc")),
        "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0"
    );
    assert_eq!(
        u32_words_to_hex(&ripemd160::ripemd160_words(b"abc")),
        "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"
    );
}

#[test]
pub fn sha256_block_boundary_kat_vectors() {
    for (len, expected) in [
        (
            55usize,
            "9f4390f8d30c2dd92ec9f095b65e2b9ae9b0a925a5258e241c9f1e910f734318",
        ),
        (
            56,
            "b35439a4ac6f0948b6d6f9e3c6af0f5f590ce20f1bde7090ef7970686ec6738a",
        ),
        (
            63,
            "7d3e74a05d7db15bce4ad9ec0658ea98e3f06eeecf16b4c6fff2da457ddc2f34",
        ),
        (
            64,
            "ffe054fe7ae0cb6dc65c3af9b61d5209f439851db43d0ba5997337df154668eb",
        ),
        (
            65,
            "635361c48bb9eab14198e76ea8ab7f1a41685d6ad62aa9146d301d4f17eb0ae0",
        ),
        (
            127,
            "c57e9278af78fa3cab38667bef4ce29d783787a2f731d4e12200270f0c32320a",
        ),
        (
            128,
            "6836cf13bac400e9105071cd6af47084dfacad4e5e302c94bfed24e013afb73e",
        ),
        (
            129,
            "c12cb024a2e5551cca0e08fce8f1c5e314555cc3fef6329ee994a3db752166ae",
        ),
    ] {
        assert_eq!(
            u32_words_to_hex(&sha256::sha256_words(&vec![b'a'; len])),
            expected,
            "SHA-256 digest mismatch at {len} bytes"
        );
    }
}

#[test]
pub fn wave_b_hmac_and_kdf_kat_vectors() -> Result<(), String> {
    assert_eq!(
        u32_words_to_hex(&hmac_md5::hmac_md5(&[0x0b; 16], b"Hi There")),
        "9294727a3638bb1c13f48ef8158bfc9d"
    );
    assert_eq!(
        u32_words_to_hex(&hmac_sha1::hmac_sha1(&[0x0b; 20], b"Hi There")),
        "b617318655057264e28bc0b6fb378c8ef146be00"
    );

    let salt = hex_bytes("000102030405060708090a0b0c")?;
    let ikm = hex_bytes("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")?;
    let info = hex_bytes("f0f1f2f3f4f5f6f7f8f9")?;
    let prk = hkdf_extract::hkdf_extract(&salt, &ikm);
    assert_eq!(
        u32_words_to_hex(&prk),
        "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5"
    );
    let prk_bytes = hex_bytes(&u32_words_to_hex(&prk))?;
    let okm = hkdf_expand::hkdf_expand(&prk_bytes, &info, 42)?;
    assert_eq!(
        bytes_to_hex(&okm),
        "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"
    );

    assert_eq!(
        bytes_to_hex(&pbkdf2_sha256::pbkdf2_sha256(b"password", b"salt", 1, 32)?),
        "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"
    );
    assert_eq!(
        bytes_to_hex(&pbkdf2_sha256::pbkdf2_sha256(b"password", b"salt", 2, 32)?),
        "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43"
    );

    let params = Argon2idParams {
        memory_kib: 32,
        iterations: 2,
        parallelism: 1,
        output_len: 32,
    };
    assert_eq!(
        argon2id::argon2id(b"password", b"somesalt", params)?.len(),
        32
    );
    assert_ne!(siphash13::siphash13(b"", 0), siphash13::siphash13(b"a", 0));
    Ok(())
}

#[test]
pub fn wave_b_kdf_bomb_defenses_are_actionable() {
    assert!(
        pbkdf2_sha256::pbkdf2_sha256(b"p", b"s", 0, 32).is_err_and(|error| error.contains("Fix:"))
    );
    assert!(pbkdf2_sha256::pbkdf2_sha256(b"p", b"s", 1_000_001, 32)
        .is_err_and(|error| error.contains("Fix:")));
    assert!(hkdf_expand::hkdf_expand(b"p", b"i", 8161).is_err_and(|error| error.contains("Fix:")));
    assert!(argon2id::argon2id(
        b"p",
        b"s",
        Argon2idParams {
            memory_kib: 1_048_577,
            iterations: 1,
            parallelism: 1,
            output_len: 32,
        },
    )
    .is_err_and(|error| error.contains("Fix:")));
}

pub fn hex_bytes(hex: &str) -> Result<Vec<u8>, String> {
    if hex.len() % 2 != 0 {
        return Err(format!(
            "Fix: provide an even number of hex digits, got {}",
            hex.len()
        ));
    }
    hex.as_bytes()
        .chunks_exact(2)
        .map(|pair| {
            let hi = hex_nibble(pair[0])?;
            let lo = hex_nibble(pair[1])?;
            Ok((hi << 4) | lo)
        })
        .collect()
}

/// `hex_nibble` function.
pub fn hex_nibble(byte: u8) -> Result<u8, String> {
    match byte {
        b'0'..=b'9' => Ok(byte - b'0'),
        b'a'..=b'f' => Ok(byte - b'a' + 10),
        b'A'..=b'F' => Ok(byte - b'A' + 10),
        _ => Err(format!(
            "Fix: use ASCII hex digits only, got byte 0x{byte:02x}"
        )),
    }
}