Skip to content

feat(confidential): RWA compliance profile (designated auditor, approval gate, forced transfer) - #976

Open
bidzyyys wants to merge 5 commits into
mainfrom
feat/confidential-rwa-compliance
Open

bidzyyys wants to merge 5 commits into
mainfrom
feat/confidential-rwa-compliance

Conversation

@bidzyyys

@bidzyyys bidzyyys commented Nov 13, 2026 •

Copy link
Copy Markdown

Summary

Adds an opt-in RWA compliance profile to the confidential token. It lets an issuer of a regulated asset keep balances and amounts hidden from the public while enforcing compliance, without a new circuit and without changing any ConfidentialToken entry point.

This is a design alternative to the "Recipient-Side Compliance Proofs" proposal, opened for comparison and discussion. No issue is linked on purpose.

What it adds

  • RwaHooks — a Hooks implementation for regulated deployments:
    • fails closed until both the compliance configuration and the RWA configuration are set, and the eligibility gate (policy or SAC passthrough) cannot be removed;
    • binds every account to one designated auditor, fixed at first configuration;
    • applies freeze, policy and SAC gates to every party, including the spender;
    • optional approval gate: when an approver is configured, deposit, withdraw, confidential_transfer and confidential_transfer_from additionally require its authorization (require_auth_for_args) over a contract-managed approval epoch, the parties and the operation's payload. The approver cannot be a party to the operation.
  • ConfidentialRwa — set_rwa_config, bump_approval_epoch, rwa_config, approval_epoch, and can_send / can_receive (aligned with ERC-8424 canSend / canReceive).
  • ConfidentialForcedTransfer — forced_transfer moves a public amount from a frozen account to another confidential account inside the pool. It reuses the audited clawback proof and verification key; the destination public input carries its own domain tag (18), and both entry points share the clawback nonce, so a proof executes once and in one mode. Recovery of a lost account is a full-balance forced transfer.
  • Docs — docs/compliance.md (RWA Profile: Designated Auditor, Approval Gate, Forced Transfer), docs/rwa.md (integrator guide and ERC-8424 / ERC-3643 / SEP-8 mapping), protocol tables (domain separators, wallet state, indexer).
  • Example — examples/confidential/rwa-token with one role per duty.

How compliance is split

Rule Where it runs
Eligibility, freeze On-chain, on public addresses
Balance and volume rules (caps, limits, lockups) Issuer's compliance service, which holds the designated auditor key: monitored and remediated (no approver), or enforced before settlement (approver configured)

A transfer never references the recipient's commitments, so incoming activity still cannot invalidate anyone's proof.

Compared with recipient-side compliance proofs

Recipient-side proofs This PR
Rule covered Per-account cap Any rule the compliance service evaluates, per identity, including allowances
Online party Recipient's auditor per capped transfer None without an approver; the issuer's service with one
Concurrent inbound transfers One per recipient state Not limited by recipient state
New circuit / second proof Yes No
Trust for the cap Proven Approver trusted for correctness

Where the other proposal is better: the cap is proven and holds against the issuer's own service. This PR accepts an integrity assumption on the approver because that party is the issuer, who already holds freeze and seizure powers.

Security notes

  • Approval never replaces the operation's own authorizer or its proof. An approver cannot move, create or destroy value.
  • A contract approver that itself invokes the token is authorized by the host without the approval arguments being matched; docs/compliance.md#approval-gate states what such a contract must check. Approval services should issue detached authorization entries only.
  • forced_transfer and clawback proofs have the auditor's secret key as witness. The verifier accepts only non-zero-knowledge proofs today, so neither should be used on a public network until that changes. Nothing on-chain enforces this.
  • The only change to already audited logic: the clawback public-input assembly is extracted into a private helper shared with forced_transfer. The bytes are unchanged; the existing clawback tests, including the blob-binding replay test, pass.

