soft3/hemera/rs/src/lib.rs

// ---
// tags: hemera, rust
// crystal-type: source
// crystal-domain: comp
// ---
//! Poseidon2 hash over the Goldilocks field (Hemera parameters).
//!
//! # WARNING
//!
//! **This is novel, unaudited cryptography.** The parameter set, sponge
//! construction, and self-bootstrapping round constant generation have not
//! been reviewed by third-party cryptographers. Do not use in production
//! systems where cryptographic guarantees are required. Use at your own risk.
//!
//! **The Hemera hash is not finalized and may change.** Hash outputs, round
//! constants, and the sponge construction are subject to breaking changes
//! until a stable release.
//!
//! ---
//!
//! This crate provides a streaming hash API backed by the Poseidon2
//! algebraic hash function operating over the Goldilocks prime field
//! (p = 2^64 - 2^32 + 1).
//!
//! # Hemera Parameters
//!
//! - **Field**: Goldilocks (p = 2^64 - 2^32 + 1)
//! - **State width**: t = 16
//! - **Full rounds**: R_F = 8
//! - **Partial rounds**: R_P = 16
//! - **Full-round S-box**: x^7
//! - **Partial-round S-box**: x^(-1) (field inversion)
//! - **Rate**: 8 elements (56 input bytes per block)
//! - **Capacity**: 8 elements
//! - **Output**: 4 elements (32 bytes)
//! - **Padding**: 0x01 || 0x00*
//! - **Encoding**: little-endian canonical
//!
//! # Examples
//!
//! ```
//! use cyber_hemera::{hash, derive_key};
//!
//! let digest = hash(b"hello world");
//! println!("{digest}");
//!
//! let key = derive_key("my app v1", b"key material");
//! ```

#![no_std]

extern crate alloc;

#[cfg(feature = "std")]
extern crate std;

pub mod batch;
pub mod stream;
pub mod bootstrap;
pub mod cdc;
pub mod constants;
mod encoding;
pub mod field;
pub mod merkle;
mod params;
pub mod permutation;
mod sponge;
pub mod sparse;
pub mod step_sponge;
pub mod trace;
pub mod tree;
#[cfg(feature = "async")]
pub mod async_io;
#[cfg(feature = "async")]
pub mod stream_async;

// Re-export all Hemera parameters so downstream crates never hardcode them.
pub use params::{
    CAPACITY, CHUNK_SIZE, COLLISION_BITS, OUTPUT_BYTES, OUTPUT_ELEMENTS, RATE, RATE_BYTES,
    ROUNDS_F, ROUNDS_P, ROUNDS_TOTAL, SBOX_DEGREE, WIDTH,
};
pub use merkle::{merkle_verify_path, Side};
pub use step_sponge::StepSponge;
pub use sponge::{Hash, Hasher, OutputReader};

/// Hash the input bytes and return a 32-byte digest.
pub fn hash(input: &[u8]) -> Hash {
    let mut hasher = Hasher::new();
    hasher.update(input);
    hasher.finalize()
}

/// Hash the input bytes with a key.
pub fn keyed_hash(key: &[u8; OUTPUT_BYTES], input: &[u8]) -> Hash {
    let mut hasher = Hasher::new_keyed(key);
    hasher.update(input);
    hasher.finalize()
}

/// Derive a key from a context string and key material.
///
/// This is a two-phase operation:
/// 1. Hash the context string with domain separation
/// 2. Use the context hash to seed a second hasher that absorbs the key material
pub fn derive_key(context: &str, key_material: &[u8]) -> [u8; OUTPUT_BYTES] {
    let ctx_hasher = Hasher::new_derive_key_context(context);
    let ctx_hash = ctx_hasher.finalize();
    let mut material_hasher = Hasher::new_derive_key_material(&ctx_hash);
    material_hasher.update(key_material);
    let result = material_hasher.finalize();
    *result.as_bytes()
}

#[cfg(test)]
mod tests {
    extern crate std;
    use std::{vec, vec::Vec};
    use super::*;

