Skip to content

Orchard-ZSA reference implementation #372

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 123 commits into
base: main
Choose a base branch
from
Draft

Orchard-ZSA reference implementation #372

wants to merge 123 commits into from

Conversation

PaulLaux
Copy link

@PaulLaux PaulLaux commented Jan 31, 2023

This PR implements draft ZIP 226 (Transfer and Burn of Zcash Shielded Assets) and draft ZIP 227 (Issuance of Zcash Shielded Assets).

  • Burning mechanism: the ability to publicly burn ZSA notes. This is done by adding a burn field inside the Orchard bundle and the ability to utilize it. In addition the changes to bvk and bsk to support this change. See ZSA burn functionality QED-it/orchard#35 for a full list of changes.
/// A bundle of actions to be applied to the ledger.
#[derive(Clone)]
pub struct Bundle<T: Authorization, V, D:  OrchardDomainCommon> {
    /// The list of actions that make up this bundle.
    actions: NonEmpty<Action<T::SpendAuth>, D>,
    /// Orchard-specific transaction-level flags for this bundle.
    flags: Flags,
    /// The net value moved out of the Orchard shielded pool.
    /// This is the sum of Orchard spends minus the sum of Orchard outputs.
    value_balance: V,
    /// Assets intended for burning
    burn: Vec<(AssetId, V)>,
    /// The root of the Orchard commitment tree that this bundle commits to.
    anchor: Anchor,
    /// The authorization for this bundle.
    authorization: T,
}
  • Note Encryption: We extended the Domain trait in the zcash_note_encryption crate to support various lengths for COMPACT_NOTE_SIZE by converting the relevant constants into trait-specific types. Later, implemented OrchardDomainV3 to support the new note structure.
  • Updated the Python implementation to support the changes. See Testing encryption v3 QED-it/zcash-test-vectors#6 and the zsa1 branch for details.

Also included:

  • To avoid the ambiguity of the word "type", note_type was renamed to asset of type assetID.
  • Test vectors for AssetID derivation AssetID test vectors QED-it/orchard#34
  • Split note mechanism.
  • Circuit changes to support AssetID, split notes, and all other new functionality.
  • Backward compatibility for Orchard Bundles

The proposed changes should be considered a draft, not a final version.

PaulLaux and others added 17 commits December 6, 2022 15:02
* Added .circleci/config.yml
Implements the issuer keys as

    IssuerAuthorizingKey -> isk
    IssuerVerifyingKey -> ik

