use crate::OpSpec;
use vyre_spec::GoldenSample;
pub const VYRE_OP_METADATA: vyre_spec::OpMetadata = vyre_spec::OpMetadata {
id: "primitive.hash.murmur3_32",
layer: vyre_spec::Layer::L2,
category: vyre_spec::MetadataCategory::A,
version: 1,
description: "hash murmur3_32",
signature: "(Bytes) -> U32",
strictness: "strict",
archetype_signature: "(Bytes) -> U32",
};
pub const GOLDEN: &[vyre_spec::GoldenSample] = &[
GoldenSample {
op_id: "primitive.hash.murmur3_32",
input: &[],
expected: &[0x00, 0x00, 0x00, 0x00],
reason: "MurmurHash3 x86 32 seed 0 empty input",
},
GoldenSample {
op_id: "primitive.hash.murmur3_32",
input: b"a",
expected: &[0xB2, 0x69, 0x25, 0x3C],
reason: "MurmurHash3 x86 32 seed 0 of a (LE bytes of 0x3C2569B2)",
},
GoldenSample {
op_id: "primitive.hash.murmur3_32",
input: b"ab",
expected: &[0x5F, 0xD7, 0xBF, 0x9B],
reason: "MurmurHash3 x86 32 seed 0 of ab (LE bytes of 0x9BBFD75F)",
},
GoldenSample {
op_id: "primitive.hash.murmur3_32",
input: b"abcd",
expected: &[0x6A, 0x67, 0xED, 0x43],
reason: "MurmurHash3 x86 32 seed 0 of abcd",
},
];
pub const KAT: &[vyre_spec::KatVector] = &[
vyre_spec::KatVector {
input: &[],
expected: &[0x00, 0x00, 0x00, 0x00],
source: "hand-verified reference vector from Rust semantics",
},
vyre_spec::KatVector {
input: b"a",
expected: &[0xB2, 0x69, 0x25, 0x3C],
source: "LE bytes of 0x3C2569B2 (MurmurHash3 x86 32 seed 0)",
},
vyre_spec::KatVector {
input: b"ab",
expected: &[0x5F, 0xD7, 0xBF, 0x9B],
source: "LE bytes of 0x9BBFD75F (MurmurHash3 x86 32 seed 0)",
},
vyre_spec::KatVector {
input: b"abcd",
expected: &[0x6A, 0x67, 0xED, 0x43],
source: "hand-verified reference vector from Rust semantics",
},
];
pub const ADVERSARIAL: &[vyre_spec::AdversarialInput] = &[vyre_spec::AdversarialInput {
input: &[],
reason: "empty input exercises validation and boundary handling",
}];
fn cpu(input: &[u8]) -> Vec<u8> {
let seed: u32 = 0;
let len = input.len() as u32;
let nblocks = len / 4;
let mut h1 = seed;
let c1: u32 = 0xcc9e_2d51;
let c2: u32 = 0x1b87_3593;
for i in 0..nblocks as usize {
let mut k1 = u32::from_le_bytes([
input[i * 4],
input[i * 4 + 1],
input[i * 4 + 2],
input[i * 4 + 3],
]);
k1 = k1.wrapping_mul(c1);
k1 = k1.rotate_left(15);
k1 = k1.wrapping_mul(c2);
h1 ^= k1;
h1 = h1.rotate_left(13);
h1 = h1.wrapping_mul(5).wrapping_add(0xe654_6b64);
}
let tail = &input[(nblocks as usize * 4)..];
let mut k1: u32 = 0;
match tail.len() {
3 => {
k1 ^= u32::from(tail[2]) << 16;
k1 ^= u32::from(tail[1]) << 8;
k1 ^= u32::from(tail[0]);
k1 = k1.wrapping_mul(c1);
k1 = k1.rotate_left(15);
k1 = k1.wrapping_mul(c2);
h1 ^= k1;
}
2 => {
k1 ^= u32::from(tail[1]) << 8;
k1 ^= u32::from(tail[0]);
k1 = k1.wrapping_mul(c1);
k1 = k1.rotate_left(15);
k1 = k1.wrapping_mul(c2);
h1 ^= k1;
}
1 => {
k1 ^= u32::from(tail[0]);
k1 = k1.wrapping_mul(c1);
k1 = k1.rotate_left(15);
k1 = k1.wrapping_mul(c2);
h1 ^= k1;
}
_ => {}
}
h1 ^= len;
h1 ^= h1 >> 16;
h1 = h1.wrapping_mul(0x85eb_ca6b);
h1 ^= h1 >> 13;
h1 = h1.wrapping_mul(0xc2b2_ae35);
h1 ^= h1 >> 16;
h1.to_le_bytes().to_vec()
}
fn wgsl() -> String {
r"
fn vyre_op(index: u32, input_len: u32) -> u32 {
let c1: u32 = 0xCC9E2D51u;
let c2: u32 = 0x1B873593u;
let nblocks = input_len / 4u;
var h1: u32 = 0u; // seed = 0
// Body: process 4-byte blocks
for (var i: u32 = 0u; i < nblocks; i = i + 1u) {
var k1 = input.data[i];
k1 = k1 * c1;
k1 = (k1 << 15u) | (k1 >> 17u); // rotate_left(15)
k1 = k1 * c2;
h1 = h1 ^ k1;
h1 = (h1 << 13u) | (h1 >> 19u); // rotate_left(13)
h1 = h1 * 5u + 0xE6546B64u;
}
// Tail: remaining bytes
let tail_start = nblocks * 4u;
let tail_len = input_len - tail_start;
var k1: u32 = 0u;
if (tail_len >= 3u) {
let b2_word = tail_start + 2u;
k1 = k1 ^ (((input.data[b2_word / 4u] >> ((b2_word % 4u) * 8u)) & 0xFFu) << 16u);
}
if (tail_len >= 2u) {
let b1_word = tail_start + 1u;
k1 = k1 ^ (((input.data[b1_word / 4u] >> ((b1_word % 4u) * 8u)) & 0xFFu) << 8u);
}
if (tail_len >= 1u) {
let b0_word = tail_start;
k1 = k1 ^ ((input.data[b0_word / 4u] >> ((b0_word % 4u) * 8u)) & 0xFFu);
k1 = k1 * c1;
k1 = (k1 << 15u) | (k1 >> 17u);
k1 = k1 * c2;
h1 = h1 ^ k1;
}
// Finalization mix
h1 = h1 ^ input_len;
h1 = h1 ^ (h1 >> 16u);
h1 = h1 * 0x85EBCA6Bu;
h1 = h1 ^ (h1 >> 13u);
h1 = h1 * 0xC2B2AE35u;
h1 = h1 ^ (h1 >> 16u);
return h1;
}
"
.to_string()
}
#[inline]
pub fn vyre_op() -> OpSpec {
OpSpec::builder("primitive.hash.murmur3_32")
.signature(crate::spec::types::OpSignature {
inputs: vec![crate::spec::types::DataType::Bytes],
output: crate::spec::types::DataType::U32,
})
.cpu_fn(cpu)
.wgsl_fn(wgsl)
.category(crate::Category::A {
composition_of: vec!["primitive.hash.murmur3_32"],
})
.laws(vec![crate::spec::law::AlgebraicLaw::Bounded {
lo: 0,
hi: u32::MAX,
}])
.overflow_contract(crate::spec::types::OverflowContract::Unchecked)
.strictness(crate::spec::types::Strictness::Strict)
.version(1)
.boundary_values(vec![
crate::spec::types::BoundaryValue {
label: "empty",
inputs: vec![0],
},
crate::spec::types::BoundaryValue {
label: "single_zero",
inputs: vec![0],
},
crate::spec::types::BoundaryValue {
label: "single_ff",
inputs: vec![255],
},
crate::spec::types::BoundaryValue {
label: "four_bytes",
inputs: vec![1, 2, 3, 4],
},
])
.equivalence_classes(vec![
crate::spec::types::EquivalenceClass::specific("empty input", vec![0]),
crate::spec::types::EquivalenceClass::specific(
"aligned 4-byte",
vec![0x41, 0x42, 0x43, 0x44],
),
crate::spec::types::EquivalenceClass::specific(
"unaligned tail",
vec![0x61, 0x62, 0x63],
),
])
.expect("Fix: checked-in conform spec must satisfy the typestate builder")
}
#[cfg(test)]
mod tests {
use super::cpu;
#[test]
fn murmur3_empty() {
let result = cpu(&[]);
let hash = u32::from_le_bytes([result[0], result[1], result[2], result[3]]);
assert_eq!(hash, 0);
}
}
#[cfg(test)]
mod proptests {
#[test]
fn coverage_artifacts_are_registered() {
assert!(!super::KAT.is_empty());
assert!(!super::ADVERSARIAL.is_empty());
}
}