Skip to content

feat(circom): CRL non-membership via full fixed-depth SMT (benchmark) - #875

Draft
kilianglas wants to merge 1 commit into
mainfrom
bench/crl-smt-full
Draft

feat(circom): CRL non-membership via full fixed-depth SMT (benchmark)#875
kilianglas wants to merge 1 commit into
mainfrom
bench/crl-smt-full

Conversation

@kilianglas

Copy link
Copy Markdown
Contributor

Candidate 2 of 3 for credential revocation list (CRL) support. Not for merge — one of three branches opened so the designs can be compared on measured constraint cost. Related: bench/crl-imt-low-leaf, bench/crl-smt-compressed.

What this proves

The credential is not revoked, where the revocation list is a full fixed-depth sparse Merkle tree keyed on the u64 credential id. The key is the leaf position, so the tree depth equals the key width (64) and a live credential is simply an empty (0) leaf at that position.

Non-membership is therefore one plain Merkle inclusion proof of an empty leaf — no comparators, no low leaf, no auxiliary witness. Soundness comes from position = key: no sibling set reproduces the root with leaf = 0 if the committed leaf is 1.

This variant needs no new gadget. It is BinaryMerkleRoot(64) with leaf = 0 and index = cred_id, both already in the repo and already exercised by the account-tree path. That makes it by far the lowest-risk candidate to implement.

Measured cost

circom 2.2.3, R1CS totals (non-linear + linear):

circuit constraints delta vs baseline
OPRFNullifierProof on main 73,767
gadget alone (BinaryMerkleRoot(64)) 31,556
gadget alone, fixed-depth variant 31,169
OPRFNullifierProof with gadget 105,323 +31,556 (+42.8%)

This is the most expensive of the three candidates, and its cost is independent of how many credentials are actually revoked — depth is the key width, so a CRL with 10 entries costs the same as one with a million.

The second template drops the dynamic-depth machinery in BinaryMerkleRoot (per-level IsEqual, trailing index-range constraints), which this variant does not need. It saves only 387 constraints (1.2%), so that machinery is not worth avoiding. Depth sweep: d32 15,780 · d48 23,668 · d64 31,556 — about 493 constraints per level, the same rate as the other two candidates.

A hashed key is not viable here: it would demand ~254 levels, roughly 125k constraints for the gadget alone. That constrains this variant to a per-issuer tree, since a raw u64 cred_id is only unique per issuer.

Scope

Data structure and in-circuit gadget only. Tree maintenance — maintainer service, on-chain root anchoring, insert batching, reorg handling — is deliberately out of scope.

Un-revocation is trivial in this design: clear the leaf back to 0, one independent write.

Tests

circom/tests/tests/crl_non_membership_smt_full.test.js covers an unrevoked key plus rejection of a revoked key, a mismatched sibling path and a wrong root, for both templates. The existing oprf_nullifier KAT is extended with the CRL witness and still produces the same nullifier. Full suite green.

Fixtures were generated with taceo-poseidon2, the crate the Rust side already uses, so they independently confirm the circom Poseidon2 tree math.

Candidate 2 of 3 for credential revocation list support. The credential
id is the leaf position in a depth-64 sparse Merkle tree, so proving the
credential is not revoked is just inclusion of an empty leaf at that
position — no comparators, no low leaf, no auxiliary witness.

This needs no new gadget: it is BinaryMerkleRoot(64) with leaf = 0 and
index = cred_id. A second template drops the dynamic-depth machinery so
its cost can be measured separately.

Not for merge — opened to measure constraint cost against the indexed
Merkle tree and compressed SMT candidates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant