inherence_verifier/
binding.rs1use ark_bn254::Fr;
10use ark_ff::PrimeField;
11use sha2::{Digest, Sha256};
12
13use crate::error::VerifyError;
14
15fn hex32(s: &str) -> Result<[u8; 32], VerifyError> {
17 let stripped = s.trim_start_matches("0x").trim_start_matches("sha256:");
18 let bytes = hex::decode(stripped).map_err(|_| {
19 VerifyError::SchemaViolation(format!("expected 32-byte hex, got {s:?}"))
20 })?;
21 if bytes.len() != 32 {
22 return Err(VerifyError::SchemaViolation(
23 format!("expected 32-byte hex, got {} bytes", bytes.len())));
24 }
25 let mut arr = [0u8; 32];
26 arr.copy_from_slice(&bytes);
27 Ok(arr)
28}
29
30pub fn compute_binding_hash(
32 action_hash: &str,
33 contract_hash: &str,
34 decision_bit: u8,
35 vk_hash: &str,
36) -> Result<[u8; 32], VerifyError> {
37 let action_bytes = hex32(action_hash)?;
38 let contract_bytes = hex32(contract_hash)?;
39 let vk_bytes = hex32(vk_hash)?;
40 if decision_bit > 1 {
41 return Err(VerifyError::SchemaViolation(
42 format!("decision_bit must be 0 or 1, got {decision_bit}")));
43 }
44 let mut hasher = Sha256::new();
45 hasher.update(&action_bytes);
46 hasher.update(&contract_bytes);
47 hasher.update(&[decision_bit]);
48 hasher.update(&vk_bytes);
49 let digest = hasher.finalize();
50 let mut out = [0u8; 32];
51 out.copy_from_slice(&digest[..]);
52 Ok(out)
53}
54
55pub fn binding_hash_to_fr(hash: &[u8; 32]) -> Fr {
57 let mut truncated = *hash;
58 truncated[0] &= 0x1F;
59 Fr::from_be_bytes_mod_order(&truncated)
60}
61
62pub fn public_inputs_fr(
64 action_hash: &str,
65 contract_hash: &str,
66 decision_bit: u8,
67 vk_hash: &str,
68) -> Result<Vec<Fr>, VerifyError> {
69 let binding = compute_binding_hash(action_hash, contract_hash, decision_bit, vk_hash)?;
70 let decision_fr = if decision_bit == 1 { Fr::from(1u64) } else { Fr::from(0u64) };
71 Ok(vec![decision_fr, binding_hash_to_fr(&binding)])
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn worked_example_from_spec_6_7_8() {
80 let h = compute_binding_hash(
84 "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
85 "0x2222222222222222222222222222222222222222222222222222222222222222",
86 1,
87 "0x5555555555555555555555555555555555555555555555555555555555555555",
88 ).unwrap();
89 assert_eq!(h.len(), 32);
92 let _fr = binding_hash_to_fr(&h);
94 }
95
96 #[test]
97 fn truncation_collapses_top_3_bits() {
98 let mut h1 = [0xff; 32];
99 let mut h2 = [0xff; 32];
100 h1[0] = 0xff; h2[0] = 0x1f; let f1 = binding_hash_to_fr(&h1);
104 let f2 = binding_hash_to_fr(&h2);
105 assert_eq!(f1, f2);
106 }
107}