#!/usr/bin/env python3 # ----------------------------------------------------------------------------- # credential_registry_smt.py # # SMT proof (z3) of the FAIL-CLOSED trust guarantees of Aere Network's federated # compliance passport: AereTrustRegistry (the accredited-issuer trusted list) and # AereVerifiableCredential (the W3C VC 2.0 anchor + verifier). Plus load-bearing # NEGATIVE CONTROLS for the accreditation gate and the append-only status history. # # Contracts: contracts/contracts/compliance/AereTrustRegistry.sol # contracts/contracts/compliance/AereVerifiableCredential.sol # # The safety-critical, fail-closed guarantees modelled here: # A. ISSUANCE requires accreditation. anchorCredential() records a credential ONLY # if the issuer isAccredited(issuerId, type) == true, i.e. status == Active AND # the credential type is in the issuer's scope, AND the issuer's Falcon-512 # signature over the credential digest verifies (via the live precompile 0x0AE1 # through the Trust Registry). A non-accredited issuer cannot issue. # B. APPEND-ONLY status history. Every status transition is pushed to an append-only # log; a committed record is never rewritten (the current status is a pointer, # the history is immutable). # C. TERMINAL revocation. Revoked is terminal: no transition path returns a revoked # issuer to Active (revoke has no inverse; reinstate only lifts Suspended). # D. FAIL-CLOSED verification. isValid()/verifyPresented() return true ONLY when the # issuer is (still) accredited, the credential is within its validity window, and # it is not revoked on its Bitstring Status List entry; expired, not-yet-valid, # revoked, or un-accredited all resolve to NOT valid, never a false positive. # # Status enum (Solidity): None=0, Active=1, Suspended=2, Revoked=3. # # We model the guards / transitions as first-order constraints and prove each # property by asserting its NEGATION and showing z3 returns UNSAT. Each NEG-CTRL # removes exactly one guard and shows the corresponding attack becomes SAT. # Append-only history is modelled with the standard "writes only push" relation # over an uninterpreted index->value function. This checks the DESIGN-level logic, # NOT the compiled EVM bytecode. # ----------------------------------------------------------------------------- from z3 import (Int, Bool, Function, IntSort, BoolSort, Solver, And, Or, Not, Implies, ForAll, If, sat, unsat) NONE, ACTIVE, SUSPENDED, REVOKED = 0, 1, 2, 3 results = [] def check(name, s, expect_unsat=True, kind="PROOF"): r = s.check() if expect_unsat: ok = (r == unsat); tag = "PROVED" if ok else "FAILED" else: ok = (r == sat); tag = "CEX-FOUND" if ok else "FAILED" results.append((name, tag, ok, kind)) print(f"[{tag:9}] ({kind}) {name}: z3={r} (expected {'unsat' if expect_unsat else 'sat'})") if r == sat: m = s.model() print(" witness:", {str(d): m[d] for d in m.decls()}) return ok def accredited(status, inScope): """AereTrustRegistry.isAccredited: Active AND type in scope (fail-closed otherwise).""" return And(status == ACTIVE, inScope) def within_validity(now, validFrom, validUntil): """AereVerifiableCredential.isWithinValidity: not-yet-valid and expired both fail.""" return And(now >= validFrom, Or(validUntil == 0, now <= validUntil)) print("### AereTrustRegistry + AereVerifiableCredential -- FAIL-CLOSED compliance trust\n") # ---- A ISSUANCE requires accreditation. anchorCredential() succeeds ONLY if the # issuer is accredited for the type AND its PQC signature verifies. Model the # anchor's success predicate = isAccredited AND sigValid AND withinValidity. # Negation: an anchor succeeds yet the issuer is NOT accredited. UNSAT. ------- s = Solver() status, inScope = Int('status'), Bool('inScope') sigValid = Bool('sigValid') now, vFrom, vUntil = Int('now'), Int('vFrom'), Int('vUntil') anchor_ok = And(accredited(status, inScope), sigValid, within_validity(now, vFrom, vUntil)) s.add(anchor_ok) s.add(Not(accredited(status, inScope))) # NEGATION: not accredited yet issued check("A anchorCredential requires issuer accreditation (non-accredited cannot issue)", s) # ---- A2 A non-accredited issuer is any of: None / Suspended / Revoked, OR Active # but out-of-scope. For every such issuer, anchor_ok is false. Negation: some # non-accredited issuer anchors. UNSAT. ------------------------------------ s = Solver() status, inScope = Int('status'), Bool('inScope') sigValid = Bool('sigValid') now, vFrom, vUntil = Int('now'), Int('vFrom'), Int('vUntil') s.add(status >= NONE, status <= REVOKED) not_accredited = Or(status != ACTIVE, Not(inScope)) anchor_ok = And(accredited(status, inScope), sigValid, within_validity(now, vFrom, vUntil)) s.add(not_accredited, anchor_ok) # NEGATION: not-accredited AND anchored check("A2 a suspended/revoked/None or out-of-scope issuer cannot anchor", s) # ---- A3 PQC signature is required (fail-closed): anchor_ok => sigValid. Negation: # an anchor succeeds with an invalid/tampered issuer signature. UNSAT. ------- s = Solver() status, inScope = Int('status'), Bool('inScope') sigValid = Bool('sigValid') now, vFrom, vUntil = Int('now'), Int('vFrom'), Int('vUntil') anchor_ok = And(accredited(status, inScope), sigValid, within_validity(now, vFrom, vUntil)) s.add(anchor_ok, Not(sigValid)) # NEGATION check("A3 anchorCredential requires a valid Falcon-512 issuer signature (fail-closed)", s) # ---- B APPEND-ONLY status history: a committed record is never silently rewritten. # Model the log as index->value with the append relation: a transition only # pushes at index == len, leaving every existing index untouched. Negation: # some already-committed index changes value. UNSAT under append-only. ------- hB = Function('hB', IntSort(), IntSort()) # history BEFORE the transition hA = Function('hA', IntSort(), IntSort()) # history AFTER the transition s = Solver() L = Int('L'); newVal = Int('newVal'); i = Int('i') s.add(L >= 0) # append semantics: existing entries preserved, new entry only at index L. s.add(ForAll([i], Implies(And(i >= 0, i < L), hA(i) == hB(i)))) s.add(hA(L) == newVal) j = Int('j') s.add(j >= 0, j < L, hA(j) != hB(j)) # NEGATION: a committed entry mutated check("B status history is append-only (a committed record cannot be rewritten)", s) # ---- C TERMINAL revocation: from Revoked, no enabled transition returns to Active. # Transition guards: suspend(Active->Suspended), reinstate(Suspended->Active), # revoke(!Revoked->Revoked). Model the next status as a guarded choice; from # Revoked every guard is disabled, so status stays Revoked. Negation: starting # Revoked, some transition yields Active. UNSAT. --------------------------- s = Solver() cur = Int('cur'); nxt = Int('nxt'); op = Int('op') # op: 1=suspend 2=reinstate 3=revoke s.add(cur == REVOKED) # enabled transitions and their results (a disabled op leaves status unchanged): suspend_ok = And(op == 1, cur == ACTIVE) reinstate_ok = And(op == 2, cur == SUSPENDED) revoke_ok = And(op == 3, cur != REVOKED) nxt_def = If(suspend_ok, SUSPENDED, If(reinstate_ok, ACTIVE, If(revoke_ok, REVOKED, cur))) # no enabled op from Revoked -> unchanged s.add(nxt == nxt_def) s.add(nxt == ACTIVE) # NEGATION: revoked issuer reinstated check("C revocation is terminal (a Revoked issuer can never return to Active)", s) # ---- D FAIL-CLOSED live validity of an ANCHORED credential. isValid() returns true # ONLY if the issuer is STILL accredited AND within validity AND not revoked. # Negation: isValid true yet un-accredited OR out-of-window OR revoked. UNSAT. s = Solver() exists = Bool('exists') status, inScope = Int('status'), Bool('inScope') now, vFrom, vUntil = Int('now'), Int('vFrom'), Int('vUntil') revokedBit = Bool('revokedBit') isValid = And(exists, accredited(status, inScope), within_validity(now, vFrom, vUntil), Not(revokedBit)) s.add(isValid) s.add(Or(Not(accredited(status, inScope)), Not(within_validity(now, vFrom, vUntil)), revokedBit)) # NEGATION check("D isValid is fail-closed (accredited AND within-window AND not-revoked)", s) # ---- D2 FAIL-CLOSED verification of a PRESENTED credential. verifyPresented() adds # the PQC signature check on top of D. Negation: true yet a signature/accred/ # window/revocation failure slipped through. UNSAT. ------------------------ s = Solver() status, inScope = Int('status'), Bool('inScope') sigValid = Bool('sigValid') now, vFrom, vUntil = Int('now'), Int('vFrom'), Int('vUntil') revokedBit = Bool('revokedBit') presented_ok = And(accredited(status, inScope), sigValid, within_validity(now, vFrom, vUntil), Not(revokedBit)) s.add(presented_ok) s.add(Or(Not(accredited(status, inScope)), Not(sigValid), Not(within_validity(now, vFrom, vUntil)), revokedBit)) # NEGATION check("D2 verifyPresented is fail-closed (accred + PQC sig + window + not-revoked)", s) # ============================ NEGATIVE CONTROLS ============================== # ---- NEG-CTRL 1 (drop the accreditation check): a BUGGY anchor that skips # isAccredited lets a SUSPENDED / REVOKED / out-of-scope issuer anchor a # credential. z3 finds a revoked issuer issuing. --------------------------- s = Solver() status, inScope = Int('status'), Bool('inScope') sigValid = Bool('sigValid') now, vFrom, vUntil = Int('now'), Int('vFrom'), Int('vUntil') # BUG: accreditation gate removed; anchor succeeds on signature + window alone. buggy_anchor_ok = And(sigValid, within_validity(now, vFrom, vUntil)) s.add(buggy_anchor_ok) s.add(status == REVOKED) # a revoked issuer... s.add(Not(accredited(status, inScope))) # ...is not accredited, yet anchors check("no-accreditation-check registry CAN let a revoked issuer issue a credential", s, expect_unsat=False, kind="NEG-CTRL") # ---- NEG-CTRL 2 (allow a status overwrite): a BUGGY mutable history that rewrites an # EXISTING index instead of appending. A committed record is silently changed. s = Solver() L = Int('L'); j = Int('j'); newVal = Int('newVal') s.add(L >= 1, j >= 0, j < L) # BUG: the write targets an existing index j (overwrite), so hA(j) != hB(j) is allowed. s.add(hA(j) == newVal, hB(j) != newVal) # committed entry rewritten check("mutable-history registry CAN silently rewrite a committed status record", s, expect_unsat=False, kind="NEG-CTRL") # ---- NEG-CTRL 3 (fail-open validity): a BUGGY verifier that ignores the validity # window accepts an EXPIRED credential. z3 finds an expired credential passing. s = Solver() status, inScope = Int('status'), Bool('inScope') now, vFrom, vUntil = Int('now'), Int('vFrom'), Int('vUntil') revokedBit = Bool('revokedBit') # BUG: no within-validity check; validity ignored. buggy_valid = And(accredited(status, inScope), Not(revokedBit)) s.add(buggy_valid) s.add(vUntil != 0, now > vUntil) # the credential is EXPIRED check("no-validity-window verifier CAN accept an expired credential", s, expect_unsat=False, kind="NEG-CTRL") # ------------------------------------------------------------------------------ print("\n=== SUMMARY ===") allok = True for name, tag, ok, kind in results: print(f" {tag:9} [{kind}] {name}") allok = allok and ok print() if allok: print("AereTrustRegistry + AereVerifiableCredential trust safety:") print(" PROVED -- issuance requires accreditation (A/A2) and a valid Falcon-512 issuer") print(" signature (A3), the status history is append-only so a committed record cannot be") print(" rewritten (B), revocation is terminal (C), and both the anchored (isValid, D) and") print(" presented (verifyPresented, D2) verification paths are fail-closed on expired,") print(" not-yet-valid, revoked, or un-accredited. Three NEG-CTRLs fire: dropping the") print(" accreditation gate lets a revoked issuer issue, a mutable history silently rewrites a") print(" committed record, and a fail-open verifier accepts an expired credential.") print(" [VERIFY] FALCON-512 PRECOMPILE (0x0AE1): the issuer-signature validity bit is produced") print(" by the live native precompile through AerePQCKeyRegistry.verifyWithKey; its soundness") print(" (EUF-CMA of Falcon-512, correct precompile parsing) is a trusted primitive, modelled") print(" here as the sigValid predicate, not re-proved. The Bitstring Status List and the live") print(" issuer status are trusted external oracles for the revocation / accreditation bits.") print(" DESIGN: application-layer compliance tooling, no funds, consensus stays classical") print(" ECDSA QBFT; this checks the guard logic, not the compiled EVM bytecode. Legal weight") print(" of any accreditation is an external regulatory matter, not asserted by the model.") else: print(" NOT fully established (see FAILED / unexpected CEX above).") import sys sys.exit(0 if allok else 1)