    #[test]
    fn hash_basic() {
        let h = hash(b"hello");
        assert_ne!(h.as_bytes(), &[0u8; OUTPUT_BYTES]);
    }

    #[test]
    fn hash_deterministic() {
        let h1 = hash(b"test");
        let h2 = hash(b"test");
        assert_eq!(h1, h2);
    }

    #[test]
    fn hash_different_inputs() {
        assert_ne!(hash(b""), hash(b"a"));
        assert_ne!(hash(b"a"), hash(b"b"));
        assert_ne!(hash(b"ab"), hash(b"ba"));
    }

    #[test]
    fn hash_matches_streaming() {
        let data = b"streaming consistency test with enough data to cross boundaries!!";
        let direct = hash(data);
        let streamed = {
            let mut h = Hasher::new();
            h.update(&data[..10]);
            h.update(&data[10..]);
            h.finalize()
        };
        assert_eq!(direct, streamed);
    }

    #[test]
    fn keyed_hash_differs_from_plain() {
        let data = b"test";
        assert_ne!(hash(data), keyed_hash(&[0u8; OUTPUT_BYTES], data));
    }

    #[test]
    fn keyed_hash_different_keys() {
        let data = b"test";
        let h1 = keyed_hash(&[0u8; OUTPUT_BYTES], data);
        let h2 = keyed_hash(&[1u8; OUTPUT_BYTES], data);
        assert_ne!(h1, h2);
    }

    #[test]
    fn derive_key_basic() {
        let key = derive_key("my context", b"material");
        assert_ne!(key, [0u8; OUTPUT_BYTES]);
    }

    #[test]
    fn derive_key_differs_from_hash() {
        let data = b"material";
        let h = hash(data);
        let k = derive_key("context", data);
        assert_ne!(h.as_bytes(), &k);
    }

    #[test]
    fn derive_key_different_contexts() {
        let k1 = derive_key("context A", b"material");
        let k2 = derive_key("context B", b"material");
        assert_ne!(k1, k2);
    }

    #[test]
    fn derive_key_different_materials() {
        let k1 = derive_key("context", b"material A");
        let k2 = derive_key("context", b"material B");
        assert_ne!(k1, k2);
    }

    #[test]
    fn xof_extends_hash() {
        let mut xof = Hasher::new().update(b"xof test").finalize_xof();
        let mut out = [0u8; OUTPUT_BYTES * 2];
        xof.fill(&mut out);
        // First OUTPUT_BYTES match finalize.
        let h = hash(b"xof test");
        assert_eq!(&out[..OUTPUT_BYTES], h.as_bytes());
    }

    #[test]
    fn large_input() {
        let data = vec![0x42u8; 10_000];
        let h = hash(&data);
        assert_ne!(h.as_bytes(), &[0u8; OUTPUT_BYTES]);

        // Streaming equivalence.
        let mut hasher = Hasher::new();
        for chunk in data.chunks(137) {
            hasher.update(chunk);
        }
        assert_eq!(h, hasher.finalize());
    }

    #[test]
    fn hash_empty() {
        let h = hash(b"");
        assert_ne!(h.as_bytes(), &[0u8; OUTPUT_BYTES]);
    }

    #[test]
    fn hash_single_byte_avalanche() {
        // Each single-byte input should produce a wildly different hash
        let hashes: Vec<_> = (0..=255u8).map(|b| hash(&[b])).collect();
        for i in 0..256 {
            for j in (i + 1)..256 {
                assert_ne!(hashes[i], hashes[j], "collision at bytes {i} and {j}");
            }
        }
    }

    #[test]
    fn keyed_hash_empty_input() {
        let h = keyed_hash(&[0u8; OUTPUT_BYTES], b"");
        assert_ne!(h.as_bytes(), &[0u8; OUTPUT_BYTES]);
    }

    #[test]
    fn derive_key_long_context() {
        // Context longer than one rate block
        let long_ctx = "a]".repeat(100);
        let k = derive_key(&long_ctx, b"material");
        assert_ne!(k, [0u8; OUTPUT_BYTES]);
    }

