Setup_Signer
rule
HSM provisions a long-term signing key; public key is published.
[ Fr(~sk) ] --[ SignerInit($Signer) ]-> [ !SignerKey($Signer, ~sk) , !Pk($Signer, pk(~sk)) , Out(pk(~sk)) ]
The SymmetriQ authorization protocol is modeled in the Tamarin prover and verified against a Dolev–Yao adversary. Five lemmas — authentication, policy binding, no-replay, integrity, and executability — are discharged automatically.
Tamarin v1.12.0 · processing time 1.16s
All properties hold for every trace allowed by the protocol model, under a network attacker that controls every message.
Authentication
verified
6 steps
Integrity
verified
8 steps
Controls every wire. Can intercept, drop, replay, reorder, and inject any message between Subject, Policy Engine, Signing Service, and Verifier.
Signatures and hashes are modeled as perfect — the only way to produce a valid signature is to hold the secret key.
Modeled explicitly: a Compromise_HSM rule leaks the signing key. Authentication then degrades gracefully — accepted intents are either honestly signed or post-compromise.
Five rules capture the entire authorization flow. The Allowed() fact is linear — once consumed by the signer it cannot be reused, which is what gives us policy binding.
rule
HSM provisions a long-term signing key; public key is published.
[ Fr(~sk) ] --[ SignerInit($Signer) ]-> [ !SignerKey($Signer, ~sk) , !Pk($Signer, pk(~sk)) , Out(pk(~sk)) ]
rule
Models the worst case — adversary obtains the signing key.
[ !SignerKey($Signer, sk) ] --[ Compromised($Signer) ]-> [ Out(sk) ]
rule
Policy engine emits a single-use Allowed() fact bound to the bundle hash and intent parameters.
let ph = h(<'sq.treasury.v3', $did, $target, $recipient, $amount>)
intent = <$did, $target, $recipient, $amount, ~nonce, $expiry, ph>
[ Fr(~nonce) ]
--[ PolicyAllowed($did, $target, $recipient, $amount, ~nonce, $expiry, ph) ]->
[ Allowed(intent) ]rule
Signing service signs ONLY intents the policy engine allowed. Linear consumption prevents reuse for a different intent.
[ Allowed(intent), !SignerKey($Signer, sk) ] --[ Signed($Signer, did, target, recipient, amount, nonce, expiry, ph) ]-> [ Out(<intent, sign(intent, sk)>) ]
rule
On-chain verifier accepts iff signature verifies AND nonce has not been consumed.
[ In(<intent, sig>), !Pk($Signer, pk_s) ] --[ Eq(verify(sig, intent, pk_s), true) , Verified($Signer, did, target, recipient, amount, nonce, expiry, ph) , UniqueNonce(nonce) ]-> [ ]
Each lemma is stated over traces of the protocol. Tamarin discharges them automatically by symbolic backward search.
Every intent the on-chain verifier accepts was signed earlier by the legitimate signer, unless the HSM was already compromised.
All s did t r a n e ph #i.
Verified(s, did, t, r, a, n, e, ph) @ #i
==> (Ex #j. Signed(s, did, t, r, a, n, e, ph) @ #j & #j < #i)
| (Ex #k. Compromised(s) @ #k & #k < #i)Nothing is signed without a prior matching allow decision from the policy engine. The policy engine is the sole source of authorization.
All s did t r a n e ph #i. Signed(s, did, t, r, a, n, e, ph) @ #i ==> Ex #j. PolicyAllowed(did, t, r, a, n, e, ph) @ #j & #j < #i
Each nonce is consumed at most once on-chain, even though the network adversary may resubmit (intent, signature) arbitrarily many times.
All n #i #j. UniqueNonce(n) @ #i & UniqueNonce(n) @ #j ==> #i = #j
If a tuple (did, target, recipient, amount, nonce, expiry, policyHash) is accepted, those exact values were signed — no field can be tampered with in flight.
All s did t r a n e ph #i.
Verified(s, did, t, r, a, n, e, ph) @ #i
==> ( (Ex #j. Signed(s, did, t, r, a, n, e, ph) @ #j & #j < #i)
| (Ex #k. Compromised(s) @ #k & #k < #i) )
& (All n2 #i2.
UniqueNonce(n2) @ #i2 & UniqueNonce(n) @ #i & n2 = n
==> #i2 = #i)Sanity: at least one honest trace exists where an intent is policy-allowed, signed, and verified without HSM compromise. Confirms the model is not vacuously true.
Ex s did t r a n e ph #i #j.
PolicyAllowed(did, t, r, a, n, e, ph) @ #j
& Verified(s, did, t, r, a, n, e, ph) @ #i
& not (Ex #k. Compromised(s) @ #k)analyzed: SymmetriQ.spthy processing time: 1.16s authentication (all-traces) : verified (6 steps) policy_binding (all-traces) : verified (3 steps) no_replay (all-traces) : verified (2 steps) integrity (all-traces) : verified (8 steps) executable (exists-trace): verified (6 steps)
Reproduce the proof
Install Tamarin (nix run nixpkgs#tamarin-prover), download SymmetriQ.spthy, and run tamarin-prover --prove SymmetriQ.spthy.