Skip to content

feat: shielded credit pool with Orchard ZK proofs (Medusa)#3076

Draft
QuantumExplorer wants to merge 58 commits intov3.1-devfrom
feat/zk
Draft

feat: shielded credit pool with Orchard ZK proofs (Medusa)#3076
QuantumExplorer wants to merge 58 commits intov3.1-devfrom
feat/zk

Conversation

@QuantumExplorer
Copy link
Member

@QuantumExplorer QuantumExplorer commented Feb 9, 2026

Issue being fixed or feature implemented

Implements the shielded credit pool for Dash Platform (protocol version 12), enabling private value transfers using the Zcash Orchard protocol adapted for Platform's credit system. This is the core privacy feature for Dash Platform, allowing users to shield, transfer, unshield, and withdraw credits without revealing transaction amounts or linkage between sender and receiver.

What was done?

Five new state transition types

Type ID Name Direction Description
15 Shield Transparent → Pool Deposit platform credits into the shielded pool from address balances
16 ShieldedTransfer Pool → Pool Transfer value privately within the pool (ZK proof is the sole authorization)
17 Unshield Pool → Transparent Withdraw credits from the pool to a platform address
18 ShieldFromAssetLock Core L1 → Pool Deposit directly from a Dash Core asset lock
19 ShieldedWithdrawal Pool → Core L1 Withdraw credits from the pool to a Dash Core address

Each transition carries an Orchard bundle: serialized actions, a Halo 2 zero-knowledge proof, and RedPallas signatures. The Orchard circuit proves value conservation (inputs = outputs + value_balance) without revealing individual amounts.

Cryptographic foundation

  • Halo 2 ZK proof system (no trusted setup) for proving note ownership and value conservation
  • Sinsemilla hash-based commitment tree (depth 32) for note commitments, backed by GroveDB's CommitmentTree element type
  • RedPallas re-randomizable Schnorr signatures for spend authorization
  • Platform sighash (SHA-256("DashPlatformSighash" || bundle_commitment || extra_data)) cryptographically binds Orchard bundle data to platform-specific transparent fields
  • Anchor recording at block end — stores commitment tree Merkle roots by block height so clients can reference historical tree states in spend proofs

On-chain state structure

The shielded pool lives under [AddressBalances, "s"] with:

Key Element Type Purpose
[1] CommitmentTree (Sinsemilla, epoch_size=2048) Note commitments (cmx + encrypted note)
[2] NormalTree Nullifiers (spent note markers, prevents double-spend)
[5] SumItem Total pool balance (aggregate credits in the pool)
[6] NormalTree Anchors (block_height → Merkle root for spend proofs)

Nullifier tracking for recent block sync lives under SavedBlockTransactions with expiration-based cleanup.

Validation pipeline

  • Basic structure validation: bundle field checks, action count limits, proof presence, value_balance sign constraints, anchor non-zero check
  • Shielded minimum fee check: stateless validation of value_balance >= min_fee (placed before proof verification to reject underpaying bundles cheaply)
  • ZK proof verification: Halo 2 proof + RedPallas spend auth signatures + binding signature via BatchValidator
  • State validation: anchor exists on-chain, nullifiers not already spent, pool has sufficient notes for outgoing transfers
  • Consensus errors: 12 new error types covering all shielded failure modes (invalid anchor, nullifier reuse, insufficient fee, invalid proof, etc.)

Fee model

Shielded fees are embedded in value_balance and cryptographically bound to the ZK proof:

min_fee = proof_verification_fee (100M) + num_actions × (processing_fee (3M) + storage_fee (312 × 27,400))

The binding signature proves value_balance was not tampered with after bundle construction.

SDK support

  • Rust SDK (rs-sdk): ShieldedTransitionBuilder for constructing all 5 transition types, ClientCommitmentTree for wallet-side note tracking and Merkle witness generation, nullifier sync module for detecting spent notes
  • JavaScript SDK (js-dash-sdk): platform.shielded.shield(), shieldedTransfer(), unshield(), shieldedWithdrawal(), shieldFromAssetLock() methods
  • WASM DPP (wasm-dpp, wasm-dpp2): JavaScript bindings for shielded state transition types and Orchard bundle handling
  • DAPI client (rs-dapi-client): gRPC transport for GetShieldedPoolState, GetShieldedEncryptedNotes, GetShieldedAnchors, GetShieldedNullifiers

