vauban-claim 0.1.0

Vauban Claim Algebra — reference implementation of draft-vauban-claim-algebra-00 (post-quantum claim sextuplet + 5 composition operators, canonical CBOR/JSON codec).
Documentation
//! Poseidon-felt252 hashing (Starknet field) behind the `poseidon` feature.
//!
//! Used by `ClaimRef` as a ZK-friendly alternative to SHA-256. The hash is
//! computed over canonical CBOR bytes, split into ≤31-byte chunks (felt252
//! maximum encoding), each converted to a field element, then hashed with the
//! Starknet Poseidon permutation.

use alloc::vec::Vec;

/// Compute Poseidon-felt252 hash of arbitrary bytes.
///
/// # Algorithm
/// 1. Split input into 31-byte chunks (felt252 = 252 bits, max safe encoding
///    without modular reduction ambiguity).
/// 2. Convert each chunk to a [`starknet_crypto::Felt`] (big-endian).
/// 3. Hash all felts with `poseidon_hash_many`.
/// 4. Return the 32-byte big-endian encoding of the resulting felt.
///
/// An empty input produces the hash of a single zero felt.
pub fn poseidon_felt252(data: &[u8]) -> [u8; 32] {
    use starknet_crypto::{poseidon_hash_many, Felt};

    if data.is_empty() {
        return felt_to_bytes(&poseidon_hash_many(&[]));
    }

    let felts: Vec<Felt> = data
        .chunks(31)
        .map(Felt::from_bytes_be_slice)
        .collect();

    felt_to_bytes(&poseidon_hash_many(&felts))
}

fn felt_to_bytes(f: &starknet_crypto::Felt) -> [u8; 32] {
    f.to_bytes_be()
}

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

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

    #[test]
    fn poseidon_empty_input() {
        let h = poseidon_felt252(b"");
        assert_eq!(h.len(), 32);
        // Empty input must be deterministic
        assert_eq!(h, poseidon_felt252(b""));
    }

    #[test]
    fn poseidon_different_inputs() {
        let h1 = poseidon_felt252(b"hello");
        let h2 = poseidon_felt252(b"world");
        assert_ne!(h1, h2);
    }

    #[test]
    fn poseidon_single_chunk() {
        // 31 bytes = exactly one felt252 chunk
        let data = [0xabu8; 31];
        let h = poseidon_felt252(&data);
        assert_eq!(h.len(), 32);
    }

    #[test]
    fn poseidon_multi_chunk() {
        // 63 bytes = 2 chunks + 1 byte
        let data = [0xcd; 63];
        let h = poseidon_felt252(&data);
        assert_eq!(h.len(), 32);
    }

    #[test]
    fn poseidon_large_input() {
        // 1KB — exercises many rounds
        let data = vec![0x42; 1024];
        let h = poseidon_felt252(&data);
        assert_eq!(h.len(), 32);
    }
}