Testing

  • packages/confidential: 278 tests pass (159 new, in src/compliance/test/rwa.rs). examples/confidential/rwa-token: 22 tests pass.
  • Approval-gate tests run with enforced authorization (mock_auths), with hand-built argument tuples and negatives for a missing approver entry, another signer, wrong or swapped parties, amount, payload, epoch and function name.
  • Each of the 22 invariants of the design has at least one test that fails when it is broken; a mutation pass over the highest-risk lines killed 51 of 52 mutants (the survivor needs a Poseidon2 output of zero).
  • cargo +nightly fmt --all -- --check, cargo clippy --release --locked --all-targets -- -D warnings (both crates) and check_error_codes.py pass locally.
  • Line coverage of the changed files (local cargo llvm-cov): compliance/mod.rs 100%, compliance/storage.rs 98.3%.

Known gaps

  • The verifier is mocked, as elsewhere in the package. Mode separation between forced transfer and clawback is tested on the assembled public inputs and with a blob-binding mock, not with real proofs.
  • The test vectors for the new destination hash are checked on the contract side in Rust and, for their Poseidon2 step, independently in the Noir library (fixtures_match_testdata, passing in the noir workflow). The inner address_to_field step still has no Noir implementation, as before this PR.
  • No test drives a contract approver that invokes the token directly; that path is a documented limit of the approval gate.
  • clawback keeps its existing unchecked nonce increment (safe under the workspace's overflow-checks = true); forced_transfer uses a checked one. Aligning clawback touches audited code and is left to maintainers.

How this was produced

Research, design, implementation, tests, docs and reviews were produced with AI (Claude and OpenAI Codex) following the Dev3 Soroban workflow, as a timed comparison with a human-led design. The design went through four Codex review rounds, three adopter-role red-team rounds and a fresh-eyes regression pass before coding; the code went through soroban-pr-review (twice, the second time on this PR), soroban-basic-review, soroban-btt, two Codex review rounds and a final whole-feature review. None found a defect in the production code; their test and documentation findings are addressed in the follow-up commits. It still needs human review line by line.

PR Checklist

  • Tests
  • Documentation

@coderabbitai

coderabbitai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

Walkthrough

Adds an opt-in RWA compliance profile to the confidential token package. The profile includes designated-auditor checks, eligibility and approver gates, and forced transfers between confidential accounts. The change also adds protocol documentation and an RWA token example with integration tests.

Changes

Confidential RWA profile

Layer / File(s) Summary
RWA interfaces and operation gates
packages/confidential/src/compliance/*, packages/confidential/src/compliance/test/mod.rs, packages/confidential/docs/compliance.md, packages/confidential/docs/rwa.md, packages/confidential/docs/README.md, packages/confidential/README.md, packages/confidential/CLAUDE.md
Adds ConfidentialRwa and RwaHooks configuration and operation checks. The profile supports designated-auditor binding, eligibility queries, optional approver authorization, and approval epochs. Documentation describes its configuration and integration.
Forced-transfer proof and settlement
packages/confidential/src/compliance/storage.rs, packages/confidential/src/storage.rs, packages/confidential/src/lib.rs, packages/confidential/circuits/clawback/src/main.nr, packages/confidential/circuits/lib/testdata/*, packages/confidential/docs/protocol/*, packages/confidential/docs/indexer.md, packages/confidential/docs/sdk/*, packages/confidential/CLAUDE.md
Adds forced-transfer destination encoding and proof handling. A forced transfer moves commitments from a frozen source to an eligible registered recipient, increments the shared nonce, and emits an event. Protocol and wallet documentation covers event attribution and recovery.
RWA token example and tests
examples/confidential/README.md, examples/confidential/rwa-token/*
Adds RwaTokenContract with role-guarded compliance, RWA, clawback, and forced-transfer entry points. Tests cover configuration, role separation, eligibility, approver authorization, epoch changes, and forced transfers.

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant RwaTokenContract
  participant ComplianceStorage
  participant SorobanEvents
  Operator->>RwaTokenContract: Call forced_transfer with source, destination, amount, and data
  RwaTokenContract->>ComplianceStorage: Forward transfer fields and decoded proof
  ComplianceStorage->>ComplianceStorage: Validate source, recipient, proof, commitments, and nonce
  ComplianceStorage->>SorobanEvents: Emit ForcedTransfer
  ComplianceStorage-->>RwaTokenContract: Return after settlement
Loading

Suggested reviewers: brozorec

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title names the feature (RWA compliance profile) and its three main parts: designated auditor, approval gate, and forced transfer. The changeset adds these in packages/confidential (RwaHooks, Conf…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed The pull request description is complete and relevant. It explains the RWA compliance profile, implementation scope, security considerations, testing, known gaps, and documentation changes. The Tests …
Full details: Title check

Explanation

The title names the feature (RWA compliance profile) and its three main parts: designated auditor, approval gate, and forced transfer. The changeset adds these in packages/confidential (RwaHooks, ConfidentialRwa, ConfidentialForcedTransfer), the rwa-token example, and the docs. The title is specific and matches the main change. It is slightly long at 95 characters, but this does not make it unclear.

Full details: Description check

Explanation

The pull request description is complete and relevant. It explains the RWA compliance profile, implementation scope, security considerations, testing, known gaps, and documentation changes. The Tests and Documentation checklist items are complete. No issue is linked, but the author explicitly explains that this is intentional.

Finishing Touches

Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Review coverage is incomplete: 1 file could not be fully reviewed. Findings from completed review steps are included; see review info for details.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

A rabbit checks the auditor’s name,
Then guards each transfer in the game.
A frozen source can send anew,
With proof and nonce carried through.
The ledger marks the path it drew.

Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 87e92039-70e9-4493-9ed2-3f5dc843ae51
Commits

Reviewing files that changed from the base of the PR and between c98fb34 and 6b66956.

Files ignored due to path filters: 1

  • Cargo.lock is excluded by !**/*.lock
Files selected for processing: 24

  • examples/confidential/README.md
  • examples/confidential/rwa-token/Cargo.toml
  • examples/confidential/rwa-token/src/contract.rs
  • examples/confidential/rwa-token/src/lib.rs
  • examples/confidential/rwa-token/src/test.rs
  • packages/confidential/CLAUDE.md
  • packages/confidential/README.md
  • packages/confidential/circuits/clawback/src/main.nr
  • packages/confidential/circuits/lib/testdata/forced_destination_to_field.json
  • packages/confidential/docs/README.md
  • packages/confidential/docs/compliance.md
  • packages/confidential/docs/indexer.md
  • packages/confidential/docs/protocol/domain-separators.md
  • packages/confidential/docs/protocol/interface.md
  • packages/confidential/docs/protocol/wallet-state.md
  • packages/confidential/docs/rwa.md
  • packages/confidential/docs/sdk/crypto-core.md
  • packages/confidential/docs/sdk/wallet.md
  • packages/confidential/src/compliance/mod.rs
  • packages/confidential/src/compliance/storage.rs
  • packages/confidential/src/compliance/test/mod.rs
  • packages/confidential/src/compliance/test/rwa.rs
  • packages/confidential/src/lib.rs
  • packages/confidential/src/storage.rs
Files not fully reviewed because they exceed a per-file size limit: 1

  • packages/confidential/src/compliance/test/rwa.rs

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

@codecov

codecov Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.96373% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/confidential/src/compliance/storage.rs 98.92% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

…val gate, forced transfer)