gRPC API (platform.proto)

New endpoints for light client syncing:

  • getShieldedPoolState — pool parameters, total balance, note count
  • getShieldedEncryptedNotes — encrypted notes by global position range (with V1 proof support for BulkAppendTree)
  • getShieldedAnchors — historical anchors by block height
  • getShieldedNullifiers — published nullifiers for spent note detection

Platform versioning

  • New DriveAbciStateTransitionProcessingMethodVersions fields: store_nullifiers_to_recent_block_storage, cleanup_recent_block_storage_nullifiers (OptionalFeatureVersion, None for v1-v6, Some(0) for v7)
  • New DriveAbciBlockEndMethodVersions field: record_shielded_pool_anchor (OptionalFeatureVersion)
  • Drive initialization v3 (create_initial_state_structure: 3) creates the shielded pool tree structure
  • New DriveShieldedMethodVersions for nullifier query configuration
  • New DPP serialization versions for all 5 shielded state transition types

Drive operations

  • ShieldedTransitionActionDriveOperation conversion for each transition type
  • DriveOperationFinalizeTask::RecordShieldedAnchor — post-commit callback to record commitment tree anchor
  • Nullifier insertion, duplicate checking, and expiration-based cleanup
  • Estimated cost models for shielded operations (Sinsemilla hash costs, CommitmentTree insert costs)

Documentation

  • docs/SHIELDED_CLIENT_INTEGRATION.md — comprehensive 850+ line guide covering key management, bundle construction, note tracking, light client syncing, trial decryption, and fee model
  • book/src/state-transitions/return-proofs.md — shielded transition proof verification documentation

Packages modified

Package Changes
rs-dpp 5 new state transition types, shielded builder, 12 consensus errors, platform sighash, bundle serialization
rs-drive Shielded pool paths, state transition action conversion, nullifier operations, estimated costs, finalization tasks
rs-drive-abci Validation pipeline (basic + state + ZK proof), anchor recording, nullifier cleanup, strategy tests
rs-platform-version Version fields for all shielded operations across drive, drive-abci, and DPP
dapi-grpc 4 new gRPC endpoints in platform.proto + generated clients (JS, Python, Java, ObjC, Web)
rs-sdk ShieldedTransitionBuilder, ClientCommitmentTree integration, nullifier sync
rs-dapi-client gRPC transport for shielded queries
js-dash-sdk 5 shielded platform methods
wasm-dpp / wasm-dpp2 WASM bindings for shielded types
rs-drive-proof-verifier Shielded pool state proof verification

How Has This Been Tested?

  • Strategy tests covering all 5 shielded transition types: run_chain_shield_transitions, run_chain_shield_from_asset_lock_transitions, run_chain_shielded_transfer_transitions, run_chain_unshield_transitions, run_chain_shielded_withdrawal_transitions
  • All existing strategy tests pass (98 total) — version gating ensures shielded pool operations are no-ops on protocol versions < 12
  • Basic structure validation unit tests for each transition type
  • State validation tests (invalid anchor, duplicate nullifier, insufficient fee, insufficient pool notes)
  • Fee regression tests verified: shielded pool tree adds +51,300 processing credits to AddressBalances Merk operations; protocol v11 fee tests confirm old values remain correct

Breaking Changes

None. The shielded pool is introduced in protocol version 12 and is fully version-gated. All new version fields use OptionalFeatureVersion set to None for protocol versions ≤ 11 and Some(0) for protocol version 12. Existing tests and behavior are unaffected.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

Important

Review skipped

Draft detected.

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6e738148-cb95-466d-a278-4b1f0c64dc33

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
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/zk

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

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

@github-actions github-actions bot added this to the v3.1.0 milestone Feb 9, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 11, 2026

❌ gRPC Query Coverage Report

Total: 61 queries — 58 implemented, 2 ignored, 1 missing

❌ Missing

  • getTokenPreProgrammedDistributions (line 96)
⏭️ Ignored (@sdk-ignore) (2)
  • broadcastStateTransition — Write-only endpoint, not a query
  • getConsensusParams — Consensus params fetched via Tenderdash RPC
