Skip to content

feat(confidential): Noir set_operator circuit#730

Merged
brozorec merged 17 commits into
mainfrom
feat/confidential-set-operator-circuit
May 29, 2026
Merged

feat(confidential): Noir set_operator circuit#730
brozorec merged 17 commits into
mainfrom
feat/confidential-set-operator-circuit

Conversation

@brozorec
Copy link
Copy Markdown
Collaborator

@brozorec brozorec commented May 27, 2026

Summary

  • Implements the SetOperator circuit per design doc §7.7 (issue Noir circuit: SetOperator #707): owner balance split (S1-S4, S9-S11), delegation + allowance escrow with the dvk-handoff ECDH expanded into its three §7.11 sub-constraints (S5-S8, S12, S13), and the owner-auditor block (S_a1-S_a5).
  • 24 public inputs in the design-doc canonical order; constraint count is 128 ACIR / 72 Brillig for main.
  • VK published at vks/set_operator.vk.json via the existing scripts/extract_vks.sh pipeline.

Closes #707.

Notes for reviewers

  • Base is feat/confidential-withdraw-circuit (the parent feature branch), not main. Retarget once the parent merges.
  • Y_op is treated as path-(2) of §10.8 (constrained on-curve at registration R1, trusted from wrapper storage). The wrapper that consumes this VK must load Y_op from the operator's stored spending_key, not from caller input — same doctrine as PVK_B in Transfer.
  • K_aud_s is path-(3) (public-input key) and carries the in-circuit assert_on_curve_non_identity check before S_a2.
  • r_e is shared between the auditor block (S_a1) and the dvk escrow (S12(b)); S12(a) forces escrowed_dvk_r_x == R_e.x so the on-chain BytesN<64> escrowed_dvk encoding (R_x ‖ dvk_cipher, §7.11) cannot drift from the auditor-block ephemeral.
  • assert(r_e != 0) runs first in main to reject the trivial-escrow / identity-collapse case before any scalar_mul against r_e.

Test plan

  • nargo test --package circuit_set_operator — 26 tests pass (happy path + full_allowance_escrow boundary + 18 isolated negative tests covering every constraint S1-S13 + S_a1-S_a5 including both escrowed_dvk limbs, on-curve / identity rejection for K_aud_s, r_e = 0, and wrong op_i / Y_op / wrap / sk)
  • nargo test (workspace) green
  • LC_ALL=C nargo info | grep '^|' | LC_ALL=C sort matches the updated constraints.baseline
  • bash scripts/extract_vks.sh regenerates vks/set_operator.vk.json byte-identically

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added SetOperator circuit enabling new confidential token operations with per-operator allowance management, balance verification, and secure escrowed key handling capabilities.
  • Tests

    • Established comprehensive test suite validating constraint enforcement, boundary conditions, edge case handling, tamper-detection scenarios, and multiple failure modes to ensure circuit robustness, security integrity, and reliable operation across confidential token workflows.

Review Change Stack

brozorec added 13 commits May 22, 2026 12:02
Replaces the per-plaintext encrypt_auditor_tx / encrypt_auditor_bal
helpers with the per-channel Poseidon2 sponge described in design doc
v0.5 -> v0.6 (Section 2.5, Section 8.1). One absorb of
(delta_channel, S.x, sigma) is followed by N squeezes from the same
permutation; rate=3 lets every current call serve all squeezes without
a second permutation. AUDITOR_TX and AUDITOR_BALANCE domain tags are
dropped in favour of AUDITOR_SENDER (delta_aud_s) and AUDITOR_RECIPIENT
(delta_aud_r) at the same numeric slots (10, 11).

Adds the sponge_squeeze_2 gadget for nargo info accounting and pins
the new sponge outputs in testdata/sponge_squeeze_{1,2}.json. The
constraints baseline gains one row; no existing primitive's output
changed.
Implements the spend-side block of the Withdraw circuit from design doc
Section 7.5: owner key ownership (W1), wrapper-bound viewing key (W2),
spendable balance opening (W3), range validity on v, a, and v - a (W4),
deterministic randomness for the new commitment (W5), the refreshed
spendable commitment (W6), and the encrypted balance scalar (W7).

The auditor block (W_a1-W_a5, K_aud_s + r_e + R_e + b_tilde_aud_s) lands
in a follow-up commit so this one stays focused on balance conservation
and key ownership; once that lands the public input signature grows
from 10 to the design doc's frozen 15 fields.

Tests reuse the lib's pinned (sk, wrap, sigma, v, r) fixtures so any
drift in commit / vk_from_sk / encrypt_balance / derive_spend_r is
caught by `withdraw_fixtures_match_lib` before the rest of the harness
runs. Negative coverage: under-funded withdrawal (W4), v or a out of
range, wrong sk (W1), wrong balance opening (W3), wrong wrap (W2/W6/W7
chain), tampered b_tilde (W7), tampered C_spend' (W6). VK extraction
script and constraints baseline updated.
Adds the sender-auditor visibility block to the Withdraw circuit
(design doc Section 7.5, Section 8.1): the ephemeral key R_e = r_e * H
(W_a1), the sender-auditor ECDH shared secret S_{a,s} = r_e * K_{aud,s}
(W_a2), the single-squeeze sender-channel sponge mask m_b (W_a3), the
sender-auditor balance checkpoint ciphertext b_tilde_aud_s = (v - a) +
m_b (W_a4), and the r_e != 0 non-collapse constraint (W_a5).

The public input signature grows from 10 to the design-doc-frozen 15
fields, in canonical order (C_spend, Y, wrap, K_aud_s, a, C_spend',
sigma, b_tilde, R_e, b_tilde_aud_s). K_aud_s carries an explicit
on-curve + non-identity check at the entrypoint because the verifier
doesn't check curve membership and an off-curve K_aud_s would break the
soundness of W_a2 (Section 10.8).

`encrypt_auditor_sender_balance` lands in the lib as the canonical
W_a3+W_a4 composition; the same helper covers RevokeOperator V_a3/V_a5
and SetOperator S_a3/S_a5 when those circuits land. Pinned via testdata
and a lib round-trip test that ties it back to sponge_squeeze_1.

Auditor-side negative coverage: r_e = 0 (W_a5), wrong r_e (W_a1),
off-curve K_aud_s, identity K_aud_s, valid-but-wrong K_aud_s, tampered
b_tilde_aud_s (W_a4). Closes the under-funded-withdrawal + tampered-
ciphertext criteria from issue #705.
VK extracted via `bash scripts/extract_vks.sh` after the Withdraw circuit
landed in the workspace. Same UltraHonk universal-SRS pipeline as the
register VK -- no new trusted-setup contribution required. The committed
JSON is the integration contract with the on-chain verifier (#701); CI
re-runs the extraction script and diffs against this copy.
Aligns W4 with design doc §7.5 / §2.6 / §3.4: the value domain is the
SEP-41 non-negative i128 range [0, 2^127), not [0, 2^128). The circuit
now uses Noir stdlib's `Field::assert_max_bit_size::<127>()` directly
(the 127-bit decomposition spelled out in §2.6) instead of the lib's
u128-cast helper. Doc comments for W4 and the public-input table for
`a` are restated against the tighter bound and reference §3.4 for the
entrypoint-level `a >= 0` check.

Test boundaries retarget to 2^127 (the smallest value W4 must reject),
not 2^128. Withdraw VK regenerated; constraint count drops from 130 to
92 ACIR opcodes (stdlib's assert_max_bit_size is cheaper than the
u128 round-trip).

The lib's `assert_range_128` primitive is now unused by Withdraw but
stays in place for backwards compat -- a separate cleanup can remove
it once no callers reference it.
The test previously fired W3 (commit(v_huge, R) != stale C_SPEND) before
W4 ever ran, so the range check was never the load-bearing assertion.
Recompute C_spend from v_huge so W3 passes and the very first W4 line
(v.assert_max_bit_size::<127>()) becomes the failing constraint.

rejects_a_out_of_range already isolates W4 (V is unchanged, W3 passes,
W4 hits a.assert_max_bit_size::<127>() on a_huge = 2^127) so it is left
as-is.
The design doc added delta_addr = 1 (§2.7), shifting every other tag up
by one. Realign the lib constants, re-pin all Poseidon-derived fixtures
and testdata, regenerate VKs, and fix a stale W_a5 label in the withdraw
test (now W8).
Implements the SetOperator circuit per design doc §7.7 (issue #707):
owner balance split (S1-S4, S9-S11), delegation + allowance escrow with
the dvk-handoff ECDH expanded into its three §7.11 sub-constraints
(S5-S8, S12, S13), and the owner-auditor block (S_a1-S_a5). 24 public
inputs in the design-doc canonical order.

Tests cover the happy path, the v_a = v boundary, S4 underflow / range
overflow, the wrong op_i case, wrong Y_op (S12(b) via the cipher),
every tampered ciphertext including both escrowed_dvk limbs, r_e = 0
(S13), and on-curve / non-identity rejection for K_aud_s.
Pastes the values emitted by `print_fixtures` into the tests.nr globals and
adds circuit_set_operator's row to constraints.baseline (128 ACIR opcodes,
72 Brillig). `set_operator_fixtures_match_lib` and
`set_operator_auditor_fixtures_match_lib` re-derive the same values from the
lib primitives so a future lib drift fails the test immediately.
VK extracted via `bash scripts/extract_vks.sh` after the SetOperator circuit
landed in the workspace. Same UltraHonk universal-SRS pipeline as the
register / withdraw / transfer VKs -- no new trusted-setup contribution
required. The committed JSON is the integration contract with the on-chain
verifier (#701); CI re-runs the extraction script and diffs against this
copy.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 85030e85-e975-406a-9aba-990f09c2dc0c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/confidential-set-operator-circuit

Comment @coderabbitai help to get the list of available commands and usage tips.

@brozorec brozorec self-assigned this May 27, 2026
@brozorec brozorec changed the title feat(confidential): SetOperator Noir circuit (#707) feat(confidential): Noir set_operator circuit May 27, 2026
@brozorec brozorec linked an issue May 27, 2026 that may be closed by this pull request
5 tasks
@codecov
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.71%. Comparing base (0c9e3fd) to head (5ee2ea1).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #730   +/-   ##
=======================================
  Coverage   96.71%   96.71%           
=======================================
  Files          67       67           
  Lines        6798     6798           
=======================================
  Hits         6575     6575           
  Misses        223      223           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/tokens/src/confidential/circuits/set_operator/src/tests.nr`:
- Line 117: The test contains a typo variable name "yoy" assigned from y_op.y
which breaks the CI fixture-check; rename the variable to the intended name
(e.g., "y" or "y_op_y") in the assignment (replace "let yoy = y_op.y" in the
test) and update all its usages (e.g., the "{yoy}" occurrence in the fixture
printer) to the new identifier so the fixture token check passes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e093e8a0-1944-4afd-b8ff-046f54790b44

📥 Commits

Reviewing files that changed from the base of the PR and between 1400304 and 29b1327.

📒 Files selected for processing (7)
  • packages/tokens/src/confidential/circuits/Nargo.toml
  • packages/tokens/src/confidential/circuits/constraints.baseline
  • packages/tokens/src/confidential/circuits/scripts/extract_vks.sh
  • packages/tokens/src/confidential/circuits/set_operator/Nargo.toml
  • packages/tokens/src/confidential/circuits/set_operator/src/main.nr
  • packages/tokens/src/confidential/circuits/set_operator/src/tests.nr
  • packages/tokens/src/confidential/circuits/vks/set_operator.vk.json

Comment thread packages/tokens/src/confidential/circuits/set_operator/src/tests.nr Outdated
`yoy` (Y_op.y in print_fixtures) trips the workflow-pinned typos ruleset,
which flags it as a misspelling of `you`. Rename `yox` / `yoy` to
`yopx` / `yopy` to match the pattern (Y_op-x, Y_op-y) and avoid the
substring match. Test-only change; no circuit semantics affected.
@brozorec brozorec requested a review from bidzyyys May 27, 2026 09:31
Base automatically changed from feat/confidential-withdraw-circuit to main May 27, 2026 12:49
Copy link
Copy Markdown

@bidzyyys bidzyyys left a comment

Choose a reason for hiding this comment

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

Looks good!

brozorec added 2 commits May 29, 2026 11:31
CI runs LC_ALL=C sort to normalize nargo info output; the baseline rows
were in insertion order instead of sort order, so check-test-info failed
after the set_operator circuit was added.
@brozorec brozorec merged commit 4ffa219 into main May 29, 2026
10 checks passed
@brozorec brozorec deleted the feat/confidential-set-operator-circuit branch May 29, 2026 09:58
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.

Noir circuit: SetOperator

2 participants