use ark_bn254::Fr;
use ark_ff::PrimeField;
use sha2::{Digest, Sha256};
use crate::error::VerifyError;
fn hex32(s: &str) -> Result<[u8; 32], VerifyError> {
let stripped = s.trim_start_matches("0x").trim_start_matches("sha256:");
let bytes = hex::decode(stripped).map_err(|_| {
VerifyError::SchemaViolation(format!("expected 32-byte hex, got {s:?}"))
})?;
if bytes.len() != 32 {
return Err(VerifyError::SchemaViolation(
format!("expected 32-byte hex, got {} bytes", bytes.len())));
}
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
Ok(arr)
}
pub fn compute_binding_hash(
action_hash: &str,
contract_hash: &str,
decision_bit: u8,
vk_hash: &str,
) -> Result<[u8; 32], VerifyError> {
let action_bytes = hex32(action_hash)?;
let contract_bytes = hex32(contract_hash)?;
let vk_bytes = hex32(vk_hash)?;
if decision_bit > 1 {
return Err(VerifyError::SchemaViolation(
format!("decision_bit must be 0 or 1, got {decision_bit}")));
}
let mut hasher = Sha256::new();
hasher.update(&action_bytes);
hasher.update(&contract_bytes);
hasher.update(&[decision_bit]);
hasher.update(&vk_bytes);
let digest = hasher.finalize();
let mut out = [0u8; 32];
out.copy_from_slice(&digest[..]);
Ok(out)
}
pub fn binding_hash_to_fr(hash: &[u8; 32]) -> Fr {
let mut truncated = *hash;
truncated[0] &= 0x1F;
Fr::from_be_bytes_mod_order(&truncated)
}
pub fn public_inputs_fr(
action_hash: &str,
contract_hash: &str,
decision_bit: u8,
vk_hash: &str,
) -> Result<Vec<Fr>, VerifyError> {
let binding = compute_binding_hash(action_hash, contract_hash, decision_bit, vk_hash)?;
let decision_fr = if decision_bit == 1 { Fr::from(1u64) } else { Fr::from(0u64) };
Ok(vec![decision_fr, binding_hash_to_fr(&binding)])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn worked_example_from_spec_6_7_8() {
let h = compute_binding_hash(
"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"0x2222222222222222222222222222222222222222222222222222222222222222",
1,
"0x5555555555555555555555555555555555555555555555555555555555555555",
).unwrap();
assert_eq!(h.len(), 32);
let _fr = binding_hash_to_fr(&h);
}
#[test]
fn truncation_collapses_top_3_bits() {
let mut h1 = [0xff; 32];
let mut h2 = [0xff; 32];
h1[0] = 0xff; h2[0] = 0x1f; let f1 = binding_hash_to_fr(&h1);
let f2 = binding_hash_to_fr(&h2);
assert_eq!(f1, f2);
}
}