Upstream PR 471 (OrchardZSA) – internal review and fixes#200
Conversation
* 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 <3682187+PaulLaux@users.noreply.github.com> Co-authored-by: Paul <lauxpaul@protonmail.com> Co-authored-by: Aurélien Nicolas <info@nau.re> Co-authored-by: Daniel Benarroch <danielbenarroch92@gmail.com>
+ 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).
Make IVK::from_bytes public
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 !
… = nf_old_pub) (#62) Currently, every new note commitment is calculated using rho_new = nf_old = DeriveNullifier_nk(rho_old, psi_old, cm_old). For split notes, we would like to evaluate the new note commitment with rho_new = nf_old_pub (a random nullifier which is stored in the instance nf_old_pub). For all remaining notes, nf_old = nf_old_pub. As such, implementing rho_new = nf_old_pub for all notes will not affect those remaining notes (and only affect split notes).
| |_, Unbound { proof, bsk }| { | ||
| Authorized::from_parts( | ||
| proof, | ||
| VerBindingSig::new(OrchardSighashVersion::V0, bsk.sign(rng, &sighash)), |
There was a problem hiding this comment.
str4d (Link): Non-blocking: The sighash version needs to be passed into the method, because it is a property of the sighash (and only the caller knows which version was used). Given that this will need an API refactor across several crates to ensure consistency, let's not do it in this PR (which is complicated enough as it is).
| /// No version (used for Orchard and TXv5 compatibility). | ||
| /// TXv5 does not require the sighash versioning bytes. | ||
| NoVersion = u8::MAX, | ||
| } |
There was a problem hiding this comment.
str4d (Link): A repr(u8) enum with unique values matching the sighashVersions cannnot work here, because:
- The v0 sighash for tx v6 will almost certainly be different from the v0 sighash for tx v7.
- No PCZT encoding has been defined yet.
- We do not know in the
orchardcrate what the actual encoding of the sighash version will be; it requires knowledge of the transaction version in which the sighash is used (in addition to the sighash algorithm). - Future sighash versions may have
associatedData, which cannot be conveyed in an enum like this.
Instead of using version numbers for the enum variants, they should be the semantic digest operation that is expected here; the mapping from that to actual serialized version numbers should happen in zcash_primitives. This also means the NoVersion variant is unnecessary (the difference is handled by the tx serializer).
| } | |
| /// The kind of data that a sighash commits to. | |
| /// | |
| /// This is used to implement [sighash versioning] for transactions containing Orchard | |
| /// bundles. | |
| /// | |
| /// [sighash versioning]: https://zips.z.cash/zip-0246#sighash-versioning | |
| #[derive(Debug, Clone, Eq, PartialEq)] | |
| pub enum OrchardSighashKind { | |
| /// The "default" sighash that commits to all effecting data of the transaction. | |
| /// | |
| /// Corresponds to the Orchard parts of the following specifications: | |
| /// - [ZIP 244](https://zips.z.cash/zip-0244#s-4-orchard-digest) | |
| /// - [ZIP 246](https://zips.z.cash/zip-0246#s-4-orchard-digest) | |
| AllEffecting, | |
| } |
(Then when a new digest algorithm is defined for a given tx version that commits to a subset of data, we'd add a new variant here representing the commitment to the subset of Orchard; likewise if a bug is found in a given tx version's AllEffecting committed data, we'd add a new variant representing the fixed commitment strategy.)
As a result, this entire module should be rewritten to avoid mentioning "versioning" (other than by motivating reference in documentation like above) as that is a transaction-level concern, not an Orchard bundle-level concern.
| /// some note created prior to this action (adding a nullifier to the global ledger). | ||
| #[derive(Debug, Clone)] | ||
| pub struct Action<A> { | ||
| pub struct Action<A, P: OrchardPrimitives> { |
There was a problem hiding this comment.
str4d (Link): Document the change to this struct in the changelog.
| nf in arb_nullifier(), | ||
| rk in arb_spendauth_verification_key(), | ||
| note in arb_note(output_value), | ||
| asset in arb_asset_base(), |
There was a problem hiding this comment.
str4d (Link): This is in the wrong place. The asset for the action should be an argument of arb_unauthorized_action alongside the spend and output values, otherwise the caller cannot leverage those existing arguments in any meaningful way.
| asset in arb_asset_base(), |
| note in arb_note(output_value), | ||
| rng_seed in prop::array::uniform32(prop::num::u8::ANY), | ||
| fake_sighash in prop::array::uniform32(prop::num::u8::ANY), | ||
| asset in arb_asset_base(), |
There was a problem hiding this comment.
str4d (Link): Ditto:
| asset in arb_asset_base(), |
| @@ -1,3 +1,5 @@ | |||
| //! `Add` chip implemetation. | |||
There was a problem hiding this comment.
str4d (Link): ```suggestion
//! Add chip implementation.
| #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] | ||
| pub enum IssueSighashVersion { | ||
| /// Version V0. | ||
| V0, |
There was a problem hiding this comment.
str4d (Link): I think most of the same rationale applies here as for the Orchard sighash kind, i.e. this should be AllEffecting instead of V0.
I agree that it should be a separate enum from the Orchard sighash kind, at least for the current approach. This would in theory allow a TransactionData to be constructed that claimed to use incompatible OrchardSighashKind and IssueSighashKind values, but that should be detected and rejected at tx serialization time. I'll see if I can think of a cleaner approach...
| /// | ||
| /// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit | ||
| pub(super) fn derive( | ||
| /// [notecommit]: https://zips.z.cash/zip-0226#note-structure-commitment |
There was a problem hiding this comment.
str4d (Link): ```suggestion
/// [notecommit]: https://zips.z.cash/zip-0226#note-structure-and-commitment
| /// $NoteCommit^Orchard$. | ||
| /// | ||
| /// Defined in [Zcash Protocol Spec § 5.4.8.4: Sinsemilla commitments][concretesinsemillacommit]. | ||
| /// Defined in [ZIP-226: Transfer and Burn of Zcash Shielded Assets][notecommit]. |
There was a problem hiding this comment.
str4d (Link): Do not remove the existing reference; it is the correct reference for $NoteCommit^Orchard$. Instead reference both locations, and also mention $NoteCommit^OrchardZSA$ in the docstring.
| mod zcash_note_encryption_domain; | ||
|
|
||
| pub use { | ||
| compact_action::CompactAction, orchard_domain::OrchardDomain, |
There was a problem hiding this comment.
str4d (Link): It feels very wrong for a CompactAction to be considered an "Orchard primitive", when it is actually derived from a more fundamental value (Action). This is a misunderstanding of the primitives module, and gets to a complaint I raised with you months ago that I didn't think the deletion of the note_encryption module was the right way to approach the refactor.
Non-blocking because it doesn't affect the correctness of the PR (and the history of this PR is more valuable to me than ease of introspecting the history of the note encryption logic), but be warned that I will likely go through after this PR is merged (and maybe after the corresponding librustzcash PR is merged) and undo a bunch of your refactoring (with the effect of having logically reduced the diff of this PR, even if it doesn't reduce the actual diff that gets merged).
In the meantime:
- Document these additions to the
primitivesmodule in the changelog.
| impl Ord for AssetBase { | ||
| fn cmp(&self, other: &Self) -> Ordering { | ||
| let self_coord = self.0.to_affine().coordinates().unwrap(); | ||
| let other_coord = other.0.to_affine().coordinates().unwrap(); |
There was a problem hiding this comment.
str4d (Link): Even though self.0 cannot be the identity, I do not like that the .unwrap()s here. We should instead just compare the byte representations of self and other.
| impl AssetBase { | ||
| /// Deserialize the AssetBase from a byte array. | ||
| pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> { | ||
| pallas::Point::from_bytes(bytes).map(AssetBase) |
| /// | ||
| /// Defined in [ZIP-227: Issuance of Zcash Shielded Assets][assetdigest]. | ||
| /// | ||
| /// [assetdigest]: https://zips.z.cash/zip-0227.html#specification-asset-identifier-asset-digest-and-asset-base |
There was a problem hiding this comment.
str4d (Link): ```suggestion
/// Derives the Asset Digest for the given ZSA asset.
///
/// Defined in [ZIP-227: Issuance of Zcash Shielded Assets][assetdigest].
///
/// [assetdigest]: https://zips.z.cash/zip-0227#asset-digests
| version: u8, | ||
| ik: &IssueValidatingKey<ZSASchnorr>, | ||
| asset_desc_hash: &[u8; 32], | ||
| ) -> Vec<u8> { |
There was a problem hiding this comment.
str4d (Link): This function mis-represents the relationship between its arguments. version is not an arbitrary parameter independent from ik and asset_desc_hash: by definition, if version != 0, the ik and asset_desc_hash parameters might not exist.
Either remove the version parameter and internally hard-code it to 0 (making this function solely for the version 0 protocol), or replace all of the arguments with an AssetId enum:
pub enum AssetId {
V0 {
ik: IssueValidatingKey<ZSASchnorr>,
asset_desc_hash: [u8; 32],
},
}and then make this function a method of AssetId (as well as asset_digest).
| let ik_encoding = ik.encode(); | ||
| let mut asset_id = Vec::with_capacity(1 + ik_encoding.len() + asset_desc_hash.len()); | ||
| asset_id.push(version); | ||
| asset_id.extend(ik_encoding); |
There was a problem hiding this comment.
str4d (Link): While you're making changes here, update the variable names to match ZIP 227:
| asset_id.extend(ik_encoding); | |
| let issuer = ik.encode(); | |
| let mut asset_id = Vec::with_capacity(1 + issuer.len() + asset_desc_hash.len()); | |
| asset_id.push(version); | |
| asset_id.extend(issuer); |
| /// Defined in [ZIP-227: Issuance of Zcash Shielded Assets][assetdigest]. | ||
| /// | ||
| /// [assetdigest]: https://zips.z.cash/zip-0227.html#specification-asset-identifier-asset-digest-and-asset-base | ||
| pub fn asset_digest(encode_asset_id: &[u8]) -> Blake2bHash { |
There was a problem hiding this comment.
str4d (Link): I don't like this being a public method and taking an arbitrary unconstrained slice. If you take my earlier suggestion of introducing an AssetId enum with this as one of its methods, then this would instead take &self which is fine.
|
|
||
| // EncodeAssetId(ik, asset_desc_hash) = version_byte || ik || asset_desc_hash | ||
| let asset_id = encode_asset_id(version_byte, ik, asset_desc_hash); | ||
| let asset_digest = asset_digest(&asset_id); |
There was a problem hiding this comment.
str4d (Link): This should be named AssetBase::custom rather than AssetBase::derive, to match the "Custom Asset" terminology from ZIP 227.
If my enum suggestion is taken:
| let asset_digest = asset_digest(&asset_id); | |
| pub fn custom(asset_id: &AssetId) -> Self | |
| let asset_digest = asset_id.asset_digest(); |
| /// | ||
| /// Defined in [ZIP-226: Transfer and Burn of Zcash Shielded Assets][assetbase]. | ||
| /// | ||
| /// [assetbase]: https://zips.z.cash/zip-0226.html#asset-identifiers |
There was a problem hiding this comment.
str4d (Link): ```suggestion
/// Defined in [ZIP 227: Issuance of Zcash Shielded Assets][assetbase].
///
/// [assetbase]: https://zips.z.cash/zip-0227#asset-bases
| } | ||
|
|
||
| /// Note type for the "native" currency (zec), maintains backward compatibility with Orchard untyped notes. | ||
| pub fn native() -> Self { |
There was a problem hiding this comment.
str4d (Link): I just noticed that ZIP 227's terminology section defines "Native Asset" as "a Custom Asset with issuance defined on the Zcash blockchain" (first introduced in QED-it/zips@97eacab), where "Custom Asset" is "any Asset other than ZEC and TAZ" (first introduced in QED-it/zips@8ed51c2). Thus the entire usage of native / is_native in this PR is inconsistent with ZIP 227, and should be reworked to use a different term.
Non-blocking for this PR, but blocking the first orchard release containing this PR; open an issue for addressing this.
| pub fn native() -> Self { | ||
| AssetBase(pallas::Point::hash_to_curve( | ||
| VALUE_COMMITMENT_PERSONALIZATION, | ||
| )(&NATIVE_ASSET_BASE_V_BYTES[..])) |
There was a problem hiding this comment.
str4d (Link): The slice notation should not be necessary hereh (it isn't present in the existing equivalent call that is deleted in this PR):
| )(&NATIVE_ASSET_BASE_V_BYTES[..])) | |
| )(&NATIVE_ASSET_BASE_V_BYTES)) |
| is_native in prop::bool::ANY, | ||
| isk in arb_issuance_authorizing_key(), | ||
| asset_desc_hash in any::<[u8; 32]>(), | ||
| ) -> AssetBase { |
There was a problem hiding this comment.
str4d (Link): Nit: we should only get arbitrary (isk, asset_desc_hash) if is_native; the generation should be conditional. I think you do this with a second sequence of arguments? We essentially want the equivalent of (!is_native).then(|| arb_zsa_asset_base()).
|
From upstream review (reply; parent not copied inline) str4d (Link): In a pairing with daira, we also noted that currently ZIP 230 defines For simplicity, we should update ZIP 230 to only allow burn amounts that fit into both a |
… with the upstream
|
Closed in favour of #200 |
This PR mirrors zcash#471 (QED-it:zsa1 → zcash:ltf/zsa) on our fork for internal review and changes.
We’ll address upstream review comments here, then propagate the final commits and responses back to zcash#471.