✅ Implemented (58)
  • getAddressInfo
  • getAddressesBranchState
  • getAddressesInfos
  • getAddressesTrunkState
  • getContestedResourceIdentityVotes
  • getContestedResourceVoteState
  • getContestedResourceVotersForIdentity
  • getContestedResources
  • getCurrentQuorumsInfo
  • getDataContract
  • getDataContractHistory
  • getDataContracts
  • getDocuments
  • getEpochsInfo
  • getEvonodesProposedEpochBlocksByIds
  • getEvonodesProposedEpochBlocksByRange
  • getFinalizedEpochInfos
  • getGroupActionSigners
  • getGroupActions
  • getGroupInfo
  • getGroupInfos
  • getIdentitiesBalances
  • getIdentitiesContractKeys
  • getIdentitiesTokenBalances
  • getIdentitiesTokenInfos
  • getIdentity
  • getIdentityBalance
  • getIdentityBalanceAndRevision
  • getIdentityByNonUniquePublicKeyHash
  • getIdentityByPublicKeyHash
  • getIdentityContractNonce
  • getIdentityKeys
  • getIdentityNonce
  • getIdentityTokenBalances
  • getIdentityTokenInfos
  • getNullifiersBranchState
  • getNullifiersTrunkState
  • getPathElements
  • getPrefundedSpecializedBalance
  • getProtocolVersionUpgradeState
  • getProtocolVersionUpgradeVoteStatus
  • getRecentAddressBalanceChanges
  • getRecentCompactedAddressBalanceChanges
  • getRecentCompactedNullifierChanges
  • getRecentNullifierChanges
  • getShieldedAnchors
  • getShieldedEncryptedNotes
  • getShieldedNullifiers
  • getShieldedPoolState
  • getStatus
  • getTokenContractInfo
  • getTokenDirectPurchasePrices
  • getTokenPerpetualDistributionLastClaim
  • getTokenStatuses
  • getTokenTotalSupply
  • getTotalCreditsInPlatform
  • getVotePollsByEndDate
  • waitForStateTransitionResult

@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

✅ DashSDKFFI.xcframework built for this PR.

SwiftPM (host the zip at a stable URL, then use):

.binaryTarget(
  name: "DashSDKFFI",
  url: "https://your.cdn.example/DashSDKFFI.xcframework.zip",
  checksum: "e806187988764d3917455f23c91160fe8f5c48c0006adc0a63754ee76a24d0f0"
)

Xcode manual integration:

  • Download 'DashSDKFFI.xcframework' artifact from the run link above.
  • Drag it into your app target (Frameworks, Libraries & Embedded Content) and set Embed & Sign.
  • If using the Swift wrapper package, point its binaryTarget to the xcframework location or add the package and place the xcframework at the expected path.

@QuantumExplorer QuantumExplorer changed the title feat: medusa feat: shielded credit pool with Orchard ZK proofs (Medusa) Mar 4, 2026
… anchors

Add OptionalFeatureVersion dispatch for record_shielded_pool_anchor_if_changed
so it is a no-op on protocol versions < 12 (which lack the shielded pool).
This fixes strategy test panics when running chains starting from older
protocol versions.

Also adds a "Shielded Pool: Anchors and Spend Proofs" chapter to the book
explaining why anchors make ZK spend proofs possible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
QuantumExplorer and others added 9 commits March 6, 2026 15:37
Accept v3.1-dev for dpp (merged feat/zk-dpp changes).
Adapt drive, drive-abci, rs-sdk, and wasm-dpp to match
updated dpp struct fields:
- Remove flags from shielded transitions and OrchardBundleParams
- value_balance: i64 -> amount/unshielding_amount: u64
- Remove user_fee_increase from ShieldFromAssetLock
- Remove builder/ directory (replaced by builder.rs from v3.1-dev)
… hardcoding