Adds an opt-in RWA profile to the confidential token's compliance module:

- RwaHooks: fail-closed hooks that bind every account to a designated
  auditor, gate every party (including the spender), and, when an approver
  is configured, require its authorization for every value-moving
  operation, bound to a contract-managed approval epoch and the operation's
  payload. The approver cannot be a party to the operation.
- ConfidentialRwa: configuration, epoch bump, can_send / can_receive.
- ConfidentialForcedTransfer: moves a public amount from a frozen account
  to another confidential account, reusing the clawback proof with a
  domain-separated destination input (tag 18) and the shared clawback nonce.
- docs/compliance.md (RWA Profile), docs/rwa.md (integrator guide), protocol
  doc tables, and the rwa-token example.

No circuit, verification key or core entry point changes. Existing
deployments are unaffected.
…nce tests under test/

- Add the tests proposed by a branching-tree pass: missing approver entry
  for withdraw and delegated transfers, exact tuple signed by another
  address or under another function name, SAC gate on the sending side,
  failing policy call, order of checks, rejected setters leaving state
  unchanged, forced-transfer bindings to source account and amount.
- Withdraw to a distinct recipient and swapped-party negatives for every
  approval tuple.
- Pin the token and amount public-input fields of a forced transfer.
- Move compliance tests to compliance/test/{mod.rs,rwa.rs}.
- Example: role holders must authorize; frozen investor is immobile.
…ry fold

