Skip to content

feat: add Zcash Shielded Assets (ZSA) support (draft)#10628

Draft
oxarbitrage wants to merge 577 commits into
v4.2.0-devfrom
zsa-support
Draft

feat: add Zcash Shielded Assets (ZSA) support (draft)#10628
oxarbitrage wants to merge 577 commits into
v4.2.0-devfrom
zsa-support

Conversation

@oxarbitrage

@oxarbitrage oxarbitrage commented May 25, 2026

Copy link
Copy Markdown
Contributor

Closes #10883

Credit: This PR contains the work of @dmidem (Dmitry Demin) and the QED-it team. It was re-opened from #10618 (originally submitted from the QED-it/zebra fork) so that CI can run within the ZcashFoundation org. All commits preserve their original authorship.

Draft notice: This PR is intentionally opened as a draft. It targets the v4.2.0-dev base branch agreed with the Zebra maintainers rather than main, since upstream Zebra has continued to diverge from QED-it's long-running zsa1 fork. It also carries a few items that are not intended to land as-is:

  • Cargo dependency patches that point to QED-it's forks of orchard and librustzcash (the ZSA-capable versions of those crates are not yet merged into their respective upstreams).
  • Some CI configuration files used for internal testing that should be removed before any final merge.

This PR is submitted for review and discussion, not for immediate merge.

Motivation

This PR adds draft Zebra support for Zcash Shielded Assets (ZSA / OrchardZSA), following the current NU7 candidate ZIPs 226, 227, 230, and 246.

ZSA extends the Orchard protocol to support custom shielded assets that can be issued, transferred, and burned within the Orchard pool using transaction V6. Supporting ZSA in Zebra requires:

  • parsing and validating the new V6 transaction format;
  • verifying OrchardZSA Halo2 proofs and issuance bundle signatures;
  • tracking issued-asset state across the non-finalized and finalized chain;
  • and exposing that state via a new RPC endpoint.

Solution

The new V6/ZSA consensus, state, and RPC paths are gated behind zcash_unstable = "nu7" and tx_v6, so they are not enabled in normal builds without those flags.

zebra-chain

Transaction V6

  • Adds Transaction::V6 with a ZSA Orchard bundle, an optional IssueData (issuance bundle), and a zip233_amount field (ZIP 233 network sustainability amount).
  • Implements serialization, deserialization, sighash, and TXID computation for V6.
  • Adds TX_V6_VERSION_GROUP_ID.

ShieldedDataFlavor abstraction

  • Introduces a ShieldedDataFlavor trait to unify V5 (Orchard vanilla) and V6 (OrchardZSA) shielded data handling. Action, AuthorizedAction, and ShieldedData are now generic over a Flavor.
  • OrchardVanilla and OrchardZSA are concrete flavor types with different associated EncryptedNote sizes (580 vs 612 bytes, per ZIP 230) and burn types (NoBurn vs Burn).
  • Adds the ENABLE_ZSA flag (bit 2 of the Orchard flags field), valid only in V6.

orchard_zsa module (new)

  • issuance: wraps orchard::IssueBundle<Signed> as IssueData, with serialization and note-commitment iteration.
  • burn: defines BurnItem, Burn, NoBurn, and compute_burn_value_commitment (for the adjusted value balance check with burned assets).
  • asset_state: defines AssetState (wrapping orchard::AssetRecord), IssuedAssetChanges, and AssetStateError. IssuedAssetChanges::validate_and_get_changes runs verify_issue_bundle and validate_bundle_burn, and is the single entry point for consensus-level asset state updates.

Other changes

  • ValueCommitment::new extended to accept an AssetBase for ZSA value commitments.
  • orchard_note_commitments() iterators changed to yield values by copy (required by the flavored generics).
  • zcash_primitives::consensus::BlockHeightzcash_protocol::consensus::BlockHeight import update.

zebra-consensus

  • Restricts V6 transactions to NU7+ and validates the version group ID.
  • Verifies OrchardZSA Halo2 proofs using ZSA-specific verifying keys.
  • Adds ENABLE_ZSA flag gating and rejects coinbase transactions that set it.
  • Collects per-transaction sighashes during async block verification and reassembles them in block order, emitting them as transaction_sighashes in SemanticallyVerifiedBlock. This is required because issuance bundle signature verification is keyed on the sighash, which must be available at state-commit time.
  • Adds a zebra-consensus/src/orchard_zsa.rs module with ZSA-specific consensus checks and tests.
  • Extends the ZIP-317 fee calculation with the ZSA asset-creation cost term (nAssetCreations), using a conservative placeholder while the upstream librustzcash / orchard APIs are still being finalized.

zebra-state

  • Propagates IssuedAssetChanges from SemanticallyVerifiedBlock through the state write path.
  • Extends Chain (non-finalized state) to accumulate and roll back issued-asset changes per block.
  • Adds an orchard_issued_assets column family to the RocksDB-backed finalized state, persisting asset state keyed by AssetBase.
  • Adds a ReadRequest::AssetState { asset_base, include_non_finalized } / ReadResponse::AssetState(Option<AssetState>) pair, with a read::asset_state() helper that merges non-finalized and finalized views.
  • Updates database schema snapshots to reflect the new column family.

zebra-rpc

  • Adds a getassetstate RPC method (declared unconditionally in the Rpc trait; returns an error when tx_v6 is disabled) accepting a hex-encoded asset base and an optional include_non_finalized flag.
  • Adds V6-specific fields to getrawtransaction (verbosity 1) and getblock (verbosity 2) output: zip233amount, burnexists, issuanceexists, and Orchard enableZSA.