Test vectors generated with zcash_test_vectors repo
* Added NoteType to Notes
* Added NoteType to value commitment derivation
* Circleci project setup (#1)

* Added .circleci/config.yml

* Added NoteType to Notes

* reformated file

* updated `derive` for NoteType

* added note_type to value commit derivation

* rustfmt

* updated ci config

* updated ci config

* updated ci config

* updated derive for note_type

* added test for arb note_type

* added test for `native` note type

* zsa-note-encryption: introduce AssetType and encode and decode it in note plaintexts

* zsa-note-encryption: extend the size of compact notes to include asset_type

* fixed clippy warrnings

* rustfmt

* zsa-note-encryption: document parsing requirement

* zsa-note-encryption: revert support of ZSA compact action

* zsa_value: add NoteType method is_native

* zsa-note-encryption: remove dependency on changes in the other crate

* zsa-note-encryption: extract memo of ZSA notes

* zsa-note-encryption: tests (zcash_test_vectors 77c73492)

* zsa-note-encryption: simplify roundtrip test

* zsa-note-encryption: more test vectors (zcash_test_vectors c10da464)

* Circleci project setup (#1)

* Added .circleci/config.yml

* issuer keys implementation (#5)

Implements the issuer keys as

    IssuerAuthorizingKey -> isk
    IssuerVerifyingKey -> ik

Test vectors generated with zcash_test_vectors repo

* Added NoteType to Notes (#2)

* Added NoteType to Notes
* Added NoteType to value commitment derivation

* zsa-note-encryption: use both native and ZSA in proptests

* zsa-note-encryption: test vector commit 51398c93

* zsa-note-encryption: fix after merge

Co-authored-by: Paul <[email protected]>
Co-authored-by: Paul <[email protected]>
Co-authored-by: Aurélien Nicolas <[email protected]>
Co-authored-by: Daniel Benarroch <[email protected]>
+ Updated test bsk_consistent_with_bvk to verify mixed note types.
+ Added NoteType support to the builder and the bundle.
+ added split_flag to SpentInfo and as input to the Circuit (currently commented out)
+ added conditional cv_sum calculation (currently commented out)
+ added padding to actions
- added IssueBundle and IssueAction
- added a builder for IssueBundle
- added verify_issue_bundle() for consensus verification.
- unit tests.
added tests in `tests/zsa.rs`
* disabled split notes and proof check for zsa transfer
* fixes and suggestions

* changed "issuer" to "issuance" as per zcash#356 (comment)

* terminology fixes

* updated naming
* rename 2 note_type -> asset as per  zcash#356 (comment)

* added a dedicated type for "IssuanceAuth"

* disabled codecov github action due to bad behavior. 

* extracted "is_asset_desc_of_valid_size()" into asset_id.rs
* improved `verify_issue_bundle()`
Added a method to add assets to burn to the Builder

bvk computation now includes the burnt assets

Added Tests for bsk/bvk consistency for burning

Added E2E tests for assets burning
Added CI badge to README
Added `OrchardDomainV3` on top of the encryption generalization (QED-it/librustzcash#18).

not for review: note_encryption.rs, note_encryptionv2v3.rs and src/test_vectors/note_encryption.rs. These files represent two possible approaches for backward compatibility and will be finalized down the road. (the files were excluded from the build).
@PaulLaux PaulLaux mentioned this pull request Jan 31, 2023
alexeykoren and others added 12 commits February 9, 2023 12:44
When split_flag is set, the following values are modified
* v_net is equal to -v_new instead of v_old - v_new
* cv_net is evaluated with this new value of v_net

The following constraints are modified
* (v_old - v_new = magnitude * sign) becomes (v_old * (1-split_flag) - v_new = magnitude * sign) to take into
   account the new value of v_net
* nf_old = nf_old_pub is only checked when split_flag=0
 * the new constraint asset_old = asset_new is always checked regardless of the value of split_flag
- Renamed AssetId to AssetBase
- Changed the  AssetBase implementation to support the zip update.
- Updated visibility for various members of issuance.rs
…#49)

This PR updates the test-vectors from the updates to the zcash-test-vectors repository (see here).

The keys test is also updated to now use the asset base from the test vectors instead of just using the native asset.
In the circuit, we update value_commit_orchard to take into account asset.
Previously, value_commit_orchard returns cv_net = [v_net] ValueCommitV + [rcv] ValueCommitR..
Now, value_commit_orchard returns cv_net = [v_net] asset + [rcv] ValueCommitR.
ValueCommitV and ValueCommitR are constants
v_net is equal to sign * magnitude where sign is in {-1, 1} and magnitude is an unsigned integer on 64 bits.

To evaluate [v_net] asset where v_net = sign * magnitude, we perform the following steps
1. verify that magnitude is on 64 bits
2. evaluate commitment=[magnitude]asset with the variable-base long-scalar multiplication
3. evaluate result=[sign]commitment with the new mul_sign gate
We would like to have a constant-time evaluation of the note commitment for both ZEC and ZSA.
ZEC_note_commitment=Extract_P(SinsemillaHashToPoint(zec_personalization, common_bits) + [rcm]R)
ZSA_note_commitment=Extract_P(SinsemillaHashToPoint(zsa_personalization, common_bits || asset) + [rcm]R)

R is the same constant for ZEC and ZSA note commitments.
1. Added a new error, `ValueSumOverflow`, that occurs if the sum value overflows when adding new supply amounts.
2. Created a new `supply_info` module containing `SupplyInfo` and `AssetSupply` structures, with `add_supply` function and unit tests for it.
3. Renamed the `are_note_asset_ids_derived_correctly` function to `verify_supply`, changed its behavior to verify and compute asset supply, added unit tests for it.
4. Updated the `verify_issue_bundle` function to use the changes mentioned above, updated its description, and added new unit tests.
5. Renamed errors with `...NoteType` suffix in the name to `...AssetBase`.
6.  Added `update_finalization_set` method to `SupplyInfo` and use after the calls of `verify_issue_bundle function` (if needed), instead of mutating the finalization set inside `verify_issue_bundle`.
- Add getter method for Bundle.burn field
For zcash_note_encryption, we have to use version 0.2 with QEDIT patch.
In the circuit, we update note_commit to take into account asset.
Previously, note_commit returns cm = hash(Q_ZEC, msg) + [rcm]R.
Now, note_commit returns
- cm = hash(Q_ZEC, msg) + [rcm]R for ZEC note
- cm = hash(Q_ZSA, msg || asset) + [rcm]R for ZSA note

We now evaluate note_commit with the following steps
1. evaluate **hash_zec = hash(Q_ZEC, msg)**
2. evaluate **hash_zsa = hash(Q_ZSA, msg || asset)**
3. select **hash = hash_zec if is_native_asset**
                         **= hash_zsa otherwise**
4. evaluate **cm = hash + [rcm]R**
5. check some constraints on msg and asset and their decompositions
6. return **cm**

The following modifications are required to update note_commit:
- add a is_native_asset witness (and check that it is a boolean and its
value is correct according to asset)
- add a MUX chip to evaluate a multiplexer on Pallas points

Warning: we increased the size of the Orchard circuit !
dmidem and others added 6 commits February 4, 2025 20:04
… functions (#138)

This PR modifies the `verify_issue_bundle` function and refactors the
`supply_info.rs` module by removing `SupplyInfo` and introducing a new
generic type, `AssetInfo`, in place of the previous `AssetSupply`
struct. `AssetInfo` allows to properly distinguish the asset
verification logic between reference notes required for first issuances
versus optional references for subsequent issuances.

#### Key Changes

1. **Rename `AssetSupply` struct to `AssetInfo`**  

2. **Rename `IssueAction::verify_supply` to `IssueAction::verify` and
update its return value**
- The `verify` method now returns only the sum of the action's note
values instead of `AssetInfo`.
- The `is_finalized` and `reference_note` action properties are now
handled directly in the `verify_issue_bundle` function.

3. **Revise `verify_issue_bundle`**  
- Modifies the function signature to use a `get_global_asset_state`
callback instead of relying on a `finalization` set.
- Changes the return type to a `HashMap<AssetBase, AssetInfo>` (instead
of `SupplyInfo`).
- Updates the function so that a reference note is mandatory for the
first appearance of any asset. Tests and documentation have been updated
accordingly.

4. **Remove `supply_info.rs`**  
- Deletes the `SupplyInfo` struct and related tests, since
`verify_issue_bundle` now returns a `HashMap` of `AssetInfo` objects.

5. **Test updates**  
- Revises tests to align with the new `verify_issue_bundle` signature
and the `get_global_asset_state` approach.

6. **Add issuance workflow test**
- Adds a new test `issue_bundle_verify_with_global_state` that performs
a series of bundle creations and verifications, with a global state
simulation.

7. **Miscellaneous fixes**  
- Simplifies `Error` variant imports in `issuance.rs` by referencing the
local `Error` enum directly.
This PR addresses a couple of cargo clippy warnings in PR
#138.
This PR extends the derives for orchard protocol marker types
("flavors") by adding `PartialEq` and `Eq` to `OrchardVanilla` and
`OrchardZSA`. This is needed to reuse these marker types directly in
Zebra without introducing duplicated types.
- Added std and circuit features to ZSA
- Replaced HashMap/HashSet with BTreeMap/BTreeSet
  (required implementing ordering for AssetBase)
- Added ZSA fields to PCZT structures
- Upgraded Rust to version 1.70.0 (done in a later commit on zcash/orchard)
- Replaced all std imports
- Implemented regression tests using fixed bundle outputs to verify
  backward compatibility with zcash/orchard repo
Comment on lines +208 to 215
pub struct Bundle<A: Authorization, V, D: OrchardDomainCommon> {
/// The list of actions that make up this bundle.
actions: NonEmpty<Action<T::SpendAuth>>,
actions: NonEmpty<Action<A::SpendAuth, D>>,
/// Orchard-specific transaction-level flags for this bundle.
flags: Flags,
/// The net value moved out of the Orchard shielded pool.
///
/// This is the sum of Orchard spends minus the sum of Orchard outputs.
value_balance: V,
/// Assets intended for burning
burn: Vec<(AssetBase, NoteValue)>,
/// The root of the Orchard commitment tree that this bundle commits to.
anchor: Anchor,
/// Block height after which this Bundle's Actions are invalid by consensus.
///
/// For the OrchardZSA protocol, `expiry_height` is set to 0, indicating no expiry.
/// This field is reserved for future use.
expiry_height: u32,
/// The authorization for this bundle.
authorization: T,
authorization: A,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

@str4d and I had an extensive discussion about this type in a pairing. Our conclusion is that parameterizing the Bundle type via the D parameter causes the downstream code that consumes Orchard bundles to be exposed to details about note encryption that are too low-level for the consumer. Also, the fact that we are parameterizing with D but are not fully concretizing the contents of the bundle relative to that parameter enables the representation of mixed states (e.g. D = OrchardVanilla but with burn nonempty) that we do not want to exist.

Instead, there should be separate Bundle and BundleZsa types; this then means that:

  • the OrchardDomainCommon type may not need to exist, and we can use separate implementations of zcash_note_encryption::Domain.
  • the new fields, expiry_height and burn will only exist in the BundleZsa type.

The Action type then doesn't need to be parameterized by the domain type; it can just be parameterized by the type of the enc_ciphertext, and each Bundle type can concretely set this to a ciphertext type that directly matches what is used in the domain. This will facilitate the memo changes from ZIP 231 (for which the concrete length will be different than at present, and for which zcash_note_encryption::Domain::Memo will be set to the type of the memo key.)

At the top level, what we will do is route scanning of V6 transaction outputs into one batch decryptor, and scanning of V5 outputs into a separate batch decryption.

Copy link
Author

@PaulLaux PaulLaux Apr 17, 2025

Choose a reason for hiding this comment

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

Actually, having the parameter D does not expose much detail to the downstream consumer (unless explicitly requested).
The consumer have either

let bundle: Bundle<_, _, Orcahrd> = ...

Or

let bundle: Bundle<_, _, OrcahrdZSA> = ...

and can proceed with the Bundle<_> operations. But this almost never happens since most of the handling is unified. (We have it mostly in tests.)

The major consideration for this type of parametrization is clean code reuse: in most cases Bundle<_,_,Orchard> and Bundle<_,_,OrchardZSA> are treated the same and we can have nice abstraction in the form of

    let bundle: Bundle<_, i64, D> = builder.build(rng);
    bundle
        .create_proof(&pk, rng)
        .unwrap()
        .apply_signatures(rng, [0; 32], &[])
        .unwrap()

In critical places like

pub fn build<V: TryFrom<i64>, D: OrchardFlavor>(
        self,
        rng: impl RngCore,
    ) -> Result<Bundle<InProgress<Unproven, Unauthorized>, V, D>, BundleMetadata), BuildError> {

or

 pub fn prepare<R: RngCore + CryptoRng>(
        self,
        mut rng: R,
        sighash: [u8; 32],
) -> Bundle<InProgress<P, PartiallyAuthorized>, V, D> {

or

pub fn apply_signatures<R: RngCore + CryptoRng>(
        self,
        mut rng: R,
        sighash: [u8; 32],
        signing_keys: &[SpendAuthorizingKey],
) -> Result<Bundle<Authorized, V, D>, BuildError> {

among many others.

Later it is propogated Action:

fn build<D: OrchardDomainCommon>(
        self,
        mut rng: impl RngCore,
) -> (Action<SigningMetadata, D>, Witnesses) {

Where also most of the logic is shared between an Orchard action and an OrchardZSA action.

All while the difference between the Bundles or the Actions is encapsulated inside OrchardFlavor and called transparently by the consumers.

All while fully supporting backward compatibility.

Removing the parameter D and creating two different types will obliterate all these nice properties, and the code will be harder to maintain and harder to reason about, as most of the logic for both is the same.

That being said, we can change the handling for the note encryption part to align with ZIP 231 once it is done and we can have it (or we can do it on top).

Also, the burn handling can be changed to have a dedicated empty type for Orchard and non-empty type for OrchardZSA as we did for Zebra's ShieldedData:

pub struct ShieldedData<FL: Flavor> {
   ...
    /// Assets intended for burning
    /// Denoted as `vAssetBurn` in the spec (ZIP 230).
    pub burn: FL::BurnType,
}

Forcing the burn to be empty for Orchard.

Other solutions for Burn and note encryption are also possible.

However, we strongly recommend keeping the D parameter for Bundle for the reasons mentioned above.

Commitment structure is reorganized to reflect the change in location of
burn field

---------

Co-authored-by: Vivek Arte <[email protected]>
@PaulLaux PaulLaux force-pushed the zsa1 branch 2 times, most recently from 724e98a to 190a50c Compare April 17, 2025 11:57
PaulLaux and others added 19 commits April 17, 2025 14:58
This is a redo of #150 

chery picked 3f9dda7 and resolved
conflicts
A redo of #146 

Merge zcash/orchard up to commit 4fa6d3b into zsa1 with following
modifications
- Added std and circuit features to ZSA
- Replaced HashMap/HashSet with BTreeMap/BTreeSet (required implementing
ordering for AssetBase)
- Added ZSA fields to PCZT structures
- Upgraded Rust to version 1.71.0 (done in a later commit on
zcash/orchard)
- Replaced all std imports
- Implemented regression tests using fixed bundle outputs to verify
backward compatibility with zcash/orchard repo
Due to a change in the default memo, the output of orchard_digest changed.
Merge zcash/orchard (commit  fcb14de) into zsa1  with following modifications
- Update tests comparing orchard digest against a fixed value (the default memo value has been updated)
- Import the last version of generated test_vectors files (see QED-it/zcash-test-vectors#26)
- Fix clippy errors
- Expose (Extracted)NoteCommitment in public API
- Direct evaluation of note commitments (outside the circuit)
- Rename new_with_personalization by new_with_separate_domains
- A fixed size for asset_id
- Removed RTL chars to avoid confusing the text editor.
- Minor spacing fixes
Update compute_asset_desc_hash function
- Now accepts &NonEmpty<u8> instead of &[u8]
- Returns [u8; 32] directly (no longer wrapped in Result)
- Panics if asset_desc is not valid UTF-8

Update get_burn_tuple function
- Now takes asset_desc_hash as input instead of asset_desc
- Changes the function’s visibility to private

Cleanup unused code
- Removed unused errors: WrongAssetDescSize, InvalidAssetDescHashLength
- Removed unused constant: MAX_ASSET_DESCRIPTION_SIZE
- Removed unused function: is_asset_desc_of_valid_size
To use our latest version of zcash_note_encryption:
- Remove the version of zcash_note_encryption from Cargo.toml
- Update Cargo.lock
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.

8 participants