Follow-up to the RWA profile after a final whole-feature review:

- interface.md: ForcedTransfer in the event schema and the fold lists.
- indexer.md, sdk/wallet.md, sdk/crypto-core.md: a ForcedTransfer naming
  the account as its source is a proofless fold and a recovery anchor,
  like Clawback.
- clawback circuit header comment: the forced-transfer derivation of the
  destination input (comment only; no constraint change).
- README.md: point to the RWA profile and docs/rwa.md.
- ClawbackNonce docs: the nonce counts seizures and forced transfers.
- CLAUDE.md: compliance tests now live under src/compliance/test/.
@bidzyyys
bidzyyys force-pushed the feat/confidential-rwa-compliance branch from 1251f5d to c5dea2e Compare November 13, 2026 13:00
@bidzyyys
bidzyyys marked this pull request as ready for review November 13, 2026 13:06
@bidzyyys
bidzyyys requested review from catalina-oz and a balanced review from Copilot November 13, 2026 13:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Security-sensitive circuit reuse with unconstrained destination inputs requires specialist cryptographic review and independent real-proof validation beyond the existing mocked-verifier coverage.

No open issues 🎉

What changed in this PR

Adds an opt-in real-world asset (RWA) compliance profile to the confidential token library, supporting regulated deployments without changing core token entry points or adding a circuit.

Changes:

  • Adds designated-auditor checks, eligibility gates, and optional approval authorization with epoch-based invalidation.
  • Adds forced transfers between confidential accounts, reusing the clawback circuit with a separate destination domain and shared replay nonce.
  • Provides a role-separated example, tests, and integration guidance for issuers, wallets, approval services, and indexers.