Each transition type determines the correct flags byte:
- Shield/ShieldFromAssetLock: FLAGS_OUTPUTS_ONLY (0x02) - dummy spends, real outputs
- Unshield/ShieldedWithdrawal: FLAGS_SPENDS_ONLY (0x01) - real spends, dummy outputs
- ShieldedTransfer: FLAGS_SPENDS_AND_OUTPUTS (0x03) - both real
# Conflicts:
#	packages/rs-dpp/src/shielded/builder/mod.rs
#	packages/rs-dpp/src/shielded/builder/shield.rs
#	packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs
#	packages/rs-dpp/src/shielded/builder/shielded_transfer.rs
#	packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs
#	packages/rs-dpp/src/shielded/builder/unshield.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funds_transfer/tests.rs
#	packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs
#	packages/rs-drive/src/drive/initialization/v3/mod.rs
#	packages/rs-drive/src/drive/shielded/estimated_costs.rs
#	packages/rs-drive/src/drive/shielded/mod.rs
#	packages/rs-drive/src/drive/shielded/prove/mod.rs
#	packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/mod.rs
#	packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/v0/mod.rs
#	packages/rs-drive/src/fees/op.rs
#	packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs
#	packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/mod.rs
#	packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_transfer_transition.rs
#	packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_withdrawal_transition.rs
#	packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs
#	packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs
#	packages/rs-drive/src/state_transition_action/shielded/mod.rs
#	packages/rs-drive/src/state_transition_action/shielded/shield/transformer.rs
#	packages/rs-drive/src/state_transition_action/shielded/shield/v0/transformer.rs
#	packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/transformer.rs
#	packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/v0/transformer.rs
#	packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/transformer.rs
#	packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/v0/transformer.rs
#	packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/transformer.rs
#	packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/v0/transformer.rs
#	packages/rs-drive/src/state_transition_action/shielded/unshield/transformer.rs
#	packages/rs-drive/src/state_transition_action/shielded/unshield/v0/transformer.rs
#	packages/rs-drive/src/util/batch/drive_op_batch/drive_methods/apply_drive_operations/v0/mod.rs
#	packages/rs-drive/src/util/batch/drive_op_batch/finalize_task.rs
#	packages/rs-drive/src/util/batch/drive_op_batch/shielded.rs
#	packages/rs-drive/src/util/grove_operations/grove_get_proved_path_query_v1/v0/mod.rs
#	packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs
#	packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs
#	packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/mod.rs
#	packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs
#	packages/rs-drive/src/verify/shielded/verify_nullifiers_trunk_query/v0/mod.rs
#	packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/mod.rs
#	packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs
#	packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/mod.rs
#	packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/v0/mod.rs
#	packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/mod.rs
#	packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs
#	packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs
Accept rs-drive from v3.1-dev and update drive-abci callers:
- Notes/anchor extraction moved inside drive transformers
- cleanup_expired_nullifiers renamed to cleanup_expired_nullifier_compactions
- Nullifier query return types changed from tuples to structs
- Element::empty_commitment_tree now returns Result

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	Cargo.lock
#	packages/rs-drive-abci/Cargo.toml
#	packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs
#	packages/rs-drive-abci/src/execution/platform_events/block_processing_end_events/mod.rs
#	packages/rs-drive-abci/src/execution/platform_events/block_processing_end_events/record_shielded_pool_anchor/v0/mod.rs
#	packages/rs-drive-abci/src/execution/types/execution_event/mod.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/check_tx_verification/v0/mod.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/basic_structure.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/shielded_proof.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/processor/v0/mod.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shield/tests.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shield_from_asset_lock/tests.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shield_from_asset_lock/transform_into_action/v0/mod.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_transfer/tests.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_transfer/transform_into_action/v0/mod.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_withdrawal/tests.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_withdrawal/transform_into_action/v0/mod.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/test_helpers.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/unshield/tests.rs
#	packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/unshield/transform_into_action/v0/mod.rs
#	packages/rs-drive-abci/src/query/service.rs
#	packages/rs-drive-abci/src/query/shielded/anchors/v0/mod.rs
#	packages/rs-drive-abci/src/query/shielded/mod.rs
#	packages/rs-drive-abci/tests/strategy_tests/strategy.rs
#	packages/rs-drive-abci/tests/strategy_tests/test_cases/mod.rs
#	packages/rs-drive-abci/tests/strategy_tests/test_cases/shielded_tests.rs
#	packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs
#	packages/wasm-dpp2/src/state_transitions/proof_result.rs
Re-enable all shielded code that was commented out on v3.1-dev because
the OperationType shielded variants and dapi-grpc protobuf types were
not available there. On feat/zk these dependencies exist.

- Restore shielded query endpoints in query/service.rs
- Restore shielded module declarations in query/shielded/mod.rs
- Restore ShieldedState full impl, imports, helper methods, and match
  arms in strategy tests
- Fix drive-proof-verifier nullifier type mismatches (struct vs tuple)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	.github/grpc-queries-cache.json
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant