Skip to content

feat(resilient_civic_participation): poc#73

Merged
rymnc merged 5 commits into
masterfrom
feat/civic-participation-impl
May 27, 2026
Merged

feat(resilient_civic_participation): poc#73
rymnc merged 5 commits into
masterfrom
feat/civic-participation-impl

Conversation

@rymnc

@rymnc rymnc commented May 19, 2026

Copy link
Copy Markdown
Member
USE_MOCK_VERIFIERS=false cargo test --test golden_path --release -- --nocapture
running 1 test
=== [1/13] Deploy anvil + contracts ===
  anvil endpoint:    http://localhost:58413
  petition registry: 0x5fc8d32690cc91d4c39d9d3abcbd16989f875707
  bounty token:      0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0
  deployer:          0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
=== [2/13] Ensure signer VK ===
  signer VK ready in 472.218875ms
=== [3/13] Fund and approve bounty (1000000000 units) ===
  minted 1000000000 to deployer
  approved registry to spend 1000000000 from deployer
=== [4/13] Enroll 6 signers (classes 826 x3, 840 x3) ===
  enrolled signer 1/6 (class 826, ri_leaf_index 0)
  enrolled signer 2/6 (class 826, ri_leaf_index 1)
  enrolled signer 3/6 (class 826, ri_leaf_index 2)
  enrolled signer 4/6 (class 840, ri_leaf_index 3)
  enrolled signer 5/6 (class 840, ri_leaf_index 4)
  enrolled signer 6/6 (class 840, ri_leaf_index 5)
  RI root after enrollment: 0x0ed4a37ad60c0c85c9cead58c379628f7d819d77224a12cb6f91c65a7dc07493
=== [5/13] Build predicate and derive petition id ===
  predicate encoded:    78 bytes
  current block:        7
  close_at_block:       37
  predicate_hash:       0x278bcc138592478f9435ad3db17886b4eeaf0d0b42da0b8bcc6cc09cf7ec69f1
=== [6/13] Register petition on chain ===
  registered petition_id 0x00a0edbe160e50a04150f1fe9c1f392a3ab76a086aa6023608ea34e443eb544e in 1.613792ms
  bounty escrowed:      1000000000
=== [7/13] Generate signer proofs (6 total) ===
  signer 1/6 (class 826): proving...
  signer 1/6: proof generated in 1.412647208s (14656 bytes)
  signer 2/6 (class 826): proving...
  signer 2/6: proof generated in 1.413840167s (14656 bytes)
  signer 3/6 (class 826): proving...
  signer 3/6: proof generated in 1.408466583s (14656 bytes)
  signer 4/6 (class 840): proving...
  signer 4/6: proof generated in 1.402540459s (14656 bytes)
  signer 5/6 (class 840): proving...
  signer 5/6: proof generated in 1.432067959s (14656 bytes)
  signer 6/6 (class 840): proving...
  signer 6/6: proof generated in 1.414785459s (14656 bytes)
  all 6 signer proofs in 8.484464917s
=== [8/13] Build recursive batch proof (multi-minute) ===
  batch proof ready in 21.861739167s (11456 bytes)
  new_running_root:        0x2d0d6a2a4020450476b55952fd9a4678c2668d808265088fc999bfac8142dee0
  batch_versioned_hash:    0x01a9759f964fa54597fa27aa1a91ea100f27baf1601c4e869252de46db76d1ed
  new_leaf_count:          6
=== [9/13] Build EIP-4844 sidecar and KZG openings ===
  KZG commitment:    48 bytes
  KZG proofs concat: 1152 bytes across 24 eval points
  KZG openings consistent with SNARK bls_fields
=== [10/13] Publish batch on chain ===
  publishBatch confirmed in 2.377557042s
  batchCount = 1
=== [11/13] Advance chain into DisputeWindow ===
  current block: 9 | target: 638 | mining 629 blocks
  block after mine: 638
=== [12/13] Build resolution proof ===
test golden_path_anvil_real has been running for over 60 seconds
  resolution proof ready in 34.166682208s (11456 bytes)
=== [13/13] Submit resolution and verify bounty payout ===
  deployer balance pre-resolve:  0
  resolve confirmed in 561.842125ms
  deployer balance post-resolve: 1000000000
=== Golden path complete in 69.63089975s ===

A petition system where signers prove eligibility against an external ResilientIdentity credential root, sign once per petition with cross-petition unlinkability, and produce an outcome verifiable from durable chain state after the dispute window closes. Key properties:

  • Per-petition forward secrecy. Each signer maintains a Forward-Secure Ratchet Tree (FSRT) chain over N = 2^24 slot values. After each finalised signing the slot's seed is overwritten in place; an audit-time device compromise reveals nothing about past signed slots beyond what the prior seed material had already produced.
  • Cross-petition unlinkability. The signer SNARK exposes only (petition_id, slot, class_tag, nullifier, identity_tag). The same RI leaf signs different petitions under distinct slot indices and distinct per-slot ratchet values; no on-chain linkability between two signings of the same signer beyond the predicate-match intersection bound.
  • Operator-free outcome verifiability. After close_at_block + dispute window, anyone can reconstruct the leaf set L from blob bytes, recompute (b, b_per_class), and re-verify the resolution SNARK. The organiser, RI issuer, relayers, and resolver can all go offline; the chain holds enough state to settle the outcome.

@rymnc rymnc self-assigned this May 19, 2026
@rymnc rymnc force-pushed the feat/civic-participation-impl branch 7 times, most recently from 2ba940d to e467df8 Compare May 20, 2026 15:37
@rymnc rymnc marked this pull request as ready for review May 20, 2026 16:05
@rymnc rymnc force-pushed the feat/civic-participation-impl branch 2 times, most recently from e9e41d2 to 7c5e0fc Compare May 21, 2026 21:39

@oskarth oskarth left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left three follow-up comments on the lifecycle/dispute fixes from 019353f.

/// when bountyToken is denominated near 1:1 with ETH (e.g., WETH).
/// For tokens with different decimals the wei-vs-token-unit
/// comparison treats wei numerically; the cap still bounds payout.
function markUnresolved(bytes32 petitionId) external {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we split the resolve and markUnresolved deadlines? Right now both become callable at closeAtBlock + RESOLUTION_DEADLINE_BLOCKS, so a caller can tombstone/refund the petition in the same block range where a valid resolver is first allowed to submit. A short grace period after resolution opens, or separate resolution_open_at / unresolved_at thresholds, would avoid that race.

@rymnc rymnc May 25, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in e7105cd

PetitionRecord storage rec = petitions[dp.petitionId];
_advanceStateOnRead(rec);
// SPEC line 107: dispute only during DisputeWindow.
if (rec.state != PetitionState.DisputeWindow) revert InvalidState();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should disputes also close once resolution opens? The new resolve gate prevents early resolution, but _preflightDispute still accepts disputes for the whole DisputeWindow, including after closeAtBlock + RESOLUTION_DEADLINE_BLOCKS. If the intent is that resolution happens after the dispute period is over, this path probably needs a matching upper-bound check.

@rymnc rymnc May 25, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in e7105cd

if (pi.slot != rec.slot) revert PriorStateMismatch();
}

function _commitBatch(BatchPublicInputs calldata pi) internal {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we revisit how re-publication after rollback is indexed? _cascadeRepudiation resets nextBatchIndex to the disputed index, but _commitBatch still appends the replacement batch to the array while emitting the reset logical index. Later dispute(batchIndex) indexes directly into batches[petitionId], so the emitted index and storage index can diverge after a rollback. Either truncating/superseding the tail or separating logical batch ids from array positions would make this easier to reason about.

@rymnc rymnc May 25, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in e7105cd

Base automatically changed from chore/resilient-civic-participation-spec to master May 25, 2026 04:08
@rymnc rymnc force-pushed the feat/civic-participation-impl branch from 37aba02 to e7105cd Compare May 25, 2026 04:52
@rymnc rymnc requested review from Meyanis95 and oskarth May 25, 2026 04:52

@oskarth oskarth left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not good to go yet. The Solidity lifecycle fixes are present, but the Rust shadow registry still implements the old boundaries.

  • pocs/civic-participation/resilient-civic-participation/src/registry/core.rs:601: dispute only checks PetitionState::DisputeWindow; it never rejects at close_at_block + RESOLUTION_DEADLINE_BLOCKS. SPEC.md:110 and Solidity PetitionRegistry.sol:800-810 now close disputes at the resolution-open block.
  • pocs/civic-participation/resilient-civic-participation/src/registry/core.rs:812: mark_unresolved gates on close_at_block + RESOLUTION_DEADLINE_BLOCKS, but the spec and Solidity now require + MARK_UNRESOLVED_GRACE_BLOCKS before tombstoning/refund. This preserves the old resolver/markUnresolved race in the in-memory state machine.

Please mirror the new lifecycle constants and add Rust regression tests for both boundaries.

Verified locally:

  • forge soldeer install
  • forge test passes: 38/38
  • cargo test --lib passes: 132 passed, 2 ignored

Full cargo test is still blocked locally by missing circuits/signer/target/vk_hash; the harness says to run scripts/generate-verifiers.sh first, and this machine does not have nargo installed.

@oskarth oskarth dismissed their stale review May 25, 2026 11:46

Converting this to a non-blocking suggestion; original review tone/state was too strong.

@oskarth

oskarth commented May 25, 2026

Copy link
Copy Markdown
Collaborator

Suggestion for a follow-up: the Solidity lifecycle changes look reflected in PetitionRegistry.sol, but it may be worth mirroring the same boundaries in the Rust shadow registry too.

In particular:

  • src/registry/core.rs dispute currently checks PetitionState::DisputeWindow, but does not appear to close disputes at close_at_block + RESOLUTION_DEADLINE_BLOCKS, whereas SPEC.md and Solidity now close disputes at the resolution-open block.
  • src/registry/core.rs mark_unresolved currently gates on close_at_block + RESOLUTION_DEADLINE_BLOCKS; the Solidity path now adds the 1-hour grace before unresolved marking.

Would suggest either mirroring those constants/checks in the Rust registry, or adding a short note if the shadow registry is intentionally looser than the contract. Regression tests around those two boundary blocks would make this easier to keep aligned.

@rymnc

rymnc commented May 25, 2026

Copy link
Copy Markdown
Member Author

@oskarth addressed in 625dfa4

@rymnc rymnc requested a review from oskarth May 25, 2026 11:54

@oskarth oskarth left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for mirroring the lifecycle boundaries in the Rust registry and adding regression coverage. The prior follow-up is addressed on my side.

@Meyanis95 Meyanis95 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't read all the code; I mostly checked the circuits, and it looks good. Only comment is on the dispute types (esp. 0x02 and 0x03) that can't be reached as circuit already enforces these conditions, but this does not enable any soundness or completeness bug.

@rymnc rymnc merged commit b2d47a1 into master May 27, 2026
2 checks passed
@rymnc rymnc deleted the feat/civic-participation-impl branch May 27, 2026 18:50
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.

3 participants