|
| 1 | +-- SPDX-License-Identifier: MPL-2.0 |
| 2 | +-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> |
| 3 | +-- |
| 4 | +||| Flagship semantic proof for a2mliser: attestation binding soundness. |
| 5 | +||| |
| 6 | +||| Headline domain property (A2ML cryptographic attestation): |
| 7 | +||| an attestation tag is bound to the exact content it was issued over. |
| 8 | +||| An attestation `Verifies` a document if and only if the digest carried |
| 9 | +||| by the attestation equals the digest of the document. Consequently a |
| 10 | +||| TAMPERED document (content changed while keeping the old attestation) |
| 11 | +||| has NO `Verifiable` proof: the bad case is uninhabited. |
| 12 | +||| |
| 13 | +||| This is the faithful core of "verify succeeds only for the content it |
| 14 | +||| was issued over". We model a content digest as a `Nat` (an abstract |
| 15 | +||| collision-free fingerprint of markup bytes); the binding logic is |
| 16 | +||| algorithm-agnostic, exactly as in the real engine. |
| 17 | + |
| 18 | +module A2mliser.ABI.Semantics |
| 19 | + |
| 20 | +import A2mliser.ABI.Types |
| 21 | +import Data.So |
| 22 | +import Decidable.Equality |
| 23 | + |
| 24 | +%default total |
| 25 | + |
| 26 | +-------------------------------------------------------------------------------- |
| 27 | +-- Faithful domain model |
| 28 | +-------------------------------------------------------------------------------- |
| 29 | + |
| 30 | +||| A document is some markup content fingerprinted to a digest. |
| 31 | +||| `contentDigest` is the abstract fingerprint of the markup bytes; two |
| 32 | +||| documents with different content have different digests (collision-free |
| 33 | +||| model). The `markup` field is carried for faithfulness but the binding |
| 34 | +||| is over the digest, mirroring sign-the-digest cryptography. |
| 35 | +public export |
| 36 | +record Document where |
| 37 | + constructor MkDocument |
| 38 | + markup : String |
| 39 | + contentDigest : Nat |
| 40 | + |
| 41 | +||| An attestation tag binds to one specific content digest. It is the |
| 42 | +||| (abstract) signature whose payload is `boundDigest`. |
| 43 | +public export |
| 44 | +record Attestation where |
| 45 | + constructor MkAttestation |
| 46 | + ||| The digest the attestation was issued over (what the signature covers). |
| 47 | + boundDigest : Nat |
| 48 | + ||| The signing algorithm (reuses the real ABI type for faithfulness). |
| 49 | + algorithm : SignatureAlgorithm |
| 50 | + |
| 51 | +||| Issue an attestation over a document: it binds to that document's digest. |
| 52 | +||| This is the ONLY honest way to mint an attestation for a document. |
| 53 | +public export |
| 54 | +attest : SignatureAlgorithm -> Document -> Attestation |
| 55 | +attest alg doc = MkAttestation (contentDigest doc) alg |
| 56 | + |
| 57 | +-------------------------------------------------------------------------------- |
| 58 | +-- The headline property: attestation binding |
| 59 | +-------------------------------------------------------------------------------- |
| 60 | + |
| 61 | +||| `Verifies att doc` is inhabited exactly when the attestation's bound |
| 62 | +||| digest equals the document's content digest. There is precisely ONE |
| 63 | +||| constructor, and it DEMANDS the binding equality as evidence. There is |
| 64 | +||| NO constructor for the mismatched (tampered) case — that case is |
| 65 | +||| uninhabited by construction. |
| 66 | +public export |
| 67 | +data Verifies : Attestation -> Document -> Type where |
| 68 | + ||| The attestation was issued over exactly this content. |
| 69 | + Bound : (prf : boundDigest att = contentDigest doc) -> Verifies att doc |
| 70 | + |
| 71 | +||| A document/attestation pair is `Verifiable` iff a `Verifies` proof exists. |
| 72 | +public export |
| 73 | +Verifiable : Attestation -> Document -> Type |
| 74 | +Verifiable = Verifies |
| 75 | + |
| 76 | +-------------------------------------------------------------------------------- |
| 77 | +-- Sound + complete decision procedure |
| 78 | +-------------------------------------------------------------------------------- |
| 79 | + |
| 80 | +||| Decide verifiability. Sound: a `Yes` carries a real `Verifies` witness. |
| 81 | +||| Complete: a `No` carries a refutation of every possible witness. |
| 82 | +public export |
| 83 | +decVerifies : (att : Attestation) -> (doc : Document) -> Dec (Verifies att doc) |
| 84 | +decVerifies att doc = |
| 85 | + case decEq (boundDigest att) (contentDigest doc) of |
| 86 | + Yes prf => Yes (Bound prf) |
| 87 | + No ctra => No (\(Bound prf) => ctra prf) |
| 88 | + |
| 89 | +-------------------------------------------------------------------------------- |
| 90 | +-- Certifier + soundness fact |
| 91 | +-------------------------------------------------------------------------------- |
| 92 | + |
| 93 | +||| Internal: map a raw `Dec` outcome to the ABI's own result code. |
| 94 | +||| Kept top-level so it reduces by pattern matching in the proofs below. |
| 95 | +public export |
| 96 | +certifyVia : Dec (Verifies att doc) -> AttestationResult |
| 97 | +certifyVia (Yes _) = Ok |
| 98 | +certifyVia (No _) = DigestMismatch |
| 99 | + |
| 100 | +||| Map a verification attempt to the ABI's own result code. |
| 101 | +public export |
| 102 | +certify : Attestation -> Document -> AttestationResult |
| 103 | +certify att doc = certifyVia (decVerifies att doc) |
| 104 | + |
| 105 | +||| Soundness: if the certifier returns `Ok`, a genuine binding proof exists. |
| 106 | +||| (Forces `Ok` to mean the attestation truly covers this content.) |
| 107 | +public export |
| 108 | +certifyOkSound : (att : Attestation) -> (doc : Document) |
| 109 | + -> certify att doc = Ok -> Verifies att doc |
| 110 | +certifyOkSound att doc okEq with (decVerifies att doc) |
| 111 | + certifyOkSound att doc okEq | Yes ok = ok |
| 112 | + certifyOkSound att doc Refl | No _ impossible |
| 113 | + |
| 114 | +||| Completeness/contrapositive: a `DigestMismatch` certificate means the |
| 115 | +||| pair is genuinely NOT verifiable. |
| 116 | +public export |
| 117 | +certifyMismatchRefutes : (att : Attestation) -> (doc : Document) |
| 118 | + -> certify att doc = DigestMismatch -> Not (Verifies att doc) |
| 119 | +certifyMismatchRefutes att doc mmEq with (decVerifies att doc) |
| 120 | + certifyMismatchRefutes att doc Refl | Yes _ impossible |
| 121 | + certifyMismatchRefutes att doc mmEq | No ctra = ctra |
| 122 | + |
| 123 | +-------------------------------------------------------------------------------- |
| 124 | +-- Core theorems about attestation binding |
| 125 | +-------------------------------------------------------------------------------- |
| 126 | + |
| 127 | +||| An honestly-issued attestation always verifies the document it was |
| 128 | +||| issued over (the engine never rejects untampered content). |
| 129 | +public export |
| 130 | +attestVerifies : (alg : SignatureAlgorithm) -> (doc : Document) |
| 131 | + -> Verifies (attest alg doc) doc |
| 132 | +attestVerifies alg doc = Bound Refl |
| 133 | + |
| 134 | +||| Binding soundness (tamper-evidence): if an attestation verifies BOTH a |
| 135 | +||| document and a re-issued document, their content digests are identical. |
| 136 | +||| Therefore you cannot make one attestation verify two contents that |
| 137 | +||| differ — exactly the "bound to the content it was issued over" guarantee. |
| 138 | +public export |
| 139 | +bindingUnique : (att : Attestation) -> (d1, d2 : Document) |
| 140 | + -> Verifies att d1 -> Verifies att d2 |
| 141 | + -> contentDigest d1 = contentDigest d2 |
| 142 | +bindingUnique att d1 d2 (Bound p1) (Bound p2) = trans (sym p1) p2 |
| 143 | + |
| 144 | +-------------------------------------------------------------------------------- |
| 145 | +-- Positive control (inhabited witness) |
| 146 | +-------------------------------------------------------------------------------- |
| 147 | + |
| 148 | +||| A concrete document and the attestation honestly issued over it. |
| 149 | +public export |
| 150 | +goodDoc : Document |
| 151 | +goodDoc = MkDocument "<a2ml:doc>hello</a2ml:doc>" 1729 |
| 152 | + |
| 153 | +public export |
| 154 | +goodAtt : Attestation |
| 155 | +goodAtt = attest Ed25519 goodDoc |
| 156 | + |
| 157 | +||| POSITIVE CONTROL: the honest attestation verifies the original document. |
| 158 | +public export |
| 159 | +goodVerifies : Verifies Semantics.goodAtt Semantics.goodDoc |
| 160 | +goodVerifies = Bound Refl |
| 161 | + |
| 162 | +-------------------------------------------------------------------------------- |
| 163 | +-- Negative control (the tampered document is not verifiable) |
| 164 | +-------------------------------------------------------------------------------- |
| 165 | + |
| 166 | +||| The SAME document content edited (tampered): different digest, but an |
| 167 | +||| attacker tries to reuse the old `goodAtt`. |
| 168 | +public export |
| 169 | +tamperedDoc : Document |
| 170 | +tamperedDoc = MkDocument "<a2ml:doc>HELLO (edited)</a2ml:doc>" 9999 |
| 171 | + |
| 172 | +||| NEGATIVE CONTROL: the original attestation does NOT verify the tampered |
| 173 | +||| document. Machine-checked: any putative `Verifies` proof yields the |
| 174 | +||| absurd equality `1729 = 9999`, which reduces to `Refl impossible`. |
| 175 | +public export |
| 176 | +tamperedNotVerifiable : Not (Verifies Semantics.goodAtt Semantics.tamperedDoc) |
| 177 | +tamperedNotVerifiable (Bound prf) = absurdEq prf |
| 178 | + where |
| 179 | + ||| `prf` definitionally has type `1729 = 9999`; name the concrete, |
| 180 | + ||| constructor-headed equality so its refutation reduces. |
| 181 | + absurdEq : (the Nat 1729 = the Nat 9999) -> Void |
| 182 | + absurdEq Refl impossible |
0 commit comments