File Description
packages/​confidential/​src/​storage.rs Adds forced-destination hashing and exposes the internal receiving-credit helper.
packages/​confidential/​src/​lib.rs Defines the forced-transfer destination domain tag.
packages/​confidential/​src/​compliance/​test/​mod.rs Includes the RWA test module.
packages/​confidential/​src/​compliance/​storage.rs Implements RWA configuration, approval epochs, eligibility queries, and forced transfers.
packages/​confidential/​src/​compliance/​mod.rs Adds RWA traits, hooks, errors, and events.
packages/​confidential/​README.md Introduces the profile and links its documentation.
packages/​confidential/​docs/​sdk/​wallet.md Documents forced-transfer effects on spendable blindings.
packages/​confidential/​docs/​sdk/​crypto-core.md Updates blinding-fold guidance and the domain-tag count.
packages/​confidential/​docs/​rwa.md Adds deployment, approval-service, recovery, and standards-mapping guidance.
packages/​confidential/​docs/​README.md Indexes the new guide and expanded compliance specification.
packages/​confidential/​docs/​protocol/​wallet-state.md Defines forced-transfer accumulator updates and recovery anchors.
packages/​confidential/​docs/​protocol/​interface.md Documents the forced-transfer event and wallet handling.
packages/​confidential/​docs/​protocol/​domain-separators.md Specifies destination domain tag 18.
packages/​confidential/​docs/​indexer.md Adds forced-transfer attribution, replay, and retention requirements.
packages/​confidential/​docs/​compliance.md Specifies the RWA profile, authorization rules, and forced-transfer flow.
packages/​confidential/​CLAUDE.md Updates the package map, fixture guidance, and documentation ownership notes.
packages/​confidential/​circuits/​lib/​testdata/​forced_destination_to_field.json Adds destination-hash regression vectors.
packages/​confidential/​circuits/​clawback/​src/​main.nr Updates comments describing the destination input; circuit logic is unchanged.
examples/​confidential/​rwa-token/​src/​test.rs Adds configuration, role, approval, freeze, and recovery tests.
examples/​confidential/​rwa-token/​src/​lib.rs Defines the example crate and test module.
examples/​confidential/​rwa-token/​src/​contract.rs Wires RWA hooks and role-protected administrative entry points.
examples/​confidential/​rwa-token/​Cargo.toml Declares the example crate and dependencies.
examples/​confidential/​README.md Lists the RWA example as an alternative deployment.
Cargo.lock Records the new example package and its dependencies.

Review effort: Balanced

@catalina-oz catalina-oz 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.

Reviewed commit 6b66956.

Review — stellar-confidential, confidential-rwa-token-example

  • packages/confidential/src/compliance/test/rwa.rs:2354 — forced_destination_to_field_matches_testdata_vectors checks the contract against vectors that the contract itself produced (circuits/lib/testdata/forced_destination_to_field.json says so). The test cannot catch a wrong derivation, such as a wrong tag, input order, or sponge arity. Every auditor client has to compute dest_f to build a forced-transfer proof, so a contract that drifts from the spec would reject every real forced transfer, and no test would notice. The new fixture is also missing from the documents that list cross-client vectors. circuits/lib/testdata/README.md:21-23 says every JSON file mirrors print_fixtures and names address_to_field.json as the only exception. docs/sdk/conformance.md:20 still says "Three derivations" are computed outside the core circuits. Reproduce the two vectors with an implementation other than the contract, for example a Noir test that hashes [18, <address_to_field vector output>] with the library's Poseidon2 and is covered by fixtures_match_testdata. Then list forced_destination_to_field in both documents.
  • examples/confidential/rwa-token/src/contract.rs:6-7 — the module doc says RwaHooks "runs the freeze, policy, and SAC gates on every party". That is not true for two parties. A frozen address can still register, because on_register skips the freeze gate (the PR's test frozen_address_can_register_but_not_transact confirms this). on_revoke_spender does not gate the spender. Name these two exceptions, or point to the accurate list in the RwaHooks doc.

Checked: 6 product files, 3 test files. Not run: reproduction (no finding needed one); network-limits.sh (the approval gate adds an authorization entry to each value-moving call, and its cost on top of proof verification was not measured). Unavailable: openzeppelin-platform.

…oir library

Addresses the review on the pull request:

- circuits/lib: add the FORCED_DESTINATION domain tag (18) and recompute
  both forced_destination_to_field vectors in print_fixtures and
  fixtures_match_testdata from the address_to_field outputs, so the
  vectors no longer rest on the contract implementation alone.
- Rust: pin the contract's Poseidon2 to the library's
  poseidon_with_domain fixture.
- testdata/README.md, docs/sdk/conformance.md, domain-separators.md,
  CLAUDE.md: list the new fixture and its guards.
- rwa-token example: name the two gate exceptions of RwaHooks.
Copilot AI balanced review requested due to automatic review settings November 13, 2026 13:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The new privileged settlement mode requires human security review of clawback-key reuse, mode separation, and authorization paths because mocked verifiers cannot establish real-proof security.

No open issues 🎉

Review effort: Balanced

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