zebra-test

  • Adds five OrchardZSA workflow test vector blocks (orchard-zsa-workflow-block-{1..5}.txt) exercising a complete lifecycle: issue 1000 units → transfer → burn 7 and 2 → finalize issuance → reject a subsequent issuance of 2000 (to verify the finalized flag is enforced).

Tests

  • Unit tests: AssetState byte round-trip and invalid-data, IssuedAssetChanges validation, BurnItem/Burn round-trips, EncryptedNote proptest round-trips for both flavors, ENABLE_ZSA flag gating in arbitrary generation, ZSA consensus check unit tests.
  • Integration tests: zebra-consensus/src/orchard_zsa/tests.rs::check_orchard_zsa_workflow — drives a small regtest chain of ZSA workflow blocks (issue, transfer, burn) through the full Zebra node pipeline (deserializer → consensus router → state service) via the Transcript test engine, asserting each block is accepted or rejected at the expected point and that the final state reflects the correct asset supply; mempool vector and property tests updated for V6 transactions.
  • Snapshot tests: Full insta snapshot coverage for all getassetstate RPC responses (success, not found, finalized-only, invalid hex, wrong length, invalid asset base) and updated getrawtransaction/getblock snapshots.

Specifications & References

AI Disclosure

  • AI tools were used: ChatGPT was used for code assistance in some cases during implementation.

PR Checklist

  • The PR title follows conventional commits.
  • The PR follows the contribution guidelines.
  • This change was discussed in an issue or with the team beforehand. (The v4.2.0-dev target branch was created by the Zebra maintainers for this purpose.)
  • The solution is tested.
  • The documentation and changelogs are up to date.

dmidem added 30 commits April 3, 2025 11:39
…saction::arbitrary_with function in zebra-chain/src/transaction/arbitrary.rs
@oxarbitrage

Copy link
Copy Markdown
Contributor Author

Thanks for this work — the orchard_zsa module structure is clean, with good separation of issuance, burn, and asset state tracking. The feature gating appears correct — ZSA code paths are consistently guarded behind cfg(all(zcash_unstable = "nu7", feature = "tx_v6")). We've done a first review pass across all crates.

We see two paths forward:

Option A (preferred): We extract the minimal ZSA changeset and port it onto current main, reconciling with Ironwood V6 as we go. We review, fix, and iterate there. Main has diverged by 202 commits / 499 files since the v4.2.0-dev branch point, and 57 of the PR's substantive files have also changed on main — so a direct rebase of the 577-commit history isn't practical. The minimal port would be ~58 files:

  • 7 new files (~911 lines) that can be dropped in cleanly: orchard_zsa/ module (asset_state, burn, issuance), shielded_data_flavor.rs, versioned_sig.rs, and the consensus module root.
  • 6 heavily modified files (~1,200 lines) that need manual reconciliation with Ironwood: transaction/serialize.rs, transaction.rs, rpc/types/transaction.rs, non_finalized_state/chain.rs, orchard/shielded_data.rs, consensus/transaction.rs.
  • ~45 files with small changes (1-60 lines each): adding cfg gates, match arms, imports, new fields, and state plumbing. About 30 of these are one-liners.

Tests (~62 files, ~1,200 lines) would come after the port compiles. We can take a first pass at this port if that would be helpful.

Option B: We continue reviewing on this branch as-is. Faster to start but the V6 format reconciliation with Ironwood would need to happen anyway.

Here are the high-level concerns from our first pass. We're not going into code-level details yet — those would come as inline comments during the port or a deeper review:

A. V6 format divergence with Ironwood. Main now has its own V6 transaction support (Ironwood, NU6.3) with a different structure — newtypes (ShieldedDataV6, ironwood::ShieldedData) and separate Orchard/Ironwood fields in Transaction::V6. The ZSA V6 serialization, sighash, and txid will need to be reconciled with this existing format. Also, ZIP 230 and ZIP 246 (referenced in the PR) are now marked as withdrawn. Since V6 is now taken by Ironwood, should ZSA use a new transaction version (V7)? The tx_v6 feature flag name is also ambiguous now. Do you have a plan for aligning with the current transaction format?

B. All dependencies point to QED-it forks. orchard, zcash_primitives, sapling-crypto, zcash_spec, and 7 other librustzcash crates are all pinned to QED-it GitHub commits via [patch.crates-io], not published crates.io releases. This means Zebra can't ship a release with ZSA support until the ZSA changes land in upstream orchard/librustzcash and get published. What's the timeline for upstreaming these?

C. Consensus validation architecture. verify_v6_transaction notes via FIXME that it performs no V6-specific issuance or burn semantic checks, with all validation deferred to validate_and_get_changes in zebra-state. The sighash propagation infrastructure supports this design. Is this the intended long-term architecture, or are some checks planned to move into the consensus layer? A doc comment explaining the contract would help reviewers.

D. Lockbox disbursement check relaxed. In block/check.rs, the error for empty lockbox disbursements at NU6.1 activation is commented out — the FIXME notes this is a workaround for regtest/testnet where the disbursement list isn't configured. On mainnet the list is never empty so validation still runs, but the test-network safety check is lost. Could this be fixed by configuring the test network correctly rather than relaxing the check?

E. Burn validation upstream coverage. ZIP 226 requires three burn rules: no native-asset burns (AssetBase ≠ V^Orchard), positive values (v > 0), and no duplicate assets in the burn set. These are delegated to the orchard crate's validate_bundle_burn, which is consistent with how Zebra delegates Orchard proof verification. Can you confirm the upstream function covers all three?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-feature Category: New features do-not-merge Tells Mergify not to merge this PR external-contribution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants