1#![forbid(unsafe_code)]
7
8use blake3::Hasher;
9use serde::{Deserialize, Serialize};
10use tdln_ast::SemanticUnit;
11use thiserror::Error;
12
13#[cfg(feature = "ed25519")]
14use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
15#[cfg(feature = "ed25519")]
16use std::convert::TryInto;
17
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
19pub struct ProofBundle {
20 pub ast_cid: [u8; 32],
21 pub canon_cid: [u8; 32],
22 pub rules_applied: Vec<String>,
24 pub preimage_hashes: Vec<[u8; 32]>,
26 #[cfg(feature = "ed25519")]
28 pub signatures: Vec<Vec<u8>>,
29}
30
31#[derive(Debug, Error)]
32pub enum ProofError {
33 #[error("invalid bundle shape")]
34 Invalid,
35 #[error("signature missing")]
36 NoSignature,
37 #[error("signature verify failed")]
38 VerifyFailed,
39}
40
41pub fn build_proof(
43 ast: &SemanticUnit,
44 canon_json: &[u8],
45 rules: &[impl AsRef<str>],
46) -> ProofBundle {
47 let ast_cid = ast.cid_blake3();
48 let mut h = Hasher::new();
49 h.update(canon_json);
50 let canon_cid = h.finalize().into();
51 ProofBundle {
52 ast_cid,
53 canon_cid,
54 rules_applied: rules.iter().map(|r| r.as_ref().to_string()).collect(),
55 preimage_hashes: vec![],
56 #[cfg(feature = "ed25519")]
57 signatures: vec![],
58 }
59}
60
61fn bundle_digest(bundle: &ProofBundle) -> [u8; 32] {
63 let mut h = Hasher::new();
64 h.update(&bundle.ast_cid);
65 h.update(&bundle.canon_cid);
66 for r in &bundle.rules_applied {
67 h.update(r.as_bytes());
68 }
69 h.finalize().into()
70}
71
72#[cfg(feature = "ed25519")]
73pub fn sign(bundle: &mut ProofBundle, sk: &SigningKey) {
74 let msg = bundle_digest(bundle);
75 let sig = sk.sign(&msg);
76 bundle.signatures.push(sig.to_bytes().to_vec());
77}
78
79pub fn verify_proof(bundle: &ProofBundle) -> Result<(), ProofError> {
81 if bundle.ast_cid == [0; 32] || bundle.canon_cid == [0; 32] {
83 return Err(ProofError::Invalid);
84 }
85 Ok(())
86}
87
88#[cfg(feature = "ed25519")]
89pub fn verify_signatures(bundle: &ProofBundle, keys: &[VerifyingKey]) -> Result<(), ProofError> {
90 if bundle.signatures.is_empty() {
91 return Err(ProofError::NoSignature);
92 }
93 let msg = bundle_digest(bundle);
94 for (sig_bytes, vk) in bundle.signatures.iter().zip(keys.iter().cycle()) {
95 let sig_array: [u8; 64] = sig_bytes
96 .as_slice()
97 .try_into()
98 .map_err(|_| ProofError::VerifyFailed)?;
99 let sig = Signature::from_bytes(&sig_array);
100 vk.verify(&msg, &sig)
101 .map_err(|_| ProofError::VerifyFailed)?;
102 }
103 Ok(())
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 #[cfg(feature = "ed25519")]
110 use ed25519_dalek::{SigningKey, VerifyingKey};
111 #[test]
112 fn shape_ok() {
113 let ast = SemanticUnit::from_intent("book a table for two");
114 let canon = ast.canonical_bytes();
115 let pb = build_proof(&ast, &canon, &["normalize", "slots"]);
116 assert!(verify_proof(&pb).is_ok());
117 }
118
119 #[cfg(feature = "ed25519")]
120 #[test]
121 fn sign_and_verify() {
122 let ast = SemanticUnit::from_intent("set timer 5 minutes");
123 let canon = ast.canonical_bytes();
124 let mut pb = build_proof(&ast, &canon, &["normalize"]);
125 let sk = SigningKey::from_bytes(&[7u8; 32]);
126 let vk: VerifyingKey = (&sk).into();
127 sign(&mut pb, &sk);
128 assert!(verify_signatures(&pb, &[vk]).is_ok());
129 }
130}