feat(circom): CRL non-membership via compressed SMT (benchmark) - #876
Draft
kilianglas wants to merge 1 commit into
Draft
feat(circom): CRL non-membership via compressed SMT (benchmark)#876kilianglas wants to merge 1 commit into
kilianglas wants to merge 1 commit into
Conversation
Candidate 3 of 3 for credential revocation list support. Follows the Iden3 / Polygon ID revocation model: leaves sit at their shortest distinguishing depth, so the level count tracks the number of revoked entries rather than the key width. Vendors iden3's smt/ circuits (absent from this repo's partial circomlib copy) with SMTHash1/SMTHash2 ported to Poseidon2, plus the missing Switcher template. Both non-membership cases are covered: the path terminating on an empty node and on a different leaf. Not for merge — opened to measure constraint cost against the indexed Merkle tree and full SMT candidates. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Candidate 3 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-full.What this proves
The credential is not revoked, where the revocation list is a compressed sparse Merkle tree following the Iden3 / Polygon ID revocation model (
checkClaimNotRevoked). Leaves sit at the shortest depth that distinguishes them, so the level count tracks the number of revoked entries rather than the key width.Non-membership has two cases, both handled:
is_old_0 = 1— the path terminates on an empty node.is_old_0 = 0— the path terminates on a different leaf, and the circuit enforceskey != old_key. This case exists only in a compressed tree; a full fixed-depth tree always terminates on an empty node.What this vendors
The repo carries a partial copy of circomlib that omits Iden3
smt/, so this branch adds it undercircom/smt/, plus the missingSwitchertemplate:smt_verifier.circom,smt_lev_ins.circom,smt_verifier_sm.circom,smt_verifier_level.circom— from iden3/circomlib, logic unchanged.smt_hash_poseidon2.circom— the one real change:SMTHash1/SMTHash2ported from classic Poseidon to the Poseidon2 primitives this repo uses.SMTHash2uses the same compression mode asbinary_merkle_root.circom, so both tree implementations agree on node hashing.This is the largest amount of net-new / ported circuit code of the three candidates, and therefore the highest implementation risk.
Measured cost
circom 2.2.3, R1CS totals (non-linear + linear):
OPRFNullifierProofonmainOPRFNullifierProofwith gadgetLevel sweep (gadget alone): n24 14,232 · n28 16,236 · n32 18,240 · n40 22,248 · n48 26,256 — about 501 constraints per level.
That per-level rate is the notable result: the indexed Merkle tree costs ~493 and the full SMT ~493, so the per-level state machine here adds only ~1.6%. Cost is essentially just levels × 493 for all three candidates, which reduces the whole comparison to tree depth.
Sizing nLevels
Depth is probabilistic, and a key whose distinguishing path exceeds
nLevelscannot be represented — a liveness failure. Measured over 7 trials with uniform 64-bit keys:So
nLevelsneeds roughly2 × log2(N)plus margin — around 48 for a 1M-entry CRL, which puts this candidate at 26,256 constraints, not far below the full SMT variant. The 2x depth ratio previously assumed in the design notes is confirmed.Hashing the key is a safety requirement, not just a collision fix: with a raw issuer-chosen
id, an issuer could mint ids sharing a long prefix and deliberately push paths pastnLevels. Truncating a hash to shrink depth is unsafe in the other direction — a birthday collision would cause false revocation.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.
Updates are independent per key, and un-revocation is a local leaf removal with re-compression, which is materially simpler than the indexed tree.
Tests
circom/tests/tests/crl_non_membership_smt_compressed.test.jscovers both non-membership cases plus rejection of a revoked key, a forgedis_old_0, a wrong root and a tampered sibling. The existingoprf_nullifierKAT is extended with the CRL witness and still produces the same nullifier. Full suite green.Fixtures come from an independent compressed-SMT implementation in Rust built on
taceo-poseidon2, so the tests confirm the Poseidon2 port against a second implementation rather than against itself.