    #[test]
    fn derive_key_long_material() {
        // Material longer than one rate block
        let material = vec![0x42u8; 1000];
        let k = derive_key("ctx", &material);
        assert_ne!(k, [0u8; OUTPUT_BYTES]);
    }
}

/// Property-based tests using proptest.
#[cfg(test)]
mod proptests {
    extern crate std;
    use super::*;
    use proptest::prelude::*;

    proptest! {
        #[test]
        fn hash_is_deterministic(data in proptest::collection::vec(any::<u8>(), 0..500)) {
            prop_assert_eq!(hash(&data), hash(&data));
        }

        #[test]
        fn streaming_matches_oneshot(data in proptest::collection::vec(any::<u8>(), 0..500)) {
            let oneshot = hash(&data);
            let mut hasher = Hasher::new();
            // Feed in random-ish chunk sizes
            let mut pos = 0;
            let mut chunk_size = 1;
            while pos < data.len() {
                let end = (pos + chunk_size).min(data.len());
                hasher.update(&data[pos..end]);
                pos = end;
                chunk_size = (chunk_size * 3 + 1) % 71; // pseudo-random sizes
                if chunk_size == 0 { chunk_size = 1; }
            }
            prop_assert_eq!(oneshot, hasher.finalize());
        }

        #[test]
        fn xof_prefix_matches_finalize(data in proptest::collection::vec(any::<u8>(), 0..200)) {
            let hash_result = hash(&data);
            let mut xof = {
                let mut h = Hasher::new();
                h.update(&data);
                h.finalize_xof()
            };
            let mut xof_bytes = [0u8; OUTPUT_BYTES];
            xof.fill(&mut xof_bytes);
            prop_assert_eq!(hash_result.as_bytes(), &xof_bytes);
        }

        #[test]
        fn keyed_hash_differs_from_plain(
            data in proptest::collection::vec(any::<u8>(), 1..200),
            key in proptest::collection::vec(any::<u8>(), OUTPUT_BYTES..=OUTPUT_BYTES),
        ) {
            let key_arr: [u8; OUTPUT_BYTES] = key.try_into().unwrap();
            let plain = hash(&data);
            let keyed = keyed_hash(&key_arr, &data);
            prop_assert_ne!(plain, keyed);
        }

        #[test]
        fn clone_consistency(
            prefix in proptest::collection::vec(any::<u8>(), 0..100),
            suffix in proptest::collection::vec(any::<u8>(), 0..100),
        ) {
            let mut h1 = Hasher::new();
            h1.update(&prefix);
            let mut h2 = h1.clone();
            h1.update(&suffix);
            h2.update(&suffix);
            prop_assert_eq!(h1.finalize(), h2.finalize());
        }
    }
}

Homonyms

