Skip to main content
Access must be revocable. In Nullis, revocation happens by rotating the approved root and bumping the policy version. Stale-version proofs are then rejected on-chain — without the contract ever learning whose access was revoked.

How it works

The issuer publishes a new root that omits the revoked credential and bumps the version. A proof that binds the old root no longer matches the policy’s current root, so verify_and_execute rejects it and emits a REVOKED receipt.

The policy lifecycle

Revocation is one move in a small, explicit lifecycle — all handled by the Policy module of the contract:
FunctionEffectEvent
create_policyRegister a policy + its first root and versionPolicyPublished
publish_rootSet the approved root for a versionPolicyPublished
rotate_rootPublish a new root, bump the version — this is revocationRootRotated
disable_policyStop all execution under a policy

A worked example

1

Before — v1

Policy 1001 is active at version 1 with approved_root = root₁, which includes Bob’s commitment. Bob can prove membership and transact.
2

Revoke — rotate to v2

The issuer rebuilds the tree without Bob, computes root₂, and calls rotate_root(1001, root₂). The policy is now at version 2. RootRotated is emitted.
3

After — Bob is blocked

Bob’s existing proof binds root₁ and policy_version: 1. The contract checks the proof’s root against the policy’s current root (root₂, v2) — they don’t match. Result: ActionRejected, receipt REVOKED, nothing executes. Amina and Carla, still in root₂, are unaffected.

The honest trade-off

Root rotation invalidates existing Merkle witnesses. When the root rotates, every still-approved user must refresh their membership path to keep proving — because the tree changed shape. This is a real operational cost, and Nullis discloses it rather than hiding it.
This is the deliberate v0 choice: it is simple, on-chain-verifiable, and correct. It trades a witness-refresh cost for a design a reviewer can fully audit today.

The roadmap path — in-circuit non-membership

The roadmap replaces “prove membership in an approved set” with “prove non-membership in a revocation set”, using a sparse Merkle tree. The circuit itself proves a credential is not revoked, so honest users never have to refresh witnesses when someone else is removed.
In-circuit non-membership is a roadmap item. Nullis does not claim it until it ships stably. What runs today is root rotation with versioning — and the negative suite proves stale-version proofs are rejected on-chain.

What’s tested

The negative suite exercises the whole revocation surface directly:

Stale-root blocked

A proof binding an old root is rejected → REVOKED.

Disabled policy blocked

disable_policy stops all execution.

Expired policy blocked

Past-expiry proofs are rejected.

See it in the evidence

The full negative suite, green in CI.