Repository navigation
Conversation
WalkthroughAdds 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. ChangesConfidential RWA profile
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
Suggested reviewers: Pre-merge checks:
|
Codecov Report❌ Patch coverage is
📢 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/.
1251f5d to
c5dea2e
Compare
…ng-balance resets
There was a problem hiding this comment.
🔵 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
left a comment
There was a problem hiding this comment.
Reviewed commit 6b66956.
Review — stellar-confidential, confidential-rwa-token-example
-
packages/confidential/src/compliance/test/rwa.rs:2354—forced_destination_to_field_matches_testdata_vectorschecks the contract against vectors that the contract itself produced (circuits/lib/testdata/forced_destination_to_field.jsonsays so). The test cannot catch a wrong derivation, such as a wrong tag, input order, or sponge arity. Every auditor client has to computedest_fto 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-23says every JSON file mirrorsprint_fixturesand namesaddress_to_field.jsonas the only exception.docs/sdk/conformance.md:20still 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 byfixtures_match_testdata. Then listforced_destination_to_fieldin both documents. -
examples/confidential/rwa-token/src/contract.rs:6-7— the module doc saysRwaHooks"runs the freeze, policy, and SAC gates on every party". That is not true for two parties. A frozen address can stillregister, becauseon_registerskips the freeze gate (the PR's testfrozen_address_can_register_but_not_transactconfirms this).on_revoke_spenderdoes not gate the spender. Name these two exceptions, or point to the accurate list in theRwaHooksdoc.
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.
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
ConfidentialTokenentry 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— aHooksimplementation for regulated deployments:deposit,withdraw,confidential_transferandconfidential_transfer_fromadditionally 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, andcan_send/can_receive(aligned with ERC-8424canSend/canReceive).ConfidentialForcedTransfer—forced_transfermoves 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/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).examples/confidential/rwa-tokenwith one role per duty.How compliance is split
A transfer never references the recipient's commitments, so incoming activity still cannot invalidate anyone's proof.
Compared with recipient-side compliance proofs
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
docs/compliance.md#approval-gatestates what such a contract must check. Approval services should issue detached authorization entries only.forced_transferandclawbackproofs 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.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, insrc/compliance/test/rwa.rs).examples/confidential/rwa-token: 22 tests pass.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.cargo +nightly fmt --all -- --check,cargo clippy --release --locked --all-targets -- -D warnings(both crates) andcheck_error_codes.pypass locally.cargo llvm-cov):compliance/mod.rs100%,compliance/storage.rs98.3%.Known gaps
fixtures_match_testdata, passing in thenoir workflow). The inneraddress_to_fieldstep still has no Noir implementation, as before this PR.clawbackkeeps its existing unchecked nonce increment (safe under the workspace'soverflow-checks = true);forced_transferuses a checked one. Aligningclawbacktouches 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