warriors/trisha/wgpu/lib.rs
soft3/glia/run/lib.rs
soft3/mir/src/lib.rs
soft3/foculus/src/lib.rs
cyb/optica/src/lib.rs
soft3/strata/src/lib.rs
warriors/trisha/rs/lib.rs
cyb/shell/src/lib.rs
cyb/core/src/lib.rs
soft3/glia/import/lib.rs
warriors/trisha/honeycrisp/lib.rs
neural/trident/src/lib.rs
soft3/crate/src/lib.rs
cyb/honeycrisp/src/lib.rs
cyb/prysm/rs/lib.rs
soft3/lens/src/lib.rs
soft3/tru/rs/lib.rs
soft3/cybergraph/src/lib.rs
soft3/nox/rs/lib.rs
soft3/mudra/src/lib.rs
soft3/radio/cyber-bao/src/lib.rs
neural/rs/dialect/src/lib.rs
soft3/lens/assayer/src/lib.rs
cyb/honeycrisp/aruminium/src/lib.rs
soft3/lens/brakedown/src/lib.rs
soft3/bbg/rs/src/lib.rs
neural/rs/sigil/src/lib.rs
soft3/radio/iroh-willow/src/lib.rs
cyb/crates/cyb/src/lib.rs
neural/rs/link/src/lib.rs
cyb/honeycrisp/unimem/src/lib.rs
soft3/lens/porphyry/src/lib.rs
soft3/radio/iroh-docs/src/lib.rs
soft3/radio/iroh-car/src/lib.rs
soft3/radio/iroh-dns-server/src/lib.rs
soft3/radio/iroh/src/lib.rs
cyb/crates/cyb-reserve/src/lib.rs
soft3/strata/ext/src/lib.rs
soft3/radio/iroh-relay/src/lib.rs
neural/rune/rs/parse/lib.rs
neural/rs/codegen/src/lib.rs
soft3/radio/iroh-blobs/src/lib.rs
soft3/zheng/rs/src/lib.rs
soft3/strata/kuro/rs/lib.rs
soft3/tok/rs/src/lib.rs
soft3/conformance/rs/src/lib.rs
soft3/radio/iroh-base/src/lib.rs
neural/rs/macros/src/lib.rs
neural/rs/darwin-sys/src/lib.rs
soft3/radio/iroh-ffi/src/lib.rs
cyb/honeycrisp/rane/src/lib.rs
neural/rune/rs/prysm/lib.rs
soft3/lens/core/src/lib.rs
neural/rs/mir-format/src/lib.rs
neural/rs/core/src/lib.rs
soft3/strata/nebu/rs/lib.rs
neural/rune/rs/compile/lib.rs
neural/rune/rs/interp/lib.rs
soft3/strata/compute/src/lib.rs
soft3/lens/binius/src/lib.rs
soft3/strata/proof/src/lib.rs
neural/eidos/rs/src/lib.rs
neural/rune/rs/mold/lib.rs
soft3/strata/core/src/lib.rs
soft3/hemera/wgsl/src/lib.rs
neural/rune/rs/subject/lib.rs
soft3/soma/kernel/src/lib.rs
neural/rune/rs/lower/lib.rs
cyb/honeycrisp/acpu/src/lib.rs
neural/rune/rs/parse-pure/lib.rs
soft3/radio/iroh-gossip/src/lib.rs
neural/rune/rs/lex/lib.rs
neural/rune/rs/ast/lib.rs
soft3/lens/ikat/src/lib.rs
soft3/tape/impl/rust/src/lib.rs
cyb/evy/crates/evy_radio/src/lib.rs
neural/inf/rs/lex/src/lib.rs
cyb/evy/forks/bevy_animation/src/lib.rs
neural/inf/rs/oracle/src/lib.rs
neural/trident/editor/zed/src/lib.rs
warriors/trisha/.vendor/twenty-first/src/lib.rs
warriors/erga/rs/pool/src/lib.rs
cyb/wysm/crates/c_api/src/lib.rs
cyb/evy/forks/bevy_anti_alias/src/lib.rs
soft3/strata/trop/rs/src/lib.rs
cyb/evy/forks/bevy_pbr/src/lib.rs
soft3/radio/quinn/quinn-udp/src/lib.rs
warriors/trisha/.vendor/triton-vm/src/lib.rs
soft3/strata/genies/rs/src/lib.rs
soft3/radio/nettools/portmapper/src/lib.rs
warriors/erga/rs/wallet/src/lib.rs
soft3/lytics/rs/core/src/lib.rs
cyb/wysm/crates/c_api/macro/lib.rs
soft3/strata/kuro/wgsl/src/lib.rs
cyb/evy/forks/bevy_tasks/src/lib.rs
neural/inf/rs/plan/src/lib.rs
cyb/wysm/crates/ir/src/lib.rs
cyb/evy/forks/bevy_gizmos/src/lib.rs
soft3/strata/genies/wgsl/src/lib.rs
warriors/erga/rs/autolykos/src/lib.rs
cyb/evy/forks/bevy_sprite/src/lib.rs
cyb/evy/crates/evy_prysm_core/src/lib.rs
warriors/trisha/.vendor/triton-constraint-circuit/src/lib.rs
cyb/evy/forks/bevy_transform/src/lib.rs
cyb/evy/crates/evy_engine_dispatch/src/lib.rs
warriors/trisha/.vendor/triton-air/src/lib.rs
cyb/evy/forks/bevy_diagnostic/src/lib.rs
cyb/wysm/crates/fuzz/src/lib.rs
neural/inf/rs/source/src/lib.rs
cyb/wysm/crates/wasmi/src/lib.rs
cyb/evy/forks/bevy_ecs/src/lib.rs
soft3/strata/jali/rs/src/lib.rs
warriors/trisha/.vendor/triton-isa/src/lib.rs
soft3/lytics/rs/event/src/lib.rs
soft3/strata/trop/wgsl/src/lib.rs
cyb/evy/crates/evy_dialect/src/lib.rs
warriors/trisha/.vendor/triton-constraint-builder/src/lib.rs
cyb/evy/crates/evy_platform_caps/src/lib.rs
cyb/evy/forks/bevy_sprite_render/src/lib.rs
cyb/evy/forks/bevy_gizmos_render/src/lib.rs
cyb/wysm/crates/collections/src/lib.rs
neural/inf/rs/ast/src/lib.rs
cyb/evy/forks/bevy_mesh/src/lib.rs
soft3/radio/quinn/bench/src/lib.rs
cyb/wysm/crates/c_api/artifact/lib.rs
warriors/erga/rs/rtable-bench/src/lib.rs
cyb/wysm/crates/core/src/lib.rs
soft3/strata/jali/wgsl/src/lib.rs
soft3/radio/iroh/bench/src/lib.rs
warriors/erga/rs/blake-bench/src/lib.rs
neural/inf/rs/parse/src/lib.rs
cyb/evy/forks/bevy_post_process/src/lib.rs
soft3/radio/quinn/quinn/src/lib.rs
cyb/evy/crates/evy_ecs_storage/src/lib.rs
warriors/erga/rs/app/src/lib.rs
neural/inf/rs/lower/src/lib.rs
cyb/evy/forks/bevy_core_pipeline/src/lib.rs
cyb/evy/forks/bevy_render/src/lib.rs
neural/rs/tests/macro-integration/src/lib.rs
soft3/radio/iroh-ffi/iroh-js/src/lib.rs
cyb/evy/forks/bevy_image/src/lib.rs
cyb/evy/crates/evy_engine_tasks/src/lib.rs
warriors/erga/rs/mine-bench/src/lib.rs
soft3/strata/nebu/wgsl/src/lib.rs
soft3/radio/nettools/netwatch/src/lib.rs
warriors/erga/rs/miner/src/lib.rs
neural/inf/rs/value/src/lib.rs
cyb/wysm/crates/wast/src/lib.rs
soft3/radio/tests/integration/src/lib.rs
cyb/wysm/crates/wasi/src/lib.rs
cyb/evy/crates/evy_diagnostic/src/lib.rs
soft3/radio/quinn/perf/src/lib.rs
soft3/radio/quinn/quinn-proto/src/lib.rs
cyb/evy/forks/naga/src/lib.rs
neural/inf/rs/eval/src/lib.rs
cyb/evy/crates/evy_engine_core/src/lib.rs
neural/inf/rs/cozo/cozo-lib-python/src/lib.rs
neural/inf/rs/cozo/cozo-lib-swift/src/lib.rs
neural/inf/rs/cozo/cozorocks/src/lib.rs
neural/inf/rs/cozo/cozo-lib-java/src/lib.rs
neural/inf/rs/cozo/cozo-lib-c/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/src/lib.rs
neural/inf/rs/cozo/cozo-lib-nodejs/src/lib.rs
neural/inf/rs/cozo/cozo-core/src/lib.rs
neural/inf/rs/cozo/cozo-lib-wasm/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/unimem/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/rane/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/unimem/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/acpu/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/aruminium/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/aruminium/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/rane/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/acpu/src/lib.rs

Graph