13 Merkle Revocation and Verification Freshness
A Merkle inclusion proof establishes membership in an issuer-authenticated active-credential snapshot. It does not by itself establish current validity.
13.1 Architecture and Responsibility
Issuers publish signed active-credential roots. Holders prove membership within ZKP without disclosing the credential leaf or path. Verifiers authenticate the snapshot and report its age; services decide whether that evidence is adequate. The protocol imposes no universal age or version-lag acceptance window. Individual issuers may issue non-revocable claims; that signed capability must remain distinguishable from a revocable credential whose status was not checked.
13.2 Signed Snapshot
SignedSnapshot: issuer, statusSeries, merkleRoot, version statusAsOf, validUntil, issuerSignature
All fields above are one canonically encoded signature payload. statusAsOf is when the issuer confirmed the represented state, not when someone downloaded it. Versions are ordered only within the same issuer and status series. validUntil indicates the normal update/expiry boundary; passing it does not make an authentic signature a forgery.
The Leaf identifies the original signed claim or credential according to the issuance profile. Extraction and repackaging must preserve that reference. A full-VC leaf requires proof of membership of the extracted claim in that original VC. A per-claim leaf includes the original signed claim conditions. Outer holder signatures cannot change the original revocation capability, expiry or delegation.
13.3 Merkle Tree Construction
13.3.1 Hash Function
SHA-256: Merkle Tree construction (ZKP circuit compatible)
SHA3-512: Other purposes (quantum safe)
13.3.2 Tree Construction Algorithm
Input: activeVCHashes = [h_1, h_2, ..., h_n]
Output: merkleRoot
Algorithm:
1. Padding (adjust to power of 2)
if n not power of 2:
pad with H("") until next power of 2
2. Level 0 (leaves)
leaves = activeVCHashes + padding
3. Upward computation
while len(leaves) > 1:
new_level = []
for i in range(0, len(leaves), 2):
parent = SHA-256(leaves[i] || leaves[i+1])
new_level.append(parent)
leaves = new_level
4. Return root
return leaves[0]
13.3.3 Merkle Proof Generation
Input:
- vcHash (VC to prove)
- activeVCHashes (all active VCs)
- merkleRoot (for verification)
Output: MerkleProof
Algorithm:
1. Find VC position
leafIndex = activeVCHashes.indexOf(vcHash)
if leafIndex == -1: return None
2. Collect proof path
siblingHashes = []
currentIndex = leafIndex
currentLevel = activeVCHashes + padding
while len(currentLevel) > 1:
siblingIndex = currentIndex XOR 1
siblingHashes.append(currentLevel[siblingIndex])
currentIndex = currentIndex / 2
currentLevel = computeParentLevel(currentLevel)
3. Return proof
return MerkleProof {
leafIndex,
siblingHashes,
treeDepth = log_2(paddedLeafCount)
}
13.3.4 Merkle Proof Verification
Input:
- vcHash (VC to verify)
- proof (MerkleProof)
- merkleRoot (expected root)
Output: Bool
Algorithm:
1. Start from leaf
currentHash = vcHash
currentIndex = proof.leafIndex
2. Compute to root
for siblingHash in proof.siblingHashes:
if currentIndex % 2 == 0:
currentHash = SHA-256(currentHash || siblingHash)
else:
currentHash = SHA-256(siblingHash || currentHash)
currentIndex = currentIndex / 2
3. Compare with root
return currentHash == merkleRoot
13.4 Circuit and Verification Contract
The circuit verifies the original claim signature and trusted provenance, subject/presentation binding, expiry, delegation scope, and status references. If status is checked, it computes the original leaf and verifies its Merkle path against the exact public root. The verifier additionally authenticates the issuer, series, version and signed time associated with that root. Public inputs are cryptographically bound to the verified proof, not independent display fields.
VerificationObservation: proofValid statusAtSnapshotActive statusAsOf: optional timestamp snapshotAgeSeconds: optional integer snapshotExpired: optional boolean currentStatus: unknown | confirmedRevoked newerAuthenticatedSnapshotKnown Acceptance = verifier_policy(observation)
The implementation also retains root/version/issuer/series provenance for each observation. Multiple original claims and delegations yield multiple observations; none may be hidden by an aggregate label of "latest".
13.5 Verifier Processing
Verify signatures, attribute predicates, subject relationships and the expected presentation context. An invalid cryptographic proof fails even in emergency mode.
For a status-checking presentation, authenticate the snapshot and require the proof’s root and version to match it. Reject mismatched metadata.
Calculate age from the signed statusAsOf only when the verifier’s clock is trusted. An unavailable clock or unsupported future timestamp gives unknown freshness; a profile may specify a bounded clock skew.
Report whether the normal validUntil boundary has passed. Do not replace this fact with an unconditional protocol rejection.
Retain the newest authenticated snapshot per issuer/series. An older presentation must not overwrite it. A newer root without an updated inclusion proof means insufficient current evidence, not confirmed individual revocation.
Preserve individually confirmed revocation. Old positive evidence never clears that fact. Absence or failure of a membership proof alone cannot establish an individual credential’s current revocation.
Apply the service policy and record the decision separately from these facts. Policies may tighten or relax freshness requirements without changing cryptographic verification results.
For example, if root v100 was published at 10:00, a credential revoked at 10:10 may still have a correct proof against v100 at 10:11. A new session challenge does not update that state snapshot. The result is "active at 10:00; current status unknown", unless separately authenticated individual revocation is known.
13.6 Offline and Disaster Operation
Both old-snapshot and no-snapshot presentations are supported. With no root, verify the original signature, attributes and presentation binding using a status-unchecked proof profile. Report statusAsOf = none and currentStatus = unknown. Do not rewrite the issuer-signed revocationEnabled flag to false to enable this route.
A verifier may grant an emergency exception even where normal policy would refuse service. It must retain the stale/unknown/confirmed-revoked state and record that business exception separately. Neither cached roots nor atomic online issuance guarantees immediate revocation or global uniqueness while offline.
13.7 Lean Model and Limits
verifyZKPWithRevocation returns an observation, and acceptByRevocationPolicy applies a separate policy. Signature and proof verification are supplied as explicit verifier callbacks; their soundness and binding to the supplied payload are implementation obligations. retainNewerSnapshot models monotonic authenticated state per series.
The membership types and proof-generation helpers are abstract models, not production Merkle algorithms or circuits. The theorem invalid_membership_cannot_generate_zkp assumes an invalid proof against its particular root. It does not state that an already revoked credential cannot prove membership in any historical root. Field checks and type-tag checks must not be described as a proof of protocol-wide cryptographic security.
13.8 Acceptance Criteria
Test authentic old roots, absent roots, stale downloads, modified signed times, wrong roots/versions, unknown clocks, future times, known newer roots and confirmed individual revocation. Test normal and emergency policies against the same immutable observations. Authenticate input before updating cached state, keep series separate and ensure old evidence cannot roll back known facts.