diff --git a/CHANGELOG.md b/CHANGELOG.md index 27200fabb..09857dc6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,305 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +This release introduces `orchard::bundle::BundleVersion`, the `(value pool, protocol +version)` of an Orchard bundle, built from the new `orchard::ValuePool` and +`orchard::ProtocolVersion` types. Each `Bundle` now carries its `BundleVersion` as +non-serialized context, so a bundle can be serialized and committed to without separately +supplying a — possibly mismatching — version, and is encodable and committable by +construction. The post-NU 6.3 Action circuit enforces the cross-address restriction. +Existing callers keep the current behavior by constructing bundles with +`BundleVersion::orchard_v2()` and `BundleVersion::orchard_v2().default_flags()` (and +`OrchardCircuitVersion::FixedPostNu6_2` when building proving/verifying keys). + +### Added +- NU6.3 and Ironwood bundle-version APIs: + - `orchard::ValuePool`, the value pool an Orchard bundle belongs to (`Orchard` or + `Ironwood`), and `orchard::ProtocolVersion`, the Orchard protocol version + (`InsecureV1`, the historical pre-NU6.2 protocol that uses the unsound circuit; `V2`, + NU6.2; `V3`, NU6.3, which also instantiates the Ironwood pool). + - `orchard::bundle::BundleVersion`, the `(value pool, protocol version)` of an Orchard + bundle. Its `const fn` constructors `orchard_insecure_v1`, `orchard_v2`, `orchard_v3`, + and `ironwood_v3` make only the valid combinations representable. It determines the + note plaintext version (`BundleVersion::note_version`), the circuit version + (`BundleVersion::circuit_version`, when the `circuit` feature is enabled), the + flag-byte interpretation (pre-NU6.3 rules, where bit 2 is reserved and cross-address + transfers are implicitly enabled, vs NU6.3 rules, where bit 2 is `enableCrossAddress`), + and whether consensus mandates the cross-address restriction (the builder then chooses + the value within that constraint). `BundleVersion::value_pool` and + `BundleVersion::protocol_version` return the bundle's `ValuePool` and + `ProtocolVersion`; the Ironwood pool (`ironwood_v3`) shares the post-NU6.3 circuit and + uses V3 note plaintexts. `BundleVersion::default_flags` returns the least-restrictive + `Flags` consensus permits under the bundle version (spends and outputs enabled, + cross-address transfers enabled except where the version mandates the restriction), + the suitable default for the builder that a caller may restrict further. + - `orchard::bundle::TxVersion`, the transaction version (`V5` or `V6`) a + bundle's commitments are computed for. At NU6.3 an Orchard bundle may be + encoded in a v5 or a v6 transaction; the two use different commitment + personalizations and place the anchor in different digests (v5 in the + transaction-ID effects, v6 in the authorizing data). It is passed to + `Bundle::commitment`, `Bundle::authorizing_commitment`, and the + `hash_bundle_*_empty` helpers. +- Cross-address restriction APIs: + - `orchard::bundle::Flags::CROSS_ADDRESS_DISABLED`, the restricted flag set. It + cannot be encoded in pre-NU6.3 formats. + - `orchard::bundle::Flags::cross_address_enabled` + - `orchard::circuit::OrchardCircuitVersion::PostNu6_3`, the circuit version that + enforces the `disableCrossAddress` public input. The post-NU 6.3 circuit has + its own proving and verifying keys. + - `orchard::circuit::OrchardCircuitVersion::supports_cross_address_restriction`, + and `orchard::circuit::{ProvingKey, VerifyingKey}::supports_cross_address_restriction`, + introspection for whether a circuit version (or a key's circuit version) + constrains the `disableCrossAddress` public input. + - `orchard::circuit::VerifyingKey::circuit_version` +- `orchard::bundle::CommitmentError`, with its `InvalidTransactionVersion` variant, + returned by the bundle commitment APIs when an Ironwood bundle's commitment is requested + for a `TxVersion::V5` transaction. +- `orchard::Bundle::bundle_version`, returning the `BundleVersion` the bundle is encoded + under, and `orchard::Bundle::flag_byte`, the infallible byte encoding of the bundle's + flags under that version. +- `orchard::bundle::BatchError` (requires the `circuit` feature), with its + `RestrictionUnsupportedByKey` variant, returned by + `orchard::bundle::BatchValidator::add_bundle` when a restricted bundle is added + with a verifying key whose circuit version cannot enforce the cross-address + restriction. +- Wallet-controlled change-output APIs, the only way to retain shielded value in + a bundle that disables cross-address transfers: + - `orchard::builder::ChangeInfo`, the change-output counterpart of `OutputInfo`, + recording the full viewing key that owns the recipient (validated on + construction) so the builder can fabricate the paired same-expanded-receiver + spend. + - `orchard::builder::Builder::add_change_output` + - `orchard::builder::Builder::changes` + - `impl orchard::builder::OutputView for ChangeInfo` +- New builder errors: + - `orchard::builder::BuildError::{CrossAddressDisabled, InvalidNoteVersion, UnrepresentableFlags, CoinbaseSpendsEnabled}` + - `orchard::builder::OutputError::{SpendsDisabled, CrossAddressDisabled, RecipientNotOwned}` +- Ironwood and note-version APIs: + - `orchard::NoteVersion`, the note plaintext version selector, with variants + `V2` (the ZIP 212 Orchard note plaintext format) and `V3` (the + quantum-recoverable Ironwood note plaintext version defined in ZIP 2005). + - `orchard::Note::version`, returning the note's plaintext version. + - `orchard::note_encryption::{NoteEncryptionDomain, DomainVersion, OrchardVersion, IronwoodVersion}`, + the sealed marker-domain API underlying `OrchardDomain` and `IronwoodDomain`. + - `orchard::note_encryption::{IronwoodDomain, IronwoodNoteEncryption}`, matching + `OrchardDomain` note-encryption behavior but accepting V3 note plaintexts + during parsing. +- PCZT note-version and cross-address APIs: + - `orchard::pczt::{Spend, Output}::note_version`, the generated getters for the + note plaintext version of a parsed spend or output. + - `orchard::pczt::Bundle::bundle_version`, the generated getter for the bundle's + `BundleVersion`, and `orchard::pczt::Bundle::flag_byte`, the infallible byte encoding of + its flags under that version. + - `orchard::pczt::Bundle::verify_cross_address_restriction`, so that Signers can + check the cross-address restriction's same-expanded-receiver structural + property before signing. It is a no-op for bundles that permit cross-address + transfers. + - `orchard::pczt::ParseError::InvalidNoteVersion` + - `orchard::pczt::VerifyError::DisallowedCrossAddressTransfer` + - `orchard::pczt::IoFinalizerError::CrossAddressRestriction` + - `orchard::pczt::ProverError::DisallowedCrossAddressTransfer`, wrapping the + underlying `orchard::pczt::VerifyError`. + +### Changed +- Bundle construction now requires an explicit `BundleVersion` and `Flags`: + - `orchard::builder::Builder::new` now takes + `(BundleType, BundleVersion, Flags, Anchor)` and returns + `Result`; the `Flags` are validated against the + `BundleVersion` (rejected with `BuildError::UnrepresentableFlags`), and a + `BundleType::Coinbase` builder requires spends-disabled flags (rejected with + `BuildError::CoinbaseSpendsEnabled`). `BundleVersion::default_flags` supplies a + suitable default that the caller may restrict further. The builder derives the + circuit version from the bundle version rather than from an explicit + `OrchardCircuitVersion`. + - `orchard::builder::bundle` now takes a `BundleVersion` and `Flags` in place of + the circuit-version argument, and takes the wallet-controlled change outputs + as a separate `changes: Vec` argument (plain `outputs` and + `changes` are distinct). It rejects supplied `OutputInfo`/`ChangeInfo` values + whose note version does not match the `BundleVersion`, returning + `BuildError::InvalidNoteVersion`. + - `orchard::builder::BundleType` no longer carries any flag settings; it is now + just the construction policy, `Transactional { bundle_required }` or + `Coinbase`. The bundle's `Flags` are supplied separately to the builder. `flags` + are no longer derived from the bundle type, so `BundleType::flags` has been + removed. + - `orchard::builder::BundleType::num_actions` now takes a `Flags` (in place of a + `BundleVersion`), reading the spend/output/cross-address policy directly from it. + For bundles that disable cross-address transfers, `num_actions` counts + `num_spends + num_outputs` requested actions (a requested spend and a requested + output never share an action) rather than the maximum of the two, and + `BundleMetadata` maps them to distinct actions; wallets estimating fees (e.g. per + ZIP 317) must account for the larger action count. + - `orchard::builder::OutputInfo::{new, dummy}` now take an `orchard::NoteVersion`, + so callers choose between V2 Orchard notes and V3 Ironwood notes; + builder-created outputs use the note version associated with the selected + `BundleVersion`. + - `orchard::builder::BundleMetadata::output_action_index` now indexes the plain + outputs first, followed by the wallet-controlled change outputs. +- For `BundleVersion::orchard_v3()`, the builder constructs + withdrawal/change bundles that disable cross-address transfers: every action's + output is addressed to the expanded receiver of the note it spends. The + fabricated zero-value output paired with each real spend carries a randomized, + undecryptable note ciphertext rather than one encrypted to the spent note's + receiver, so neither the owning wallet nor a holder of that receiver's incoming + viewing key can use it to detect the spend. Ordinary outputs are rejected + (`Builder::add_output` returns `OutputError::CrossAddressDisabled`); retained + shielded value must be added with `Builder::add_change_output`, which rejects a + recipient not owned by the full viewing key (`OutputError::RecipientNotOwned`) + and requires spends to be enabled (`OutputError::SpendsDisabled`). The cross-address + bit is now a caller-supplied flag rather than a builder-chosen default: + `BundleVersion::default_flags` returns the least-restrictive flag set consensus permits + — cross-address transfers enabled, except for the Orchard pool under + `BundleVersion::orchard_v3()`, where consensus mandates the restriction — and a caller + may restrict it further (a tighter choice the bundle version permits) before passing the + flags to the builder. Coinbase bundles follow the same constraints as non-coinbase + bundles: post-NU6.3 Orchard coinbase transactions cannot contain Orchard actions, so + post-NU6.3 coinbase bundle construction in this crate is only useful for + `BundleVersion::ironwood_v3()`. +- `orchard::bundle::Flags::{to_byte, from_byte}` now take a + `BundleVersion`. Bit 2 (`enableCrossAddress`) is only representable for + the Ironwood pool post-NU6.3; it is rejected for pre-NU6.3 (where bit 2 is + reserved) and for Orchard post-NU6.3 (where consensus mandates the cross-address + restriction). `to_byte` now returns `Option`, yielding `None` when the flag + set is not representable under the given bundle version. A byte with bit 2 + clear is interpreted differently per epoch: an unrestricted bundle before NU6.3, + a restricted bundle under NU6.3. A `Bundle` exposes the infallible + `Bundle::flag_byte` for its own flag encoding. +- A bundle's `BundleVersion` is now carried by the bundle itself rather than passed to its + decryption, recovery, and commitment methods: + - `orchard::Bundle::{decrypt_outputs_with_keys, decrypt_output_with_key}` and + `orchard::Bundle::{recover_outputs_with_ovks, recover_output_with_ovk}` no longer take a + version argument; they use the bundle's own `BundleVersion` (so an Ironwood bundle's + helpers discover its V3 notes). + - `orchard::Bundle::commitment` and + `orchard::Bundle::::authorizing_commitment` no longer take a + `BundleVersion` (the bundle supplies it); they still take a `TxVersion`, which selects + the commitment personalization strings and the anchor placement. The ZIP-244 digest — + and therefore the transaction ID and sighash — depends on both the bundle's version and + the transaction version: under a NU6.3 protocol an unrestricted bundle's flag byte sets + bit 2, and `TxVersion::V6` uses the v6 personalization strings and commits the anchor in + the authorizing commitment instead of the effects commitment. Callers must construct the + bundle with the version matching the transaction and pass the matching `TxVersion`; + these APIs check only that the combination is representable, not that it is + consensus-valid. Both now return only + `Err(CommitmentError::InvalidTransactionVersion)` (for an Ironwood bundle in a v5 + transaction); because flags are validated when the bundle is constructed, commitment can + no longer fail on unrepresentable flags. + - `orchard::bundle::commitments::{hash_bundle_txid_empty, hash_bundle_auth_empty}`, which + operate on an absent bundle (and so hash no flags), now take a `ValuePool` and a + `TxVersion`, and return `Result`, rejecting an Ironwood pool + in a v5 transaction with `CommitmentError::InvalidTransactionVersion`. +- Bundle construction now takes a `BundleVersion` and validates flags against it: + - `orchard::Bundle::::from_parts` and + `orchard::Bundle::::try_from_parts` now take a `BundleVersion`, and + reject a flag set that cannot be encoded under it with the new + `orchard::bundle::BundleError::UnrepresentableFlags` variant. `from_parts` is now + fallible (returns `Result<_, BundleError>`) for this reason, so a constructed `Bundle` + is always encodable and committable. + - `try_from_parts` no longer takes an `orchard::bundle::ProofSizeEnforcement`: the + canonical proof-size check (GHSA-2x4w-pxqw-58v9) is derived from the bundle version, + enforced for every version except the historical pre-NU6.2 Orchard pool + (`BundleVersion::orchard_insecure_v1`), whose already-committed transactions may carry + non-canonical proofs. +- Circuit APIs now require explicit circuit versions: + - `orchard::circuit::Circuit::from_action_context` now takes an + `OrchardCircuitVersion` instead of implicitly selecting `FixedPostNu6_2`. + - `orchard::circuit::Instance::from_parts` now takes an `orchard::bundle::Flags` + instead of separate spend/output enable booleans, so the cross-address + restriction is carried into the public instances. + - `orchard::circuit::{ProvingKey, VerifyingKey}::build` now take an + `OrchardCircuitVersion` — pass `FixedPostNu6_2` for the previous behavior, or + `PostNu6_3` for restricted proofs. + - `orchard::Proof::{create, verify}` and `orchard::Bundle::verify_proof` reject + instances that disable cross-address transfers unless the key's circuit + version supports the restriction, returning + `halo2_proofs::plonk::Error::InvalidInstances`; with pre-NU 6.3 keys, proving + a restricted builder-created bundle returns `BuildError::Proof`, and PCZT + proving returns `ProverError::ProofFailed`. Restricted bundles can still be + constructed and round-tripped (`Bundle::::try_from_parts` and + `orchard::pczt::Bundle::extract` preserve the flag), with enforcement at + proving and verification. +- `orchard::bundle::BatchValidator` (requires the `circuit` feature) now binds its + verifying key at construction: `BatchValidator::new` now takes a + `&orchard::circuit::VerifyingKey`, and `BatchValidator::validate` no longer takes + one. `BatchValidator::add_bundle` now returns `Result<(), BatchError>`, rejecting + a bundle that disables cross-address transfers — without adding it to the batch — + when the verifying key's circuit version does not support the cross-address + restriction. Other proof or signature failures still surface as `validate` + returning `false`. +- `orchard::Note::from_parts` now takes an `orchard::NoteVersion`, so callers + choose between V2 Orchard notes and V3 Ironwood notes. Orchard note decryption + domains now reject note plaintext versions that do not match their domain, and + `orchard::note_encryption::OrchardDomain` is now an alias for + `NoteEncryptionDomain`. Encryption uses the note's own + `NoteVersion`; the `OrchardNoteEncryption` and `IronwoodNoteEncryption` aliases + differ only in which note plaintext versions they accept during parsing and + decryption. +- PCZT parsing and role checks now carry pool and note-version context: + - `orchard::pczt::Bundle::parse` now takes a `BundleVersion` and rejects + flags and output note versions that do not match it. + - `orchard::pczt::{Spend, Output}::parse` now take the `orchard::NoteVersion` for + the parsed spend or output. + - `orchard::pczt::Bundle::create_proof` now builds the Action circuits for the + provided `ProvingKey`'s circuit version (previously always `FixedPostNu6_2`) + and checks the cross-address restriction's same-expanded-receiver property, + returning `ProverError::DisallowedCrossAddressTransfer` (or + `ProverError::MissingRecipient` if a `recipient` field is unset). + - `orchard::pczt::Bundle::finalize_io` verifies the cross-address restriction + before modifying the bundle, returning + `IoFinalizerError::CrossAddressRestriction` (wrapping the underlying + `VerifyError`) and leaving the bundle unmodified if the PCZT is missing + recipient data or violates the restriction. +- `test-dependencies`-only: + - `orchard::note::testing::arb_note`, + `orchard::bundle::testing::{arb_action, arb_unauthorized_action}`, and + `orchard::bundle::testing::{arb_action_n, arb_unauthorized_action_n}` now take + an `orchard::NoteVersion`. + - `orchard::bundle::testing::{arb_bundle, arb_unauthorized_bundle}` now construct the + bundle with a generated `BundleVersion` (via the new + `orchard::bundle::testing::arb_bundle_version` strategy), choosing flags + consistent with that version. + - `orchard::bundle::testing::arb_flags` is unchanged and only generates flag sets + with cross-address transfers enabled, representable under every bundle + version other than Orchard post-NU6.3; use the new + `arb_flags_ironwood_post_nu6_3` for Ironwood post-NU6.3 flag sets that may + disable cross-address transfers. +- `unstable-voting-circuits`-only (not covered by the crate's semver guarantees): + - `orchard::Note::{new, dummy}` now take an `orchard::NoteVersion`. + - `RandomSeed::rcm` is replaced by `RandomSeed::{rcm_v2, rcm_v3}`: `rcm_v2` is the + rcm derivation for V2 (ZIP 212) notes, and `rcm_v3` the derivation for V3 + (ZIP 2005, Ironwood) notes. + +### Removed +- `orchard::bundle::ProofSizeEnforcement`; `Bundle::try_from_parts` now derives the + canonical proof-size check from the `BundleVersion` (enforced for every version except + `BundleVersion::orchard_insecure_v1`). +- `orchard::builder::Builder::new_for_version`; use + `Builder::new(bundle_type, bundle_version, flags, anchor)`. +- `orchard::builder::bundle_for_version`; use `builder::bundle` with + `BundleVersion` and a `Vec`. +- `orchard::builder::BundleType::DISABLED`; construct the builder with a `Flags` value + that disables spends and outputs instead. +- Zero-argument `orchard::circuit::{ProvingKey, VerifyingKey}::build`; pass an + `OrchardCircuitVersion` explicitly. +- `orchard::circuit::{ProvingKey, VerifyingKey}::build_for_version`; use + `build(version)`. +- `orchard::circuit::Circuit::from_action_context_for_version`; use + `Circuit::from_action_context(..., circuit_version)`. +- `orchard::Proof::add_to_batch` is no longer public; it could add restricted + instances to a raw batch later finalized against a verifying key whose circuit + version does not enforce the cross-address restriction. Use + `orchard::bundle::BatchValidator`, which binds its verifying key at construction. +- The `Default` implementations for `orchard::circuit::OrchardCircuitVersion`, + `orchard::circuit::Circuit`, and `orchard::bundle::BatchValidator`; callers must + choose a circuit version explicitly, and `BatchValidator` must be constructed + with `BatchValidator::new`, which requires a verifying key. + +### Fixed +- The `Display` output of `orchard::builder::BuildError::OutputsDisabled` + previously described spends rather than outputs. + ## [0.14.0] - 2026-06-02 ### Added @@ -209,7 +508,7 @@ and this project adheres to Rust's notion of ### Changed - MSRV is now 1.70 -- Migrated to `nonempty 0.11`, `incrementalmerkletree 0.8`, `shardtree 0.6`, +- Migrated to `nonempty 0.11`, `incrementalmerkletree 0.8`, `shardtree 0.6`, `zcash_spec 0.2`, `zip32 0.2` - `orchard::builder::Builder::add_output` now takes a `[u8; 512]` for its `memo` argument instead of an optional value. diff --git a/Cargo.lock b/Cargo.lock index ef7d367c8..591084896 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1432,7 +1432,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "orchard" -version = "0.14.0" +version = "0.15.0-pre.1" dependencies = [ "aes", "bitvec", diff --git a/Cargo.toml b/Cargo.toml index af477a665..32e509cd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "orchard" -version = "0.14.0" +version = "0.15.0-pre.1" authors = [ "Sean Bowe ", "Jack Grigg ", diff --git a/benches/circuit.rs b/benches/circuit.rs index 22f43eb51..b83958978 100644 --- a/benches/circuit.rs +++ b/benches/circuit.rs @@ -8,7 +8,8 @@ use pprof::criterion::{Output, PProfProfiler}; use orchard::{ builder::{Builder, BundleType}, - circuit::{ProvingKey, VerifyingKey}, + bundle::BundleVersion, + circuit::{OrchardCircuitVersion, ProvingKey, VerifyingKey}, keys::{FullViewingKey, Scope, SpendingKey}, value::NoteValue, Anchor, Bundle, @@ -21,11 +22,17 @@ fn criterion_benchmark(c: &mut Criterion) { let sk = SpendingKey::from_bytes([7; 32]).unwrap(); let recipient = FullViewingKey::from(&sk).address_at(0u32, Scope::External); - let vk = VerifyingKey::build(); - let pk = ProvingKey::build(); + let vk = VerifyingKey::build(OrchardCircuitVersion::FixedPostNu6_2); + let pk = ProvingKey::build(OrchardCircuitVersion::FixedPostNu6_2); let create_bundle = |num_recipients| { - let mut builder = Builder::new(BundleType::DEFAULT, Anchor::from_bytes([0; 32]).unwrap()); + let mut builder = Builder::new( + BundleType::DEFAULT, + BundleVersion::orchard_v2(), + BundleVersion::orchard_v2().default_flags(), + Anchor::from_bytes([0; 32]).unwrap(), + ) + .unwrap(); for _ in 0..num_recipients { builder .add_output(None, recipient, NoteValue::from_raw(10), [0; 512]) diff --git a/benches/note_decryption.rs b/benches/note_decryption.rs index 920ccbffd..44db99f25 100644 --- a/benches/note_decryption.rs +++ b/benches/note_decryption.rs @@ -1,7 +1,8 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use orchard::{ builder::{Builder, BundleType}, - circuit::ProvingKey, + bundle::BundleVersion, + circuit::{OrchardCircuitVersion, ProvingKey}, keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendingKey}, note_encryption::{CompactAction, OrchardDomain}, value::NoteValue, @@ -15,7 +16,7 @@ use pprof::criterion::{Output, PProfProfiler}; fn bench_note_decryption(c: &mut Criterion) { let rng = OsRng; - let pk = ProvingKey::build(); + let pk = ProvingKey::build(OrchardCircuitVersion::FixedPostNu6_2); let fvk = FullViewingKey::from(&SpendingKey::from_bytes([7; 32]).unwrap()); let valid_ivk = fvk.to_ivk(Scope::External); @@ -44,7 +45,13 @@ fn bench_note_decryption(c: &mut Criterion) { .collect(); let bundle = { - let mut builder = Builder::new(BundleType::DEFAULT, Anchor::from_bytes([0; 32]).unwrap()); + let mut builder = Builder::new( + BundleType::DEFAULT, + BundleVersion::orchard_v2(), + BundleVersion::orchard_v2().default_flags(), + Anchor::from_bytes([0; 32]).unwrap(), + ) + .unwrap(); // The builder pads to two actions, and shuffles their order. Add two recipients // so the first action is always decryptable. builder diff --git a/src/action.rs b/src/action.rs index 18336b412..22df672cd 100644 --- a/src/action.rs +++ b/src/action.rs @@ -201,7 +201,7 @@ pub(crate) mod testing { note_encryption::{OrchardDomain, OrchardNoteEncryption}, primitives::redpallas::{self, testing::arb_valid_spendauth_keypair}, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment}, - Note, + Note, NoteVersion, }; use super::Action; @@ -228,10 +228,10 @@ pub(crate) mod testing { prop_compose! { /// Generate an action without authorization data. - pub fn arb_unauthorized_action(spend_value: NoteValue, output_value: NoteValue)( + pub fn arb_unauthorized_action(note_version: NoteVersion, spend_value: NoteValue, output_value: NoteValue)( nf in arb_nullifier(), (_, rk) in arb_valid_spendauth_keypair(), - note in arb_note(output_value), + note in arb_note(output_value, note_version), rng_seed in prop::array::uniform32(prop::num::u8::ANY), ) -> Action<()> { let cmx = ExtractedNoteCommitment::from(note.commitment()); @@ -254,10 +254,10 @@ pub(crate) mod testing { prop_compose! { /// Generate an action with invalid (random) authorization data. - pub fn arb_action(spend_value: NoteValue, output_value: NoteValue)( + pub fn arb_action(note_version: NoteVersion, spend_value: NoteValue, output_value: NoteValue)( nf in arb_nullifier(), (rsk, rk) in arb_valid_spendauth_keypair(), - note in arb_note(output_value), + note in arb_note(output_value, note_version), enc_rng_seed in prop::array::uniform32(prop::num::u8::ANY), rng_seed in prop::array::uniform32(prop::num::u8::ANY), fake_sighash in prop::array::uniform32(prop::num::u8::ANY), diff --git a/src/address.rs b/src/address.rs index bc79e5027..aa3ec74be 100644 --- a/src/address.rs +++ b/src/address.rs @@ -47,6 +47,16 @@ impl Address { &self.pk_d } + /// Returns whether `self` and `other` correspond to the same expanded receiver, i.e. + /// have equal `(g_d, pk_d)`. + /// + /// This matches the equality notion used by the `disableCrossAddress` circuit + /// constraint, and is intentionally distinct from `PartialEq` on `Address`, which + /// compares the raw diversifier encoding. + pub(crate) fn same_expanded_receiver(&self, other: &Self) -> bool { + self.g_d() == other.g_d() && self.pk_d() == other.pk_d() + } + /// Serializes this address to its "raw" encoding as specified in [Zcash Protocol Spec § 5.6.4.2: Orchard Raw Payment Addresses][orchardpaymentaddrencoding] /// /// [orchardpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding diff --git a/src/builder.rs b/src/builder.rs index 109613d46..75aa7887f 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -8,15 +8,16 @@ use core::iter; use ff::Field; use pasta_curves::pallas; use rand::{prelude::SliceRandom, CryptoRng, RngCore}; +use zcash_note_encryption::ENC_CIPHERTEXT_SIZE; use crate::{ address::Address, - bundle::{Authorization, Authorized, Bundle, Flags}, + bundle::{Authorization, Authorized, Bundle, BundleVersion, Flags}, keys::{ FullViewingKey, OutgoingViewingKey, Scope, SpendAuthorizingKey, SpendValidatingKey, SpendingKey, }, - note::{ExtractedNoteCommitment, Note, Nullifier, Rho, TransmittedNoteCiphertext}, + note::{ExtractedNoteCommitment, Note, NoteVersion, Nullifier, Rho, TransmittedNoteCiphertext}, note_encryption::OrchardNoteEncryption, primitives::redpallas::{self, Binding, SpendAuth}, tree::{Anchor, MerklePath}, @@ -36,54 +37,68 @@ use { const MIN_ACTIONS: usize = 2; /// An enumeration of rules for Orchard bundle construction. +/// +/// This selects only the construction discipline; the bundle's [`Flags`] are supplied separately +/// to the builder (see [`Builder::new`] and [`BundleVersion::default_flags`]). #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BundleType { /// A transactional bundle will be padded if necessary to contain at least 2 actions, /// irrespective of whether any genuine actions are required. Transactional { - /// The flags that control whether spends and/or outputs are enabled for the bundle. - flags: Flags, /// A flag that, when set to `true`, indicates that a bundle should be produced even if no /// spends or outputs have been added to the bundle; in such a circumstance, all of the /// actions in the resulting bundle will be dummies. bundle_required: bool, }, - /// A coinbase bundle is required to have no non-dummy spends. No padding is performed. + /// A coinbase bundle performs no padding and requires the bundle's flags to disable spends. + /// + /// Since coinbase transactions have `enableSpends = 0`, every spend must be a dummy. Coinbase + /// transactions are not otherwise any different wrt cross-address restrictions from other + /// transactions that have dummy inputs. Coinbase, } impl BundleType { - /// The default bundle type has all flags enabled, and does not require a bundle to be produced - /// if no spends or outputs have been added to the bundle. + /// The default bundle type: a transactional bundle that is not required to be produced if no + /// spends or outputs have been added. pub const DEFAULT: BundleType = BundleType::Transactional { - flags: Flags::ENABLED, - bundle_required: false, - }; - - /// The DISABLED bundle type does not permit any bundle to be produced, and when used in the - /// builder will prevent any spends or outputs from being added. - pub const DISABLED: BundleType = BundleType::Transactional { - flags: Flags::from_parts(false, false), bundle_required: false, }; - /// Returns the number of logical actions that builder will produce in constructing a bundle - /// of this type, given the specified numbers of spends and outputs. + /// Returns the number of logical actions that the builder will produce in constructing a bundle + /// of this type with the given `flags`, given the specified numbers of spends and outputs. + /// + /// In the current implementation, for a bundle (regardless of type) that disables + /// cross-address transfers, a requested spend and a requested output do not share an + /// action (each is paired with a fabricated zero-valued counterpart), so the number of + /// requested actions is `num_spends + num_outputs` rather than `max(num_spends, num_outputs)`. + /// Wallets estimating fees (e.g. per [ZIP 317]) must account for this larger action + /// count. /// /// Returns an error if the specified number of spends and outputs is incompatible with - /// this bundle type. + /// this bundle type and flags. + /// + /// [ZIP 317]: https://zips.z.cash/zip-0317 pub fn num_actions( &self, + flags: Flags, num_spends: usize, num_outputs: usize, ) -> Result { - let num_requested_actions = core::cmp::max(num_spends, num_outputs); - match self { - BundleType::Transactional { - flags, - bundle_required, - } => { + BundleType::Transactional { bundle_required } => { + // When cross-address transfers are disabled, every action's output is + // addressed to the note it spends. For this implementation, a requested + // spend and a requested output never share an action: each is paired with + // a fabricated zero-valued counterpart instead. + let num_requested_actions = if !flags.cross_address_enabled() { + num_spends + .checked_add(num_outputs) + .ok_or("num_spends + num_outputs overflowed")? + } else { + core::cmp::max(num_spends, num_outputs) + }; + if !flags.spends_enabled() && num_spends > 0 { Err("Spends are disabled, so num_spends must be zero") } else if !flags.outputs_enabled() && num_outputs > 0 { @@ -105,14 +120,6 @@ impl BundleType { } } } - - /// Returns the set of flags and the anchor that will be used for bundle construction. - pub fn flags(&self) -> Flags { - match self { - BundleType::Transactional { flags, .. } => *flags, - BundleType::Coinbase => Flags::SPENDS_DISABLED, - } - } } /// An error type for the kinds of errors that can occur during bundle construction. @@ -121,7 +128,7 @@ impl BundleType { pub enum BuildError { /// Spends are disabled for the provided bundle type. SpendsDisabled, - /// Spends are disabled for the provided bundle type. + /// Outputs are disabled for the provided bundle type. OutputsDisabled, /// The anchor provided to this builder doesn't match the Merkle path used to add a spend. AnchorMismatch, @@ -140,6 +147,16 @@ pub enum BuildError { DuplicateSignature, /// The bundle being constructed violated the construction rules for the requested bundle type. BundleTypeNotSatisfiable, + /// Cross-address transfers are disabled for the bundle being constructed, and an + /// output is not a wallet-controlled change output. + CrossAddressDisabled, + /// A supplied output or change output has a note version that is + /// inconsistent with the bundle version. + InvalidNoteVersion, + /// The builder's flags cannot be encoded under its [`BundleVersion`]. + UnrepresentableFlags, + /// A coinbase bundle was requested with flags that enable spends. + CoinbaseSpendsEnabled, } impl fmt::Display for BuildError { @@ -148,7 +165,13 @@ impl fmt::Display for BuildError { match self { MissingSignatures => f.write_str("Required signatures were missing during build"), #[cfg(feature = "circuit")] - Proof(e) => f.write_str(&format!("Could not create proof: {}", e)), + Proof(halo2_proofs::plonk::Error::InvalidInstances) => f.write_str( + "Could not create proof: provided instances do not match the circuit, or \ + the cross-address restriction is not supported by the proving key's \ + circuit version", + ), + #[cfg(feature = "circuit")] + Proof(e) => write!(f, "Could not create proof: {e}"), ValueSum(_) => f.write_str("Overflow occurred during value construction"), InvalidExternalSignature => f.write_str("External signature was invalid"), DuplicateSignature => f.write_str("Signature valid for more than one input"), @@ -156,10 +179,26 @@ impl fmt::Display for BuildError { f.write_str("Bundle structure did not conform to requested bundle type.") } SpendsDisabled => f.write_str("Spends are not enabled for the requested bundle type."), - OutputsDisabled => f.write_str("Spends are not enabled for the requested bundle type."), + OutputsDisabled => { + f.write_str("Outputs are not enabled for the requested bundle type.") + } AnchorMismatch => { f.write_str("All spends must share the anchor requested for the transaction.") } + CrossAddressDisabled => f.write_str( + "Cross-address transfers are disabled for this bundle: every output must \ + be a wallet-controlled change output.", + ), + InvalidNoteVersion => f.write_str( + "A supplied output or change output has a note version that does not match \ + the bundle version.", + ), + UnrepresentableFlags => f.write_str( + "The requested flags cannot be encoded under the requested bundle version.", + ), + CoinbaseSpendsEnabled => { + f.write_str("A coinbase bundle was requested with flags that enable spends.") + } } } } @@ -212,6 +251,15 @@ impl std::error::Error for SpendError {} pub enum OutputError { /// Outputs aren't enabled for this builder. OutputsDisabled, + /// Spends aren't enabled for this builder. A wallet-controlled change output in a bundle + /// that disables cross-address transfers pairs with a fabricated spend, so we do not support + /// change outputs when both spends and cross-address transfers are disabled. + SpendsDisabled, + /// Cross-address transfers are disabled for this builder, so ordinary outputs cannot + /// be added; use [`Builder::add_change_output`] for wallet-controlled change. + CrossAddressDisabled, + /// The full viewing key provided does not own the recipient address. + RecipientNotOwned, } impl fmt::Display for OutputError { @@ -219,6 +267,12 @@ impl fmt::Display for OutputError { use OutputError::*; f.write_str(match self { OutputsDisabled => "Outputs are not enabled for this builder", + SpendsDisabled => "Spends are not enabled for this builder", + CrossAddressDisabled => { + "Cross-address transfers are disabled for this builder; use \ + add_change_output for wallet-controlled change" + } + RecipientNotOwned => "FullViewingKey does not own the recipient address", }) } } @@ -260,8 +314,8 @@ impl SpendInfo { /// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes]. /// /// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes - fn dummy(rng: &mut impl RngCore) -> Self { - let (sk, fvk, note) = Note::dummy(rng, None); + fn dummy(note_version: NoteVersion, rng: &mut impl RngCore) -> Self { + let (sk, fvk, note) = Note::dummy(rng, None, note_version); let merkle_path = MerklePath::dummy(rng); SpendInfo { @@ -318,6 +372,7 @@ impl SpendInfo { value: Some(self.note.value()), rho: Some(self.note.rho()), rseed: Some(*self.note.rseed()), + note_version: self.note.version(), fvk: Some(self.fvk), witness: Some(self.merkle_path), alpha: Some(alpha), @@ -329,12 +384,29 @@ impl SpendInfo { } /// Information about a specific output to receive funds in an [`Action`]. +/// +/// This carries a plain output to an arbitrary recipient. For wallet-controlled change, +/// which additionally records the owning full viewing key, see [`ChangeInfo`]. #[derive(Debug)] pub struct OutputInfo { ovk: Option, recipient: Address, value: NoteValue, memo: [u8; 512], + note_version: NoteVersion, + /// When set, `build` fills `enc_ciphertext` with random bytes instead of encrypting the + /// note to `recipient`. This is used only for the zero-valued output that a builder with + /// cross-address actions disabled pairs with a real spend, which is addressed to the + /// spent note's own receiver. A real ciphertext there would trial-decrypt under that + /// receiver's incoming viewing key in the same action that carries the spend's nullifier, + /// letting anyone who holds the ivk -- including a quantum adversary who recovered it from + /// the published address -- detect the spend. The note and its commitment are unchanged. + /// + /// In a PCZT the output still carries its explicit `recipient`, `value`, and `rseed` with no + /// `user_address`, so a signer fails to recover anything from the ciphertext, reads the zero + /// value, and classifies it as a dummy output it tolerates -- rather than reconstructing the + /// expected ciphertext and rejecting the mismatch. + randomized_ciphertext: bool, } impl OutputInfo { @@ -343,6 +415,7 @@ impl OutputInfo { ovk: Option, recipient: Address, value: NoteValue, + note_version: NoteVersion, memo: [u8; 512], ) -> Self { Self { @@ -350,17 +423,35 @@ impl OutputInfo { recipient, value, memo, + note_version, + randomized_ciphertext: false, + } + } + + /// Constructs the zero-valued output that a builder with cross-address actions + /// disabled pairs with a real spend. + /// + /// `recipient` is the spent note's own receiver, so the output's `enc_ciphertext` is + /// randomized rather than encrypted to it (see the `randomized_ciphertext` field). + fn fabricated_for_spend(note_version: NoteVersion, recipient: Address) -> Self { + Self { + ovk: None, + recipient, + value: NoteValue::ZERO, + memo: [0u8; 512], + note_version, + randomized_ciphertext: true, } } /// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes]. /// /// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes - pub fn dummy(rng: &mut impl RngCore) -> Self { + pub fn dummy(note_version: NoteVersion, rng: &mut impl RngCore) -> Self { let fvk: FullViewingKey = (&SpendingKey::random(rng)).into(); let recipient = fvk.address_at(0u32, Scope::External); - Self::new(None, recipient, NoteValue::ZERO, [0u8; 512]) + Self::new(None, recipient, NoteValue::ZERO, note_version, [0u8; 512]) } /// Builds the output half of an action. @@ -375,15 +466,32 @@ impl OutputInfo { mut rng: impl RngCore, ) -> (Note, ExtractedNoteCommitment, TransmittedNoteCiphertext) { let rho = Rho::from_nf_old(nf_old); - let note = Note::new(self.recipient, self.value, rho, &mut rng); + let note = Note::new(self.recipient, self.value, rho, self.note_version, &mut rng); let cm_new = note.commitment(); let cmx = cm_new.into(); + // The Orchard and Ironwood encryptor aliases share encryption behavior; + // `Note::version()` selects the note plaintext lead byte. let encryptor = OrchardNoteEncryption::new(self.ovk.clone(), note, self.memo); + // `encryptor` still supplies a valid non-identity `epk` and, because these outputs use + // `ovk = None`, a random `out_ciphertext`. Only `enc_ciphertext` is replaced. + let enc_ciphertext = if self.randomized_ciphertext { + assert_eq!( + self.value, + NoteValue::ZERO, + "a randomized note ciphertext must never stand in for a nonzero-valued note", + ); + let mut enc_ciphertext = [0u8; ENC_CIPHERTEXT_SIZE]; + rng.fill_bytes(&mut enc_ciphertext); + enc_ciphertext + } else { + encryptor.encrypt_note_plaintext() + }; + let encrypted_note = TransmittedNoteCiphertext { epk_bytes: encryptor.epk().to_bytes().0, - enc_ciphertext: encryptor.encrypt_note_plaintext(), + enc_ciphertext, out_ciphertext: encryptor.encrypt_outgoing_plaintext(cv_net, &cmx, &mut rng), }; @@ -400,6 +508,7 @@ impl OutputInfo { crate::pczt::Output { cmx, + note_version: self.note_version, encrypted_note, recipient: Some(self.recipient), value: Some(self.value), @@ -414,6 +523,55 @@ impl OutputInfo { } } +/// Information about a wallet-controlled change output. +/// +/// This is an [`OutputInfo`] to a `recipient` owned by `fvk`, with that ownership recorded. +/// In a bundle that disables cross-address transfers it is the only way to retain shielded +/// value: the builder pairs it with a fabricated zero-valued spend controlled by `fvk` at +/// `recipient`, in the same action. In a bundle that permits cross-address transfers it is +/// equivalent to the underlying [`OutputInfo`] (the ownership is validated when the +/// `ChangeInfo` is constructed, then plays no further role). +#[derive(Debug)] +pub struct ChangeInfo { + output: OutputInfo, + fvk: FullViewingKey, + scope: Scope, +} + +impl ChangeInfo { + /// Constructs a wallet-controlled change output to `recipient`, owned by `fvk`. + /// + /// Returns [`OutputError::RecipientNotOwned`] if `fvk` does not own `recipient`. The + /// recorded `fvk`/scope are used only to fabricate the paired same-expanded-receiver spend in a + /// bundle that disables cross-address transfers; they do not affect the output's note, + /// commitment, or ciphertext. + pub fn new( + fvk: FullViewingKey, + ovk: Option, + recipient: Address, + value: NoteValue, + note_version: NoteVersion, + memo: [u8; 512], + ) -> Result { + let scope = fvk + .scope_for_address(&recipient) + .ok_or(OutputError::RecipientNotOwned)?; + Ok(Self { + output: OutputInfo::new(ovk, recipient, value, note_version, memo), + fvk, + scope, + }) + } + + /// Discards the wallet-ownership information, yielding the underlying plain output. + /// + /// Used on the build path for bundles that permit cross-address transfers, where the + /// ownership has already served its purpose (validation at construction). + fn into_output(self) -> OutputInfo { + self.output + } +} + /// Information about a specific [`Action`] we plan to build. #[derive(Debug)] struct ActionInfo { @@ -436,23 +594,15 @@ impl ActionInfo { self.spend.note.value() - self.output.value } - /// Builds the action. + /// Builds the action for a given circuit version. /// /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend]. /// - /// The circuit version defaults to [`OrchardCircuitVersion::FixedPostNu6_2`], - /// which should be used for all new proofs. + /// The circuit version must be consistent between actions in a bundle. /// /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend #[cfg(feature = "circuit")] - fn build(self, rng: impl RngCore) -> (Action, Circuit) { - self.build_for_version(rng, OrchardCircuitVersion::FixedPostNu6_2) - } - - /// Builds the action for a given circuit version. This must be consistent - /// between actions in a bundle. - #[cfg(feature = "circuit")] - fn build_for_version( + fn build( self, mut rng: impl RngCore, circuit_version: OrchardCircuitVersion, @@ -511,12 +661,12 @@ impl ActionInfo { #[cfg(feature = "circuit")] pub type UnauthorizedBundle = Bundle, V>; -/// Metadata about a bundle created by [`bundle`] or [`Builder::build`] that is not -/// necessarily recoverable from the bundle itself. +/// Metadata about a bundle created by [`bundle`] or [`Builder::build`] that is not necessarily +/// recoverable from the bundle itself. /// /// This includes information about how [`Action`]s within the bundle are ordered (after -/// padding and randomization) relative to the order in which spends and outputs were -/// provided (to [`bundle`]), or the order in which [`Builder`] mutations were performed. +/// padding and randomization) relative to the order in which spends and outputs were provided +/// (to [`bundle`]), or the order in which [`Builder`] mutations were performed. #[derive(Debug, Clone, PartialEq, Eq)] pub struct BundleMetadata { spend_indices: Vec, @@ -543,19 +693,26 @@ impl BundleMetadata { /// For the purpose of improving indistinguishability, actions are padded and note /// positions are randomized when building bundles. This means that the bundle /// consumer cannot assume that e.g. the first spend they added corresponds to the - /// first action in the bundle. + /// first action in the bundle. In a bundle that disables cross-address transfers, + /// each spend's action contains a fabricated zero-valued output (to the spent note's + /// own address), so no requested output shares the returned action index. pub fn spend_action_index(&self, n: usize) -> Option { self.spend_indices.get(n).copied() } /// Returns the index within the bundle of the [`Action`] corresponding to the `n`-th - /// output specified in bundle construction. If a [`Builder`] was used, this refers to - /// the output added by the `n`-th call to [`Builder::add_output`]. + /// requested output. Requested outputs are numbered as: + /// * all plain outputs (in the order they were added with [`Builder::add_output`], or their + /// order in the `outputs` vector passed to [`bundle`]); + /// * followed by all wallet-controlled change outputs (in the order they were added with + /// [`Builder::add_change_output`], or their order in the `changes` vector passed to [`bundle`]). /// /// For the purpose of improving indistinguishability, actions are padded and note /// positions are randomized when building bundles. This means that the bundle /// consumer cannot assume that e.g. the first output they added corresponds to the - /// first action in the bundle. + /// first action in the bundle. In a bundle that disables cross-address transfers, + /// each output's action contains a fabricated wallet-controlled zero-valued spend (at + /// the change address), so no requested spend shares the returned action index. pub fn output_action_index(&self, n: usize) -> Option { self.output_indices.get(n).copied() } @@ -565,77 +722,77 @@ impl BundleMetadata { /// to receive funds. #[derive(Debug)] pub struct Builder { + bundle_type: BundleType, + bundle_version: BundleVersion, + flags: Flags, spends: Vec, outputs: Vec, - bundle_type: BundleType, + changes: Vec, anchor: Anchor, - // Only proving (the `circuit` feature) consults the circuit version. - #[cfg(feature = "circuit")] - circuit_version: OrchardCircuitVersion, } impl Builder { - /// Constructs a new empty builder for an Orchard bundle. - #[cfg_attr( - feature = "circuit", - doc = "", - doc = "When proving, the circuit version defaults to `FixedPostNu6_2`, which should be used", - doc = "for all new proofs; use [`Builder::new_for_version`] to choose another." - )] - pub fn new(bundle_type: BundleType, anchor: Anchor) -> Self { - Self::new_internal( - bundle_type, - anchor, - #[cfg(feature = "circuit")] - OrchardCircuitVersion::FixedPostNu6_2, - ) - } - - /// Constructs a new empty builder for an Orchard bundle with a given - /// circuit version. + /// Constructs a new empty builder for an Orchard bundle following `bundle_version` with the + /// given `flags`. /// - /// Setting this to [`OrchardCircuitVersion::InsecurePreNu6_2`] produces a - /// bundle whose proof must be created with an insecure proving key (see - /// [`ProvingKey::build_for_version`]); this is intended only for - /// reproducing pre-NU6.2 proofs in tests, never for proving transactions - /// for the network. + /// `bundle_version` is the [`ValuePool`](crate::ValuePool) (Orchard or Ironwood) and + /// [`ProtocolVersion`](crate::ProtocolVersion) of the bundles created by this builder. It + /// influences the circuit version, the flag-byte format, and the cross-address policy, and is + /// threaded into building, committing, and parsing. See [`BundleVersion`]. /// - /// [`ProvingKey::build_for_version`]: crate::circuit::ProvingKey::build_for_version - #[cfg(feature = "circuit")] - pub fn new_for_version( - bundle_type: BundleType, - anchor: Anchor, - circuit_version: OrchardCircuitVersion, - ) -> Self { - Self::new_internal(bundle_type, anchor, circuit_version) - } - - fn new_internal( + /// `flags` are the bundle's flags; [`BundleVersion::default_flags`] provides a suitable default + /// that a caller may restrict further. + /// + /// # Errors + /// + /// Returns [`BuildError::UnrepresentableFlags`] if `flags` cannot be encoded under + /// `bundle_version`, or [`BuildError::CoinbaseSpendsEnabled`] if `bundle_type` is + /// [`BundleType::Coinbase`] but `flags` enable spends. + pub fn new( bundle_type: BundleType, + bundle_version: BundleVersion, + flags: Flags, anchor: Anchor, - #[cfg(feature = "circuit")] circuit_version: OrchardCircuitVersion, - ) -> Self { - Builder { + ) -> Result { + if flags.to_byte(bundle_version).is_none() { + return Err(BuildError::UnrepresentableFlags); + } + if matches!(bundle_type, BundleType::Coinbase) && flags.spends_enabled() { + return Err(BuildError::CoinbaseSpendsEnabled); + } + Ok(Builder { + bundle_type, + bundle_version, + flags, spends: vec![], outputs: vec![], - bundle_type, + changes: vec![], anchor, - #[cfg(feature = "circuit")] - circuit_version, - } + }) + } + + /// Returns the note version associated with this builder's bundle version. + fn note_version(&self) -> NoteVersion { + self.bundle_version.note_version() } /// Adds a note to be spent in this transaction. /// - /// - `note` is a spendable note, obtained by trial-decrypting an [`Action`] using the - /// [`zcash_note_encryption`] crate instantiated with [`OrchardDomain`]. + /// - `note` is a spendable note, obtained by trial-decrypting an [`Action`] + /// under the bundle's version. /// - `merkle_path` can be obtained using the [`incrementalmerkletree`] crate /// instantiated with [`MerkleHashOrchard`]. /// /// Returns an error if the given Merkle path does not have the required anchor for /// the given note. /// - /// [`OrchardDomain`]: crate::note_encryption::OrchardDomain + /// In a bundle that disables cross-address transfers, each spend is paired with a + /// fabricated zero-valued output addressed to the spent note's own receiver. That output's + /// note ciphertext is randomized rather than encrypted to the receiver, so it is + /// undecryptable: the owning wallet does not see it when scanning, and no holder of the + /// receiver's incoming viewing key -- including a quantum adversary who recovered it from + /// the published address -- can use it to detect the spend. + /// /// [`MerkleHashOrchard`]: crate::tree::MerkleHashOrchard pub fn add_spend( &mut self, @@ -643,8 +800,7 @@ impl Builder { note: Note, merkle_path: MerklePath, ) -> Result<(), SpendError> { - let flags = self.bundle_type.flags(); - if !flags.spends_enabled() { + if !self.flags.spends_enabled() { return Err(SpendError::SpendsDisabled); } @@ -661,6 +817,10 @@ impl Builder { } /// Adds an address which will receive funds in this transaction. + /// + /// In a bundle that disables cross-address transfers, ordinary outputs cannot be + /// constructed (each action's output is addressed to the note it spends); retained + /// value must be added with [`Builder::add_change_output`] instead. pub fn add_output( &mut self, ovk: Option, @@ -668,13 +828,68 @@ impl Builder { value: NoteValue, memo: [u8; 512], ) -> Result<(), OutputError> { - let flags = self.bundle_type.flags(); - if !flags.outputs_enabled() { + if !self.flags.outputs_enabled() { return Err(OutputError::OutputsDisabled); } + if !self.flags.cross_address_enabled() { + return Err(OutputError::CrossAddressDisabled); + } - self.outputs - .push(OutputInfo::new(ovk, recipient, value, memo)); + self.outputs.push(OutputInfo::new( + ovk, + recipient, + value, + self.note_version(), + memo, + )); + + Ok(()) + } + + /// Adds a wallet-controlled change output, to an address owned by `fvk`. + /// + /// This is the only way to retain shielded value in a bundle that disables + /// cross-address transfers: the builder pairs the change output with a fabricated + /// zero-valued spend at `recipient`, controlled by `fvk`, in the same action. + /// (Withdrawals leave such a bundle through its positive value balance; its real + /// spends are each paired with a fabricated zero-valued output to the spent note's + /// own address.) The fabricated spend's authorization is produced by the normal + /// signing flow -- [`Bundle::apply_signatures`] with the [`SpendAuthorizingKey`] + /// matching `fvk` -- exactly like the bundle's real spends. + /// + /// This may also be used in bundles that permit cross-address transfers, where it + /// behaves like [`Builder::add_output`] plus an ownership check, so wallet change + /// logic can be uniform across bundle kinds. + /// + /// Note that the builder does not constrain the sign of the bundle's value balance: + /// a bundle that disables cross-address transfers can still have a negative balance + /// (value entering the Orchard pool from the rest of the transaction). Whether such + /// bundles are acceptable is a transaction-level concern outside this crate. + /// + /// Returns an error if outputs are disabled for this builder's bundle type, if the + /// bundle disables cross-address transfers but has spends disabled (the paired + /// fabricated spend could not be created), or if `fvk` does not own `recipient`. + pub fn add_change_output( + &mut self, + fvk: FullViewingKey, + ovk: Option, + recipient: Address, + value: NoteValue, + memo: [u8; 512], + ) -> Result<(), OutputError> { + if !self.flags.outputs_enabled() { + return Err(OutputError::OutputsDisabled); + } + // In a bundle that disables cross-address transfers, every change output pairs with + // a fabricated wallet-controlled spend, so spends must be enabled. (In a bundle that + // permits cross-address transfers, a change output is just an owned output and does + // not require spends.) + if !self.flags.cross_address_enabled() && !self.flags.spends_enabled() { + return Err(OutputError::SpendsDisabled); + } + + let change = ChangeInfo::new(fvk, ovk, recipient, value, self.note_version(), memo)?; + self.changes.push(change); Ok(()) } @@ -691,6 +906,12 @@ impl Builder { &self.outputs } + /// Returns the wallet-controlled change outputs that will be produced by the + /// transaction being constructed. + pub fn changes(&self) -> &Vec { + &self.changes + } + /// The net value of the bundle to be built. The value of all spends, /// minus the value of all outputs. /// @@ -711,28 +932,38 @@ impl Builder { .iter() .map(|output| NoteValue::ZERO - output.value), ) + .chain( + self.changes + .iter() + .map(|change| NoteValue::ZERO - change.output.value), + ) .try_fold(ValueSum::zero(), |acc, note_value| acc + note_value) .ok_or(BalanceError::Overflow)?; i64::try_from(value_balance) .and_then(|i| V::try_from(i).map_err(|_| value::BalanceError::Overflow)) } - /// Builds a bundle containing the given spent notes and outputs. + /// Builds a bundle containing the given spent notes and outputs, under this builder's + /// [`BundleVersion`]. /// /// The returned bundle will have no proof or signatures; these can be applied with - /// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively. + /// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively. The proof must be + /// created with a [`ProvingKey`] for the circuit version consistent with the builder's + /// bundle version. #[cfg(feature = "circuit")] pub fn build>( self, rng: impl RngCore, ) -> Result, BundleMetadata)>, BuildError> { - bundle_for_version( + bundle( rng, - self.anchor, self.bundle_type, + self.bundle_version, + self.flags, + self.anchor, self.spends, self.outputs, - self.circuit_version, + self.changes, ) } @@ -744,10 +975,13 @@ impl Builder { ) -> Result<(crate::pczt::Bundle, BundleMetadata), BuildError> { build_bundle( rng, + self.bundle_version, + self.flags, self.anchor, self.bundle_type, self.spends, self.outputs, + self.changes, |pre_actions, flags, value_sum, bundle_meta, mut rng| { // Create the actions. let actions = pre_actions @@ -759,6 +993,7 @@ impl Builder { crate::pczt::Bundle { actions, flags, + bundle_version: self.bundle_version, value_sum, anchor: self.anchor, zkproof: None, @@ -771,109 +1006,134 @@ impl Builder { } } -/// Builds a bundle containing the given spent notes and outputs. +/// Builds a bundle containing the given spent notes, outputs, and wallet-controlled change +/// outputs, under the given [`BundleVersion`] (which selects the Action circuit +/// version, the flag-byte format, and the cross-address policy). +/// +/// In a bundle that disables cross-address transfers, `outputs` must be empty (every output +/// is addressed to the note it spends); retained value must be supplied as `changes`. /// -/// The returned bundle will have no proof or signatures; these can be applied with -/// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively. +/// # Errors +/// +/// Returns [`BuildError::UnrepresentableFlags`] if `flags` cannot be encoded under +/// `bundle_version`, or [`BuildError::CoinbaseSpendsEnabled`] if `bundle_type` is +/// [`BundleType::Coinbase`] but `flags` enable spends. +#[allow(clippy::too_many_arguments)] #[cfg(feature = "circuit")] pub fn bundle>( rng: impl RngCore, - anchor: Anchor, bundle_type: BundleType, + bundle_version: BundleVersion, + flags: Flags, + anchor: Anchor, spends: Vec, outputs: Vec, + changes: Vec, ) -> Result, BundleMetadata)>, BuildError> { - bundle_for_version( + build_bundle( rng, + bundle_version, + flags, anchor, bundle_type, spends, outputs, - OrchardCircuitVersion::FixedPostNu6_2, + changes, + |pre_actions, flags, value_balance, bundle_meta, rng| { + finish_unauthorized_bundle( + pre_actions, + flags, + value_balance, + bundle_meta, + rng, + anchor, + bundle_version, + ) + }, ) } -/// Builds a bundle containing the given spent notes and outputs, with the Action circuits -/// built for the given `circuit_version`. -/// -/// Only [`OrchardCircuitVersion::FixedPostNu6_2`] should be used to prove transactions for the -/// network; [`OrchardCircuitVersion::InsecurePreNu6_2`] exists only to reproduce pre-NU6.2 -/// proofs in tests, and requires an insecure proving key (see -/// [`ProvingKey::build_for_version`]) to create the proof. -/// -/// [`ProvingKey::build_for_version`]: crate::circuit::ProvingKey::build_for_version #[cfg(feature = "circuit")] -pub fn bundle_for_version>( - rng: impl RngCore, +fn finish_unauthorized_bundle, R: RngCore>( + pre_actions: Vec, + flags: Flags, + value_balance: ValueSum, + bundle_meta: BundleMetadata, + mut rng: R, anchor: Anchor, - bundle_type: BundleType, - spends: Vec, - outputs: Vec, - circuit_version: OrchardCircuitVersion, + bundle_version: BundleVersion, ) -> Result, BundleMetadata)>, BuildError> { - build_bundle( - rng, - anchor, - bundle_type, - spends, - outputs, - |pre_actions, flags, value_balance, bundle_meta, mut rng| { - let result_value_balance: V = i64::try_from(value_balance) - .map_err(BuildError::ValueSum) - .and_then(|i| { - V::try_from(i).map_err(|_| BuildError::ValueSum(value::BalanceError::Overflow)) - })?; - - // Compute the transaction binding signing key. - let bsk = pre_actions - .iter() - .map(|a| &a.rcv) - .sum::() - .into_bsk(); - - // Create the actions. - let (actions, circuits): (Vec<_>, Vec<_>) = pre_actions - .into_iter() - .map(|a| a.build_for_version(&mut rng, circuit_version)) - .unzip(); - - // Verify that bsk and bvk are consistent. - let bvk = (actions.iter().map(|a| a.cv_net()).sum::() - - ValueCommitment::derive(value_balance, ValueCommitTrapdoor::zero())) - .into_bvk(); - assert_eq!(redpallas::VerificationKey::from(&bsk), bvk); - - Ok(NonEmpty::from_vec(actions).map(|actions| { - ( - Bundle::from_parts_unchecked( - actions, - flags, - result_value_balance, - anchor, - InProgress { - proof: Unproven { - circuits, - circuit_version, - }, - sigs: Unauthorized { bsk }, - }, - ), - bundle_meta, - ) - })) - }, - ) + let circuit_version = bundle_version.circuit_version(); + let result_value_balance: V = i64::try_from(value_balance) + .map_err(BuildError::ValueSum) + .and_then(|i| { + V::try_from(i).map_err(|_| BuildError::ValueSum(value::BalanceError::Overflow)) + })?; + + // Compute the transaction binding signing key. + let bsk = pre_actions + .iter() + .map(|a| &a.rcv) + .sum::() + .into_bsk(); + + // Create the actions. + let (actions, circuits): (Vec<_>, Vec<_>) = pre_actions + .into_iter() + .map(|a| a.build(&mut rng, circuit_version)) + .unzip(); + + // Verify that bsk and bvk are consistent. + let bvk = (actions.iter().map(|a| a.cv_net()).sum::() + - ValueCommitment::derive(value_balance, ValueCommitTrapdoor::zero())) + .into_bvk(); + assert_eq!(redpallas::VerificationKey::from(&bsk), bvk); + + Ok(NonEmpty::from_vec(actions).map(|actions| { + ( + Bundle::from_parts_unchecked( + actions, + flags, + result_value_balance, + anchor, + InProgress { + proof: Unproven { + circuits, + circuit_version, + }, + sigs: Unauthorized { bsk }, + }, + bundle_version, + ), + bundle_meta, + ) + })) } +#[allow(clippy::too_many_arguments)] fn build_bundle( mut rng: R, + bundle_version: BundleVersion, + flags: Flags, anchor: Anchor, bundle_type: BundleType, spends: Vec, outputs: Vec, + changes: Vec, finisher: impl FnOnce(Vec, Flags, ValueSum, BundleMetadata, R) -> Result, ) -> Result { - let flags = bundle_type.flags(); + // Every build path funnels through here (the free `bundle` function, `Builder::build`, and + // `Builder::build_for_pczt`), so validate the version-dependent invariants here rather than + // trusting each caller: the flags must be encodable under the bundle version, and a coinbase + // bundle must not enable spends. `Builder::new` also enforces both up front, for fail-fast + // construction. + if flags.to_byte(bundle_version).is_none() { + return Err(BuildError::UnrepresentableFlags); + } + if matches!(bundle_type, BundleType::Coinbase) && flags.spends_enabled() { + return Err(BuildError::CoinbaseSpendsEnabled); + } + let note_version = bundle_version.note_version(); let num_requested_spends = spends.len(); if !flags.spends_enabled() && num_requested_spends > 0 { @@ -886,27 +1146,150 @@ fn build_bundle( } } - let num_requested_outputs = outputs.len(); + // Requested outputs are numbered for `BundleMetadata` as the plain `outputs` in order, + // followed by the wallet-controlled `changes` in order. + let num_plain_outputs = outputs.len(); + let num_requested_outputs = num_plain_outputs + changes.len(); if !flags.outputs_enabled() && num_requested_outputs > 0 { return Err(BuildError::OutputsDisabled); } + if outputs + .iter() + .any(|output| output.note_version != note_version) + || changes + .iter() + .any(|change| change.output.note_version != note_version) + { + return Err(BuildError::InvalidNoteVersion); + } + if !flags.spends_enabled() && !flags.cross_address_enabled() && num_requested_outputs > 0 { + return Err(BuildError::BundleTypeNotSatisfiable); + } + // When cross-address transfers are disabled, every action's output is addressed to the + // note it spends, so a plain output cannot be built; retained value must arrive as a + // wallet-controlled change output (which carries the owning fvk needed to fabricate the + // paired same-expanded-receiver spend). + if !flags.cross_address_enabled() && num_plain_outputs > 0 { + return Err(BuildError::CrossAddressDisabled); + } let num_actions = bundle_type - .num_actions(num_requested_spends, num_requested_outputs) + .num_actions(flags, num_requested_spends, num_requested_outputs) .map_err(|_| BuildError::BundleTypeNotSatisfiable)?; - // Pair up the spends and outputs, extending with dummy values as necessary. - let (pre_actions, bundle_meta) = { + let (pre_actions, bundle_meta) = if !flags.cross_address_enabled() { + // Every action's output must be addressed to the note it spends, so the + // spend/output pairing within each action is intentional: + // + // - each requested spend is paired with a fabricated zero-valued output to the + // spent note's own address; + // - each requested change output is paired with a fabricated zero-valued spend + // controlled by the wallet at the change address, because withdrawn value leaves + // the bundle through its value balance, and retained value is exactly the + // wallet's change; + // - padding actions pair a dummy spend with a zero-valued output to the dummy's + // own address, since the cross-address checks apply to dummy actions too. + // + // `outputs` is empty here (a plain output was rejected above), so the only requested + // outputs are the changes, numbered `0..changes.len()`. + // + // Only complete pairs are shuffled. + let mut pairs = Vec::with_capacity(num_actions); + + for (spend_idx, spend) in spends.into_iter().enumerate() { + let output = OutputInfo::fabricated_for_spend(note_version, spend.note.recipient()); + pairs.push((Some(spend_idx), None, spend, output)); + } + + for (chg_idx, change) in changes.into_iter().enumerate() { + let ChangeInfo { output, fvk, scope } = change; + let rho = Rho::from_nf_old(Nullifier::dummy(&mut rng)); + let note = Note::new( + output.recipient, + NoteValue::ZERO, + rho, + note_version, + &mut rng, + ); + let spend = SpendInfo { + // The wallet controls this spend: it is signed through the normal + // signing flow, by the spend authorizing key matching `fvk`. + dummy_sk: None, + fvk, + scope, + note, + merkle_path: MerklePath::dummy(&mut rng), + }; + pairs.push((None, Some(chg_idx), spend, output)); + } + + while pairs.len() < num_actions { + let spend = SpendInfo::dummy(note_version, &mut rng); + let output = OutputInfo::new( + None, + spend.note.recipient(), + NoteValue::ZERO, + note_version, + [0u8; 512], + ); + pairs.push((None, None, spend, output)); + } + + // Shuffle the action pairs, so that learning the position of a specific action + // doesn't reveal anything on its own about its meaning in the transaction + // context. The spend/output pairing inside each action is intentional. + pairs.shuffle(&mut rng); + + let mut bundle_meta = BundleMetadata::new(num_requested_spends, num_requested_outputs); + let pre_actions = pairs + .into_iter() + .enumerate() + .map(|(action_idx, (spend_idx, out_idx, spend, output))| { + // Record the post-randomization spend location + if let Some(spend_idx) = spend_idx { + bundle_meta.spend_indices[spend_idx] = action_idx; + } + + // Record the post-randomization output location + if let Some(out_idx) = out_idx { + bundle_meta.output_indices[out_idx] = action_idx; + } + + assert!( + spend + .note + .recipient() + .same_expanded_receiver(&output.recipient), + "cross-address-disabled actions pair a spend with an output to the \ + same expanded receiver by construction", + ); + + ActionInfo::new(spend, output, &mut rng) + }) + .collect::>(); + + (pre_actions, bundle_meta) + } else { + // Pair up the spends and outputs, extending with dummy values as necessary. let mut indexed_spends = spends .into_iter() - .chain(iter::repeat_with(|| SpendInfo::dummy(&mut rng))) + .chain(iter::repeat_with(|| { + SpendInfo::dummy(note_version, &mut rng) + })) .enumerate() .take(num_actions) .collect::>(); + // Plain outputs first, then change outputs collapsed to plain outputs (their + // ownership was validated when each `ChangeInfo` was constructed and plays no + // further role when cross-address transfers are permitted). This ordering matches + // the `BundleMetadata` output numbering. let mut indexed_outputs = outputs .into_iter() - .chain(iter::repeat_with(|| OutputInfo::dummy(&mut rng))) + .chain(changes.into_iter().map(ChangeInfo::into_output)) + .chain(iter::repeat_with(|| { + OutputInfo::dummy(note_version, &mut rng) + })) .enumerate() .take(num_actions) .collect::>(); @@ -979,6 +1362,15 @@ pub struct Unproven { #[cfg(feature = "circuit")] impl InProgress { /// Creates the proof for this bundle. + /// + /// # Errors + /// + /// Returns [`halo2_proofs::plonk::Error::InvalidInstances`] if any provided + /// instance has `disableCrossAddress = 1` and `pk` is not an + /// [`OrchardCircuitVersion::PostNu6_3`] proving key. + /// + /// Also returns an error if `pk` does not match the circuit version this + /// bundle's actions were built for, or if proof creation fails. pub fn create_proof( &self, pk: &ProvingKey, @@ -998,6 +1390,16 @@ impl Bundle, V> { } /// Creates the proof for this bundle. + /// + /// # Errors + /// + /// Returns [`BuildError::Proof`] containing + /// [`halo2_proofs::plonk::Error::InvalidInstances`] if this bundle disables + /// cross-address transfers and `pk` is not an + /// [`OrchardCircuitVersion::PostNu6_3`] proving key. + /// + /// Also returns an error if `pk` does not match this bundle's + /// [`circuit_version`](Self::circuit_version), or if proof creation fails. pub fn create_proof( self, pk: &ProvingKey, @@ -1248,6 +1650,12 @@ impl OutputView for OutputInfo { } } +impl OutputView for ChangeInfo { + fn value>(&self) -> V { + self.output.value() + } +} + /// Generators for property testing. #[cfg(all(feature = "circuit", any(test, feature = "test-dependencies")))] #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] @@ -1263,13 +1671,13 @@ pub mod testing { use crate::{ address::testing::arb_address, - bundle::{Authorized, Bundle}, - circuit::ProvingKey, + bundle::{Authorized, Bundle, BundleVersion}, + circuit::{OrchardCircuitVersion, ProvingKey}, keys::{testing::arb_spending_key, FullViewingKey, SpendAuthorizingKey, SpendingKey}, note::testing::arb_note, tree::{Anchor, MerkleHashOrchard, MerklePath}, value::{testing::arb_positive_note_value, NoteValue, MAX_NOTE_VALUE}, - Address, Note, + Address, Note, NoteVersion, }; use super::{Builder, BundleType}; @@ -1295,7 +1703,14 @@ pub mod testing { /// Create a bundle from the set of arbitrary bundle inputs. fn into_bundle>(mut self) -> Bundle { let fvk = FullViewingKey::from(&self.sk); - let mut builder = Builder::new(BundleType::DEFAULT, self.anchor); + let bundle_version = BundleVersion::orchard_v2(); + let mut builder = Builder::new( + BundleType::DEFAULT, + bundle_version, + bundle_version.default_flags(), + self.anchor, + ) + .unwrap(); for (note, path) in self.notes.into_iter() { builder.add_spend(fvk.clone(), note, path).unwrap(); @@ -1310,7 +1725,7 @@ pub mod testing { .unwrap(); } - let pk = ProvingKey::build(); + let pk = ProvingKey::build(OrchardCircuitVersion::FixedPostNu6_2); builder .build(&mut self.rng) .unwrap() @@ -1336,7 +1751,8 @@ pub mod testing { ( // generate note values that we're certain won't exceed MAX_NOTE_VALUE in total notes in vec( - arb_positive_note_value(MAX_NOTE_VALUE / n_notes as u64).prop_flat_map(arb_note), + arb_positive_note_value(MAX_NOTE_VALUE / n_notes as u64) + .prop_flat_map(|value| arb_note(value, NoteVersion::V2)), n_notes ), output_amounts in vec( @@ -1392,35 +1808,121 @@ pub mod testing { #[cfg(all(test, feature = "circuit"))] mod tests { use rand::rngs::OsRng; + use rand::RngCore; - use super::Builder; + use super::{ + bundle, BuildError, Builder, ChangeInfo, MaybeSigned, OutputError, OutputInfo, SpendInfo, + }; use crate::{ builder::BundleType, - bundle::{Authorized, Bundle}, - circuit::ProvingKey, + bundle::{Authorized, Bundle, BundleVersion, Flags}, + circuit::{OrchardCircuitVersion, ProvingKey}, constants::MERKLE_DEPTH_ORCHARD, - keys::{FullViewingKey, Scope, SpendingKey}, - tree::EMPTY_ROOTS, + keys::{FullViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, + note::{NoteVersion, Nullifier, Rho}, + pczt::{ProverError, VerifyError}, + tree::{MerklePath, EMPTY_ROOTS}, value::NoteValue, + Address, Anchor, Note, }; + fn note_with_path( + rng: &mut impl RngCore, + recipient: Address, + value: NoteValue, + note_version: NoteVersion, + ) -> (Note, MerklePath, Anchor) { + let rho = Rho::from_nf_old(Nullifier::dummy(rng)); + let note = Note::new(recipient, value, rho, note_version, &mut *rng); + let merkle_path = MerklePath::dummy(rng); + let anchor = merkle_path.root(note.commitment().into()); + + (note, merkle_path, anchor) + } + + fn transactional(bundle_required: bool) -> BundleType { + BundleType::Transactional { bundle_required } + } + #[test] - fn shielding_bundle() { - let pk = ProvingKey::build(); - let mut rng = OsRng; + fn default_flags_match_pool_policy() { + // The builder's default flags enable spends and outputs and leave cross-address transfers + // as permissive as consensus allows. The bundle version leaves the cross-address choice free + // everywhere except Orchard from NU6.3 onward, where it is mandatorily disabled. + for bundle_version in [ + BundleVersion::orchard_insecure_v1(), + BundleVersion::orchard_v2(), + BundleVersion::ironwood_v3(), + ] { + let flags = bundle_version.default_flags(); + assert!(flags.spends_enabled()); + assert!(flags.outputs_enabled()); + assert!(flags.cross_address_enabled()); + } - let sk = SpendingKey::random(&mut rng); + // Orchard from NU6.3 onward mandates the cross-address restriction. + let flags = BundleVersion::orchard_v3().default_flags(); + assert!(flags.spends_enabled()); + assert!(flags.outputs_enabled()); + assert!(!flags.cross_address_enabled()); + + // The default flag bytes follow from the settings above. + assert_eq!( + BundleVersion::orchard_v3() + .default_flags() + .to_byte(BundleVersion::orchard_v3()), + Some(0b011), + ); + assert_eq!( + BundleVersion::ironwood_v3() + .default_flags() + .to_byte(BundleVersion::ironwood_v3()), + Some(0b111), + ); + } + + /// Creates a builder with the given `bundle_version` and `bundle_type` over the + /// empty-tree anchor, with a single 5000-zat output to a freshly derived external address. + fn output_only_builder( + rng: &mut impl RngCore, + bundle_version: BundleVersion, + bundle_type: BundleType, + ) -> Builder { + let sk = SpendingKey::random(rng); let fvk = FullViewingKey::from(&sk); let recipient = fvk.address_at(0u32, Scope::External); + // Coinbase bundles must disable spends; transactional bundles use the version's defaults. + let flags = if matches!(bundle_type, BundleType::Coinbase) { + Flags::from_parts( + false, + true, + bundle_version.permits_cross_address_transfers(), + ) + } else { + bundle_version.default_flags() + }; + let mut builder = Builder::new( - BundleType::DEFAULT, + bundle_type, + bundle_version, + flags, EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), - ); - + ) + .expect("flags are valid for the bundle version"); builder .add_output(None, recipient, NoteValue::from_raw(5000), [0u8; 512]) - .unwrap(); + .expect("output-only builders accept ordinary outputs"); + builder + } + + #[test] + fn shielding_bundle() { + let pk = ProvingKey::build(OrchardCircuitVersion::FixedPostNu6_2); + let mut rng = OsRng; + + let builder = + output_only_builder(&mut rng, BundleVersion::orchard_v2(), BundleType::DEFAULT); let balance: i64 = builder.value_balance().unwrap(); assert_eq!(balance, -5000); @@ -1436,4 +1938,682 @@ mod tests { .unwrap(); assert_eq!(bundle.value_balance(), &(-5000)) } + + #[test] + fn coinbase_bundle_builds_for_post_nu6_3() { + let mut rng = OsRng; + + // Coinbase bundles disable nonzero-valued spends. From NU6.3, consensus requires + // nActionsOrchard = 0 in a v5+ coinbase transaction (v4, still valid after NU6.3, + // has no Orchard bundle). So a post-NU6.3 coinbase bundle built by this crate must + // be an Ironwood bundle. There the builder leaves cross-address enabled by default, + // and therefore ordinary outputs build normally. + let builder = + output_only_builder(&mut rng, BundleVersion::ironwood_v3(), BundleType::Coinbase); + + let (bundle, _) = builder + .build::(&mut rng) + .expect("coinbase bundles build under the post-NU 6.3 circuit version") + .expect("a bundle is produced for the requested output"); + assert_eq!(bundle.actions().len(), 1); + assert_eq!(bundle.circuit_version(), OrchardCircuitVersion::PostNu6_3); + assert!(!bundle.flags().spends_enabled()); + assert!(bundle.flags().outputs_enabled()); + assert!(bundle.flags().cross_address_enabled()); + } + + #[test] + fn coinbase_rejects_spends_enabled_flags() { + let anchor = EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(); + let bundle_version = BundleVersion::ironwood_v3(); + + // A coinbase bundle must disable spends; the builder rejects spends-enabled flags at + // construction rather than silently producing an invalid bundle. + assert!(matches!( + Builder::new( + BundleType::Coinbase, + bundle_version, + bundle_version.default_flags(), + anchor, + ), + Err(BuildError::CoinbaseSpendsEnabled) + )); + + // Spends-disabled flags are accepted. + assert!(Builder::new( + BundleType::Coinbase, + bundle_version, + Flags::from_parts( + false, + true, + bundle_version.permits_cross_address_transfers() + ), + anchor, + ) + .is_ok()); + } + + #[test] + fn free_bundle_rejects_coinbase_spends_enabled() { + let mut rng = OsRng; + let anchor: Anchor = EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(); + let bundle_version = BundleVersion::ironwood_v3(); + + // The coinbase-spends invariant is enforced on every build path, not just at + // `Builder::new`: a direct caller of the free `bundle` function cannot silently produce a + // coinbase bundle with `enableSpends` set. + let result = bundle::( + &mut rng, + BundleType::Coinbase, + bundle_version, + bundle_version.default_flags(), // spends enabled + anchor, + vec![], + vec![], + vec![], + ); + assert!(matches!(result, Err(BuildError::CoinbaseSpendsEnabled))); + } + + #[test] + fn new_rejects_unrepresentable_flags() { + // Orchard from NU6.3 onward cannot encode cross-address-enabled flags. + let bundle_version = BundleVersion::orchard_v3(); + assert!(matches!( + Builder::new( + BundleType::DEFAULT, + bundle_version, + Flags::ENABLED, + EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), + ), + Err(BuildError::UnrepresentableFlags) + )); + } + + #[test] + fn cross_address_disabled_builder_pairs_actions() { + let mut rng = OsRng; + let spend_sk = SpendingKey::random(&mut rng); + let spend_fvk = FullViewingKey::from(&spend_sk); + let spend_recipient = spend_fvk.address_at(0u32, Scope::External); + let change_sk = SpendingKey::random(&mut rng); + let change_fvk = FullViewingKey::from(&change_sk); + let change_recipient = change_fvk.address_at(0u32, Scope::Internal); + let bundle_version = BundleVersion::orchard_v3(); + let (note, merkle_path, anchor) = note_with_path( + &mut rng, + spend_recipient, + NoteValue::from_raw(15_000), + bundle_version.note_version(), + ); + + let mut builder = Builder::new( + transactional(false), + bundle_version, + bundle_version.default_flags(), + anchor, + ) + .unwrap(); + assert_eq!( + builder.add_output( + None, + change_recipient, + NoteValue::from_raw(5_000), + [0u8; 512] + ), + Err(OutputError::CrossAddressDisabled) + ); + assert_eq!( + builder.add_change_output( + FullViewingKey::from(&SpendingKey::random(&mut rng)), + None, + change_recipient, + NoteValue::from_raw(5_000), + [0u8; 512], + ), + Err(OutputError::RecipientNotOwned) + ); + + builder + .add_spend(spend_fvk.clone(), note, merkle_path) + .unwrap(); + builder + .add_change_output( + change_fvk.clone(), + None, + change_recipient, + NoteValue::from_raw(5_000), + [0u8; 512], + ) + .unwrap(); + let balance: i64 = builder.value_balance().unwrap(); + assert_eq!(balance, 10_000); + + let (pczt_bundle, bundle_meta) = builder.build_for_pczt(&mut rng).unwrap(); + assert!(!pczt_bundle.flags().cross_address_enabled()); + assert_eq!(pczt_bundle.actions().len(), 2); + assert_eq!(i64::try_from(pczt_bundle.value_sum).unwrap(), 10_000); + pczt_bundle.verify_cross_address_restriction().unwrap(); + + let spend_action_index = bundle_meta.spend_action_index(0).unwrap(); + let change_action_index = bundle_meta.output_action_index(0).unwrap(); + assert_ne!(spend_action_index, change_action_index); + + let spend_action = &pczt_bundle.actions()[spend_action_index]; + assert_eq!( + spend_action.spend.recipient, + Some(spend_recipient), + "the real spend remains at the spent note's address" + ); + assert_eq!(spend_action.spend.value, Some(NoteValue::from_raw(15_000))); + assert!(spend_action.spend.dummy_sk.is_none()); + assert_eq!(spend_action.output.recipient, Some(spend_recipient)); + assert_eq!(spend_action.output.value, Some(NoteValue::ZERO)); + + // The spend-paired output's ciphertext is randomized, so signers (e.g. Keystone) must be + // able to classify it as a zero-valued dummy and tolerate the undecryptable ciphertext + // rather than reconstructing and rejecting it. That requires the explicit note fields to + // be present (so the commitment verifies), no `user_address`, and a zero value. + assert!(spend_action.output.user_address.is_none()); + assert!(spend_action.output.rseed.is_some()); + assert!(spend_action + .output + .verify_note_commitment(&spend_action.spend) + .is_ok()); + + let change_action = &pczt_bundle.actions()[change_action_index]; + assert_eq!(change_action.spend.recipient, Some(change_recipient)); + assert_eq!(change_action.spend.value, Some(NoteValue::ZERO)); + assert!(change_action.spend.dummy_sk.is_none()); + assert_eq!(change_action.spend.fvk.as_ref(), Some(&change_fvk)); + assert_eq!(change_action.output.recipient, Some(change_recipient)); + assert_eq!(change_action.output.value, Some(NoteValue::from_raw(5_000))); + + for action in pczt_bundle.actions() { + assert!(action + .spend + .recipient + .as_ref() + .unwrap() + .same_expanded_receiver(action.output.recipient.as_ref().unwrap())); + } + } + + #[test] + fn cross_address_disabled_padding_pairs_dummy_addresses() { + let mut rng = OsRng; + let sk = SpendingKey::random(&mut rng); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::Internal); + let mut builder = Builder::new( + transactional(true), + BundleVersion::orchard_v3(), + BundleVersion::orchard_v3().default_flags(), + EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), + ) + .unwrap(); + + builder + .add_change_output(fvk, None, recipient, NoteValue::ZERO, [0u8; 512]) + .unwrap(); + + let (pczt_bundle, bundle_meta) = builder.build_for_pczt(&mut rng).unwrap(); + assert_eq!(pczt_bundle.actions().len(), 2); + pczt_bundle.verify_cross_address_restriction().unwrap(); + + let change_action_index = bundle_meta.output_action_index(0).unwrap(); + let (_, padding_action) = pczt_bundle + .actions() + .iter() + .enumerate() + .find(|(idx, action)| *idx != change_action_index && action.spend.dummy_sk.is_some()) + .unwrap(); + + assert_eq!(padding_action.spend.value, Some(NoteValue::ZERO)); + assert_eq!(padding_action.output.value, Some(NoteValue::ZERO)); + assert!(padding_action + .spend + .recipient + .as_ref() + .unwrap() + .same_expanded_receiver(padding_action.output.recipient.as_ref().unwrap())); + } + + #[test] + fn cross_address_disabled_rejects_non_change_outputs() { + let mut rng = OsRng; + let sk = SpendingKey::random(&mut rng); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + let bundle_version = BundleVersion::orchard_v3(); + + assert!(matches!( + bundle::( + &mut rng, + transactional(false), + bundle_version, + bundle_version.default_flags(), + EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), + vec![], + vec![OutputInfo::new( + None, + recipient, + NoteValue::from_raw(5_000), + bundle_version.note_version(), + [0u8; 512], + )], + vec![], + ), + Err(BuildError::CrossAddressDisabled) + )); + + let change_output = ChangeInfo::new( + fvk, + None, + recipient, + NoteValue::from_raw(5_000), + bundle_version.note_version(), + [0u8; 512], + ) + .unwrap(); + let (bundle, bundle_meta) = bundle::( + &mut rng, + transactional(false), + bundle_version, + bundle_version.default_flags(), + EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), + vec![], + vec![], + vec![change_output], + ) + .unwrap() + .unwrap(); + + assert!(!bundle.flags().cross_address_enabled()); + assert!(bundle_meta.output_action_index(0).is_some()); + } + + #[test] + fn cross_address_disabled_rejects_spends_disabled_outputs() { + let mut rng = OsRng; + let sk = SpendingKey::random(&mut rng); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::Internal); + let bundle_type = transactional(false); + let bundle_version = BundleVersion::orchard_v3(); + // Under Orchard from NU6.3 onward this is spends-disabled and cross-address-disabled. + let flags = Flags::from_parts( + false, + true, + bundle_version.permits_cross_address_transfers(), + ); + assert!(!flags.spends_enabled()); + assert!(flags.outputs_enabled()); + assert!(!flags.cross_address_enabled()); + + let change_output = ChangeInfo::new( + fvk, + None, + recipient, + NoteValue::from_raw(5_000), + bundle_version.note_version(), + [0u8; 512], + ) + .unwrap(); + + assert!(matches!( + bundle::( + &mut rng, + bundle_type, + bundle_version, + flags, + EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), + vec![], + vec![], + vec![change_output], + ), + Err(BuildError::BundleTypeNotSatisfiable) + )); + } + + #[test] + fn builder_note_version_checks_apply_to_outputs_only() { + let mut rng = OsRng; + let sk = SpendingKey::random(&mut rng); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + let bundle_version = BundleVersion::ironwood_v3(); + let mismatched_note_version = NoteVersion::V2; + + let (note, merkle_path, anchor) = note_with_path( + &mut rng, + recipient, + NoteValue::from_raw(15_000), + mismatched_note_version, + ); + let mut builder = Builder::new( + BundleType::DEFAULT, + bundle_version, + bundle_version.default_flags(), + anchor, + ) + .unwrap(); + assert_eq!(builder.add_spend(fvk.clone(), note, merkle_path), Ok(())); + + let (note, merkle_path, anchor) = note_with_path( + &mut rng, + recipient, + NoteValue::from_raw(15_000), + mismatched_note_version, + ); + let spend = SpendInfo::new(fvk.clone(), note, merkle_path).unwrap(); + assert!(bundle::( + &mut rng, + BundleType::DEFAULT, + bundle_version, + bundle_version.default_flags(), + anchor, + vec![spend], + vec![], + vec![], + ) + .is_ok()); + + let output = OutputInfo::new( + None, + recipient, + NoteValue::from_raw(5_000), + mismatched_note_version, + [0u8; 512], + ); + assert!(matches!( + bundle::( + &mut rng, + BundleType::DEFAULT, + bundle_version, + bundle_version.default_flags(), + EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), + vec![], + vec![output], + vec![], + ), + Err(BuildError::InvalidNoteVersion) + )); + + let change = ChangeInfo::new( + fvk, + None, + recipient, + NoteValue::from_raw(5_000), + mismatched_note_version, + [0u8; 512], + ) + .unwrap(); + assert!(matches!( + bundle::( + &mut rng, + BundleType::DEFAULT, + bundle_version, + bundle_version.default_flags(), + EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), + vec![], + vec![], + vec![change], + ), + Err(BuildError::InvalidNoteVersion) + )); + } + + #[test] + fn add_change_output_validates_ownership_when_unrestricted() { + let mut rng = OsRng; + let fvk = FullViewingKey::from(&SpendingKey::random(&mut rng)); + let owned = fvk.address_at(0u32, Scope::Internal); + let foreign = + FullViewingKey::from(&SpendingKey::random(&mut rng)).address_at(0u32, Scope::External); + let bundle_version = BundleVersion::orchard_v2(); + let mut builder = Builder::new( + BundleType::DEFAULT, + bundle_version, + bundle_version.default_flags(), + EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), + ) + .unwrap(); + + // Even in a bundle that permits cross-address transfers, a change output's ownership + // is validated eagerly (the fvk is no longer dead weight). + assert_eq!( + builder.add_change_output( + fvk.clone(), + None, + foreign, + NoteValue::from_raw(5_000), + [0u8; 512] + ), + Err(OutputError::RecipientNotOwned) + ); + // An owned recipient is accepted and counts as one of the bundle's outputs. + builder + .add_change_output(fvk, None, owned, NoteValue::from_raw(5_000), [0u8; 512]) + .unwrap(); + assert_eq!(builder.changes().len(), 1); + } + + #[test] + fn add_change_output_rejects_spends_disabled_eagerly() { + let mut rng = OsRng; + let fvk = FullViewingKey::from(&SpendingKey::random(&mut rng)); + let recipient = fvk.address_at(0u32, Scope::Internal); + + // For a cross-address-disabled bundle with spends disabled and outputs enabled, + // the change output is rejected eagerly. + // + // The builder wouldn't be prevented by consensus from supporting this (fabricating + // a spend with the same expanded receiver), but there is no point: `enableSpends = 0` + // is only enforced for coinbase transactions, and coinbase transactions do not allow + // Orchard actions at all post NU6.3. So the case unsupported by the builder would only + // happen by voluntarily disabling `enableSpends` and/or `enableCrossAddress` when + // consensus does not require it. + let bundle_version = BundleVersion::orchard_v3(); + let mut builder = Builder::new( + transactional(false), + bundle_version, + Flags::from_parts( + false, + true, + bundle_version.permits_cross_address_transfers(), + ), + EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), + ) + .unwrap(); + + assert_eq!( + builder.add_change_output(fvk, None, recipient, NoteValue::from_raw(5_000), [0u8; 512]), + Err(OutputError::SpendsDisabled) + ); + } + + #[test] + fn cross_address_disabled_non_pczt_signing_flow() { + let mut rng = OsRng; + let spend_sk = SpendingKey::random(&mut rng); + let spend_fvk = FullViewingKey::from(&spend_sk); + let spend_recipient = spend_fvk.address_at(0u32, Scope::External); + let change_sk = SpendingKey::random(&mut rng); + let change_fvk = FullViewingKey::from(&change_sk); + let change_recipient = change_fvk.address_at(0u32, Scope::Internal); + let bundle_version = BundleVersion::orchard_v3(); + let (note, merkle_path, anchor) = note_with_path( + &mut rng, + spend_recipient, + NoteValue::from_raw(15_000), + bundle_version.note_version(), + ); + + let mut builder = Builder::new( + transactional(false), + bundle_version, + bundle_version.default_flags(), + anchor, + ) + .unwrap(); + builder.add_spend(spend_fvk, note, merkle_path).unwrap(); + builder + .add_change_output( + change_fvk.clone(), + None, + change_recipient, + NoteValue::from_raw(5_000), + [0u8; 512], + ) + .unwrap(); + + let bundle = builder.build::(&mut rng).unwrap().unwrap().0; + + fn num_unsigned( + bundle: &Bundle, i64>, + ) -> usize { + bundle + .actions() + .iter() + .filter(|a| matches!(a.authorization(), MaybeSigned::SigningMetadata(_))) + .count() + } + + // Both the real spend and the fabricated change spend require real signatures. + let bundle = bundle.prepare(rng, [0; 32]); + assert_eq!(num_unsigned(&bundle), 2); + + let bundle = bundle.sign(rng, &SpendAuthorizingKey::from(&spend_sk)); + assert_eq!(num_unsigned(&bundle), 1); + + let bundle = bundle.sign(rng, &SpendAuthorizingKey::from(&change_sk)); + assert_eq!(num_unsigned(&bundle), 0); + + // A change-only bundle: the padding dummy spend is signed during `prepare`, so + // a single `sign` call with the change key completes the actions. + let mut builder = Builder::new( + transactional(false), + BundleVersion::orchard_v3(), + BundleVersion::orchard_v3().default_flags(), + EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), + ) + .unwrap(); + builder + .add_change_output( + change_fvk, + None, + change_recipient, + NoteValue::from_raw(5_000), + [0u8; 512], + ) + .unwrap(); + + let bundle = builder + .build::(&mut rng) + .unwrap() + .unwrap() + .0 + .prepare(rng, [0; 32]); + assert_eq!(bundle.actions().len(), 2); + assert_eq!(num_unsigned(&bundle), 1); + + let bundle = bundle.sign(rng, &SpendAuthorizingKey::from(&change_sk)); + assert_eq!(num_unsigned(&bundle), 0); + } + + #[test] + fn restricted_pczt_structural_checks_reject_tampering() { + let pk = ProvingKey::build(OrchardCircuitVersion::PostNu6_3); + let mut rng = OsRng; + let spend_sk = SpendingKey::random(&mut rng); + let spend_fvk = FullViewingKey::from(&spend_sk); + let spend_recipient = spend_fvk.address_at(0u32, Scope::External); + let change_sk = SpendingKey::random(&mut rng); + let change_fvk = FullViewingKey::from(&change_sk); + let change_recipient = change_fvk.address_at(0u32, Scope::Internal); + let bundle_version = BundleVersion::orchard_v3(); + let (note, merkle_path, anchor) = note_with_path( + &mut rng, + spend_recipient, + NoteValue::from_raw(15_000), + bundle_version.note_version(), + ); + + let mut builder = Builder::new( + transactional(false), + bundle_version, + bundle_version.default_flags(), + anchor, + ) + .unwrap(); + builder.add_spend(spend_fvk, note, merkle_path).unwrap(); + builder + .add_change_output( + change_fvk, + None, + change_recipient, + NoteValue::from_raw(5_000), + [0u8; 512], + ) + .unwrap(); + + let (mut pczt_bundle, _) = builder.build_for_pczt(&mut rng).unwrap(); + pczt_bundle.verify_cross_address_restriction().unwrap(); + pczt_bundle.create_proof(&pk, rng).unwrap(); + + let spend_recipient = pczt_bundle.actions()[0].spend.recipient.unwrap(); + let other_recipient = loop { + let fvk = FullViewingKey::from(&SpendingKey::random(&mut rng)); + let recipient = fvk.address_at(0u32, Scope::External); + if !spend_recipient.same_expanded_receiver(&recipient) { + break recipient; + } + }; + pczt_bundle.actions_mut()[0].output.recipient = Some(other_recipient); + + assert!(matches!( + pczt_bundle.verify_cross_address_restriction(), + Err(VerifyError::DisallowedCrossAddressTransfer) + )); + assert!(matches!( + pczt_bundle.create_proof(&pk, rng), + Err(ProverError::DisallowedCrossAddressTransfer(_)) + )); + } + + #[test] + fn create_proof_supports_cross_address_disabled_only_for_post_nu6_3() { + // A cross-address-disabled bundle can only be built under `BundleVersion::orchard_v3()` + // (`BundleVersion` owns the cross-address policy), which builds post-NU6.3 + // circuits. Proving therefore requires a matching post-NU6.3 key; a pre-NU6.3 key + // is rejected as a circuit-version mismatch. The lower-level interlock that rejects + // a restricted *instance* under an unsupporting key is covered by + // `circuit::tests::restricted_statement_requires_supporting_key`. + let build_restricted = |rng: &mut OsRng| { + Builder::new( + transactional(true), + BundleVersion::orchard_v3(), + BundleVersion::orchard_v3().default_flags(), + EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), + ) + .unwrap() + .build::(rng) + .unwrap() + .unwrap() + .0 + }; + + let mut rng = OsRng; + let pk = ProvingKey::build(OrchardCircuitVersion::FixedPostNu6_2); + let bundle = build_restricted(&mut rng); + assert!(matches!( + bundle.create_proof(&pk, &mut rng), + Err(BuildError::Proof(halo2_proofs::plonk::Error::Synthesis)), + )); + + let pk = ProvingKey::build(OrchardCircuitVersion::PostNu6_3); + let bundle = build_restricted(&mut rng); + bundle.create_proof(&pk, &mut rng).unwrap(); + } } diff --git a/src/bundle.rs b/src/bundle.rs index 262c8d2d7..1fc4eb57b 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -7,7 +7,7 @@ pub mod commitments; #[cfg(feature = "circuit")] mod batch; #[cfg(feature = "circuit")] -pub use batch::BatchValidator; +pub use batch::{BatchError, BatchValidator}; use core::fmt; @@ -23,16 +23,16 @@ use crate::{ address::Address, bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data}, keys::{IncomingViewingKey, OutgoingViewingKey, PreparedIncomingViewingKey}, - note::Note, - note_encryption::OrchardDomain, + note::{Note, NoteVersion}, + note_encryption::BundleDomain, primitives::redpallas::{self, Binding, SpendAuth}, tree::Anchor, value::{ValueCommitTrapdoor, ValueCommitment, ValueSum}, - Proof, + Proof, ProtocolVersion, ValuePool, }; #[cfg(feature = "circuit")] -use crate::circuit::{Instance, VerifyingKey}; +use crate::circuit::{Instance, OrchardCircuitVersion, VerifyingKey}; #[cfg(feature = "circuit")] impl Action { @@ -45,14 +45,176 @@ impl Action { *self.nullifier(), self.rk().clone(), *self.cmx(), - flags.spends_enabled, - flags.outputs_enabled, + flags, ) .expect("this Action's rk is non-identity by construction (Action::from_parts)") } } -/// Orchard-specific flags. +/// A shielded [`ValuePool`] (Orchard or Ironwood) together with the [`ProtocolVersion`] +/// under which a bundle in that pool is built. The Ironwood pool only exists from NU6.3 +/// onward, so it is only valid in combination with [`ProtocolVersion::V3`]. +/// +/// This pins the `circuit_version` and the flag-byte format, and determines whether +/// cross-address transfers are permitted (`permits_cross_address_transfers`). +/// The `flagsOrchard` / `flagsIronwood` value emitted by the [`Builder`](crate::builder::Builder) +/// depends on that constraint. The integration layer uses the pool and consensus branch ID +/// to select the `BundleVersion` value, and threads it through bundle construction +/// and wire encoding. +/// +/// This crate has no concept of consensus branches or activation heights, so it can't +/// derive the `BundleVersion` itself. Note that bit 2 of the flags is *reserved* +/// (required to be clear) for v5, and encodes `enableCrossAddress` only for v6. The +/// correct choice of `BundleVersion` is needed to get that right, as well as +/// affecting how requested spends and outputs map to actions. Using the wrong value +/// may result in constructing a consensus-invalid transaction. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub struct BundleVersion { + value_pool: ValuePool, + protocol_version: ProtocolVersion, +} + +impl BundleVersion { + /// The [`BundleVersion`] for the [`ValuePool::Orchard`] pool under + /// [`ProtocolVersion::InsecureV1`] (the Orchard pool prior to NU6.2). + pub const fn orchard_insecure_v1() -> Self { + Self { + value_pool: ValuePool::Orchard, + protocol_version: ProtocolVersion::InsecureV1, + } + } + + /// The [`BundleVersion`] for the [`ValuePool::Orchard`] pool under + /// [`ProtocolVersion::V2`] (the Orchard pool from NU6.2 until NU6.3). + pub const fn orchard_v2() -> Self { + Self { + value_pool: ValuePool::Orchard, + protocol_version: ProtocolVersion::V2, + } + } + + /// The [`BundleVersion`] for the [`ValuePool::Orchard`] pool under + /// [`ProtocolVersion::V3`] (the Orchard pool at NU6.3 and later). + pub const fn orchard_v3() -> Self { + Self { + value_pool: ValuePool::Orchard, + protocol_version: ProtocolVersion::V3, + } + } + + /// The [`BundleVersion`] for the [`ValuePool::Ironwood`] pool under + /// [`ProtocolVersion::V3`] (the Ironwood pool, introduced at NU6.3). + pub const fn ironwood_v3() -> Self { + Self { + value_pool: ValuePool::Ironwood, + protocol_version: ProtocolVersion::V3, + } + } + + /// Returns the [`ValuePool`] to which this bundle version applies. + pub fn value_pool(&self) -> ValuePool { + self.value_pool + } + + /// Returns the [`ProtocolVersion`] under which this bundle is built. + pub fn protocol_version(&self) -> ProtocolVersion { + self.protocol_version + } + + /// The circuit version whose proving and verifying keys prove and verify actions consistent + /// with this bundle version. + /// + /// This is many-to-one: both the [`ValuePool::Orchard`] and [`ValuePool::Ironwood`] pools + /// under [`ProtocolVersion::V3`] share the post-NU6.3 circuit, so build a key with + /// `ProvingKey::build(bundle_version.circuit_version())` / + /// `VerifyingKey::build(bundle_version.circuit_version())`. + #[cfg(feature = "circuit")] + pub fn circuit_version(&self) -> OrchardCircuitVersion { + match self.protocol_version { + ProtocolVersion::InsecureV1 => OrchardCircuitVersion::InsecurePreNu6_2, + ProtocolVersion::V2 => OrchardCircuitVersion::FixedPostNu6_2, + ProtocolVersion::V3 => OrchardCircuitVersion::PostNu6_3, + } + } + + /// The [`NoteVersion`] associated with this bundle version. + /// + /// Orchard pools use V2 note plaintexts, and Ironwood pools use V3 note + /// plaintexts. + pub fn note_version(&self) -> NoteVersion { + match self.value_pool { + ValuePool::Orchard => NoteVersion::V2, + ValuePool::Ironwood => NoteVersion::V3, + } + } + + /// Whether the consensus rules for this version *permit* cross-address transfers within an + /// action (`enableCrossAddress = 1` in a v6 transaction). + /// + /// Every version permits them except the [`ValuePool::Orchard`] pool under + /// [`ProtocolVersion::V3`], which mandates the cross-address restriction. This is not + /// necessarily the same cross-address-enabled decision the + /// [`Builder`](crate::builder::Builder) makes; that is builder policy chosen within this + /// constraint. + pub(crate) fn permits_cross_address_transfers(&self) -> bool { + !matches!( + (self.protocol_version, self.value_pool), + (ProtocolVersion::V3, ValuePool::Orchard) + ) + } + + /// The default [`Flags`] for a bundle of this version: spends and outputs enabled, with the + /// cross-address bit set to the least-restrictive value the version permits (enabled unless + /// the version mandates the restriction). + /// + /// This is the prover-side default a builder uses when the caller does not restrict the bundle + /// further. Where the version leaves the cross-address choice free (e.g. the Ironwood pool), a + /// caller may instead pass a more restricted flag set such as + /// [`Flags::CROSS_ADDRESS_DISABLED`]; the chosen flags must be representable under the version. + pub fn default_flags(&self) -> Flags { + Flags::from_parts(true, true, self.permits_cross_address_transfers()) + } + + /// Whether an authorized bundle of this version must carry a canonically-sized proof. + /// + /// The historical pre-NU6.2 Orchard pool ([`ProtocolVersion::InsecureV1`]) is only used to + /// parse already-committed transactions, whose proofs cannot be re-canonicalized, so its + /// proof size is not enforced. Every later version requires a canonical proof, rejecting + /// non-canonical (e.g. padded) proofs (GHSA-2x4w-pxqw-58v9). + pub(crate) fn enforces_canonical_proof_size(&self) -> bool { + !matches!(self.protocol_version, ProtocolVersion::InsecureV1) + } +} + +/// The transaction version a bundle's commitments are computed for. +/// +/// An Orchard bundle at NU6.3 and later may be encoded in either a v5 or a v6 transaction. +/// The two formats use different commitment personalization strings and include the bundle's +/// anchor in different digests: v5 includes the anchor in the transaction-ID digest, while v6 +/// includes it in the authorizing digest. Ironwood bundles exist only in v6 transactions, so +/// attempting to compute an Ironwood commitment for a v5 transaction returns an error. +/// +/// This is independent of the [`BundleVersion`] that governs construction: the same +/// Orchard bundle can be committed under either transaction version, and the caller must pass the one +/// matching the transaction the bundle is encoded in. See [`Bundle::commitment`] and +/// [`Bundle::authorizing_commitment`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum TxVersion { + /// A v5 transaction. + V5, + /// A v6 transaction. + V6, +} + +/// Flags denoting what operations may be performed by the Orchard actions +/// in a bundle. +/// +/// `Flags` are version-agnostic: a given flag set is not inherently valid for any +/// particular [`BundleVersion`]. Whether it can be used with a version is checked +/// separately, when it is encoded for that version (see [`Flags::to_byte`]) or supplied +/// to the [`Builder`](crate::builder::Builder). #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Flags { /// Flag denoting whether Orchard spends are enabled in the transaction. @@ -67,37 +229,81 @@ pub struct Flags { /// guaranteed to be dummy notes. If `true`, the created notes may be either real or /// dummy notes. outputs_enabled: bool, + /// Flag denoting whether Orchard spends and outputs may use different expanded + /// receivers. + /// + /// If `false`, every action's output is constrained to be addressed to the same + /// expanded receiver as the note it spends; proving and verification must reject the + /// bundle unless they use a circuit key that supports the restriction. + cross_address_enabled: bool, } const FLAG_SPENDS_ENABLED: u8 = 0b0000_0001; const FLAG_OUTPUTS_ENABLED: u8 = 0b0000_0010; -const FLAGS_EXPECTED_UNSET: u8 = !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED); +const FLAG_V6_CROSS_ADDRESS_ENABLED: u8 = 0b0000_0100; +const FLAGS_ALWAYS_EXPECTED_UNSET: u8 = + !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED | FLAG_V6_CROSS_ADDRESS_ENABLED); impl Flags { - /// Construct a set of flags from its constituent parts - pub(crate) const fn from_parts(spends_enabled: bool, outputs_enabled: bool) -> Self { + /// Construct a set of flags from its constituent parts, including the cross-address bit. + /// + /// Crate-internal: the builder supplies `cross_address_enabled` from its prover-side default + /// for the bundle version (see [`Builder`](crate::builder::Builder)). + pub(crate) const fn from_parts( + spends_enabled: bool, + outputs_enabled: bool, + cross_address_enabled: bool, + ) -> Self { Flags { spends_enabled, outputs_enabled, + cross_address_enabled, } } - /// The flag set with both spends and outputs enabled. + /// The flag set for an unrestricted bundle: spends and outputs are both + /// enabled, so its actions may spend and create real notes addressed to any + /// expanded receivers. + /// + /// Like [`Self::SPENDS_DISABLED`] and [`Self::OUTPUTS_DISABLED`], this leaves + /// cross-address transfers enabled; see [`Self::CROSS_ADDRESS_DISABLED`] for + /// the restricted variant. pub const ENABLED: Flags = Flags { spends_enabled: true, outputs_enabled: true, + cross_address_enabled: true, }; - /// The flag set with spends disabled. + /// The flag set for a bundle that may create notes but not spend them: every + /// spent note is constrained to be a dummy, while outputs may be real. + /// + /// This is the flag set used by coinbase transactions, which mint value into + /// the Orchard pool without consuming existing notes. pub const SPENDS_DISABLED: Flags = Flags { spends_enabled: false, outputs_enabled: true, + cross_address_enabled: true, }; - /// The flag set with outputs disabled. + /// The flag set for a bundle that may spend notes but not create them: every + /// created note is constrained to be a dummy, while spends may be real. + /// + /// This is used to remove value from the Orchard pool without producing new + /// notes within the bundle. pub const OUTPUTS_DISABLED: Flags = Flags { spends_enabled: true, outputs_enabled: false, + cross_address_enabled: true, + }; + + /// The flag set with spends and outputs enabled and cross-address transfers disabled. + /// + /// This flag set cannot be encoded in pre-NU6.3 formats. Proof creation and + /// verification for instances built with this flag require a post-NU 6.3 circuit key. + pub const CROSS_ADDRESS_DISABLED: Flags = Flags { + spends_enabled: true, + outputs_enabled: true, + cross_address_enabled: false, }; /// Flag denoting whether Orchard spends are enabled in the transaction. @@ -118,11 +324,21 @@ impl Flags { self.outputs_enabled } + /// Flag denoting whether Orchard spends and outputs may use different expanded + /// receivers. + /// + /// If `false`, every action's output is constrained to be addressed to the same + /// expanded receiver as the note it spends; proving and verification must reject the + /// bundle unless they use a circuit key that supports the restriction. + pub fn cross_address_enabled(&self) -> bool { + self.cross_address_enabled + } + /// Serialize flags to a byte as defined in [Zcash Protocol Spec § 7.1: Transaction /// Encoding And Consensus][txencoding]. /// /// [txencoding]: https://zips.z.cash/protocol/protocol.pdf#txnencoding - pub fn to_byte(&self) -> u8 { + pub fn to_byte(&self, bundle_version: BundleVersion) -> Option { let mut value = 0u8; if self.spends_enabled { value |= FLAG_SPENDS_ENABLED; @@ -130,25 +346,86 @@ impl Flags { if self.outputs_enabled { value |= FLAG_OUTPUTS_ENABLED; } - value + + // Validate `cross_address_enabled` against what the value pool and protocol version can + // represent, and set the v6 flag bit where it carries that choice. A flag set whose + // `cross_address_enabled` value is not representable in this context cannot be encoded. + match (bundle_version.value_pool, bundle_version.protocol_version) { + // Cross-address Orchard pool transfers are always permitted prior to + // ProtocolVersion::V3; there is no flag bit, so a disabled flag set cannot be + // represented. + (ValuePool::Orchard, ProtocolVersion::InsecureV1 | ProtocolVersion::V2) => { + if !self.cross_address_enabled { + return None; + } + } + // Cross-address Orchard pool transfers are disallowed in ProtocolVersion::V3. + (ValuePool::Orchard, ProtocolVersion::V3) => { + if self.cross_address_enabled { + return None; + } + } + // The Ironwood pool encodes the caller's choice in bit 2. + (ValuePool::Ironwood, ProtocolVersion::V3) => { + if self.cross_address_enabled { + value |= FLAG_V6_CROSS_ADDRESS_ENABLED; + } + } + // The Ironwood pool is not defined prior to ProtocolVersion::V3. + (ValuePool::Ironwood, _) => return None, + } + + Some(value) } - /// Parses flags from a single byte as defined in [Zcash Protocol Spec § 7.1: - /// Transaction Encoding And Consensus][txencoding]. + /// Parses flags from a single byte as defined in [Zcash Protocol Spec § + /// 7.1: Transaction Encoding And Consensus][txencoding], according to the + /// interpretation implied by `bundle_version`. Returns `None` if + /// unexpected bits are set in the flag byte. /// - /// Returns `None` if unexpected bits are set in the flag byte. + /// The protocol specification and ZIPs 225 and 229 define bit 2 of `flags` + /// to be reserved in v5 transactions, and to encode the + /// `enableCrossAddress` flag in v6 transactions. However, we can (by + /// design) parse and validate the flags knowing only `bundle_version`: + /// bit 2 can only be 1 for a bundle in the [`ValuePool::Ironwood`] pool, and + /// otherwise MUST be 0. Assuming that has been checked, cross-address + /// transfers are always enabled prior to [`ProtocolVersion::V3`], and under + /// [`ProtocolVersion::V3`] are taken to be enabled exactly when bit 2 is set. + /// + /// Note: if the wrong value of `bundle_version` is passed for the actual + /// pool and epoch of the transaction, then a consensus-invalid transaction + /// may be constructed (see [`BundleVersion`]). /// /// [txencoding]: https://zips.z.cash/protocol/protocol.pdf#txnencoding - pub fn from_byte(value: u8) -> Option { + pub fn from_byte(value: u8, bundle_version: BundleVersion) -> Option { + // Bits 3..=7 are always reserved and MUST be 0. // https://p.z.cash/TCR:bad-txns-v5-reserved-bits-nonzero - if value & FLAGS_EXPECTED_UNSET == 0 { - Some(Self { - spends_enabled: value & FLAG_SPENDS_ENABLED != 0, - outputs_enabled: value & FLAG_OUTPUTS_ENABLED != 0, - }) - } else { - None + if value & FLAGS_ALWAYS_EXPECTED_UNSET != 0 { + return None; } + + // Bit 2 can only be 1 for an Ironwood bundle. For Orchard it MUST be 0, independent of the + // tx version: + // + // * for a v5 transaction it is still reserved and MUST be 0; + // * for a v6+ transaction it encodes `enableCrossAddress` and MUST be 0. + // + // https://p.z.cash/TCR:bad-txns-v5-reserved-bits-nonzero + let bit2 = value & FLAG_V6_CROSS_ADDRESS_ENABLED != 0; + if bit2 && bundle_version.value_pool == ValuePool::Orchard { + return None; + } + + // We have already validated bit2 against the pool type + let cross_address_enabled = match bundle_version.protocol_version { + ProtocolVersion::InsecureV1 | ProtocolVersion::V2 => true, + ProtocolVersion::V3 => bit2, + }; + Some(Self { + spends_enabled: value & FLAG_SPENDS_ENABLED != 0, + outputs_enabled: value & FLAG_OUTPUTS_ENABLED != 0, + cross_address_enabled, + }) } } @@ -173,6 +450,14 @@ pub struct Bundle { anchor: Anchor, /// The authorization for this bundle. authorization: T, + /// The value pool and protocol version this bundle is encoded under. + /// + /// This is interpretive context rather than wire data: it is never serialized, but it + /// determines how the bundle's flags are encoded and which commitment format applies. A + /// `Bundle` is only ever constructed with flags that are representable under this version + /// (see [`Bundle::try_from_parts`] / [`Bundle::from_parts`]), so the bundle is safe to + /// serialize and commit to without supplying — and possibly mismatching — a version. + bundle_version: BundleVersion, } impl fmt::Debug for Bundle { @@ -191,6 +476,7 @@ impl fmt::Debug for Bundle { .field("value_balance", &self.value_balance) .field("anchor", &self.anchor) .field("authorization", &self.authorization) + .field("bundle_version", &self.bundle_version) .finish() } } @@ -210,6 +496,19 @@ pub(crate) fn validate_proof_size(proof: &Proof, num_actions: usize) -> Result<( } } +/// Checks that `flags` can be encoded under `bundle_version`. +/// +/// Returns [`BundleError::UnrepresentableFlags`] if it cannot. This is the shared check used by +/// the checked bundle constructors so that a constructed `Bundle` is always encodable and +/// committable under the version it carries. +fn validate_flags(flags: &Flags, bundle_version: BundleVersion) -> Result<(), BundleError> { + if flags.to_byte(bundle_version).is_some() { + Ok(()) + } else { + Err(BundleError::UnrepresentableFlags) + } +} + impl Bundle { /// Constructs a `Bundle` from its constituent parts without validating the authorization. /// @@ -217,19 +516,26 @@ impl Bundle { /// either carries no proof or carries a proof that is already known to be canonical (e.g. /// one produced by [`Proof::create`]). Construction from untrusted parts must instead go /// through a checked, authorization-specific constructor such as [`Bundle::try_from_parts`]. + /// + /// `flags` must be representable under `bundle_version`. Every `Bundle` upholds this, so that + /// [`Bundle::flag_byte`] and the commitment APIs cannot fail; callers are responsible for the + /// guarantee (it is debug-asserted here). pub(crate) fn from_parts_unchecked( actions: NonEmpty>, flags: Flags, value_balance: V, anchor: Anchor, authorization: T, + bundle_version: BundleVersion, ) -> Self { + debug_assert!(flags.to_byte(bundle_version).is_some()); Bundle { actions, flags, value_balance, anchor, authorization, + bundle_version, } } @@ -262,6 +568,26 @@ impl Bundle { &self.authorization } + /// Returns the [`BundleVersion`] (value pool and protocol version) this bundle is encoded + /// under. + pub fn bundle_version(&self) -> BundleVersion { + self.bundle_version + } + + /// Returns the byte encoding of this bundle's flags, as defined in [Zcash Protocol Spec § + /// 7.1: Transaction Encoding And Consensus][txencoding], under the bundle's own + /// [`BundleVersion`]. + /// + /// Unlike [`Flags::to_byte`], this is infallible: a `Bundle` is only ever constructed with + /// flags that are representable under its version. + /// + /// [txencoding]: https://zips.z.cash/protocol/protocol.pdf#txnencoding + pub fn flag_byte(&self) -> u8 { + self.flags + .to_byte(self.bundle_version) + .expect("flags are validated against the bundle version at construction") + } + /// Construct a new bundle by applying a transformation that might fail /// to the value balance. pub fn try_map_value_balance Result>( @@ -274,6 +600,7 @@ impl Bundle { value_balance: f(self.value_balance)?, anchor: self.anchor, authorization: self.authorization, + bundle_version: self.bundle_version, }) } @@ -293,6 +620,7 @@ impl Bundle { value_balance: self.value_balance, anchor: self.anchor, authorization: step(context, authorization), + bundle_version: self.bundle_version, } } @@ -316,6 +644,7 @@ impl Bundle { value_balance: self.value_balance, anchor: self.anchor, authorization: step(context, authorization)?, + bundle_version: self.bundle_version, }) } @@ -343,7 +672,7 @@ impl Bundle { .iter() .enumerate() .filter_map(|(idx, action)| { - let domain = OrchardDomain::for_action(action); + let domain = BundleDomain::for_action(action, self.bundle_version.note_version()); prepared_keys.iter().find_map(|(ivk, prepared_ivk)| { try_note_decryption(&domain, prepared_ivk, action) .map(|(n, a, m)| (idx, (*ivk).clone(), n, a, m)) @@ -352,9 +681,9 @@ impl Bundle { .collect() } - /// Performs trial decryption of the action at `action_idx` in the bundle with the - /// specified incoming viewing key, and returns the decrypted note plaintext - /// contents if successful. + /// Performs trial decryption of the action at `action_idx` in the bundle + /// with the specified incoming viewing key, and returns the decrypted note + /// plaintext contents if successful. pub fn decrypt_output_with_key( &self, action_idx: usize, @@ -362,7 +691,7 @@ impl Bundle { ) -> Option<(Note, Address, [u8; 512])> { let prepared_ivk = PreparedIncomingViewingKey::new(key); self.actions.get(action_idx).and_then(move |action| { - let domain = OrchardDomain::for_action(action); + let domain = BundleDomain::for_action(action, self.bundle_version.note_version()); try_note_decryption(&domain, &prepared_ivk, action) }) } @@ -379,7 +708,7 @@ impl Bundle { .iter() .enumerate() .filter_map(|(idx, action)| { - let domain = OrchardDomain::for_action(action); + let domain = BundleDomain::for_action(action, self.bundle_version.note_version()); keys.iter().find_map(move |key| { try_output_recovery_with_ovk( &domain, @@ -403,7 +732,7 @@ impl Bundle { key: &OutgoingViewingKey, ) -> Option<(Note, Address, [u8; 512])> { self.actions.get(action_idx).and_then(move |action| { - let domain = OrchardDomain::for_action(action); + let domain = BundleDomain::for_action(action, self.bundle_version.note_version()); try_output_recovery_with_ovk( &domain, key, @@ -416,10 +745,19 @@ impl Bundle { } impl> Bundle { - /// Computes a commitment to the effects of this bundle, suitable for inclusion within - /// a transaction ID. - pub fn commitment(&self) -> BundleCommitment { - BundleCommitment(hash_bundle_txid_data(self)) + /// Computes this bundle's transaction-ID commitment component. + /// + /// The flag-byte encoding follows the bundle's own [`BundleVersion`]; `tx_version` selects the + /// commitment personalizations and whether the bundle anchor bytes are included here or in the + /// authorizing digest. In a v5 transaction the anchor bytes are included here; in a v6 + /// transaction they are included by [`Bundle::authorizing_commitment`] instead. + /// + /// # Errors + /// + /// Returns [`CommitmentError::InvalidTransactionVersion`] if `tx_version` is not valid for the + /// bundle's [`BundleVersion`] (e.g. an Ironwood bundle committed with [`TxVersion::V5`]). + pub fn commitment(&self, tx_version: TxVersion) -> Result { + hash_bundle_txid_data(self, tx_version).map(BundleCommitment) } /// Returns the transaction binding validating key for this bundle. @@ -452,15 +790,32 @@ impl Authorization for EffectsOnly { impl Bundle { /// Constructs an effects-only `Bundle` from its constituent parts. /// - /// An effects-only bundle carries no proof, so there is no proof size to validate. + /// An effects-only bundle carries no proof, so there is no proof size to validate, and flags + /// are not checked against circuit support (there is no proof key to check against). The flags + /// are, however, checked for representability under `bundle_version`, so that the resulting + /// bundle is safe to serialize and commit to. + /// + /// # Errors + /// + /// Returns [`BundleError::UnrepresentableFlags`] if `flags` cannot be encoded under + /// `bundle_version`. pub fn from_parts( actions: NonEmpty::SpendAuth>>, flags: Flags, value_balance: V, anchor: Anchor, authorization: EffectsOnly, - ) -> Self { - Bundle::from_parts_unchecked(actions, flags, value_balance, anchor, authorization) + bundle_version: BundleVersion, + ) -> Result { + validate_flags(&flags, bundle_version)?; + Ok(Bundle::from_parts_unchecked( + actions, + flags, + value_balance, + anchor, + authorization, + bundle_version, + )) } } @@ -512,6 +867,14 @@ pub enum BundleError { /// The length of the proof that was provided. actual: usize, }, + /// The bundle's flags cannot be encoded under its [`BundleVersion`]. This happens in two + /// cases: + /// + /// * cross-address transfers are disabled but the version specifies a pre-NU6.3 Orchard pool + /// (where cross-address transfers are implicitly enabled); + /// * cross-address transfers are enabled but the version specifies a post-NU6.3 Orchard pool + /// (where cross-address transfers are forbidden). + UnrepresentableFlags, } impl fmt::Display for BundleError { @@ -521,59 +884,112 @@ impl fmt::Display for BundleError { f, "Orchard proof has non-canonical length {actual}; expected {expected} bytes", ), + BundleError::UnrepresentableFlags => write!( + f, + "bundle flags are not representable under the bundle's value pool and protocol version", + ), } } } impl core::error::Error for BundleError {} -/// A flag type that identifies whether proof sizes are checked in bundle construction. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ProofSizeEnforcement { - /// Proofs may exceed the canonical size - Unenforced, - /// Proofs may not exceed the canonical size - Strict, +/// Errors that can occur when computing a [`Bundle`] commitment. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum CommitmentError { + /// The requested transaction version is not valid for the bundle's [`BundleVersion`]. + /// + /// Ironwood bundles exist only in v6 transactions, so an Ironwood bundle + /// (`BundleVersion::ironwood_v3()`) cannot be committed with `TxVersion::V5`. + InvalidTransactionVersion, +} + +impl fmt::Display for CommitmentError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CommitmentError::InvalidTransactionVersion => write!( + f, + "Ironwood bundles can only be committed in a v6 transaction", + ), + } + } } +impl core::error::Error for CommitmentError {} + impl Bundle { - /// Constructs an authorized `Bundle` from its constituent parts, rejecting a proof whose - /// length is not the canonical size for the number of actions. - /// - /// This is the only constructor for an authorized bundle: it validates that the proof has - /// exactly [`Proof::expected_proof_size`] bytes for `actions.len()`, so an authorized bundle - /// can never hold a non-canonical proof. This matters when building a bundle from untrusted - /// input (e.g. deserializing from bytes), as it prevents a proof from being padded with - /// arbitrary data, which would otherwise impose unbounded bandwidth and storage costs without - /// affecting proof validity (GHSA-2x4w-pxqw-58v9). + /// Constructs an authorized `Bundle` from its constituent parts. + /// + /// This is the only constructor for an authorized bundle. For every version except the + /// historical pre-NU6.2 Orchard pool ([`BundleVersion::orchard_insecure_v1`]) it + /// validates that the proof has exactly [`Proof::expected_proof_size`] bytes for + /// `actions.len()`, so such an authorized bundle can never hold a non-canonical proof. This + /// matters when building a bundle from untrusted input (e.g. deserializing from bytes), as it + /// prevents a proof from being padded with arbitrary data, which would otherwise impose + /// unbounded bandwidth and storage costs without affecting proof validity + /// (GHSA-2x4w-pxqw-58v9). Circuit-key support for the bundle flags is checked when proving or + /// verifying the proof. + /// + /// The flags are also checked for representability under `bundle_version`, so that the + /// resulting bundle is safe to serialize and commit to. + /// + /// # Errors + /// + /// Returns [`BundleError::NonCanonicalProofSize`] if the proof length is not canonical (for a + /// version that enforces it), or [`BundleError::UnrepresentableFlags`] if `flags` cannot be + /// encoded under `bundle_version`. pub fn try_from_parts( actions: NonEmpty::SpendAuth>>, flags: Flags, value_balance: V, anchor: Anchor, authorization: Authorized, - size_enforcement: ProofSizeEnforcement, + bundle_version: BundleVersion, ) -> Result { - if size_enforcement == ProofSizeEnforcement::Strict { + if bundle_version.enforces_canonical_proof_size() { validate_proof_size(authorization.proof(), actions.len())?; } + validate_flags(&flags, bundle_version)?; Ok(Bundle::from_parts_unchecked( actions, flags, value_balance, anchor, authorization, + bundle_version, )) } - /// Computes a commitment to the authorizing data within for this bundle. + /// Computes the authorizing-data commitment for this bundle. + /// + /// This together with `Bundle::commitment` binds the entire bundle. The bundle's own + /// [`BundleVersion`] and `tx_version` select the commitment personalization; in a v6 + /// transaction this digest also includes the bundle anchor bytes (in a v5 transaction they are + /// included by [`Bundle::commitment`] instead). /// - /// This together with `Bundle::commitment` bind the entire bundle. - pub fn authorizing_commitment(&self) -> BundleAuthorizingCommitment { - BundleAuthorizingCommitment(hash_bundle_auth_data(self)) + /// # Errors + /// + /// Returns [`CommitmentError::InvalidTransactionVersion`] if `tx_version` is not valid for the + /// bundle's [`BundleVersion`]. + pub fn authorizing_commitment( + &self, + tx_version: TxVersion, + ) -> Result { + hash_bundle_auth_data(self, tx_version).map(BundleAuthorizingCommitment) } /// Verifies the proof for this bundle. + /// + /// # Errors + /// + /// Returns `Err(`[`halo2_proofs::plonk::Error::InvalidInstances`]`)` if this + /// bundle disables cross-address transfers and `vk` is not an + /// [`OrchardCircuitVersion::PostNu6_3`] verifying key. + /// + /// Also returns an error if proof verification fails. + /// + /// [`OrchardCircuitVersion::PostNu6_3`]: crate::circuit::OrchardCircuitVersion::PostNu6_3 #[cfg(feature = "circuit")] pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> { self.authorization() @@ -642,9 +1058,10 @@ pub mod testing { use proptest::prelude::*; use crate::{ + bundle::BundleVersion, primitives::redpallas::{self, testing::arb_binding_signing_key}, value::{testing::arb_note_value_bounded, NoteValue, ValueSum, MAX_NOTE_VALUE}, - Anchor, Proof, + Anchor, NoteVersion, Proof, }; use super::{Action, Authorized, Bundle, Flags}; @@ -654,8 +1071,32 @@ pub mod testing { /// Marker type for a bundle that contains no authorizing data. pub type Unauthorized = super::EffectsOnly; + /// Create an arbitrary [`BundleVersion`]. + pub fn arb_bundle_version() -> impl Strategy { + prop_oneof![ + Just(BundleVersion::orchard_insecure_v1()), + Just(BundleVersion::orchard_v2()), + Just(BundleVersion::orchard_v3()), + Just(BundleVersion::ironwood_v3()), + ] + } + + /// Returns `flags` with its `cross_address_enabled` bit forced to the value representable under + /// `bundle_version`, leaving the spend/output bits untouched. + /// + /// The arbitrary-bundle strategies generate flags independently of the version; this pairs them + /// into a combination that a `Bundle` can actually be constructed from. + fn flags_for_version(bundle_version: BundleVersion, flags: Flags) -> Flags { + Flags::from_parts( + flags.spends_enabled(), + flags.outputs_enabled(), + bundle_version.permits_cross_address_transfers(), + ) + } + /// Generate an unauthorized action having spend and output values less than MAX_NOTE_VALUE / n_actions. pub fn arb_unauthorized_action_n( + note_version: NoteVersion, n_actions: usize, flags: Flags, ) -> impl Strategy)> { @@ -673,7 +1114,7 @@ pub mod testing { }; output_value_gen.prop_flat_map(move |output_value| { - arb_unauthorized_action(spend_value, output_value) + arb_unauthorized_action(note_version, spend_value, output_value) .prop_map(move |a| (spend_value - output_value, a)) }) }) @@ -681,6 +1122,7 @@ pub mod testing { /// Generate an authorized action having spend and output values less than MAX_NOTE_VALUE / n_actions. pub fn arb_action_n( + note_version: NoteVersion, n_actions: usize, flags: Flags, ) -> impl Strategy>)> { @@ -698,16 +1140,35 @@ pub mod testing { }; output_value_gen.prop_flat_map(move |output_value| { - arb_action(spend_value, output_value) + arb_action(note_version, spend_value, output_value) .prop_map(move |a| (spend_value - output_value, a)) }) }) } prop_compose! { - /// Create an arbitrary set of flags. + /// Create an arbitrary set of flags with cross-address transfers enabled. + /// This is representable for all `bundle_version` other than Orchard post-NU6.3. + /// + /// Use `arb_flags_ironwood_post_nu6_3` for a strategy that can also disable + /// cross-address transfers. pub fn arb_flags()(spends_enabled in prop::bool::ANY, outputs_enabled in prop::bool::ANY) -> Flags { - Flags::from_parts(spends_enabled, outputs_enabled) + Flags::from_parts(spends_enabled, outputs_enabled, true) + } + } + + prop_compose! { + /// Create an arbitrary set of flags that are valid for an Ironwood bundle post-NU6.3. + pub fn arb_flags_ironwood_post_nu6_3()( + spends_enabled in prop::bool::ANY, + outputs_enabled in prop::bool::ANY, + cross_address_enabled in prop::bool::ANY, + ) -> Flags { + Flags { + spends_enabled, + outputs_enabled, + cross_address_enabled, + } } } @@ -726,14 +1187,17 @@ pub mod testing { /// [`crate::builder::testing::arb_bundle`] pub fn arb_unauthorized_bundle(n_actions: usize) ( + bundle_version in arb_bundle_version(), flags in arb_flags(), ) ( - acts in vec(arb_unauthorized_action_n(n_actions, flags), n_actions), + acts in vec(arb_unauthorized_action_n(bundle_version.note_version(), n_actions, flags), n_actions), anchor in arb_base().prop_map(Anchor::from), - flags in Just(flags) + flags in Just(flags), + bundle_version in Just(bundle_version), ) -> Bundle { let (balances, actions): (Vec, Vec>) = acts.into_iter().unzip(); + let flags = flags_for_version(bundle_version, flags); Bundle::from_parts( NonEmpty::from_vec(actions).unwrap(), @@ -741,7 +1205,9 @@ pub mod testing { balances.into_iter().sum::>().unwrap(), anchor, super::EffectsOnly, + bundle_version, ) + .expect("flags are normalized to be representable under bundle_version") } } @@ -751,20 +1217,23 @@ pub mod testing { /// [`crate::builder::testing::arb_bundle`] pub fn arb_bundle(n_actions: usize) ( + bundle_version in arb_bundle_version(), flags in arb_flags(), ) ( - acts in vec(arb_action_n(n_actions, flags), n_actions), + acts in vec(arb_action_n(bundle_version.note_version(), n_actions, flags), n_actions), anchor in arb_base().prop_map(Anchor::from), sk in arb_binding_signing_key(), rng_seed in prop::array::uniform32(prop::num::u8::ANY), // A fake proof of the canonical length, so the bundle passes `try_from_parts`. fake_proof in vec(prop::num::u8::ANY, Proof::expected_proof_size(n_actions)), fake_sighash in prop::array::uniform32(prop::num::u8::ANY), - flags in Just(flags) + flags in Just(flags), + bundle_version in Just(bundle_version), ) -> Bundle { let (balances, actions): (Vec, Vec>) = acts.into_iter().unzip(); let rng = StdRng::from_seed(rng_seed); + let flags = flags_for_version(bundle_version, flags); Bundle::try_from_parts( NonEmpty::from_vec(actions).unwrap(), @@ -775,23 +1244,171 @@ pub mod testing { proof: Proof::new(fake_proof), binding_signature: sk.sign(rng, &fake_sighash), }, - super::ProofSizeEnforcement::Strict + bundle_version, ) - .expect("fake proof has the canonical length") + .expect("fake proof has the canonical length and flags are representable") } } } #[cfg(test)] -mod tests { +pub(crate) mod tests { use alloc::vec; use proptest::prelude::*; - use super::testing::arb_bundle; - use super::{Authorized, Bundle, BundleError}; + use super::testing::{arb_bundle, arb_flags, arb_flags_ironwood_post_nu6_3}; + use super::{ + Authorized, Bundle, BundleError, BundleVersion, CommitmentError, Flags, TxVersion, + }; use crate::Proof; + #[cfg(feature = "circuit")] + pub(crate) fn with_cross_address_disabled( + bundle: Bundle, + ) -> Bundle { + let mut flags = *bundle.flags(); + flags.cross_address_enabled = false; + + Bundle::from_parts_unchecked( + bundle.actions().clone(), + flags, + *bundle.value_balance(), + *bundle.anchor(), + bundle.authorization().clone(), + bundle.bundle_version(), + ) + } + + #[cfg(feature = "circuit")] + pub(crate) fn sample_authorized_bundle( + n_actions: usize, + ) -> Bundle { + use proptest::strategy::ValueTree; + + let mut runner = proptest::test_runner::TestRunner::deterministic(); + arb_bundle(n_actions) + .new_tree(&mut runner) + .expect("strategy can generate a bundle") + .current() + } + + #[test] + fn flags_byte_encoding() { + for (flags, orchard_pre_nu6_3, orchard_nu6_3, ironwood_nu6_3) in [ + (Flags::ENABLED, Some(0b011), None, Some(0b111)), + (Flags::SPENDS_DISABLED, Some(0b010), None, Some(0b110)), + (Flags::OUTPUTS_DISABLED, Some(0b001), None, Some(0b101)), + ( + Flags::CROSS_ADDRESS_DISABLED, + None, + Some(0b011), + Some(0b011), + ), + ] { + assert_eq!( + flags.to_byte(BundleVersion::orchard_v2()), + orchard_pre_nu6_3 + ); + assert_eq!(flags.to_byte(BundleVersion::orchard_v3()), orchard_nu6_3); + assert_eq!(flags.to_byte(BundleVersion::ironwood_v3()), ironwood_nu6_3); + } + } + + #[test] + fn flags_parsing_diverges_between_epochs() { + // A byte with bit 2 clear parses as an unrestricted bundle for Orchard pre-NU6.3, + // and a restricted bundle for Orchard or Ironwood post-NU6.3. + for value in 0b000..=0b011 { + let pre_nu6_3_flags = Flags::from_byte(value, BundleVersion::orchard_v2()).unwrap(); + let nu6_3_flags = Flags::from_byte(value, BundleVersion::orchard_v3()).unwrap(); + let ironwood_flags = Flags::from_byte(value, BundleVersion::ironwood_v3()).unwrap(); + + assert_eq!( + pre_nu6_3_flags.spends_enabled(), + nu6_3_flags.spends_enabled() + ); + assert_eq!( + pre_nu6_3_flags.outputs_enabled(), + nu6_3_flags.outputs_enabled() + ); + assert!(pre_nu6_3_flags.cross_address_enabled()); + assert!(!nu6_3_flags.cross_address_enabled()); + assert_eq!(ironwood_flags, nu6_3_flags); + + // Each parse round-trips to the same byte under its own era, but the + // restricted set is unrepresentable pre-NU6.3. + assert_eq!( + pre_nu6_3_flags.to_byte(BundleVersion::orchard_v2()), + Some(value) + ); + assert_eq!( + nu6_3_flags.to_byte(BundleVersion::orchard_v3()), + Some(value) + ); + assert_eq!(nu6_3_flags.to_byte(BundleVersion::orchard_v2()), None); + } + + assert_eq!( + Flags::from_byte(0b011, BundleVersion::orchard_v2()), + Some(Flags::ENABLED) + ); + assert_eq!( + Flags::from_byte(0b011, BundleVersion::orchard_v3()), + Some(Flags::CROSS_ADDRESS_DISABLED) + ); + assert_eq!( + Flags::from_byte(0b011, BundleVersion::ironwood_v3()), + Some(Flags::CROSS_ADDRESS_DISABLED) + ); + } + + #[test] + fn only_orchard_post_nu6_3_requires_the_cross_address_restriction() { + // Consensus mandates the restriction only for the Orchard pool at NU6.3; every other + // variant leaves the choice free (the builder then applies its prover-side default). + assert!(!BundleVersion::orchard_v3().permits_cross_address_transfers()); + for bundle_version in [ + BundleVersion::orchard_insecure_v1(), + BundleVersion::orchard_v2(), + BundleVersion::ironwood_v3(), + ] { + assert!(bundle_version.permits_cross_address_transfers()); + } + } + + #[test] + fn pre_nu6_3_flags_parsing_rejects_reserved_bits() { + for value in 0b100..=u8::MAX { + assert_eq!(Flags::from_byte(value, BundleVersion::orchard_v2()), None); + } + } + + #[test] + fn nu6_3_flags_parsing_handles_the_cross_address_bit() { + for value in 0b100..=0b111 { + // Orchard post-NU6.3 mandates the restriction, so bit 2 (`enableCrossAddress`) + // set is rejected there. + assert_eq!(Flags::from_byte(value, BundleVersion::orchard_v3()), None); + // Bit 2 set is only valid for the Ironwood pool, where it is recognized as + // cross-address enabled and round-trips. + let flags = Flags::from_byte(value, BundleVersion::ironwood_v3()).unwrap(); + assert!(flags.cross_address_enabled()); + assert_eq!(flags.to_byte(BundleVersion::ironwood_v3()), Some(value)); + // Pre-NU6.3 formats encode the same flag set with bit 2 reserved zero. + assert_eq!( + flags.to_byte(BundleVersion::orchard_v2()), + Some(value & 0b011) + ); + } + + // Bits 3.. are always reserved, in every NU6.3 pool. + for value in 0b1000..=u8::MAX { + assert_eq!(Flags::from_byte(value, BundleVersion::orchard_v3()), None); + assert_eq!(Flags::from_byte(value, BundleVersion::ironwood_v3()), None); + } + } + #[test] fn expected_proof_size_matches_known_values() { // The canonical proof sizes for one and two actions, fixed by the action circuit. @@ -806,15 +1423,213 @@ mod tests { ); } + #[test] + fn empty_commitments_are_domain_separated() { + use crate::bundle::commitments::{hash_bundle_auth_empty, hash_bundle_txid_empty}; + use crate::ValuePool; + + // The three commitment formats — Orchard v5, Orchard v6, Ironwood v6 — use distinct + // personalizations, so the absent-bundle digests are all different from one another. + let formats = [ + (ValuePool::Orchard, TxVersion::V5), + (ValuePool::Orchard, TxVersion::V6), + (ValuePool::Ironwood, TxVersion::V6), + ]; + for i in 0..formats.len() { + for j in (i + 1)..formats.len() { + let (pi, ti) = formats[i]; + let (pj, tj) = formats[j]; + assert_ne!( + hash_bundle_txid_empty(pi, ti).unwrap().as_bytes(), + hash_bundle_txid_empty(pj, tj).unwrap().as_bytes() + ); + assert_ne!( + hash_bundle_auth_empty(pi, ti).unwrap().as_bytes(), + hash_bundle_auth_empty(pj, tj).unwrap().as_bytes() + ); + } + } + + assert!(matches!( + hash_bundle_txid_empty(ValuePool::Ironwood, TxVersion::V5), + Err(CommitmentError::InvalidTransactionVersion) + )); + assert!(matches!( + hash_bundle_auth_empty(ValuePool::Ironwood, TxVersion::V5), + Err(CommitmentError::InvalidTransactionVersion) + )); + } + proptest! { // The property is deterministic given the actions, so a handful of cases suffices. #![proptest_config(ProptestConfig::with_cases(16))] #[test] - fn try_from_parts_enforces_canonical_proof_size(bundle in arb_bundle(3)) { + fn arb_flags_ironwood_post_nu6_3_round_trips(flags in arb_flags_ironwood_post_nu6_3()) { + let encoded = flags + .to_byte(BundleVersion::ironwood_v3()) + .expect("all Ironwood post-NU6.3 flag strategy outputs encode under Ironwood post-NU6.3"); + + prop_assert_eq!(Flags::from_byte(encoded, BundleVersion::ironwood_v3()), Some(flags)); + } + + #[test] + fn orchard_nu6_3_rejects_cross_address_enabled(flags in arb_flags()) { + // `arb_flags` always enables cross-address transfers, which Orchard post-NU6.3 + // forbids, so encoding under those restrictions must fail. The cross-address- + // disabled projection must still encode and round-trip. + prop_assert_eq!(flags.to_byte(BundleVersion::orchard_v3()), None); + + let mut disabled = flags; + disabled.cross_address_enabled = false; + let encoded = disabled + .to_byte(BundleVersion::orchard_v3()) + .expect("cross-address-disabled flags encode under Orchard post-NU6.3"); + prop_assert_eq!( + Flags::from_byte(encoded, BundleVersion::orchard_v3()), + Some(disabled) + ); + } + + #[test] + fn commitment_hashes_the_wire_flag_byte(bundle in arb_bundle(3)) { + let actions = bundle.actions().clone(); + let anchor = *bundle.anchor(); + let authorization = bundle.authorization().clone(); + let spends_enabled = bundle.flags().spends_enabled(); + let outputs_enabled = bundle.flags().outputs_enabled(); + + let enabled = Flags::from_parts(spends_enabled, outputs_enabled, true); + let disabled = Flags::from_parts(spends_enabled, outputs_enabled, false); + + // Build the same actions under different (flags, version) combinations, with `V = i64` + // so that `commitment()` is available. + let build = |flags, bundle_version| { + Bundle::from_parts_unchecked( + actions.clone(), + flags, + 0i64, + anchor, + authorization.clone(), + bundle_version, + ) + }; + + // Orchard pre-NU6.3 has cross-address implicitly enabled and Orchard NU6.3 has it + // disabled, but both encode to the same wire byte, so their commitments agree. + let legacy = build(enabled, BundleVersion::orchard_v2()); + let restricted = build(disabled, BundleVersion::orchard_v3()); + prop_assert_eq!(restricted.flag_byte(), legacy.flag_byte()); + + let restricted_commitment: [u8; 32] = + restricted.commitment(TxVersion::V5).unwrap().into(); + let legacy_commitment: [u8; 32] = legacy.commitment(TxVersion::V5).unwrap().into(); + prop_assert_eq!(restricted_commitment, legacy_commitment); + + // The unrestricted Ironwood encoding sets bit 2, producing a distinct digest. Only + // the Ironwood pool may set it; Orchard post-NU6.3 prohibits cross-address transfers. + let unrestricted = build(enabled, BundleVersion::ironwood_v3()); + let unrestricted_commitment: [u8; 32] = + unrestricted.commitment(TxVersion::V6).unwrap().into(); + prop_assert_ne!(unrestricted_commitment, restricted_commitment); + + // The restricted flag set has no pre-NU6.3 encoding, so no such bundle can be built. + prop_assert_eq!(disabled.to_byte(BundleVersion::orchard_v2()), None); + } + + #[test] + fn ironwood_rejects_v5_commitment_version(bundle in arb_bundle(3)) { + let bundle_i64 = Bundle::from_parts_unchecked( + bundle.actions().clone(), + *bundle.flags(), + 0i64, + *bundle.anchor(), + bundle.authorization().clone(), + BundleVersion::ironwood_v3(), + ); + let ironwood = Bundle::from_parts_unchecked( + bundle.actions().clone(), + *bundle.flags(), + *bundle.value_balance(), + *bundle.anchor(), + bundle.authorization().clone(), + BundleVersion::ironwood_v3(), + ); + + prop_assert!(matches!( + bundle_i64.commitment(TxVersion::V5), + Err(CommitmentError::InvalidTransactionVersion) + )); + prop_assert!(matches!( + ironwood.authorizing_commitment(TxVersion::V5), + Err(CommitmentError::InvalidTransactionVersion) + )); + } + + /// The anchor bytes are included in the transaction-ID digest for the v5 format and in + /// the authorizing digest for the v6 format, so changing only the anchor moves exactly + /// one of the two digests. The v5 and v6 Orchard formats are also domain-separated, so + /// the same bundle commits to distinct transaction-ID digests under each. + #[test] + fn anchor_placement_follows_tx_version(bundle in arb_bundle(3)) { + // Orchard post-NU6.3 cannot encode cross-address transfers, so clear the bit to keep + // the flags representable in every version under test. + let flags = Flags::from_parts( + bundle.flags().spends_enabled(), + bundle.flags().outputs_enabled(), + false, + ); + + let with_anchor = |anchor, bundle_version| { + Bundle::from_parts_unchecked( + bundle.actions().clone(), + flags, + 0i64, + anchor, + bundle.authorization().clone(), + bundle_version, + ) + }; + let anchor_a = crate::Anchor::from_bytes([0u8; 32]).unwrap(); + let anchor_b = crate::Anchor::from_bytes([6u8; 32]).unwrap(); + + for (bundle_version, tx, anchor_in_txid_digest) in [ + (BundleVersion::orchard_v3(), TxVersion::V5, true), + (BundleVersion::orchard_v3(), TxVersion::V6, false), + (BundleVersion::ironwood_v3(), TxVersion::V6, false), + ] { + let a = with_anchor(anchor_a, bundle_version); + let b = with_anchor(anchor_b, bundle_version); + let txid_a: [u8; 32] = a.commitment(tx).unwrap().into(); + let txid_b: [u8; 32] = b.commitment(tx).unwrap().into(); + let auth_a = a.authorizing_commitment(tx).unwrap().0; + let auth_b = b.authorizing_commitment(tx).unwrap().0; + if anchor_in_txid_digest { + prop_assert_ne!(txid_a, txid_b); + prop_assert_eq!(auth_a.as_bytes(), auth_b.as_bytes()); + } else { + prop_assert_eq!(txid_a, txid_b); + prop_assert_ne!(auth_a.as_bytes(), auth_b.as_bytes()); + } + } + + // The v5 and v6 Orchard formats are domain-separated, so the same bundle commits to + // distinct transaction-ID digests under each. + let a_v2 = with_anchor(anchor_a, BundleVersion::orchard_v3()); + let orchard_v5: [u8; 32] = a_v2.commitment(TxVersion::V5).unwrap().into(); + let orchard_v6: [u8; 32] = a_v2.commitment(TxVersion::V6).unwrap().into(); + prop_assert_ne!(orchard_v5, orchard_v6); + } + + #[test] + fn try_from_parts_enforces_canonical_proof_size( + bundle in arb_bundle(3) + ) { let actions = bundle.actions().clone(); let expected = Proof::expected_proof_size(actions.len()); let flags = *bundle.flags(); + // Ironwood enforces canonical proof size and accepts any cross-address flag value. + let bundle_version = BundleVersion::ironwood_v3(); let value_balance = *bundle.value_balance(); let anchor = *bundle.anchor(); let binding_signature = bundle.authorization().binding_signature().clone(); @@ -829,7 +1644,7 @@ mod tests { Proof::new(vec![0u8; proof_len]), binding_signature.clone(), ), - crate::bundle::ProofSizeEnforcement::Strict + bundle_version, ) }; @@ -848,5 +1663,121 @@ mod tests { Some(BundleError::NonCanonicalProofSize { expected, actual: expected - 1 }) ); } + + #[test] + fn try_from_parts_preserves_cross_address_disabled( + bundle in arb_bundle(3) + ) { + let actions = bundle.actions().clone(); + let mut flags = *bundle.flags(); + flags.cross_address_enabled = false; + let value_balance = *bundle.value_balance(); + let anchor = *bundle.anchor(); + let authorization = bundle.authorization().clone(); + + let bundle = Bundle::try_from_parts( + actions, + flags, + value_balance, + anchor, + authorization, + BundleVersion::orchard_v3(), + ) + .expect("canonical proof size is accepted"); + prop_assert!(!bundle.flags().cross_address_enabled()); + } + + #[test] + fn try_from_parts_checks_proof_size_with_cross_address_disabled( + bundle in arb_bundle(3) + ) { + let actions = bundle.actions().clone(); + let expected = Proof::expected_proof_size(actions.len()); + let mut flags = *bundle.flags(); + flags.cross_address_enabled = false; + let value_balance = *bundle.value_balance(); + let anchor = *bundle.anchor(); + let binding_signature = bundle.authorization().binding_signature().clone(); + + prop_assert_eq!( + Bundle::try_from_parts( + actions, + flags, + value_balance, + anchor, + Authorized::from_parts( + Proof::new(vec![0u8; expected + 1]), + binding_signature, + ), + BundleVersion::orchard_v3(), + ) + .err(), + Some(BundleError::NonCanonicalProofSize { expected, actual: expected + 1 }) + ); + } + + #[test] + fn insecure_v1_skips_proof_size_enforcement(bundle in arb_bundle(3)) { + // The historical pre-NU6.2 Orchard pool does not enforce canonical proof size, so a + // padded proof is accepted: its transaction is already committed and cannot be + // re-canonicalized. + let expected = Proof::expected_proof_size(bundle.actions().len()); + let padded = Bundle::try_from_parts( + bundle.actions().clone(), + Flags::ENABLED, + *bundle.value_balance(), + *bundle.anchor(), + Authorized::from_parts( + Proof::new(vec![0u8; expected + 1]), + bundle.authorization().binding_signature().clone(), + ), + BundleVersion::orchard_insecure_v1(), + ); + prop_assert!(padded.is_ok()); + } + } + + #[cfg(feature = "circuit")] + #[test] + fn from_parts_rejects_unrepresentable_flags() { + // A cross-address-disabled flag set has no pre-NU6.3 Orchard encoding, so a bundle + // carrying that combination cannot be constructed under `orchard_v2()`. + let bundle = sample_authorized_bundle(1); + let flags = Flags::from_parts( + bundle.flags().spends_enabled(), + bundle.flags().outputs_enabled(), + false, + ); + + assert_eq!( + Bundle::try_from_parts( + bundle.actions().clone(), + flags, + *bundle.value_balance(), + *bundle.anchor(), + bundle.authorization().clone(), + BundleVersion::orchard_v2(), + ) + .err(), + Some(BundleError::UnrepresentableFlags) + ); + } + + #[cfg(feature = "circuit")] + #[test] + fn verify_proof_rejects_cross_address_disabled_for_unsupported_keys() { + let bundle = with_cross_address_disabled(sample_authorized_bundle(1)); + + for circuit_version in [ + crate::circuit::OrchardCircuitVersion::InsecurePreNu6_2, + crate::circuit::OrchardCircuitVersion::FixedPostNu6_2, + ] { + let vk = crate::circuit::VerifyingKey::build(circuit_version); + + assert!(matches!( + bundle.verify_proof(&vk), + Err(halo2_proofs::plonk::Error::InvalidInstances) + )); + } } } diff --git a/src/bundle/batch.rs b/src/bundle/batch.rs index 2786bdd69..6b502dfc0 100644 --- a/src/bundle/batch.rs +++ b/src/bundle/batch.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use core::fmt; use halo2_proofs::plonk; use pasta_curves::vesta; @@ -18,30 +19,73 @@ struct BundleSignature { signature: redpallas::batch::Item, } +/// Error returned by [`BatchValidator::add_bundle`] when a bundle's flags require a circuit +/// capability the validator's verifying key does not provide. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum BatchError { + /// The bundle disables cross-address transfers, but the validator's verifying key's + /// circuit version does not constrain the cross-address restriction (see + /// [`OrchardCircuitVersion::supports_cross_address_restriction`]). + /// + /// [`OrchardCircuitVersion::supports_cross_address_restriction`]: crate::circuit::OrchardCircuitVersion::supports_cross_address_restriction + RestrictionUnsupportedByKey, +} + +impl fmt::Display for BatchError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BatchError::RestrictionUnsupportedByKey => write!( + f, + "bundle disables cross-address transfers, but the verifying key's circuit \ + version does not constrain the cross-address restriction", + ), + } + } +} + +impl core::error::Error for BatchError {} + /// Batch validation context for Orchard. /// -/// This batch-validates proofs and RedPallas signatures. -#[derive(Debug, Default)] -pub struct BatchValidator { +/// This batch-validates proofs and RedPallas signatures. The verifying key is bound at +/// construction: every bundle added to the batch is validated against it, and +/// [`Self::add_bundle`] rejects up front any bundle whose flags the key's circuit version +/// cannot enforce. +#[derive(Debug)] +pub struct BatchValidator<'a> { proofs: plonk::BatchVerifier, signatures: Vec, + /// The verifying key every queued bundle is validated against, and which + /// [`Self::validate`] finalizes the proof batch with. + vk: &'a VerifyingKey, } -impl BatchValidator { - /// Constructs a new batch validation context. - pub fn new() -> Self { +impl<'a> BatchValidator<'a> { + /// Constructs a new batch validation context that validates against `vk`. + pub fn new(vk: &'a VerifyingKey) -> Self { BatchValidator { proofs: plonk::BatchVerifier::new(), signatures: vec![], + vk, } } /// Adds the proof and RedPallas signatures from the given bundle to the validator. + /// + /// Returns [`BatchError::RestrictionUnsupportedByKey`] if the bundle disables cross-address + /// transfers but the validator's verifying key's circuit version does not support the + /// cross-address restriction; in that case the bundle is not added to the batch. pub fn add_bundle>( &mut self, bundle: &Bundle, sighash: [u8; 32], - ) { + ) -> Result<(), BatchError> { + if !bundle.flags().cross_address_enabled() && !self.vk.supports_cross_address_restriction() + { + return Err(BatchError::RestrictionUnsupportedByKey); + } + for action in bundle.actions().iter() { self.signatures.push(BundleSignature { signature: action @@ -60,15 +104,21 @@ impl BatchValidator { .authorization() .proof() .add_to_batch(&mut self.proofs, bundle.to_instances()); + + Ok(()) } /// Batch-validates the accumulated bundles. /// /// Returns `true` if every proof and signature in every bundle added to the batch - /// validator is valid, or `false` if one or more are invalid. No attempt is made to - /// figure out which of the accumulated bundles might be invalid; if that information - /// is desired, construct separate [`BatchValidator`]s for sub-batches of the bundles. - pub fn validate(self, vk: &VerifyingKey, rng: R) -> bool { + /// validator is valid. Returns `false` if one or more proofs or signatures are + /// invalid. No attempt is made to figure out which of the accumulated bundles might + /// be invalid; if that information is desired, construct separate [`BatchValidator`]s + /// for sub-batches of the bundles. + /// + /// The cross-address-restriction capability is enforced when bundles are added (see + /// [`Self::add_bundle`]), so it is already guaranteed here. + pub fn validate(self, rng: R) -> bool { // https://p.z.cash/TCR:bad-txns-orchard-binding-signature-invalid?partial if self.signatures.is_empty() { @@ -85,7 +135,7 @@ impl BatchValidator { match validator.verify(rng) { // If signatures are valid, check the proofs. - Ok(()) => self.proofs.finalize(&vk.params, &vk.vk), + Ok(()) => self.proofs.finalize(&self.vk.params, &self.vk.vk), Err(e) => { debug!("RedPallas batch validation failed: {}", e); false @@ -93,3 +143,52 @@ impl BatchValidator { } } } + +#[cfg(test)] +mod tests { + use rand::rngs::OsRng; + + use super::{BatchError, BatchValidator}; + use crate::{ + bundle::tests::{sample_authorized_bundle, with_cross_address_disabled}, + circuit::{OrchardCircuitVersion, VerifyingKey}, + }; + + #[test] + fn add_bundle_rejects_unsupported_cross_address_restriction() { + let bundle = with_cross_address_disabled(sample_authorized_bundle(1)) + .try_map_value_balance(i64::try_from) + .expect("generated bundle value balance fits in i64"); + + // Keys whose circuit version cannot constrain the cross-address restriction reject + // the bundle at insertion, so it never enters the batch. + for circuit_version in [ + OrchardCircuitVersion::InsecurePreNu6_2, + OrchardCircuitVersion::FixedPostNu6_2, + ] { + let vk = VerifyingKey::build(circuit_version); + let mut validator = BatchValidator::new(&vk); + assert_eq!( + validator.add_bundle(&bundle, [0; 32]), + Err(BatchError::RestrictionUnsupportedByKey) + ); + } + + // The post-NU 6.3 key supports the restriction, so the bundle is accepted. + let vk = VerifyingKey::build(OrchardCircuitVersion::PostNu6_3); + let mut validator = BatchValidator::new(&vk); + assert_eq!(validator.add_bundle(&bundle, [0; 32]), Ok(())); + } + + #[test] + fn empty_batch_validates() { + for circuit_version in [ + OrchardCircuitVersion::InsecurePreNu6_2, + OrchardCircuitVersion::FixedPostNu6_2, + OrchardCircuitVersion::PostNu6_3, + ] { + let vk = VerifyingKey::build(circuit_version); + assert!(BatchValidator::new(&vk).validate(OsRng)); + } + } +} diff --git a/src/bundle/commitments.rs b/src/bundle/commitments.rs index fee2419db..6d431cd8a 100644 --- a/src/bundle/commitments.rs +++ b/src/bundle/commitments.rs @@ -2,38 +2,147 @@ use blake2b_simd::{Hash as Blake2bHash, Params, State}; -use crate::bundle::{Authorization, Authorized, Bundle}; +use crate::{ + bundle::{Authorization, Authorized, Bundle, CommitmentError, TxVersion}, + ValuePool, +}; -const ZCASH_ORCHARD_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrchardHash"; +const ZCASH_ORCHARD_V5_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrchardHash"; +const ZCASH_ORCHARD_V6_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrchardH_v6"; const ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActCHash"; const ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActMHash"; const ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActNHash"; -const ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthOrchaHash"; +const ZCASH_ORCHARD_V5_SIGS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthOrchaHash"; +const ZCASH_ORCHARD_V6_SIGS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthOrchaH_v6"; +const ZCASH_IRONWOOD_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdIronwd_H_v6"; +const ZCASH_IRONWOOD_ACTIONS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdIrnActCH_v6"; +const ZCASH_IRONWOOD_ACTIONS_MEMOS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdIrnActMH_v6"; +const ZCASH_IRONWOOD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdIrnActNH_v6"; +const ZCASH_IRONWOOD_SIGS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthIrnwdH_v6"; + +#[derive(Clone, Copy, Debug)] +struct BundleCommitmentPersonalizations { + bundle: &'static [u8; 16], + actions_compact: &'static [u8; 16], + actions_memos: &'static [u8; 16], + actions_noncompact: &'static [u8; 16], + auth: &'static [u8; 16], +} + +const ORCHARD_V5_PERSONALIZATIONS: BundleCommitmentPersonalizations = + BundleCommitmentPersonalizations { + bundle: ZCASH_ORCHARD_V5_HASH_PERSONALIZATION, + actions_compact: ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION, + actions_memos: ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION, + actions_noncompact: ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION, + auth: ZCASH_ORCHARD_V5_SIGS_HASH_PERSONALIZATION, + }; + +// Orchard v6 deliberately reuses the v5 action-level personalizations +// (compact/memos/noncompact); only the top-level `bundle` and `auth` strings gain `_v6`. +// Ironwood instead uses fresh `_v6` strings throughout. Either way the top-level digest is +// domain-separated by its `bundle`/`auth` personalization, so reusing the action-level ones +// cannot collide across formats. +const ORCHARD_V6_PERSONALIZATIONS: BundleCommitmentPersonalizations = + BundleCommitmentPersonalizations { + bundle: ZCASH_ORCHARD_V6_HASH_PERSONALIZATION, + actions_compact: ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION, + actions_memos: ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION, + actions_noncompact: ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION, + auth: ZCASH_ORCHARD_V6_SIGS_HASH_PERSONALIZATION, + }; + +const IRONWOOD_V6_PERSONALIZATIONS: BundleCommitmentPersonalizations = + BundleCommitmentPersonalizations { + bundle: ZCASH_IRONWOOD_HASH_PERSONALIZATION, + actions_compact: ZCASH_IRONWOOD_ACTIONS_COMPACT_HASH_PERSONALIZATION, + actions_memos: ZCASH_IRONWOOD_ACTIONS_MEMOS_HASH_PERSONALIZATION, + actions_noncompact: ZCASH_IRONWOOD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION, + auth: ZCASH_IRONWOOD_SIGS_HASH_PERSONALIZATION, + }; + +/// The hash format used to compute a bundle's transaction-ID and authorizing digests, +/// selected from the bundle's pool and the version of the transaction it is encoded in. +/// Orchard bundles use the v5 or v6 format according to the transaction; Ironwood bundles +/// exist only in v6 transactions. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum BundleCommitmentFormat { + OrchardV5, + OrchardV6, + IronwoodV6, +} + +impl ValuePool { + fn commitment_format( + self, + tx_version: TxVersion, + ) -> Result { + match (self, tx_version) { + (ValuePool::Orchard, TxVersion::V5) => Ok(BundleCommitmentFormat::OrchardV5), + (ValuePool::Orchard, TxVersion::V6) => Ok(BundleCommitmentFormat::OrchardV6), + (ValuePool::Ironwood, TxVersion::V5) => Err(CommitmentError::InvalidTransactionVersion), + (ValuePool::Ironwood, TxVersion::V6) => Ok(BundleCommitmentFormat::IronwoodV6), + } + } +} + +impl BundleCommitmentFormat { + fn personalizations(self) -> BundleCommitmentPersonalizations { + match self { + BundleCommitmentFormat::OrchardV5 => ORCHARD_V5_PERSONALIZATIONS, + BundleCommitmentFormat::OrchardV6 => ORCHARD_V6_PERSONALIZATIONS, + BundleCommitmentFormat::IronwoodV6 => IRONWOOD_V6_PERSONALIZATIONS, + } + } + + fn includes_anchor_in_txid_digest(self) -> bool { + matches!(self, BundleCommitmentFormat::OrchardV5) + } + + fn includes_anchor_in_authorizing_digest(self) -> bool { + matches!( + self, + BundleCommitmentFormat::OrchardV6 | BundleCommitmentFormat::IronwoodV6 + ) + } +} fn hasher(personal: &[u8; 16]) -> State { Params::new().hash_length(32).personal(personal).to_state() } -/// Write disjoint parts of each Orchard shielded action as 3 separate hashes +/// Write disjoint parts of each bundle action as 3 separate hashes /// as defined in [ZIP-244: Transaction Identifier Non-Malleability][zip244]: /// * \[(nullifier, cmx, ephemeral_key, enc_ciphertext\[..52\])*\] personalized -/// with ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION +/// with the format's compact-action personalization string /// * \[enc_ciphertext\[52..564\]*\] (memo ciphertexts) personalized -/// with ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION +/// with the format's action-memos personalization string /// * \[(cv, rk, enc_ciphertext\[564..\], out_ciphertext)*\] personalized -/// with ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION +/// with the format's non-compact-action personalization string +/// +/// Then, hash these together along with (flags, value_balance_orchard, and — for the v5 +/// transaction format only — anchor_orchard), personalized with the format's bundle +/// personalization string. In the v6 format the anchor is included by +/// `hash_bundle_auth_data` instead. /// -/// Then, hash these together along with (flags, value_balance_orchard, anchor_orchard), -/// personalized with ZCASH_ORCHARD_ACTIONS_HASH_PERSONALIZATION +/// Returns [`CommitmentError::InvalidTransactionVersion`] if `tx_version` is not valid for the +/// bundle's [`BundleVersion`]. /// /// [zip244]: https://zips.z.cash/zip-0244 +/// [`BundleVersion`]: crate::bundle::BundleVersion pub(crate) fn hash_bundle_txid_data>( bundle: &Bundle, -) -> Blake2bHash { - let mut h = hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION); - let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION); - let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION); - let mut nh = hasher(ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION); + tx_version: TxVersion, +) -> Result { + let format = bundle + .bundle_version() + .value_pool() + .commitment_format(tx_version)?; + let personalizations = format.personalizations(); + let mut h = hasher(personalizations.bundle); + let mut ch = hasher(personalizations.actions_compact); + let mut mh = hasher(personalizations.actions_memos); + let mut nh = hasher(personalizations.actions_noncompact); for action in bundle.actions().iter() { ch.update(&action.nullifier().to_bytes()); @@ -52,18 +161,29 @@ pub(crate) fn hash_bundle_txid_data>( h.update(ch.finalize().as_bytes()); h.update(mh.finalize().as_bytes()); h.update(nh.finalize().as_bytes()); - h.update(&[bundle.flags().to_byte()]); + h.update(&[bundle.flag_byte()]); h.update(&(*bundle.value_balance()).into().to_le_bytes()); - h.update(&bundle.anchor().to_bytes()); - h.finalize() + if format.includes_anchor_in_txid_digest() { + h.update(&bundle.anchor().to_bytes()); + } + Ok(h.finalize()) } /// Construct the commitment for the absent bundle as defined in /// [ZIP-244: Transaction Identifier Non-Malleability][zip244] /// /// [zip244]: https://zips.z.cash/zip-0244 -pub fn hash_bundle_txid_empty() -> Blake2bHash { - hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION).finalize() +pub fn hash_bundle_txid_empty( + value_pool: ValuePool, + tx_version: TxVersion, +) -> Result { + Ok(hasher( + value_pool + .commitment_format(tx_version)? + .personalizations() + .bundle, + ) + .finalize()) } /// Construct the commitment to the authorizing data of an @@ -71,8 +191,15 @@ pub fn hash_bundle_txid_empty() -> Blake2bHash { /// Identifier Non-Malleability][zip244] /// /// [zip244]: https://zips.z.cash/zip-0244 -pub(crate) fn hash_bundle_auth_data(bundle: &Bundle) -> Blake2bHash { - let mut h = hasher(ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION); +pub(crate) fn hash_bundle_auth_data( + bundle: &Bundle, + tx_version: TxVersion, +) -> Result { + let format = bundle + .bundle_version() + .value_pool() + .commitment_format(tx_version)?; + let mut h = hasher(format.personalizations().auth); h.update(bundle.authorization().proof().as_ref()); for action in bundle.actions().iter() { h.update(&<[u8; 64]>::from(action.authorization())); @@ -80,13 +207,25 @@ pub(crate) fn hash_bundle_auth_data(bundle: &Bundle) -> Blake2 h.update(&<[u8; 64]>::from( bundle.authorization().binding_signature(), )); - h.finalize() + if format.includes_anchor_in_authorizing_digest() { + h.update(&bundle.anchor().to_bytes()); + } + Ok(h.finalize()) } /// Construct the commitment for an absent bundle as defined in /// [ZIP-244: Transaction Identifier Non-Malleability][zip244] /// /// [zip244]: https://zips.z.cash/zip-0244 -pub fn hash_bundle_auth_empty() -> Blake2bHash { - hasher(ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION).finalize() +pub fn hash_bundle_auth_empty( + value_pool: ValuePool, + tx_version: TxVersion, +) -> Result { + Ok(hasher( + value_pool + .commitment_format(tx_version)? + .personalizations() + .auth, + ) + .finalize()) } diff --git a/src/circuit.rs b/src/circuit.rs index 63eb5d71e..c6e8b1ed2 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -25,6 +25,7 @@ use self::{ }; use crate::{ builder::SpendInfo, + bundle::Flags, constants::{ OrchardCommitDomains, OrchardFixedBases, OrchardFixedBasesFull, OrchardHashDomains, MERKLE_DEPTH_ORCHARD, @@ -84,6 +85,7 @@ const RK_Y: usize = 5; const CMX: usize = 6; const ENABLE_SPEND: usize = 7; const ENABLE_OUTPUT: usize = 8; +const DISABLE_CROSS_ADDRESS: usize = 9; /// Configuration needed to use the Orchard Action circuit. #[derive(Clone, Debug)] @@ -107,47 +109,73 @@ pub struct Config { /// Selects which version of the Orchard Action circuit to build. /// -/// The two versions produce different verifying keys: the fixed circuit anchors the -/// variable-base scalar-multiplication base (see `halo2_gadgets`), the pre-NU6.2 one does -/// not. [`FixedPostNu6_2`] is used for all proving and current verification; -/// [`InsecurePreNu6_2`] reconstructs the historical (NU5..NU6.2) verifying key solely to -/// verify proofs produced before NU6.2. +/// [`FixedPostNu6_2`] and [`InsecurePreNu6_2`] produce different verifying keys: the fixed +/// circuit anchors the variable-base scalar-multiplication base (see `halo2_gadgets`), while +/// the pre-NU6.2 one does not. [`PostNu6_3`] extends the fixed circuit by enforcing the +/// same-address check, i.e. `(g_d^old, pk_d^old) = (g_d^new, pk_d^new)`, when the +/// boolean `disableCrossAddress` public input is set. /// /// This is a runtime value rather than a type parameter: it is carried in [`Circuit`] and /// chosen when building a [`ProvingKey`] or [`VerifyingKey`], so the circuit version can be /// threaded dynamically (e.g. across an FFI boundary). /// +/// Please note that the public exposure of APIs using `InsecurePreNu6_2` is intentional, +/// and is strictly necessary for verifying the block chain from NU5 activation and for +/// creating proofs needed by tests that operate at past epochs. These APIs cannot be +/// used accidentally without passing an `OrchardCircuitVersion` that is clearly labelled +/// "insecure". This is not a security vulnerability. +/// /// [`FixedPostNu6_2`]: OrchardCircuitVersion::FixedPostNu6_2 /// [`InsecurePreNu6_2`]: OrchardCircuitVersion::InsecurePreNu6_2 -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +/// [`PostNu6_3`]: OrchardCircuitVersion::PostNu6_3 +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum OrchardCircuitVersion { /// The insecure pre-NU6.2 circuit, in which the variable-base scalar-multiplication base /// is not anchored to the real base. For reconstructing the historical (NU5..NU6.2) /// verifying key only — never for proving or current verification. InsecurePreNu6_2, - /// The fixed circuit, active from NU6.2 onward. Used for all proving and current + /// The fixed circuit, active from NU6.2 onward. Used for all current network proving and /// verification. - #[default] FixedPostNu6_2, + /// The post-NU 6.3 circuit. This uses the fixed circuit with additional constraints + /// enforcing the `disableCrossAddress` public input. + PostNu6_3, } impl OrchardCircuitVersion { + /// Whether this circuit version enforces the `disableCrossAddress` public input. + /// + /// Statements with `disableCrossAddress = 1` can be proven and verified only with + /// keys for a circuit version that constrains the flag. [`PostNu6_3`] constrains it; + /// older circuit versions leave it unconstrained, so they cannot enforce — and must + /// not be asked to attest to — the restriction. + /// + /// [`PostNu6_3`]: OrchardCircuitVersion::PostNu6_3 + pub fn supports_cross_address_restriction(self) -> bool { + match self { + OrchardCircuitVersion::InsecurePreNu6_2 | OrchardCircuitVersion::FixedPostNu6_2 => { + false + } + OrchardCircuitVersion::PostNu6_3 => true, + } + } + /// The corresponding `halo2_gadgets` variable-base scalar-mul circuit version. fn halo2_version(self) -> CircuitVersion { match self { OrchardCircuitVersion::InsecurePreNu6_2 => CircuitVersion::InsecureUnanchoredBase, - OrchardCircuitVersion::FixedPostNu6_2 => CircuitVersion::AnchoredBase, + OrchardCircuitVersion::FixedPostNu6_2 | OrchardCircuitVersion::PostNu6_3 => { + CircuitVersion::AnchoredBase + } } } } /// The Orchard Action circuit. /// -/// The `circuit_version` field selects which circuit to build; it defaults to -/// [`OrchardCircuitVersion::FixedPostNu6_2`], so a default `Circuit` is the current (fixed) -/// circuit. [`OrchardCircuitVersion::InsecurePreNu6_2`] exists only to rebuild the historical -/// verifying key. -#[derive(Clone, Debug, Default)] +/// The `circuit_version` field selects which circuit to build. Callers must choose it +/// explicitly instead of relying on a default. +#[derive(Clone, Debug)] pub struct Circuit { pub(crate) path: Value<[MerkleHashOrchard; MERKLE_DEPTH_ORCHARD]>, pub(crate) pos: Value, @@ -172,12 +200,41 @@ pub struct Circuit { } impl Circuit { + /// Returns an empty circuit with all private witnesses unknown. + /// + /// This is used for circuit shape-dependent operations, such as generating keys + /// or rendering the circuit layout, where witness values are not required but the + /// selected circuit version still determines the configured constraints. + fn empty(circuit_version: OrchardCircuitVersion) -> Self { + Circuit { + path: Value::unknown(), + pos: Value::unknown(), + g_d_old: Value::unknown(), + pk_d_old: Value::unknown(), + v_old: Value::unknown(), + rho_old: Value::unknown(), + psi_old: Value::unknown(), + rcm_old: Value::unknown(), + cm_old: Value::unknown(), + alpha: Value::unknown(), + ak: Value::unknown(), + nk: Value::unknown(), + rivk: Value::unknown(), + g_d_new: Value::unknown(), + pk_d_new: Value::unknown(), + v_new: Value::unknown(), + psi_new: Value::unknown(), + rcm_new: Value::unknown(), + rcv: Value::unknown(), + circuit_version, + } + } + /// This constructor is public to enable creation of custom builders. /// If you are not creating a custom builder, use [`Builder`] to compose /// and authorize a transaction. /// - /// Constructs a `Circuit` for the current (fixed) circuit version from the following - /// components: + /// Constructs a `Circuit` for the given `circuit_version` from the following components: /// - `spend`: [`SpendInfo`] of the note spent in scope of the action /// - `output_note`: a note created in scope of the action /// - `alpha`: a scalar used for randomization of the action spend validating key @@ -193,25 +250,6 @@ impl Circuit { output_note: Note, alpha: pallas::Scalar, rcv: ValueCommitTrapdoor, - ) -> Option { - Self::from_action_context_for_version( - spend, - output_note, - alpha, - rcv, - OrchardCircuitVersion::FixedPostNu6_2, - ) - } - - /// Like [`Circuit::from_action_context`], but builds the circuit for the given - /// `circuit_version`. Only [`OrchardCircuitVersion::FixedPostNu6_2`] should be used for - /// proving; [`OrchardCircuitVersion::InsecurePreNu6_2`] exists to reconstruct historical - /// proofs (e.g. for testing that pre-NU6.2 proofs still verify). - pub fn from_action_context_for_version( - spend: SpendInfo, - output_note: Note, - alpha: pallas::Scalar, - rcv: ValueCommitTrapdoor, circuit_version: OrchardCircuitVersion, ) -> Option { (Rho::from_nf_old(spend.note.nullifier(&spend.fvk)) == output_note.rho()).then(|| { @@ -228,12 +266,11 @@ impl Circuit { ) -> Circuit { let sender_address = spend.note.recipient(); let rho_old = spend.note.rho(); - let psi_old = spend.note.rseed().psi(&rho_old); - let rcm_old = spend.note.rseed().rcm(&rho_old); + let psi_old = spend.note.psi(); + let rcm_old = spend.note.rcm(); - let rho_new = output_note.rho(); - let psi_new = output_note.rseed().psi(&rho_new); - let rcm_new = output_note.rseed().rcm(&rho_new); + let psi_new = output_note.psi(); + let rcm_new = output_note.rcm(); Circuit { path: Value::known(spend.merkle_path.auth_path()), @@ -260,15 +297,9 @@ impl Circuit { } } -impl plonk::Circuit for Circuit { - type Config = Config; - type FloorPlanner = floor_planner::V1; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut plonk::ConstraintSystem) -> Self::Config { +impl Config { + /// Configures the Orchard Action constraint system shared by every circuit version. + fn configure(meta: &mut plonk::ConstraintSystem) -> Self { // Advice columns used in the Orchard circuit. let advices = [ meta.advice_column(), @@ -285,8 +316,11 @@ impl plonk::Circuit for Circuit { // Constrain v_old - v_new = magnitude * sign (https://p.z.cash/ZKS:action-cv-net-integrity?partial). // Either v_old = 0, or calculated root = anchor (https://p.z.cash/ZKS:action-merkle-path-validity?partial). - // Constrain v_old = 0 or enable_spends = 1 (https://p.z.cash/ZKS:action-enable-spend). - // Constrain v_new = 0 or enable_outputs = 1 (https://p.z.cash/ZKS:action-enable-output). + // Constrain v_old = 0 or enable_spend = 1 (https://p.z.cash/ZKS:action-enable-spend). + // Constrain v_new = 0 or enable_output = 1 (https://p.z.cash/ZKS:action-enable-output). + // + // This gate is also reused for the same-address check; see + // [`Circuit::synthesize_cross_address_checks`]. let q_orchard = meta.selector(); meta.create_gate("Orchard circuit checks", |meta| { let q_orchard = meta.query_selector(q_orchard); @@ -298,8 +332,8 @@ impl plonk::Circuit for Circuit { let root = meta.query_advice(advices[4], Rotation::cur()); let anchor = meta.query_advice(advices[5], Rotation::cur()); - let enable_spends = meta.query_advice(advices[6], Rotation::cur()); - let enable_outputs = meta.query_advice(advices[7], Rotation::cur()); + let enable_spend = meta.query_advice(advices[6], Rotation::cur()); + let enable_output = meta.query_advice(advices[7], Rotation::cur()); let one = Expression::Constant(pallas::Base::one()); @@ -315,12 +349,12 @@ impl plonk::Circuit for Circuit { v_old.clone() * (root - anchor), ), ( - "v_old = 0 or enable_spends = 1", - v_old * (one.clone() - enable_spends), + "v_old = 0 or enable_spend = 1", + v_old * (one.clone() - enable_spend), ), ( - "v_new = 0 or enable_outputs = 1", - v_new * (one - enable_outputs), + "v_new = 0 or enable_output = 1", + v_new * (one - enable_output), ), ], ) @@ -456,15 +490,31 @@ impl plonk::Circuit for Circuit { new_note_commit_config, } } +} +/// Cells carrying the addresses of an action's spent and newly created notes, returned +/// from the shared synthesis logic so that circuit versions can impose additional +/// constraints on them. +struct AddressPoints { + g_d_old: NonIdentityPoint>, + pk_d_old: NonIdentityPoint>, + g_d_new: NonIdentityPoint>, + pk_d_new: NonIdentityPoint>, +} + +impl Circuit { + /// Synthesizes the Orchard Action checks common to every circuit version, + /// parameterized by `self.circuit_version`, returning the cells carrying the old + /// and new note addresses so that circuit versions can impose additional + /// constraints on them. #[allow(non_snake_case)] - fn synthesize( + fn synthesize_base( &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), plonk::Error> { + config: &Config, + layouter: &mut impl Layouter, + ) -> Result { // Load the Sinsemilla generator lookup table used by the whole circuit. - SinsemillaChip::load(config.sinsemilla_config_1.clone(), &mut layouter)?; + SinsemillaChip::load(config.sinsemilla_config_1.clone(), layouter)?; // Construct the ECC chip. let ecc_chip = config.ecc_chip(self.circuit_version.halo2_version()); @@ -717,28 +767,30 @@ impl plonk::Circuit for Circuit { derived_cm_old.constrain_equal(layouter.namespace(|| "cm_old equality"), &cm_old)?; } - // New note commitment integrity (https://p.z.cash/ZKS:action-cmx-new-integrity?partial). - { - // Witness g_d_new - let g_d_new = { - let g_d_new = self.g_d_new.map(|g_d_new| g_d_new.to_affine()); - NonIdentityPoint::new( - ecc_chip.clone(), - layouter.namespace(|| "witness g_d_new_star"), - g_d_new, - )? - }; + // Witness g_d_new, used in the new note commitment integrity check below and, + // for the post-NU 6.3 circuit, in the cross-address checks. + let g_d_new = { + let g_d_new = self.g_d_new.map(|g_d_new| g_d_new.to_affine()); + NonIdentityPoint::new( + ecc_chip.clone(), + layouter.namespace(|| "witness g_d_new_star"), + g_d_new, + )? + }; - // Witness pk_d_new - let pk_d_new = { - let pk_d_new = self.pk_d_new.map(|pk_d_new| pk_d_new.inner().to_affine()); - NonIdentityPoint::new( - ecc_chip.clone(), - layouter.namespace(|| "witness pk_d_new"), - pk_d_new, - )? - }; + // Witness pk_d_new, used in the new note commitment integrity check below and, + // for the post-NU 6.3 circuit, in the cross-address checks. + let pk_d_new = { + let pk_d_new = self.pk_d_new.map(|pk_d_new| pk_d_new.inner().to_affine()); + NonIdentityPoint::new( + ecc_chip.clone(), + layouter.namespace(|| "witness pk_d_new"), + pk_d_new, + )? + }; + // New note commitment integrity (https://p.z.cash/ZKS:action-cmx-new-integrity?partial). + { // ρ^new = nf^old let rho_new = nf_old.inner().clone(); @@ -806,7 +858,7 @@ impl plonk::Circuit for Circuit { )?; region.assign_advice_from_instance( - || "enable spends", + || "enable spend", config.primary, ENABLE_SPEND, config.advices[6], @@ -814,7 +866,7 @@ impl plonk::Circuit for Circuit { )?; region.assign_advice_from_instance( - || "enable outputs", + || "enable output", config.primary, ENABLE_OUTPUT, config.advices[7], @@ -825,45 +877,237 @@ impl plonk::Circuit for Circuit { }, )?; + Ok(AddressPoints { + g_d_old, + pk_d_old, + g_d_new, + pk_d_new, + }) + } + + /// Enforces the post-NU 6.3 cross-address restriction for one action: when + /// `disableCrossAddress` is nonzero, the spent note and output note must be + /// addressed to the same expanded receiver, meaning equal `(g_d, pk_d)`. + /// + /// This reuses the existing "Orchard circuit checks" gate instead of adding a + /// new gate. The gate already has a product constraint, + /// `v_old * (root - anchor) = 0`, with exactly the shape needed for + /// `disableCrossAddress * (old_coord - new_coord) = 0`. + /// + /// The post-NU 6.3 circuit enables that gate on four extra rows, one per affine coordinate of + /// `(g_d, pk_d)`, with each row wired as: + /// + /// ```text + /// v_old <- disableCrossAddress + /// v_new <- 0 (constant) + /// magnitude <- disableCrossAddress + /// sign <- 1 (constant) + /// root <- old coordinate + /// anchor <- new coordinate + /// enable_spend <- 1 (constant) + /// enable_output <- 1 (constant) + /// ``` + /// + /// With this layout, the gate constraints become: + /// + /// ```text + /// v_old - v_new = magnitude * sign -> disableCrossAddress - 0 = disableCrossAddress * 1 + /// v_old * (root - anchor) = 0 -> disableCrossAddress * (old_coord - new_coord) = 0 + /// v_old * (1 - enable_spend) = 0 -> disableCrossAddress * (1 - 1) = 0 + /// v_new * (1 - enable_output) = 0 -> 0 * (1 - 1) = 0 + /// ``` + /// + /// The second line is the actual cross-address check. Any nonzero + /// `disableCrossAddress` value forces each old coordinate to equal the + /// corresponding new coordinate. The public API encodes `disableCrossAddress` + /// as 0 or 1, but this algebra does not rely on a boolean constraint. + /// + /// The two otherwise-unused advice columns are also filled with copies of + /// `disableCrossAddress` so these rows occupy every advice column; that prevents + /// the floor planner from overlapping another selector-enabled region with the + /// check rows. + fn synthesize_cross_address_checks( + config: &Config, + layouter: &mut impl Layouter, + addrs: &AddressPoints, + ) -> Result<(), plonk::Error> { + let AddressPoints { + g_d_old, + pk_d_old, + g_d_new, + pk_d_new, + } = addrs; + + layouter.assign_region( + || "post-NU 6.3 cross-address checks", + |mut region| { + let coordinate_checks = [ + ("g_d x", g_d_old.inner().x(), g_d_new.inner().x()), + ("g_d y", g_d_old.inner().y(), g_d_new.inner().y()), + ("pk_d x", pk_d_old.inner().x(), pk_d_new.inner().x()), + ("pk_d y", pk_d_old.inner().y(), pk_d_new.inner().y()), + ]; + + for (offset, (label, old_coord, new_coord)) in + coordinate_checks.into_iter().enumerate() + { + // Copy disableCrossAddress from the public input at + // primary[DISABLE_CROSS_ADDRESS] into advices[0] for this + // coordinate-check row. + let cross_address_disabled = region.assign_advice_from_instance( + || "disableCrossAddress", + config.primary, + DISABLE_CROSS_ADDRESS, + config.advices[0], + offset, + )?; + + // Fill the v_new, magnitude, and sign cells so the reused + // value-balance constraint reads: + // disableCrossAddress - 0 = disableCrossAddress * 1. + region.assign_advice_from_constant( + || "zero", + config.advices[1], + offset, + pallas::Base::zero(), + )?; + cross_address_disabled.copy_advice( + || "disableCrossAddress magnitude", + &mut region, + config.advices[2], + offset, + )?; + region.assign_advice_from_constant( + || "positive sign", + config.advices[3], + offset, + pallas::Base::one(), + )?; + + // Copy the old coordinate into the gate's root cell and the + // new coordinate into its anchor cell for the equality check. + old_coord.copy_advice( + || format!("old {label}"), + &mut region, + config.advices[4], + offset, + )?; + new_coord.copy_advice( + || format!("new {label}"), + &mut region, + config.advices[5], + offset, + )?; + + // Set both enable flags to one so the unrelated enable checks + // in q_orchard are neutralized on these rows. + region.assign_advice_from_constant( + || "one (neutralize enable_spend check)", + config.advices[6], + offset, + pallas::Base::one(), + )?; + region.assign_advice_from_constant( + || "one (neutralize enable_output check)", + config.advices[7], + offset, + pallas::Base::one(), + )?; + + // Occupy the otherwise-unused rightmost advice columns so the + // floor planner cannot lay out another region (and enable its + // gate) on these rows. + cross_address_disabled.copy_advice( + || "disableCrossAddress padding", + &mut region, + config.advices[8], + offset, + )?; + cross_address_disabled.copy_advice( + || "disableCrossAddress padding", + &mut region, + config.advices[9], + offset, + )?; + + config.q_orchard.enable(&mut region, offset)?; + } + + Ok(()) + }, + ) + } +} + +impl plonk::Circuit for Circuit { + type Config = Config; + type FloorPlanner = floor_planner::V1; + + fn without_witnesses(&self) -> Self { + Self::empty(self.circuit_version) + } + + fn configure(meta: &mut plonk::ConstraintSystem) -> Self::Config { + Config::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), plonk::Error> { + let addrs = self.synthesize_base(&config, &mut layouter)?; + + if self.circuit_version.supports_cross_address_restriction() { + Self::synthesize_cross_address_checks(&config, &mut layouter, &addrs)?; + } + Ok(()) } } /// The verifying key for the Orchard Action circuit. /// -/// Build with [`VerifyingKey::build`] for the current (fixed) circuit, or -/// [`VerifyingKey::build_for_version`] to reconstruct the historical verifying key. The key -/// verifies only proofs created for the same circuit version. +/// Build with [`VerifyingKey::build`] for an explicit circuit version. #[derive(Debug)] pub struct VerifyingKey { pub(crate) params: halo2_proofs::poly::commitment::Params, pub(crate) vk: plonk::VerifyingKey, + circuit_version: OrchardCircuitVersion, } impl VerifyingKey { - /// Builds the verifying key for the current (fixed, NU6.2-onward) circuit. - pub fn build() -> Self { - Self::build_for_version(OrchardCircuitVersion::FixedPostNu6_2) - } - /// Builds the verifying key for the given circuit version. - pub fn build_for_version(circuit_version: OrchardCircuitVersion) -> Self { + /// + /// See [`OrchardCircuitVersion`] for which version to use. + pub fn build(circuit_version: OrchardCircuitVersion) -> Self { let params = halo2_proofs::poly::commitment::Params::new(K); - let circuit = Circuit { - circuit_version, - ..Default::default() - }; + let circuit = Circuit::empty(circuit_version); let vk = plonk::keygen_vk(¶ms, &circuit).unwrap(); - VerifyingKey { params, vk } + VerifyingKey { + params, + vk, + circuit_version, + } + } + + /// The circuit version this verifying key was built for. + pub fn circuit_version(&self) -> OrchardCircuitVersion { + self.circuit_version + } + + /// Returns whether this verifying key supports the cross-address restriction. + pub fn supports_cross_address_restriction(&self) -> bool { + self.circuit_version.supports_cross_address_restriction() } } /// The proving key for the Orchard Action circuit. /// -/// Build with [`ProvingKey::build`] for the current (fixed) circuit. The resulting proofs -/// verify only under a [`VerifyingKey`] for the same circuit version. +/// Build with [`ProvingKey::build`] for an explicit circuit version. +/// The resulting proofs verify only under a compatible [`VerifyingKey`]. #[derive(Debug)] pub struct ProvingKey { params: halo2_proofs::poly::commitment::Params, @@ -872,22 +1116,12 @@ pub struct ProvingKey { } impl ProvingKey { - /// Builds the proving key for the current (fixed, NU6.2-onward) circuit. - pub fn build() -> Self { - Self::build_for_version(OrchardCircuitVersion::FixedPostNu6_2) - } - /// Builds the proving key for the given circuit version. /// - /// Only [`OrchardCircuitVersion::FixedPostNu6_2`] should be used to prove transactions for - /// the network; [`OrchardCircuitVersion::InsecurePreNu6_2`] exists only to reproduce - /// historical proofs (e.g. for testing that pre-NU6.2 proofs still verify). - pub fn build_for_version(circuit_version: OrchardCircuitVersion) -> Self { + /// See [`OrchardCircuitVersion`] for which version to use. + pub fn build(circuit_version: OrchardCircuitVersion) -> Self { let params = halo2_proofs::poly::commitment::Params::new(K); - let circuit = Circuit { - circuit_version, - ..Default::default() - }; + let circuit = Circuit::empty(circuit_version); let vk = plonk::keygen_vk(¶ms, &circuit).unwrap(); let pk = plonk::keygen_pk(¶ms, vk, &circuit).unwrap(); @@ -903,6 +1137,11 @@ impl ProvingKey { pub fn circuit_version(&self) -> OrchardCircuitVersion { self.circuit_version } + + /// Returns whether this proving key supports the cross-address restriction. + pub fn supports_cross_address_restriction(&self) -> bool { + self.circuit_version.supports_cross_address_restriction() + } } /// Public inputs to the Orchard Action circuit. @@ -919,6 +1158,7 @@ pub struct Instance { cmx: ExtractedNoteCommitment, enable_spend: bool, enable_output: bool, + cross_address_disabled: bool, } impl Instance { @@ -928,6 +1168,13 @@ impl Instance { /// pipelines for many proofs, where you don't want to pass around the full bundle. /// Use [`Bundle::verify_proof`] instead if you have the full bundle. /// + /// The provided [`Flags`] are encoded into the spend/output enable public inputs and + /// the `disableCrossAddress` public input, which is set to the negation of + /// [`Flags::cross_address_enabled`]. If cross-address transfers are disabled, + /// callers must use a proving or verifying key whose circuit version supports the + /// cross-address restriction; [`Proof::create`], [`Proof::verify`], and + /// [`crate::bundle::BatchValidator`] enforce this. + /// /// Returns `None` if `rk` is the identity [`pasta_curves::pallas::Point`]. /// zcashd v6.12.1 and Zebra 4.3.1 both added a consensus rule rejecting /// transactions whose Orchard actions have an identity `rk`; the Zcash @@ -945,8 +1192,7 @@ impl Instance { nf_old: Nullifier, rk: VerificationKey, cmx: ExtractedNoteCommitment, - enable_spend: bool, - enable_output: bool, + flags: Flags, ) -> Option { (!rk.is_identity()).then_some(Instance { anchor, @@ -954,8 +1200,9 @@ impl Instance { nf_old, rk, cmx, - enable_spend, - enable_output, + enable_spend: flags.spends_enabled(), + enable_output: flags.outputs_enabled(), + cross_address_disabled: !flags.cross_address_enabled(), }) } @@ -984,18 +1231,23 @@ impl Instance { &self.cmx } - /// Returns whether spends are enabled for this instance. + /// Returns whether the spend is enabled for this instance. pub(crate) fn enable_spend(&self) -> bool { self.enable_spend } - /// Returns whether outputs are enabled for this instance. + /// Returns whether the output is enabled for this instance. pub(crate) fn enable_output(&self) -> bool { self.enable_output } - fn to_halo2_instance(&self) -> [[vesta::Scalar; 9]; 1] { - let mut instance = [vesta::Scalar::zero(); 9]; + /// Returns whether cross-address transfers are disabled for this instance. + pub(crate) fn cross_address_disabled(&self) -> bool { + self.cross_address_disabled + } + + fn to_halo2_instance(&self) -> [[vesta::Scalar; 10]; 1] { + let mut instance = [vesta::Scalar::zero(); 10]; instance[ANCHOR] = self.anchor.inner(); instance[CV_NET_X] = self.cv_net.x(); @@ -1013,6 +1265,13 @@ impl Instance { instance[CMX] = self.cmx.inner(); instance[ENABLE_SPEND] = vesta::Scalar::from(u64::from(self.enable_spend)); instance[ENABLE_OUTPUT] = vesta::Scalar::from(u64::from(self.enable_output)); + // Instance columns are zero-padded over the evaluation domain, so for statements + // where this flag is false, this encoding is commitment-identical to the historical + // nine-row encoding. Pre-NU 6.3 circuits leave this row unconstrained, which is why + // restricted statements must never reach those keys (see `Proof::create` and + // `Proof::verify`). + instance[DISABLE_CROSS_ADDRESS] = + vesta::Scalar::from(u64::from(self.cross_address_disabled)); [instance] } @@ -1021,9 +1280,18 @@ impl Instance { impl Proof { /// Creates a proof for the given circuits and instances. /// - /// The resulting proof verifies only under a [`VerifyingKey`] for the same circuit version - /// (see [`OrchardCircuitVersion`]). Returns an error if any circuit's version does not match - /// `pk`'s version, since `pk` could not produce a valid proof for it. + /// The resulting proof verifies only under a compatible [`VerifyingKey`] (see + /// [`OrchardCircuitVersion`]). + /// + /// Returns [`plonk::Error::Synthesis`] if any circuit's version does not match `pk`'s + /// version, since `pk` could not produce a valid proof for it. + /// + /// Returns [`plonk::Error::InvalidInstances`] if any instance has + /// `disableCrossAddress = 1` and `pk` is not an + /// [`OrchardCircuitVersion::PostNu6_3`] proving key. + /// + /// All instances of a bundle carry the same `disableCrossAddress` value; that uniformity + /// is the bundle layer's invariant, and is not checked here. pub fn create( pk: &ProvingKey, circuits: &[Circuit], @@ -1036,6 +1304,11 @@ impl Proof { { return Err(plonk::Error::Synthesis); } + if instances.iter().any(Instance::cross_address_disabled) + && !pk.supports_cross_address_restriction() + { + return Err(plonk::Error::InvalidInstances); + } let instances: Vec<_> = instances.iter().map(|i| i.to_halo2_instance()).collect(); let instances: Vec> = instances @@ -1057,7 +1330,21 @@ impl Proof { } /// Verifies this proof with the given instances. + /// + /// # Errors + /// + /// Returns [`plonk::Error::InvalidInstances`] if any instance has + /// `disableCrossAddress = 1` and `vk` is not an + /// [`OrchardCircuitVersion::PostNu6_3`] verifying key. + /// + /// Also returns an error if proof verification fails. pub fn verify(&self, vk: &VerifyingKey, instances: &[Instance]) -> Result<(), plonk::Error> { + if instances.iter().any(Instance::cross_address_disabled) + && !vk.supports_cross_address_restriction() + { + return Err(plonk::Error::InvalidInstances); + } + let instances: Vec<_> = instances.iter().map(|i| i.to_halo2_instance()).collect(); let instances: Vec> = instances .iter() @@ -1072,11 +1359,22 @@ impl Proof { /// Adds this proof to the given batch for verification with the given instances. /// - /// Use this API if you want more control over how proof batches are processed. If you - /// just want to batch-validate Orchard bundles, use [`bundle::BatchValidator`]. + /// Internal to [`BatchValidator`], which is the only public batch path. A raw batch + /// does not know which [`VerifyingKey`] it will be finalized with, so it cannot enforce + /// that instances disabling cross-address transfers are only finalized with a key whose + /// circuit version constrains the `disableCrossAddress` public input (see + /// [`OrchardCircuitVersion::supports_cross_address_restriction`]). [`BatchValidator`] + /// binds its key at construction and rejects such bundles in [`add_bundle`] before they + /// reach this method; exposing this directly would let a caller sidestep that check by + /// finalizing the batch against an unsupported key. /// - /// [`bundle::BatchValidator`]: crate::bundle::BatchValidator - pub fn add_to_batch(&self, batch: &mut BatchVerifier, instances: Vec) { + /// [`BatchValidator`]: crate::bundle::BatchValidator + /// [`add_bundle`]: crate::bundle::BatchValidator::add_bundle + pub(crate) fn add_to_batch( + &self, + batch: &mut BatchVerifier, + instances: Vec, + ) { let instances = instances .iter() .map(|i| { @@ -1098,22 +1396,44 @@ mod tests { use ff::Field; use halo2_proofs::{circuit::Value, dev::MockProver}; - use pasta_curves::pallas; + use pasta_curves::{pallas, vesta}; use rand::{rngs::OsRng, RngCore}; use super::{Circuit, Instance, OrchardCircuitVersion, Proof, ProvingKey, VerifyingKey, K}; use crate::{ + bundle::{BundleVersion, Flags}, keys::SpendValidatingKey, - note::{Note, Rho}, + note::{Note, NoteVersion, Rho}, tree::MerklePath, value::{ValueCommitTrapdoor, ValueCommitment}, }; + /// Generates a circuit and instance whose output note is addressed to an expanded + /// receiver distinct from the spent note's. fn generate_circuit_instance( + rng: R, + circuit_version: OrchardCircuitVersion, + ) -> (Circuit, Instance) { + generate_circuit_instance_inner(rng, circuit_version, false) + } + + /// Generates a circuit and instance whose output note is addressed to the spent + /// note's expanded receiver, as the cross-address restriction requires. + fn generate_self_transfer_circuit_instance( + rng: R, + circuit_version: OrchardCircuitVersion, + ) -> (Circuit, Instance) { + generate_circuit_instance_inner(rng, circuit_version, true) + } + + fn generate_circuit_instance_inner( mut rng: R, circuit_version: OrchardCircuitVersion, + output_matches_spend: bool, ) -> (Circuit, Instance) { - let (_, fvk, spent_note) = Note::dummy(&mut rng, None); + // Note Version does not matter for this + let note_version = NoteVersion::V2; + let (_, fvk, spent_note) = Note::dummy(&mut rng, None, note_version); let sender_address = spent_note.recipient(); let nk = *fvk.nk(); @@ -1124,7 +1444,22 @@ mod tests { let alpha = pallas::Scalar::random(&mut rng); let rk = ak.randomize(&alpha); - let (_, _, output_note) = Note::dummy(&mut rng, Some(rho)); + let output_note = if output_matches_spend { + Note::new( + sender_address, + spent_note.value(), + rho, + note_version, + &mut rng, + ) + } else { + loop { + let (_, _, output_note) = Note::dummy(&mut rng, Some(rho), note_version); + if !sender_address.same_expanded_receiver(&output_note.recipient()) { + break output_note; + } + } + }; let cmx = output_note.commitment().into(); let value = spent_note.value() - output_note.value(); @@ -1143,8 +1478,8 @@ mod tests { pk_d_old: Value::known(*sender_address.pk_d()), v_old: Value::known(spent_note.value()), rho_old: Value::known(spent_note.rho()), - psi_old: Value::known(spent_note.rseed().psi(&spent_note.rho())), - rcm_old: Value::known(spent_note.rseed().rcm(&spent_note.rho())), + psi_old: Value::known(spent_note.psi()), + rcm_old: Value::known(spent_note.rcm()), cm_old: Value::known(spent_note.commitment()), alpha: Value::known(alpha), ak: Value::known(ak), @@ -1153,8 +1488,8 @@ mod tests { g_d_new: Value::known(output_note.recipient().g_d()), pk_d_new: Value::known(*output_note.recipient().pk_d()), v_new: Value::known(output_note.value()), - psi_new: Value::known(output_note.rseed().psi(&output_note.rho())), - rcm_new: Value::known(output_note.rseed().rcm(&output_note.rho())), + psi_new: Value::known(output_note.psi()), + rcm_new: Value::known(output_note.rcm()), rcv: Value::known(rcv), }, Instance { @@ -1165,29 +1500,51 @@ mod tests { cmx, enable_spend: true, enable_output: true, + cross_address_disabled: false, }, ) } + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + enum ProofFixtureEncoding { + LegacyTwoFlags, + PostNu6_3ThreeFlags, + } + fn write_test_case( mut w: W, instance: &Instance, proof: &Proof, + encoding: ProofFixtureEncoding, ) -> std::io::Result<()> { w.write_all(&instance.anchor().to_bytes())?; w.write_all(&instance.cv_net().to_bytes())?; w.write_all(&instance.nf_old().to_bytes())?; w.write_all(&<[u8; 32]>::from(instance.rk()))?; w.write_all(&instance.cmx().to_bytes())?; - w.write_all(&[ - u8::from(instance.enable_spend()), - u8::from(instance.enable_output()), - ])?; + match encoding { + ProofFixtureEncoding::LegacyTwoFlags => { + w.write_all(&[ + u8::from(instance.enable_spend()), + u8::from(instance.enable_output()), + ])?; + } + ProofFixtureEncoding::PostNu6_3ThreeFlags => { + w.write_all(&[ + u8::from(instance.enable_spend()), + u8::from(instance.enable_output()), + u8::from(instance.cross_address_disabled()), + ])?; + } + } w.write_all(proof.as_ref())?; Ok(()) } - fn read_test_case(mut r: R) -> std::io::Result<(Instance, Proof)> { + fn read_test_case( + mut r: R, + encoding: ProofFixtureEncoding, + ) -> std::io::Result<(Instance, Proof)> { let read_32_bytes = |r: &mut R| { let mut ret = [0u8; 32]; r.read_exact(&mut ret).unwrap(); @@ -1210,9 +1567,30 @@ mod tests { let cmx = crate::note::ExtractedNoteCommitment::from_bytes(&read_32_bytes(&mut r)).unwrap(); let enable_spend = read_bool(&mut r); let enable_output = read_bool(&mut r); - let instance = - Instance::from_parts(anchor, cv_net, nf_old, rk, cmx, enable_spend, enable_output) - .expect("test vectors were generated with non-identity rk"); + let (cross_address_bit, bundle_version) = match encoding { + ProofFixtureEncoding::LegacyTwoFlags => (0, BundleVersion::orchard_v2()), + ProofFixtureEncoding::PostNu6_3ThreeFlags => { + // The fixture stores the instance-level *disable* bit; the NU6.3 flag + // byte carries the *enable* bit, so invert when reconstructing. + // + // The circuit is pool-agnostic; decode under Ironwood, the pool whose flag + // byte can represent `enableCrossAddress` either way (Orchard post-NU6.3 + // rejects bit 2). + let cross_address_disabled = read_bool(&mut r); + ( + u8::from(!cross_address_disabled) << 2, + BundleVersion::ironwood_v3(), + ) + } + }; + let flags = Flags::from_byte( + u8::from(enable_spend) | (u8::from(enable_output) << 1) | cross_address_bit, + bundle_version, + ) + .expect("test vectors use canonical flag encodings"); + + let instance = Instance::from_parts(anchor, cv_net, nf_old, rk, cmx, flags) + .expect("test vectors were generated with non-identity rk"); let mut proof_bytes = vec![]; r.read_to_end(&mut proof_bytes)?; @@ -1221,34 +1599,174 @@ mod tests { Ok((instance, proof)) } - // TODO: recast as a proptest #[test] - fn round_trip() { + fn halo2_instance_includes_cross_address_disabled_flag() { + let (_, mut instance) = + generate_circuit_instance(OsRng, OrchardCircuitVersion::FixedPostNu6_2); + + let halo2_instance = instance.to_halo2_instance(); + assert_eq!(halo2_instance[0].len(), 10); + assert_eq!( + halo2_instance[0][super::DISABLE_CROSS_ADDRESS], + vesta::Scalar::zero() + ); + + instance.cross_address_disabled = true; + assert_eq!( + instance.to_halo2_instance()[0][super::DISABLE_CROSS_ADDRESS], + vesta::Scalar::one() + ); + } + + #[test] + fn cross_address_support_matches_circuit_version() { + assert!(!OrchardCircuitVersion::InsecurePreNu6_2.supports_cross_address_restriction()); + assert!(!OrchardCircuitVersion::FixedPostNu6_2.supports_cross_address_restriction()); + assert!(OrchardCircuitVersion::PostNu6_3.supports_cross_address_restriction()); + } + + #[test] + fn post_nu6_3_cross_address_restriction_is_conditional() { + let mock_verify = |circuit: &Circuit, instance: &Instance| { + MockProver::run( + K, + circuit, + instance + .to_halo2_instance() + .iter() + .map(|p| p.to_vec()) + .collect(), + ) + .unwrap() + .verify() + }; + + // An unrestricted cross-address statement is satisfiable... + let (circuit, mut instance) = + generate_circuit_instance(OsRng, OrchardCircuitVersion::PostNu6_3); + assert_eq!(mock_verify(&circuit, &instance), Ok(())); + + // ...but setting `disableCrossAddress` makes it unsatisfiable... + instance.cross_address_disabled = true; + assert!(mock_verify(&circuit, &instance).is_err()); + + // ...while a restricted self-transfer statement is satisfiable. + let (circuit, mut instance) = + generate_self_transfer_circuit_instance(OsRng, OrchardCircuitVersion::PostNu6_3); + instance.cross_address_disabled = true; + assert_eq!(mock_verify(&circuit, &instance), Ok(())); + } + + #[test] + fn post_nu6_3_restricted_statement_proves_and_verifies() { let mut rng = OsRng; + let (circuit, mut instance) = + generate_self_transfer_circuit_instance(&mut rng, OrchardCircuitVersion::PostNu6_3); + instance.cross_address_disabled = true; - let (circuits, instances): (Vec<_>, Vec<_>) = iter::once(()) - .map(|()| generate_circuit_instance(&mut rng, OrchardCircuitVersion::FixedPostNu6_2)) - .unzip(); + let pk = ProvingKey::build(OrchardCircuitVersion::PostNu6_3); + let vk = VerifyingKey::build(OrchardCircuitVersion::PostNu6_3); - let vk = VerifyingKey::build(); + let proof = Proof::create( + &pk, + core::slice::from_ref(&circuit), + core::slice::from_ref(&instance), + &mut rng, + ) + .unwrap(); + assert!(proof.verify(&vk, core::slice::from_ref(&instance)).is_ok()); + } - // Test that the pinned verification key (representing the circuit) is as expected. - // Set ORCHARD_CIRCUIT_TEST_GENERATE_NEW_PROOF to regenerate it (and the proof below). - { - if std::env::var_os("ORCHARD_CIRCUIT_TEST_GENERATE_NEW_PROOF").is_some() { - std::fs::write( - "src/circuit_data/circuit_description_fixed", - format!("{:#?}\n", vk.vk.pinned()), - ) + // FixedPostNu6_2 leaves instance row 9 (`disableCrossAddress`) unconstrained, so a + // freshly created proof can satisfy a restricted statement at the raw halo2 level + // without enforcing anything about addresses. This test documents that hazard and + // pins the API checks that close it. + #[test] + fn restricted_statement_requires_supporting_key() { + use halo2_proofs::transcript::{Blake2bRead, Blake2bWrite}; + + let mut rng = OsRng; + let (circuit, mut instance) = + generate_circuit_instance(&mut rng, OrchardCircuitVersion::FixedPostNu6_2); + instance.cross_address_disabled = true; + + let pk = ProvingKey::build(OrchardCircuitVersion::FixedPostNu6_2); + let vk = VerifyingKey::build(OrchardCircuitVersion::FixedPostNu6_2); + + let raw_instances = instance.to_halo2_instance(); + let raw_instances: Vec<_> = raw_instances.iter().map(|i| &i[..]).collect(); + let raw_instances = [&raw_instances[..]]; + + let mut transcript = Blake2bWrite::<_, vesta::Affine, _>::init(vec![]); + super::plonk::create_proof( + &pk.params, + &pk.pk, + core::slice::from_ref(&circuit), + &raw_instances, + &mut rng, + &mut transcript, + ) + .unwrap(); + let proof_bytes = transcript.finalize(); + + let strategy = super::SingleVerifier::new(&vk.params); + let mut transcript = Blake2bRead::init(&proof_bytes[..]); + assert!(super::plonk::verify_proof( + &vk.params, + &vk.vk, + strategy, + &raw_instances, + &mut transcript, + ) + .is_ok()); + + assert!(matches!( + Proof::create( + &pk, + core::slice::from_ref(&circuit), + core::slice::from_ref(&instance), + &mut rng, + ), + Err(super::plonk::Error::InvalidInstances), + )); + + let proof = Proof::new(proof_bytes); + assert!(matches!( + proof.verify(&vk, core::slice::from_ref(&instance)), + Err(super::plonk::Error::InvalidInstances), + )); + } + + // Set ORCHARD_CIRCUIT_TEST_GENERATE_NEW_PROOF to regenerate the pinned circuit description + // for this version. + fn pinned_circuit_description( + circuit_version: OrchardCircuitVersion, + path: &str, + expected: &str, + ) -> VerifyingKey { + let vk = VerifyingKey::build(circuit_version); + + if std::env::var_os("ORCHARD_CIRCUIT_TEST_GENERATE_NEW_PROOF").is_some() { + std::fs::write(path, format!("{:#?}\n", vk.vk.pinned())) .expect("should be able to write new circuit description"); - } else { - assert_eq!( - format!("{:#?}\n", vk.vk.pinned()), - include_str!("circuit_data/circuit_description_fixed").replace("\r\n", "\n") - ); - } + } else { + assert_eq!( + format!("{:#?}\n", vk.vk.pinned()), + expected.replace("\r\n", "\n") + ); } + vk + } + + // TODO: recast as a proptest + fn round_trip_for_version(circuit_version: OrchardCircuitVersion, vk: &VerifyingKey) { + let mut rng = OsRng; + + let (circuits, instances): (Vec<_>, Vec<_>) = iter::once(()) + .map(|()| generate_circuit_instance(&mut rng, circuit_version)) + .unzip(); + // Test that the proof size is as expected. let expected_proof_size = { let circuit_cost = @@ -1256,6 +1774,9 @@ mod tests { K, &circuits[0], ); + // These sizes are identical for every circuit version: the post-NU 6.3 circuit reuses the + // existing Orchard checks gate on spare rows and adds no columns or + // commitments, leaving the proof shape unchanged. assert_eq!(usize::from(circuit_cost.proof_size(1)), 4992); assert_eq!(usize::from(circuit_cost.proof_size(2)), 7264); // The constants in `Proof::expected_proof_size` must track the circuit's actual @@ -1286,17 +1807,36 @@ mod tests { ); } - let pk = ProvingKey::build(); + let pk = ProvingKey::build(circuit_version); let proof = Proof::create(&pk, &circuits, &instances, &mut rng).unwrap(); - assert!(proof.verify(&vk, &instances).is_ok()); + assert!(proof.verify(vk, &instances).is_ok()); assert_eq!(proof.0.len(), expected_proof_size); } - // Proves with the proving key for `proving_version` and checks that the proof verifies - // under the verifying key for the same version, but not under the verifying key for - // `other_version`. The two circuit versions have different verifying keys, so a proof is - // bound to the version it was created with. - fn proof_is_bound_to_circuit_version( + #[test] + fn round_trip_fixed() { + let vk = pinned_circuit_description( + OrchardCircuitVersion::FixedPostNu6_2, + "src/circuit_data/circuit_description_fixed", + include_str!("circuit_data/circuit_description_fixed"), + ); + round_trip_for_version(OrchardCircuitVersion::FixedPostNu6_2, &vk); + } + + #[test] + fn round_trip_post_nu6_3() { + let vk = pinned_circuit_description( + OrchardCircuitVersion::PostNu6_3, + "src/circuit_data/circuit_description_post_nu6_3", + include_str!("circuit_data/circuit_description_post_nu6_3"), + ); + round_trip_for_version(OrchardCircuitVersion::PostNu6_3, &vk); + } + + // Proves with the proving key for `proving_version` and checks that the proof verifies under + // the verifying key for the same version, but not under a version with a different verifying + // key. + fn proof_is_rejected_by_other_circuit_version( proving_version: OrchardCircuitVersion, other_version: OrchardCircuitVersion, ) { @@ -1305,30 +1845,50 @@ mod tests { let (circuit, instance) = generate_circuit_instance(&mut rng, proving_version); let instances = core::slice::from_ref(&instance); - let pk = ProvingKey::build_for_version(proving_version); + let pk = ProvingKey::build(proving_version); let proof = Proof::create(&pk, &[circuit], instances, &mut rng).unwrap(); // Verifies under the matching version's verifying key. - let vk_matching = VerifyingKey::build_for_version(proving_version); + let vk_matching = VerifyingKey::build(proving_version); assert!(proof.verify(&vk_matching, instances).is_ok()); // Does not verify under the other version's verifying key. - let vk_other = VerifyingKey::build_for_version(other_version); + let vk_other = VerifyingKey::build(other_version); assert!(proof.verify(&vk_other, instances).is_err()); } #[test] fn proof_verifies_against_matching_circuit_version() { - // Each prover's proof verifies under its own verifying key, and is rejected by the - // other version's verifying key. - proof_is_bound_to_circuit_version( + // Insecure proofs are rejected by the anchored circuit versions, and anchored proofs are + // rejected by the insecure verifying key. + proof_is_rejected_by_other_circuit_version( OrchardCircuitVersion::FixedPostNu6_2, OrchardCircuitVersion::InsecurePreNu6_2, ); - proof_is_bound_to_circuit_version( + proof_is_rejected_by_other_circuit_version( + OrchardCircuitVersion::PostNu6_3, + OrchardCircuitVersion::InsecurePreNu6_2, + ); + proof_is_rejected_by_other_circuit_version( OrchardCircuitVersion::InsecurePreNu6_2, OrchardCircuitVersion::FixedPostNu6_2, ); + proof_is_rejected_by_other_circuit_version( + OrchardCircuitVersion::InsecurePreNu6_2, + OrchardCircuitVersion::PostNu6_3, + ); + } + + #[test] + fn fixed_and_post_nu6_3_have_distinct_verifying_keys() { + proof_is_rejected_by_other_circuit_version( + OrchardCircuitVersion::FixedPostNu6_2, + OrchardCircuitVersion::PostNu6_3, + ); + proof_is_rejected_by_other_circuit_version( + OrchardCircuitVersion::PostNu6_3, + OrchardCircuitVersion::FixedPostNu6_2, + ); } // Proving a circuit with a proving key for a different circuit version is a misuse: the @@ -1338,38 +1898,61 @@ mod tests { fn create_rejects_mismatched_proving_key_version() { let mut rng = OsRng; - // Circuits for the insecure version, but proved with the fixed proving key. - let (circuit, instance) = - generate_circuit_instance(&mut rng, OrchardCircuitVersion::InsecurePreNu6_2); - let instances = core::slice::from_ref(&instance); - - let mismatched_pk = ProvingKey::build_for_version(OrchardCircuitVersion::FixedPostNu6_2); - - assert!(matches!( - Proof::create(&mismatched_pk, &[circuit], instances, &mut rng), - Err(super::plonk::Error::Synthesis), - )); + for (circuit_version, pk_version) in [ + ( + OrchardCircuitVersion::InsecurePreNu6_2, + OrchardCircuitVersion::FixedPostNu6_2, + ), + ( + OrchardCircuitVersion::FixedPostNu6_2, + OrchardCircuitVersion::PostNu6_3, + ), + ( + OrchardCircuitVersion::PostNu6_3, + OrchardCircuitVersion::FixedPostNu6_2, + ), + ] { + let (circuit, instance) = generate_circuit_instance(&mut rng, circuit_version); + let instances = core::slice::from_ref(&instance); + + let mismatched_pk = ProvingKey::build(pk_version); + + assert!(matches!( + Proof::create(&mismatched_pk, &[circuit], instances, &mut rng), + Err(super::plonk::Error::Synthesis), + )); + } } - #[test] - fn serialized_proof_test_case() { - let vk = VerifyingKey::build(); - + fn serialized_proof_test_case_for_version( + circuit_version: OrchardCircuitVersion, + proof_path: &str, + test_case_bytes: &[u8], + encoding: ProofFixtureEncoding, + expected_proof_size: usize, + restricted: bool, + ) { + let vk = VerifyingKey::build(circuit_version); + // Set ORCHARD_CIRCUIT_TEST_GENERATE_NEW_PROOF to regenerate this serialized proof + // fixture. The non-regeneration path embeds and verifies the checked-in fixture. if std::env::var_os("ORCHARD_CIRCUIT_TEST_GENERATE_NEW_PROOF").is_some() { let create_proof = || -> std::io::Result<()> { let mut rng = OsRng; - let (circuit, instance) = - generate_circuit_instance(OsRng, OrchardCircuitVersion::FixedPostNu6_2); + let (circuit, mut instance) = if restricted { + generate_self_transfer_circuit_instance(&mut rng, circuit_version) + } else { + generate_circuit_instance(&mut rng, circuit_version) + }; + instance.cross_address_disabled = restricted; let instances = core::slice::from_ref(&instance); - let pk = ProvingKey::build(); + let pk = ProvingKey::build(circuit_version); let proof = Proof::create(&pk, &[circuit], instances, &mut rng).unwrap(); assert!(proof.verify(&vk, instances).is_ok()); - let file = - std::fs::File::create("src/circuit_data/circuit_proof_test_case_fixed.bin")?; - write_test_case(file, &instance, &proof) + let file = std::fs::File::create(proof_path)?; + write_test_case(file, &instance, &proof, encoding) }; create_proof().expect("should be able to write new proof"); // Regeneration only writes the fixture; the non-generate run below embeds and @@ -1378,15 +1961,50 @@ mod tests { } // Parse the hardcoded proof test case. - let (instance, proof) = { - let test_case_bytes = include_bytes!("circuit_data/circuit_proof_test_case_fixed.bin"); - read_test_case(&test_case_bytes[..]).expect("proof must be valid") - }; - assert_eq!(proof.0.len(), 4992); + let (instance, proof) = + read_test_case(test_case_bytes, encoding).expect("proof must be valid"); + assert_eq!(instance.cross_address_disabled(), restricted); + assert_eq!(proof.0.len(), expected_proof_size); assert!(proof.verify(&vk, &[instance]).is_ok()); } + #[test] + fn serialized_fixed_proof_test_case() { + serialized_proof_test_case_for_version( + OrchardCircuitVersion::FixedPostNu6_2, + "src/circuit_data/circuit_proof_test_case_fixed.bin", + include_bytes!("circuit_data/circuit_proof_test_case_fixed.bin"), + ProofFixtureEncoding::LegacyTwoFlags, + 4992, + false, + ); + } + + #[test] + fn serialized_post_nu6_3_proof_test_case() { + serialized_proof_test_case_for_version( + OrchardCircuitVersion::PostNu6_3, + "src/circuit_data/circuit_proof_test_case_post_nu6_3.bin", + include_bytes!("circuit_data/circuit_proof_test_case_post_nu6_3.bin"), + ProofFixtureEncoding::PostNu6_3ThreeFlags, + 4992, + false, + ); + } + + #[test] + fn serialized_post_nu6_3_restricted_proof_test_case() { + serialized_proof_test_case_for_version( + OrchardCircuitVersion::PostNu6_3, + "src/circuit_data/circuit_proof_test_case_post_nu6_3_restricted.bin", + include_bytes!("circuit_data/circuit_proof_test_case_post_nu6_3_restricted.bin"), + ProofFixtureEncoding::PostNu6_3ThreeFlags, + 4992, + true, + ); + } + // The deployed (NU5..NU6.2) verifying key and a pre-fix proof. `InsecurePreNu6_2` // reconstructs the historical circuit, so this checks that the deployed verifying key is // reproduced exactly and that the old proof still verifies under it — the guarantee that @@ -1394,7 +2012,7 @@ mod tests { // pre-NU6.2 verifying key and a sample proof, so they are never regenerated. #[test] fn insecure_against_stored_circuit() { - let vk = VerifyingKey::build_for_version(OrchardCircuitVersion::InsecurePreNu6_2); + let vk = VerifyingKey::build(OrchardCircuitVersion::InsecurePreNu6_2); assert_eq!( format!("{:#?}\n", vk.vk.pinned()), include_str!("circuit_data/circuit_description_insecure").replace("\r\n", "\n") @@ -1403,7 +2021,8 @@ mod tests { let (instance, proof) = { let test_case_bytes = include_bytes!("circuit_data/circuit_proof_test_case_insecure.bin"); - read_test_case(&test_case_bytes[..]).expect("proof must be valid") + read_test_case(&test_case_bytes[..], ProofFixtureEncoding::LegacyTwoFlags) + .expect("proof must be valid") }; assert_eq!(proof.0.len(), 4992); assert!(proof.verify(&vk, &[instance]).is_ok()); @@ -1420,28 +2039,7 @@ mod tests { .titled("Orchard Action Circuit", ("sans-serif", 60)) .unwrap(); - let circuit: Circuit = Circuit { - path: Value::unknown(), - pos: Value::unknown(), - g_d_old: Value::unknown(), - pk_d_old: Value::unknown(), - v_old: Value::unknown(), - rho_old: Value::unknown(), - psi_old: Value::unknown(), - rcm_old: Value::unknown(), - cm_old: Value::unknown(), - alpha: Value::unknown(), - ak: Value::unknown(), - nk: Value::unknown(), - rivk: Value::unknown(), - g_d_new: Value::unknown(), - pk_d_new: Value::unknown(), - v_new: Value::unknown(), - psi_new: Value::unknown(), - rcm_new: Value::unknown(), - rcv: Value::unknown(), - circuit_version: OrchardCircuitVersion::FixedPostNu6_2, - }; + let circuit = Circuit::empty(OrchardCircuitVersion::FixedPostNu6_2); halo2_proofs::dev::CircuitLayout::default() .show_labels(false) .view_height(0..(1 << 11)) @@ -1455,6 +2053,7 @@ mod tests { use super::super::Instance; use crate::{ + bundle::Flags, note::{ExtractedNoteCommitment, Nullifier}, primitives::redpallas::{self, SpendAuth}, tree::Anchor, @@ -1490,7 +2089,7 @@ mod tests { fn rejects_identity_rk() { let (anchor, cv_net, nf_old, cmx) = dummy_other_fields(); let result = - Instance::from_parts(anchor, cv_net, nf_old, identity_rk(), cmx, true, true); + Instance::from_parts(anchor, cv_net, nf_old, identity_rk(), cmx, Flags::ENABLED); assert!(result.is_none()); } @@ -1499,7 +2098,7 @@ mod tests { let (anchor, cv_net, nf_old, cmx) = dummy_other_fields(); let rk = non_identity_rk(); let instance = - Instance::from_parts(anchor, cv_net, nf_old, rk.clone(), cmx, true, true) + Instance::from_parts(anchor, cv_net, nf_old, rk.clone(), cmx, Flags::ENABLED) .expect("non-identity rk must be accepted"); assert_eq!(instance.rk(), &rk); } diff --git a/src/circuit_data/circuit_description_post_nu6_3 b/src/circuit_data/circuit_description_post_nu6_3 new file mode 100644 index 000000000..9882736a0 --- /dev/null +++ b/src/circuit_data/circuit_description_post_nu6_3 @@ -0,0 +1,28140 @@ +PinnedVerificationKey { + base_modulus: "0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001", + scalar_modulus: "0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001", + domain: PinnedEvaluationDomain { + k: 11, + extended_k: 14, + omega: 0x181b50ad5f32119e31cbd395426d600b7a9b88bcaaa1c24eef28545aada17813, + }, + cs: PinnedConstraintSystem { + num_fixed_columns: 29, + num_advice_columns: 10, + num_instance_columns: 1, + num_selectors: 56, + gates: [ + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Product( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Product( + Scaled( + Advice { + query_index: 11, + column_index: 9, + rotation: Rotation( + -1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + ), + Negated( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Sum( + Product( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Product( + Product( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Sum( + Product( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Product( + Product( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Product( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Product( + Product( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Product( + Product( + Sum( + Sum( + Advice { + query_index: 12, + column_index: 2, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Negated( + Product( + Sum( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + Sum( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Product( + Sum( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Negated( + Product( + Sum( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + Sum( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 12, + column_index: 2, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Sum( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + Sum( + Product( + Sum( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Product( + Sum( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Product( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Product( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 12, + column_index: 2, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 12, + column_index: 2, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Negated( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + Sum( + Sum( + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 12, + column_index: 2, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + Sum( + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 12, + column_index: 2, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Negated( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Product( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 12, + column_index: 2, + rotation: Rotation( + 1, + ), + }, + Negated( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Product( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + Negated( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Product( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 12, + column_index: 2, + rotation: Rotation( + 1, + ), + }, + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Product( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + Negated( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Sum( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Product( + Sum( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Negated( + Product( + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Advice { + query_index: 12, + column_index: 2, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 19, + column_index: 19, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Sum( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Product( + Sum( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Negated( + Product( + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Product( + Sum( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 15, + column_index: 5, + rotation: Rotation( + 1, + ), + }, + ), + Sum( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + ), + Negated( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + ), + ), + Negated( + Advice { + query_index: 16, + column_index: 0, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + ), + 0x2000000000000000000000000000000011234c7e04a67c8dcc96987680000001, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 16, + column_index: 0, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 17, + column_index: 1, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Sum( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 11, + column_index: 9, + rotation: Rotation( + -1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Sum( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 11, + column_index: 9, + rotation: Rotation( + -1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Negated( + Scaled( + Product( + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + 0x2000000000000000000000000000000011234c7e04a67c8dcc96987680000001, + ), + ), + ), + Product( + Sum( + Scaled( + Sum( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 11, + column_index: 9, + rotation: Rotation( + -1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + ), + ), + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Product( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + ), + ), + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Negated( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Product( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Negated( + Scaled( + Product( + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + 0x2000000000000000000000000000000011234c7e04a67c8dcc96987680000001, + ), + ), + ), + Negated( + Scaled( + Product( + Sum( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 15, + column_index: 5, + rotation: Rotation( + 1, + ), + }, + ), + Sum( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + ), + Negated( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + ), + ), + Negated( + Advice { + query_index: 16, + column_index: 0, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + ), + 0x2000000000000000000000000000000011234c7e04a67c8dcc96987680000001, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Sum( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 11, + column_index: 9, + rotation: Rotation( + -1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Sum( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 11, + column_index: 9, + rotation: Rotation( + -1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Negated( + Scaled( + Product( + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + 0x2000000000000000000000000000000011234c7e04a67c8dcc96987680000001, + ), + ), + ), + Product( + Sum( + Scaled( + Sum( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 11, + column_index: 9, + rotation: Rotation( + -1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + ), + ), + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Product( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + ), + ), + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Negated( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 18, + column_index: 18, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Product( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Negated( + Scaled( + Product( + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + 0x2000000000000000000000000000000011234c7e04a67c8dcc96987680000001, + ), + ), + ), + Negated( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Product( + Sum( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 12, + column_index: 2, + rotation: Rotation( + 1, + ), + }, + ), + Sum( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + Negated( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + ), + Negated( + Advice { + query_index: 16, + column_index: 0, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + ), + 0x2000000000000000000000000000000011234c7e04a67c8dcc96987680000001, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 16, + column_index: 0, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 17, + column_index: 1, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 20, + column_index: 6, + rotation: Rotation( + -1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 20, + column_index: 6, + rotation: Rotation( + -1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Negated( + Scaled( + Product( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + 0x2000000000000000000000000000000011234c7e04a67c8dcc96987680000001, + ), + ), + ), + Product( + Sum( + Scaled( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 20, + column_index: 6, + rotation: Rotation( + -1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + ), + ), + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Product( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + ), + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Product( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Negated( + Scaled( + Product( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + 0x2000000000000000000000000000000011234c7e04a67c8dcc96987680000001, + ), + ), + ), + Negated( + Scaled( + Product( + Sum( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 12, + column_index: 2, + rotation: Rotation( + 1, + ), + }, + ), + Sum( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + Negated( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + ), + Negated( + Advice { + query_index: 16, + column_index: 0, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + ), + 0x2000000000000000000000000000000011234c7e04a67c8dcc96987680000001, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 20, + column_index: 6, + rotation: Rotation( + -1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 20, + column_index: 6, + rotation: Rotation( + -1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Negated( + Scaled( + Product( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + 0x2000000000000000000000000000000011234c7e04a67c8dcc96987680000001, + ), + ), + ), + Product( + Sum( + Scaled( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 20, + column_index: 6, + rotation: Rotation( + -1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + ), + ), + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Product( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + ), + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Product( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Negated( + Scaled( + Product( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + 0x2000000000000000000000000000000011234c7e04a67c8dcc96987680000001, + ), + ), + ), + Negated( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Sum( + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + Negated( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Advice { + query_index: 11, + column_index: 9, + rotation: Rotation( + -1, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Sum( + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + Negated( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Advice { + query_index: 11, + column_index: 9, + rotation: Rotation( + -1, + ), + }, + ), + ), + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Product( + Sum( + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + Negated( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Advice { + query_index: 11, + column_index: 9, + rotation: Rotation( + -1, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 21, + column_index: 1, + rotation: Rotation( + -1, + ), + }, + ), + ), + ), + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Sum( + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + Negated( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Advice { + query_index: 11, + column_index: 9, + rotation: Rotation( + -1, + ), + }, + ), + ), + ), + ), + ), + Sum( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 21, + column_index: 1, + rotation: Rotation( + -1, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Product( + Advice { + query_index: 23, + column_index: 7, + rotation: Rotation( + -1, + ), + }, + Product( + Constant( + 0x0000000000000000000000000000000010000000000000000000000000000000, + ), + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000040, + ), + ), + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Advice { + query_index: 20, + column_index: 6, + rotation: Rotation( + -1, + ), + }, + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Constant( + 0x00000000000000000000000000000000224698fc0994a8dd8c46eb2100000001, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 23, + column_index: 7, + rotation: Rotation( + -1, + ), + }, + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Constant( + 0x0000000000000000000000000000000010000000000000000000000000000000, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 23, + column_index: 7, + rotation: Rotation( + -1, + ), + }, + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 20, + column_index: 20, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 23, + column_index: 7, + rotation: Rotation( + -1, + ), + }, + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Product( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Sum( + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + Negated( + Scaled( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Sum( + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + Negated( + Scaled( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Product( + Sum( + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + Negated( + Scaled( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Sum( + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + Negated( + Scaled( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + ), + ), + Sum( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 16, + column_index: 0, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Product( + Sum( + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + Negated( + Scaled( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Sum( + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + Negated( + Scaled( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + ), + ), + ), + Sum( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 17, + column_index: 1, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + Product( + Fixed { + query_index: 22, + column_index: 22, + rotation: Rotation( + 0, + ), + }, + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + ), + ), + ), + Product( + Fixed { + query_index: 22, + column_index: 22, + rotation: Rotation( + 0, + ), + }, + Sum( + Sum( + Sum( + Sum( + Sum( + Sum( + Sum( + Sum( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000000, + ), + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Fixed { + query_index: 0, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Fixed { + query_index: 3, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Fixed { + query_index: 7, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Fixed { + query_index: 8, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Sum( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + Fixed { + query_index: 9, + column_index: 10, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Fixed { + query_index: 22, + column_index: 22, + rotation: Rotation( + 0, + ), + }, + Sum( + Sum( + Product( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Fixed { + query_index: 2, + column_index: 11, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Fixed { + query_index: 22, + column_index: 22, + rotation: Rotation( + 0, + ), + }, + Sum( + Sum( + Product( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Product( + Product( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + ), + ), + ), + Product( + Fixed { + query_index: 23, + column_index: 23, + rotation: Rotation( + 0, + ), + }, + Sum( + Sum( + Sum( + Sum( + Sum( + Sum( + Sum( + Sum( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000000, + ), + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Fixed { + query_index: 0, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Fixed { + query_index: 3, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Fixed { + query_index: 7, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Fixed { + query_index: 8, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Fixed { + query_index: 9, + column_index: 10, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Fixed { + query_index: 23, + column_index: 23, + rotation: Rotation( + 0, + ), + }, + Sum( + Sum( + Product( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Fixed { + query_index: 2, + column_index: 11, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Fixed { + query_index: 23, + column_index: 23, + rotation: Rotation( + 0, + ), + }, + Sum( + Sum( + Product( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Product( + Product( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + ), + ), + ), + Product( + Fixed { + query_index: 23, + column_index: 23, + rotation: Rotation( + 0, + ), + }, + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Sum( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + Sum( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Negated( + Product( + Advice { + query_index: 24, + column_index: 8, + rotation: Rotation( + -1, + ), + }, + Constant( + 0x0000000000000000000000000000000001000000000000000000000000000000, + ), + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Product( + Sum( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + Negated( + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Sum( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + Negated( + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000008, + ), + ), + ), + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 24, + column_index: 8, + rotation: Rotation( + -1, + ), + }, + Negated( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Sum( + Advice { + query_index: 20, + column_index: 6, + rotation: Rotation( + -1, + ), + }, + Negated( + Scaled( + Advice { + query_index: 24, + column_index: 8, + rotation: Rotation( + -1, + ), + }, + 0x1000000000000000000000000000000000000000000000000000000000000000, + ), + ), + ), + Constant( + 0x0000000000000000000000000000000400000000000000000000000000000000, + ), + ), + Negated( + Constant( + 0x00000000000000000000000000000000224698fc094cf91b992d30ed00000001, + ), + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Scaled( + Product( + Product( + Product( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + 0x0ab5e5b874a68de7b3d59fbdc8c9ead497d7a0ab23850b56323f2486d7e11b63, + ), + Scaled( + Product( + Product( + Product( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + 0x31916628e58a5abb293f0f0d886c7954240d4a7cbf7357368eca5596e996ab5e, + ), + ), + Scaled( + Product( + Product( + Product( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + 0x07c045d5f5e9e5a6d803952bbb364fdfa0a3b71a5fb1573519d1cf25d8e8345d, + ), + ), + Negated( + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Scaled( + Product( + Product( + Product( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + 0x233162630ebf9ed7f8e24f66822c2d9f3a0a464048bd770ad049cdc8d085167c, + ), + Scaled( + Product( + Product( + Product( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + 0x25cae2599892a8b0b36664548d60957d78f8365c85bbab07402270113e047a2e, + ), + ), + Scaled( + Product( + Product( + Product( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + 0x22f5b5e1e6081c9774938717989a19579aad3d8262efd83ff84d806f685f747a, + ), + ), + Negated( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 21, + column_index: 21, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Scaled( + Product( + Product( + Product( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + 0x2e29dd59c64b1037f333aa91c383346421680eabc56bc15dfee7a9944f84dbe4, + ), + Scaled( + Product( + Product( + Product( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + 0x1d1aab4ec1cd678892d15e7dceef1665cbeaf48b3a0624c3c771effa43263664, + ), + ), + Scaled( + Product( + Product( + Product( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + 0x3bf763086a18936451e0cbead65516b975872c39b59a31f615639415f6e85ef1, + ), + ), + Negated( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Product( + Product( + Product( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Product( + Product( + Product( + Sum( + Sum( + Sum( + Scaled( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + 0x0ab5e5b874a68de7b3d59fbdc8c9ead497d7a0ab23850b56323f2486d7e11b63, + ), + Scaled( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + 0x31916628e58a5abb293f0f0d886c7954240d4a7cbf7357368eca5596e996ab5e, + ), + ), + Scaled( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + 0x07c045d5f5e9e5a6d803952bbb364fdfa0a3b71a5fb1573519d1cf25d8e8345d, + ), + ), + Fixed { + query_index: 7, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Sum( + Sum( + Scaled( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + 0x0ab5e5b874a68de7b3d59fbdc8c9ead497d7a0ab23850b56323f2486d7e11b63, + ), + Scaled( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + 0x31916628e58a5abb293f0f0d886c7954240d4a7cbf7357368eca5596e996ab5e, + ), + ), + Scaled( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + 0x07c045d5f5e9e5a6d803952bbb364fdfa0a3b71a5fb1573519d1cf25d8e8345d, + ), + ), + Fixed { + query_index: 7, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Sum( + Sum( + Sum( + Scaled( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + 0x0ab5e5b874a68de7b3d59fbdc8c9ead497d7a0ab23850b56323f2486d7e11b63, + ), + Scaled( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + 0x31916628e58a5abb293f0f0d886c7954240d4a7cbf7357368eca5596e996ab5e, + ), + ), + Scaled( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + 0x07c045d5f5e9e5a6d803952bbb364fdfa0a3b71a5fb1573519d1cf25d8e8345d, + ), + ), + Fixed { + query_index: 7, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Sum( + Sum( + Scaled( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + 0x0ab5e5b874a68de7b3d59fbdc8c9ead497d7a0ab23850b56323f2486d7e11b63, + ), + Scaled( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + 0x31916628e58a5abb293f0f0d886c7954240d4a7cbf7357368eca5596e996ab5e, + ), + ), + Scaled( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + 0x07c045d5f5e9e5a6d803952bbb364fdfa0a3b71a5fb1573519d1cf25d8e8345d, + ), + ), + Fixed { + query_index: 7, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Scaled( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + 0x0ab5e5b874a68de7b3d59fbdc8c9ead497d7a0ab23850b56323f2486d7e11b63, + ), + Scaled( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + 0x31916628e58a5abb293f0f0d886c7954240d4a7cbf7357368eca5596e996ab5e, + ), + ), + Scaled( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + 0x07c045d5f5e9e5a6d803952bbb364fdfa0a3b71a5fb1573519d1cf25d8e8345d, + ), + ), + Fixed { + query_index: 7, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Sum( + Sum( + Scaled( + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + 0x2cc057f3fa14687acc59ffd00de864434543705f35e98ab5c6de463cd1404e6b, + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x32e7c439f2f967e55fd72b55df208385fadbf8ae7ae24796171840417cab7576, + ), + ), + Scaled( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + 0x2eae5df8c3115969f461778abf6c91fa1403db6f50302040942645bd7d4464e0, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Sum( + Scaled( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + 0x233162630ebf9ed7f8e24f66822c2d9f3a0a464048bd770ad049cdc8d085167c, + ), + Scaled( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + 0x25cae2599892a8b0b36664548d60957d78f8365c85bbab07402270113e047a2e, + ), + ), + Scaled( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + 0x22f5b5e1e6081c9774938717989a19579aad3d8262efd83ff84d806f685f747a, + ), + ), + Fixed { + query_index: 8, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Sum( + Sum( + Scaled( + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + 0x07bf368481067199db18b4aefe68d26d13f074fde9a18b29a1ca1516a4a1a6a0, + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x2aec6906c63f3cf1018a918b9dac5dadbb1d65040c85c1bfe82425bc1b23a059, + ), + ), + Scaled( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + 0x0952e0243aec2af01215944a64a246b276b2a7139db71b36e0541adf238e0781, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Sum( + Scaled( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + 0x2e29dd59c64b1037f333aa91c383346421680eabc56bc15dfee7a9944f84dbe4, + ), + Scaled( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 5, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + 0x1d1aab4ec1cd678892d15e7dceef1665cbeaf48b3a0624c3c771effa43263664, + ), + ), + Scaled( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 6, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + 0x3bf763086a18936451e0cbead65516b975872c39b59a31f615639415f6e85ef1, + ), + ), + Fixed { + query_index: 9, + column_index: 10, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Sum( + Sum( + Scaled( + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + 0x2fcbba6f9159a219723a63a0c09dab26aef9112e952fdbb52a418d8d73a7c908, + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x1ec7372574f3851bb4ddd4b4d6452256c5e4960d7424cd3776efab42d4fba90b, + ), + ), + Scaled( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + 0x0d0c2efd6472f12a3c26fa4b7d25b1e487a7435d30f8be81adc8933c6f3c72ee, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Advice { + query_index: 20, + column_index: 6, + rotation: Rotation( + -1, + ), + }, + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Advice { + query_index: 23, + column_index: 7, + rotation: Rotation( + -1, + ), + }, + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 24, + column_index: 8, + rotation: Rotation( + -1, + ), + }, + Negated( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Scaled( + Fixed { + query_index: 0, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Product( + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + ), + ), + ), + Product( + Fixed { + query_index: 16, + column_index: 16, + rotation: Rotation( + 0, + ), + }, + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Sum( + Sum( + Advice { + query_index: 16, + column_index: 0, + rotation: Rotation( + 1, + ), + }, + Sum( + Sum( + Product( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Fixed { + query_index: 16, + column_index: 16, + rotation: Rotation( + 0, + ), + }, + Sum( + Product( + Scaled( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Sum( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 16, + column_index: 0, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Negated( + Sum( + Sum( + Scaled( + Product( + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Product( + Fixed { + query_index: 10, + column_index: 12, + rotation: Rotation( + 0, + ), + }, + Sum( + Fixed { + query_index: 10, + column_index: 12, + rotation: Rotation( + 0, + ), + }, + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + ), + ), + ), + ), + ), + Product( + Sum( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + ), + Sum( + Advice { + query_index: 16, + column_index: 0, + rotation: Rotation( + 1, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + ), + Negated( + Advice { + query_index: 16, + column_index: 0, + rotation: Rotation( + 1, + ), + }, + ), + ), + Negated( + Advice { + query_index: 17, + column_index: 1, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + ), + ), + ), + Product( + Scaled( + Product( + Fixed { + query_index: 10, + column_index: 12, + rotation: Rotation( + 0, + ), + }, + Sum( + Fixed { + query_index: 10, + column_index: 12, + rotation: Rotation( + 0, + ), + }, + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + ), + ), + ), + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + ), + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + ), + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 24, + column_index: 24, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 16, + column_index: 0, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + ), + ), + Negated( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Advice { + query_index: 16, + column_index: 0, + rotation: Rotation( + 1, + ), + }, + Scaled( + Sum( + Sum( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 17, + column_index: 1, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + ), + ), + Scaled( + Advice { + query_index: 12, + column_index: 2, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + ), + 0x0001000000000000000000000000000000000000000000000000000000000000, + ), + ), + Negated( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + Scaled( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000020, + ), + ), + Negated( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 17, + column_index: 1, + rotation: Rotation( + 1, + ), + }, + Negated( + Sum( + Advice { + query_index: 12, + column_index: 2, + rotation: Rotation( + 1, + ), + }, + Scaled( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000020, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Scaled( + Fixed { + query_index: 3, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Product( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + ), + ), + ), + Product( + Fixed { + query_index: 17, + column_index: 17, + rotation: Rotation( + 0, + ), + }, + Sum( + Product( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Sum( + Sum( + Advice { + query_index: 15, + column_index: 5, + rotation: Rotation( + 1, + ), + }, + Sum( + Sum( + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Fixed { + query_index: 17, + column_index: 17, + rotation: Rotation( + 0, + ), + }, + Sum( + Product( + Scaled( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Sum( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 15, + column_index: 5, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Negated( + Sum( + Sum( + Scaled( + Product( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Product( + Fixed { + query_index: 13, + column_index: 13, + rotation: Rotation( + 0, + ), + }, + Sum( + Fixed { + query_index: 13, + column_index: 13, + rotation: Rotation( + 0, + ), + }, + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + ), + ), + ), + ), + ), + Product( + Sum( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + ), + Sum( + Advice { + query_index: 15, + column_index: 5, + rotation: Rotation( + 1, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + Negated( + Advice { + query_index: 15, + column_index: 5, + rotation: Rotation( + 1, + ), + }, + ), + ), + Negated( + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + ), + ), + ), + Product( + Scaled( + Product( + Fixed { + query_index: 13, + column_index: 13, + rotation: Rotation( + 0, + ), + }, + Sum( + Fixed { + query_index: 13, + column_index: 13, + rotation: Rotation( + 0, + ), + }, + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + ), + ), + ), + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Product( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Product( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 15, + column_index: 5, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + ), + ), + Negated( + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Advice { + query_index: 15, + column_index: 5, + rotation: Rotation( + 1, + ), + }, + Scaled( + Sum( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + ), + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + ), + 0x0001000000000000000000000000000000000000000000000000000000000000, + ), + ), + Negated( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + Scaled( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000020, + ), + ), + Negated( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + Negated( + Sum( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Scaled( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000020, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000010, + ), + ), + Scaled( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000020, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 12, + column_index: 2, + rotation: Rotation( + 1, + ), + }, + Negated( + Sum( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000200, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + 0x0400000000000000000000000000000000000000000000000000000000000000, + ), + ), + Scaled( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + 0x4000000000000000000000000000000000000000000000000000000000000000, + ), + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Sum( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 17, + column_index: 1, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000020, + ), + ), + Scaled( + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + 0x0020000000000000000000000000000000000000000000000000000000000000, + ), + ), + Scaled( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + 0x4000000000000000000000000000000000000000000000000000000000000000, + ), + ), + Negated( + Advice { + query_index: 16, + column_index: 0, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Constant( + 0x0000000000000000000000000000000400000000000000000000000000000000, + ), + ), + Negated( + Constant( + 0x00000000000000000000000000000000224698fc094cf91b992d30ed00000001, + ), + ), + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 13, + column_index: 3, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Sum( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 17, + column_index: 1, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000020, + ), + ), + Constant( + 0x0000000000000000000000000000100000000000000000000000000000000000, + ), + ), + Negated( + Constant( + 0x00000000000000000000000000000000224698fc094cf91b992d30ed00000001, + ), + ), + ), + Negated( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 14, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000010, + ), + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000020, + ), + ), + Scaled( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000040, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + ), + Scaled( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 25, + column_index: 25, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000040, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000020, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + 0x0400000000000000000000000000000000000000000000000000000000000000, + ), + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x4000000000000000000000000000000000000000000000000000000000000000, + ), + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Constant( + 0x0000000000000000000000000000000400000000000000000000000000000000, + ), + ), + Negated( + Constant( + 0x00000000000000000000000000000000224698fc094cf91b992d30ed00000001, + ), + ), + ), + Negated( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000010, + ), + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x4000000000000000000000000000000000000000000000000000000000000000, + ), + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000010, + ), + ), + Constant( + 0x0000000000000000000000000000100000000000000000000000000000000000, + ), + ), + Negated( + Constant( + 0x00000000000000000000000000000000224698fc094cf91b992d30ed00000001, + ), + ), + ), + Negated( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 26, + column_index: 26, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000100, + ), + ), + Scaled( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000400000000000000, + ), + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000010, + ), + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x4000000000000000000000000000000000000000000000000000000000000000, + ), + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000010, + ), + ), + Constant( + 0x0000000000000000000000000000100000000000000000000000000000000000, + ), + ), + Negated( + Constant( + 0x00000000000000000000000000000000224698fc094cf91b992d30ed00000001, + ), + ), + ), + Negated( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000200, + ), + ), + Scaled( + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + 0x0200000000000000000000000000000000000000000000000000000000000000, + ), + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x4000000000000000000000000000000000000000000000000000000000000000, + ), + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000200, + ), + ), + Constant( + 0x0000000000000000000000000000000400000000000000000000000000000000, + ), + ), + Negated( + Constant( + 0x00000000000000000000000000000000224698fc094cf91b992d30ed00000001, + ), + ), + ), + Negated( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 15, + column_index: 5, + rotation: Rotation( + 1, + ), + }, + Negated( + Sum( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + Scaled( + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Advice { + query_index: 15, + column_index: 5, + rotation: Rotation( + 1, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0400000000000000000000000000000000000000000000000000000000000000, + ), + ), + Scaled( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + 0x4000000000000000000000000000000000000000000000000000000000000000, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Advice { + query_index: 15, + column_index: 5, + rotation: Rotation( + 1, + ), + }, + Constant( + 0x0000000000000000000000000000000400000000000000000000000000000000, + ), + ), + Negated( + Constant( + 0x00000000000000000000000000000000224698fc094cf91b992d30ed00000001, + ), + ), + ), + Negated( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000010, + ), + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000020, + ), + ), + Scaled( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000040, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + ), + Scaled( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000040, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 27, + column_index: 27, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000020, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + 0x0400000000000000000000000000000000000000000000000000000000000000, + ), + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x4000000000000000000000000000000000000000000000000000000000000000, + ), + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Constant( + 0x0000000000000000000000000000000400000000000000000000000000000000, + ), + ), + Negated( + Constant( + 0x00000000000000000000000000000000224698fc094cf91b992d30ed00000001, + ), + ), + ), + Negated( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000010, + ), + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x4000000000000000000000000000000000000000000000000000000000000000, + ), + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000010, + ), + ), + Constant( + 0x0000000000000000000000000000100000000000000000000000000000000000, + ), + ), + Negated( + Constant( + 0x00000000000000000000000000000000224698fc094cf91b992d30ed00000001, + ), + ), + ), + Negated( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000100, + ), + ), + Scaled( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000400000000000000, + ), + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000010, + ), + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x4000000000000000000000000000000000000000000000000000000000000000, + ), + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000010, + ), + ), + Constant( + 0x0000000000000000000000000000100000000000000000000000000000000000, + ), + ), + Negated( + Constant( + 0x00000000000000000000000000000000224698fc094cf91b992d30ed00000001, + ), + ), + ), + Negated( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000200, + ), + ), + Scaled( + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + 0x0200000000000000000000000000000000000000000000000000000000000000, + ), + ), + Scaled( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + 0x4000000000000000000000000000000000000000000000000000000000000000, + ), + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000200, + ), + ), + Constant( + 0x0000000000000000000000000000000400000000000000000000000000000000, + ), + ), + Negated( + Constant( + 0x00000000000000000000000000000000224698fc094cf91b992d30ed00000001, + ), + ), + ), + Negated( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000007, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 15, + column_index: 5, + rotation: Rotation( + 1, + ), + }, + Negated( + Sum( + Sum( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + Scaled( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + ), + Scaled( + Advice { + query_index: 22, + column_index: 6, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Advice { + query_index: 15, + column_index: 5, + rotation: Rotation( + 1, + ), + }, + Scaled( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + 0x0400000000000000000000000000000000000000000000000000000000000000, + ), + ), + Scaled( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + 0x4000000000000000000000000000000000000000000000000000000000000000, + ), + ), + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Sum( + Sum( + Advice { + query_index: 15, + column_index: 5, + rotation: Rotation( + 1, + ), + }, + Constant( + 0x0000000000000000000000000000000400000000000000000000000000000000, + ), + ), + Negated( + Constant( + 0x00000000000000000000000000000000224698fc094cf91b992d30ed00000001, + ), + ), + ), + Negated( + Advice { + query_index: 19, + column_index: 8, + rotation: Rotation( + 1, + ), + }, + ), + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + ), + Product( + Product( + Product( + Product( + Product( + Product( + Product( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000002, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000003, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000004, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000005, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000006, + ), + Negated( + Fixed { + query_index: 28, + column_index: 28, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + ), + ), + ], + advice_queries: [ + ( + Column { + index: 0, + column_type: Advice, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 1, + column_type: Advice, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 2, + column_type: Advice, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 3, + column_type: Advice, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 4, + column_type: Advice, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 5, + column_type: Advice, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 6, + column_type: Advice, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 7, + column_type: Advice, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 8, + column_type: Advice, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 9, + column_type: Advice, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 9, + column_type: Advice, + }, + Rotation( + 1, + ), + ), + ( + Column { + index: 9, + column_type: Advice, + }, + Rotation( + -1, + ), + ), + ( + Column { + index: 2, + column_type: Advice, + }, + Rotation( + 1, + ), + ), + ( + Column { + index: 3, + column_type: Advice, + }, + Rotation( + 1, + ), + ), + ( + Column { + index: 4, + column_type: Advice, + }, + Rotation( + 1, + ), + ), + ( + Column { + index: 5, + column_type: Advice, + }, + Rotation( + 1, + ), + ), + ( + Column { + index: 0, + column_type: Advice, + }, + Rotation( + 1, + ), + ), + ( + Column { + index: 1, + column_type: Advice, + }, + Rotation( + 1, + ), + ), + ( + Column { + index: 7, + column_type: Advice, + }, + Rotation( + 1, + ), + ), + ( + Column { + index: 8, + column_type: Advice, + }, + Rotation( + 1, + ), + ), + ( + Column { + index: 6, + column_type: Advice, + }, + Rotation( + -1, + ), + ), + ( + Column { + index: 1, + column_type: Advice, + }, + Rotation( + -1, + ), + ), + ( + Column { + index: 6, + column_type: Advice, + }, + Rotation( + 1, + ), + ), + ( + Column { + index: 7, + column_type: Advice, + }, + Rotation( + -1, + ), + ), + ( + Column { + index: 8, + column_type: Advice, + }, + Rotation( + -1, + ), + ), + ], + instance_queries: [ + ( + Column { + index: 0, + column_type: Instance, + }, + Rotation( + 0, + ), + ), + ], + fixed_queries: [ + ( + Column { + index: 3, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 0, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 11, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 4, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 5, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 6, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 7, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 8, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 9, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 10, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 12, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 1, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 2, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 13, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 14, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 15, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 16, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 17, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 18, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 19, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 20, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 21, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 22, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 23, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 24, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 25, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 26, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 27, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 28, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ], + permutation: Argument { + columns: [ + Column { + index: 0, + column_type: Instance, + }, + Column { + index: 0, + column_type: Advice, + }, + Column { + index: 1, + column_type: Advice, + }, + Column { + index: 2, + column_type: Advice, + }, + Column { + index: 3, + column_type: Advice, + }, + Column { + index: 4, + column_type: Advice, + }, + Column { + index: 5, + column_type: Advice, + }, + Column { + index: 6, + column_type: Advice, + }, + Column { + index: 7, + column_type: Advice, + }, + Column { + index: 8, + column_type: Advice, + }, + Column { + index: 9, + column_type: Advice, + }, + Column { + index: 3, + column_type: Fixed, + }, + Column { + index: 8, + column_type: Fixed, + }, + Column { + index: 9, + column_type: Fixed, + }, + Column { + index: 10, + column_type: Fixed, + }, + ], + }, + lookups: [ + Argument { + input_expressions: [ + Product( + Fixed { + query_index: 14, + column_index: 14, + rotation: Rotation( + 0, + ), + }, + Sum( + Product( + Fixed { + query_index: 15, + column_index: 15, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Advice { + query_index: 10, + column_index: 9, + rotation: Rotation( + 1, + ), + }, + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + ), + ), + ), + Product( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 15, + column_index: 15, + rotation: Rotation( + 0, + ), + }, + ), + ), + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ], + table_expressions: [ + Fixed { + query_index: 1, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ], + }, + Argument { + input_expressions: [ + Product( + Fixed { + query_index: 16, + column_index: 16, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Product( + Sum( + Fixed { + query_index: 10, + column_index: 12, + rotation: Rotation( + 0, + ), + }, + Negated( + Product( + Fixed { + query_index: 10, + column_index: 12, + rotation: Rotation( + 0, + ), + }, + Sum( + Fixed { + query_index: 10, + column_index: 12, + rotation: Rotation( + 0, + ), + }, + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + ), + ), + ), + ), + ), + Advice { + query_index: 12, + column_index: 2, + rotation: Rotation( + 1, + ), + }, + ), + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + ), + ), + ), + Sum( + Product( + Fixed { + query_index: 16, + column_index: 16, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + Scaled( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 16, + column_index: 16, + rotation: Rotation( + 0, + ), + }, + ), + ), + 0x0db5218be6881f0f1431d4ea7d4afc7b29a05bafbede62b55a91eb912044ea5f, + ), + ), + Sum( + Product( + Fixed { + query_index: 16, + column_index: 16, + rotation: Rotation( + 0, + ), + }, + Sum( + Scaled( + Product( + Sum( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + 0x2000000000000000000000000000000011234c7e04a67c8dcc96987680000001, + ), + Negated( + Product( + Advice { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 1, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + ), + Scaled( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 16, + column_index: 16, + rotation: Rotation( + 0, + ), + }, + ), + ), + 0x2f0f40c2f152a01c9caf66298493d5d0944a041c2e65ba0117c24f76bf8e6483, + ), + ), + ], + table_expressions: [ + Fixed { + query_index: 1, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 11, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 12, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ], + }, + Argument { + input_expressions: [ + Product( + Fixed { + query_index: 17, + column_index: 17, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 7, + column_index: 7, + rotation: Rotation( + 0, + ), + }, + Negated( + Scaled( + Product( + Sum( + Fixed { + query_index: 13, + column_index: 13, + rotation: Rotation( + 0, + ), + }, + Negated( + Product( + Fixed { + query_index: 13, + column_index: 13, + rotation: Rotation( + 0, + ), + }, + Sum( + Fixed { + query_index: 13, + column_index: 13, + rotation: Rotation( + 0, + ), + }, + Negated( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + ), + ), + ), + ), + ), + Advice { + query_index: 18, + column_index: 7, + rotation: Rotation( + 1, + ), + }, + ), + 0x0000000000000000000000000000000000000000000000000000000000000400, + ), + ), + ), + ), + Sum( + Product( + Fixed { + query_index: 17, + column_index: 17, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + Scaled( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 17, + column_index: 17, + rotation: Rotation( + 0, + ), + }, + ), + ), + 0x0db5218be6881f0f1431d4ea7d4afc7b29a05bafbede62b55a91eb912044ea5f, + ), + ), + Sum( + Product( + Fixed { + query_index: 17, + column_index: 17, + rotation: Rotation( + 0, + ), + }, + Sum( + Scaled( + Product( + Sum( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 9, + column_index: 9, + rotation: Rotation( + 0, + ), + }, + ), + Sum( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Negated( + Sum( + Sum( + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + ), + Negated( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + 0x2000000000000000000000000000000011234c7e04a67c8dcc96987680000001, + ), + Negated( + Product( + Advice { + query_index: 8, + column_index: 8, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 5, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Negated( + Advice { + query_index: 6, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ), + ), + ), + Scaled( + Sum( + Constant( + 0x0000000000000000000000000000000000000000000000000000000000000001, + ), + Negated( + Fixed { + query_index: 17, + column_index: 17, + rotation: Rotation( + 0, + ), + }, + ), + ), + 0x2f0f40c2f152a01c9caf66298493d5d0944a041c2e65ba0117c24f76bf8e6483, + ), + ), + ], + table_expressions: [ + Fixed { + query_index: 1, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 11, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 12, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ], + }, + ], + constants: [ + Column { + index: 3, + column_type: Fixed, + }, + ], + minimum_degree: None, + }, + fixed_commitments: [ + (0x05f5862cad2888855bc3c1843a9eff57b11b592d9eb0e13354256661387f5231, 0x32236b14df85bf5f532a930232cb23a5c56ef7d67aaeed8bcb8fc10ea132cbd6), + (0x3e727e8554679f98120355d39d958dbd8755d5a7f8b42ea87f258064c4b3eb57, 0x0c0d5c23f3ee62ac1b2d986dd3033bbcab260d590e1393de3a14a4b31ae091bb), + (0x3748680dd6a91c5dec668b30fb6bf9a450aeb884b81cbc344914f265760e7131, 0x18530eaa5c58b61bd3fc38220ea484c0922524a815a8f752be955c229f9c4164), + (0x20db09a015288b471f104215c3bd6629d4f8ca0d4f4057371397a6e82eb11b46, 0x2103b39bfbc6d806d9d85a202ac53329732f7fa219252a2c5d748124bc2eacdb), + (0x10c9c4524bb7ac04ba23cb5a51ee3a0baa3eb03a9bb808af2180a978e7b74e34, 0x23b7d6025cfda49cfeacad2ac36bd86b2a080a70c5e9865729cbc24f70c65492), + (0x2f47aa0032d556f07dedc01ca7896d9c116a8b2a844e31816c3c575fd1252acb, 0x2d30aeba3d3605198dc79b6e4418ee12d4af5e8ba884f74f147d99bae787d41f), + (0x025e18a2ac49174b0cc0071867a9bdd6092910f1ebd0a3fcdb9ec35868401f81, 0x15fd2c71c464607210cac5592ba5ed38e004e96da7b6974fe472cb786632822a), + (0x31826a45bd7081692cc1a28e609d60f991cde7bad279c800fba23824b6a1ba02, 0x15dc7fbba044cc70eca03eef6c7139a0659fe43b282e07e0054f9799f2cfc07c), + (0x1234391cfa392721d30f01030be885eac42a1d94e4cbb4eb5bb045daa2a59462, 0x2e0099a245c820b195d9082bda117b2229c130f8f02e7d2869ff1d4f0a7bfcdb), + (0x300a8d3fa0b010d7bd1cca4176cd33c511197743e2f7da9216371880efdf7176, 0x3ba4a44aa32e55fa0c4bfbbb24c066f7f12319945c2e9771379caad5b4498234), + (0x0ad0f32f58a9dc70032a5ef1ee073b8f96b774e10d4570c063ed89f094c09c79, 0x3ec9cd184725c31b4085883186fcfa81c74e47328d9e30b9fadf531a8374bc1e), + (0x27bea74efc2965c110cf0a20263f089d6a670dda22cacc194b1680d88cb1827e, 0x26153faa238ecac4319ace05c6f5ae8ac49892b0a0172d311a3fe7e8279a6383), + (0x02adb7cbc9ebbbd87d7d6a41fc40cb4cf57585c6243aa204f757c9026ef20fd3, 0x327fc06fee179c6a57ed95336f9fb7854420d80dd191251a40935664ff6c8067), + (0x2d00d4ec8aa5e4b3d035131f559e4a97f896e8dbc39badb92b58a8d46b5e91df, 0x37046fb32ed8eb4ba0b4da8e1c9b56cd3832fa2ed4788f7faf4fee1f76a94c32), + (0x034b358a31de795e8bf028f774be962d68bfdcb78ae57a72d121ea601df17782, 0x232d727a912edb9323f9eeceee2c6d30fb2b828244e18a0fbd78523bccd64f19), + (0x02c283cbde85f2ad26daddeabe01b1574ce7de68f0e329ec95a4154dd4713f29, 0x208cf96aa5255b543933ee3e9bdd054d4f15265a7c8921aaee89c192af2fd284), + (0x1f777f0e4263ec4a19f30813739c640335ffa951cc3cc586b6c4095e737f95be, 0x061c07fb12cb19582eefd858a08e689acd970c8cb9ed8f7b928d88e691a2f586), + (0x13d0bd76da4ace22c0e90b098d6073551322b8c734bf37eeca67fbf19687f550, 0x3d996cc9da5a31cefb453390b906eabbcc75797bc6a6b9c9e3af2fe7b6b8beed), + (0x2b1938b5d997f22af98e4fd91d4beac686e7d7d2ae33e61c7401e980ad4a6a04, 0x3dabe7f4df0b4306cdc7e4704a4e1d5aeddbe02d1ed7b04597cc77bf06bbc0e1), + (0x15b1dbf7e60f494c47512e320773a5dc8fa89f1d08a52d438de8db937358305b, 0x03dd4fc47f7a7288612eae9e7154da3e70b8d42b6900cc525d40d1e6b78d5d42), + (0x3fa4b5cc33e30de3ac7342c84f47f4fffe7ae77dda1689c2f08050d0ab743cb1, 0x327663e39b128ec28c94486b1e5d4429000626f65230ed572c66f80406a42176), + (0x0d4b9fbfe418cd01017e2d7d71bc38ed8426aae1f9fd6fff87a8f2bc7724bf54, 0x3f9cca76921807a4986953a49c3b596f9a4926c13fe00e22d153c69cf9e2234d), + (0x0974ad1a3c0eb4c8d2c59cd820a82b7f28ea2f7a245008d403815131ff30879e, 0x00bb593cdf920cef4965f788d65eba3c3aa07d9718dfb62e3e385849a0d692a8), + (0x1e355d783cffccafc120f462461fb312773442762383ac444009653f3d8d4be6, 0x3c60e17b18492aa2c41798b409d2bcc1857ca57ee9d2fb0001584cedc8e141d6), + (0x394f6d16b0e1c208839eec9f4602136d70e19e35bf98efbb7f18c0dc1291c89c, 0x12600b765b391f03ca33984fd7619120ed4502dd8502b4f9d0902b621f5577d0), + (0x197c9e3db9dca1cc8c14731d43d66b889d34fcdf58d157c032ab483e73d4b31c, 0x0dde734ed5d271cfeea5ca9093f8926d724353328a4cd7e66c9070ec92ff2038), + (0x2c5001462dc3ca3e2747f99d365ec435c62e23f8d365cb174b249f7e55434061, 0x380fa0030ec3e29fe3b7f4511c8016856ea59db4c844886505a902b8fe1cc624), + (0x3633f6e05440d35ea2c50370aff1759fb21b0b26480e020b87f0672229b8bffa, 0x31f54661543578c4aea8b71452d2827865f0efbc68caf701be861fa13ff03b58), + (0x294720e9ba4147490f59c3760209d9a3a9ca072abeb4b5a9540493843b7d08ad, 0x0e3ecbdb93f1a8a029bdac21f8329b75cc751ce0d81ea6b566ff5185d6050a6b), + ], + permutation: VerifyingKey { + commitments: [ + (0x35f6bb0170edd937669d9ac52053322062f9de8a721ccd1a99ce227f4f7f0007, 0x23bcfd1334a5af354e598eac151e385476ba35cd05edc01a01dff9cd511f90bf), + (0x0ce00911fcbaf24401e4501b43c2307e417e42580f7f985416a68991609bac41, 0x214989802887e11e97e721fc8f4c1fd6f9807d21cd82bb4017a2d2c4469b6bab), + (0x0be1ef382f43f62dc4ebc7c340818cc6e0f180496e03176d9c7f075a5263ef60, 0x1956158b8205144bfc9392560262e95d122591a88dce7f6354c5ea994a025eaa), + (0x1f1f8d1fa97dd86ea1e7bbe02f2b78f20495e5b6e6d4222a48c3d106df9adf0a, 0x0c2a013cccd48bb0f71668e7ce8c8db57c2f0bcbba3a1aadee6cbd57b129b4d1), + (0x3a6a5522ef09a577fa531bc2b33a8e87bf0529e32379ddc6e0e9addd2e2ccd3f, 0x0006796ea4065e4818dce38be56945b8c9c616e239eada12f04f7f9fed995f70), + (0x1b545ac988d204520db1444537722117d0bda683fc268b23aa75086b88a67bfc, 0x22334f68dee7209e109b58f5319b30693a4e47630f264614b37900c0276d21a9), + (0x37ae23872bcad181f253962291ec677171b48dde28671a46617f8aa8278b8f1f, 0x21395fc4eb116bbfeda7b1020248d4a567b11f2a787f5fbe7d8f0fff78db8aaf), + (0x2a13489749f09d403736fba94ab343b5ddb311d528b18a6883e4ce501da0af83, 0x025c8457685571c23f988ecb571fa4f66246974bae5f470ee757d5dd762c022d), + (0x15b90f1a2377727b7a0fc4b9cb89a535aae13b366aa50658f86bb48fe1b3ab89, 0x0f94fbb7c4a6a0795ef3f96f9a68493a8992140ad70e56a7c641bb042f615de2), + (0x320f50ede94907c418e8ec3d53b272fb1aa13b4d147be4b0219df42e1ed3194b, 0x3161bfdf5df38c6c18cbf4fb203c38c4c06512bfcda9ad1e6e499a778c5cfd60), + (0x165e339e256356de4c8592d285a47eb7482b18fa5c68c9f498912be1f92c1f7b, 0x01cd87ef72a24c5357ace6ea7000d845e29664b1340e039bac74cb7ae0072c33), + (0x29fd517fc8e62feda801197c4f56d7da1bdf53ec353926a76e10f5200370f3de, 0x22d470316f657a0704a6e27577e98d9d905c13e433f7592d0ac5c2e757041a84), + (0x21d210b41675a1eae44cbd0f3fd27d69e30716c71873f6089cee61acacd403ab, 0x2275e97c7e84f68bfaa528a9d8be4e059f7abefd80d03fbfca774e8414a9b7c1), + (0x0f9e7de28e0f650d99d99d95c0fcd39c9dac9db5aa1973319f66922d6eb9f7d5, 0x1ba644ecc18ad711ddd33af7f695f6834e9f35c93d47a6a5273dabbe800fc7e6), + (0x0aab3ab73afac76277cd94a891de15e42ceb09f3a9865dab5c814bebfbb4453f, 0x27119fec3736d99abeeef1ad7b857db7e754e0c158780ed3dd0cdd4dc2453e10), + ], + }, +} diff --git a/src/circuit_data/circuit_proof_test_case_post_nu6_3.bin b/src/circuit_data/circuit_proof_test_case_post_nu6_3.bin new file mode 100644 index 000000000..8e5a0815f Binary files /dev/null and b/src/circuit_data/circuit_proof_test_case_post_nu6_3.bin differ diff --git a/src/circuit_data/circuit_proof_test_case_post_nu6_3_restricted.bin b/src/circuit_data/circuit_proof_test_case_post_nu6_3_restricted.bin new file mode 100644 index 000000000..f72709638 Binary files /dev/null and b/src/circuit_data/circuit_proof_test_case_post_nu6_3_restricted.bin differ diff --git a/src/keys.rs b/src/keys.rs index 6ae0df476..2a406d36e 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -989,7 +989,7 @@ mod tests { *, }; use crate::{ - note::{ExtractedNoteCommitment, RandomSeed, Rho}, + note::{ExtractedNoteCommitment, NoteVersion, RandomSeed, Rho}, value::NoteValue, Note, }; @@ -1070,18 +1070,19 @@ mod tests { assert_eq!(&addr.pk_d().to_bytes(), &tv.default_pk_d); let rho = Rho::from_bytes(&tv.note_rho).unwrap(); - let note = Note::from_parts( + let orchard_note = Note::from_parts( addr, NoteValue::from_raw(tv.note_v), rho, RandomSeed::from_bytes(tv.note_rseed, &rho).unwrap(), + NoteVersion::V2, ) .unwrap(); - let cmx: ExtractedNoteCommitment = note.commitment().into(); + let cmx: ExtractedNoteCommitment = orchard_note.commitment().into(); assert_eq!(cmx.to_bytes(), tv.note_cmx); - assert_eq!(note.nullifier(&fvk).to_bytes(), tv.note_nf); + assert_eq!(orchard_note.nullifier(&fvk).to_bytes(), tv.note_nf); let internal_rivk = fvk.rivk(Scope::Internal); assert_eq!(internal_rivk.0.to_repr(), tv.internal_rivk); diff --git a/src/lib.rs b/src/lib.rs index 5290efb39..dc3e9cb75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,7 @@ pub use address::Address; pub use bundle::Bundle; pub use constants::MERKLE_DEPTH_ORCHARD as NOTE_COMMITMENT_TREE_DEPTH; pub use constants::{L_ORCHARD_BASE, L_ORCHARD_SCALAR, L_VALUE}; -pub use note::Note; +pub use note::{Note, NoteVersion}; pub use tree::Anchor; /// A proof of the validity of an Orchard [`Bundle`]. @@ -110,10 +110,49 @@ impl Proof { /// [`Bundle::try_from_parts`]: crate::Bundle::try_from_parts pub const fn expected_proof_size(num_actions: usize) -> usize { // The proof is a fixed base size plus a fixed contribution per action. These constants - // are determined by the halo2 action circuit; see the `circuit` module's `round_trip` - // test, which cross-checks them against `CircuitCost::proof_size`. + // are determined by the halo2 action circuit; see the `circuit` module's round-trip + // tests, which cross-check them against `CircuitCost::proof_size`. const BASE: usize = 2720; const PER_ACTION: usize = 2272; BASE + PER_ACTION * num_actions } } + +/// The set of value pools supported by the Orchard protocol. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum ValuePool { + /// The Orchard value pool. + Orchard, + /// The Ironwood value pool. + Ironwood, +} + +/// The versions of the Orchard protocol. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum ProtocolVersion { + /// The original version of the protocol, used in Zcash prior to NU6.2, only instantiated for + /// the Orchard value pool. + /// + /// Uses the historical unsound Orchard circuit. Cross-address transfers are permitted and + /// notes use the V2 plaintext format. Used to reconstruct the historical verifying key and to + /// parse/verify historical bundles, not to build new ones. + InsecureV1, + /// The version of the Orchard protocol used in Zcash for NU6.2, only instantiated for the + /// Orchard value pool. + /// + /// Uses the post-NU6.2 fixed Orchard circuit. Cross-address transfers are permitted and notes + /// use the V2 plaintext format. + V2, + /// The version of the Orchard protocol used in Zcash NU6.3, instantiated for both the Orchard + /// and Ironwood value pools. + /// + /// Uses the post-NU6.3 circuit for both the Orchard and Ironwood value pools. + /// + /// For transactional bundles affecting the [`ValuePool::Orchard`] value pool, + /// `enableCrossAddress = 0` is required by consensus, so cross-address transfers are + /// prohibited and Orchard actions are disallowed in coinbase. Notes use V2 plaintexts. + /// + /// For transactional bundles affecting the [`ValuePool::Ironwood`] value pool, cross-address + /// transfers are permitted and notes use V3 plaintexts. + V3, +} diff --git a/src/note.rs b/src/note.rs index 78d2f0cdc..b147f85d7 100644 --- a/src/note.rs +++ b/src/note.rs @@ -2,6 +2,7 @@ use core::fmt; use memuse::DynamicUsage; +use blake2b_simd::Params as Blake2bParams; use ff::PrimeField; use group::GroupEncoding; use pasta_curves::pallas; @@ -10,11 +11,14 @@ use subtle::CtOption; use crate::{ keys::{EphemeralSecretKey, FullViewingKey, Scope, SpendingKey}, - spec::{to_base, to_scalar, NonZeroPallasScalar, PrfExpand}, + spec::{to_base, to_scalar, NonIdentityPallasPoint, NonZeroPallasScalar, PrfExpand}, value::NoteValue, Address, }; +const PRF_EXPAND_PERSONALIZATION: &[u8; 16] = b"Zcash_ExpandSeed"; +const ZIP2005_ORCHARD_QR_RCM_DOMAIN_SEPARATOR: u8 = 0x0B; + #[cfg(not(feature = "unstable-voting-circuits"))] pub(crate) mod commitment; #[cfg(feature = "unstable-voting-circuits")] @@ -23,6 +27,41 @@ pub mod commitment; pub use self::commitment::NoteCommitTrapdoor; pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment}; +/// Note plaintext version. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum NoteVersion { + /// The [ZIP 212] Orchard note plaintext format, identified by lead byte + /// `0x02`. + /// + /// [ZIP 212]: https://zips.z.cash/zip-0212 + V2, + /// The quantum-recoverable Ironwood note plaintext version defined in + /// [ZIP 2005]. + /// + /// [ZIP 2005]: https://zips.z.cash/zip-2005 + V3, +} + +impl NoteVersion { + /// Returns the note plaintext lead byte signaling this version. + pub(crate) const fn lead_byte(self) -> u8 { + match self { + Self::V2 => 0x02, + Self::V3 => 0x03, + } + } + + /// Parses a note plaintext lead byte into the corresponding version, + /// returning `None` if the byte is not a recognized version. + pub(crate) fn from_lead_byte(b: u8) -> Option { + match b { + 0x02 => Some(Self::V2), + 0x03 => Some(Self::V3), + _ => None, + } + } +} + #[cfg(not(feature = "unstable-voting-circuits"))] pub(crate) mod nullifier; #[cfg(feature = "unstable-voting-circuits")] @@ -125,15 +164,71 @@ impl RandomSeed { self.esk_inner(rho).unwrap() } + /// The rcm derivation for V2 (ZIP 212) notes. + /// /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend]. /// /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] - pub(crate) fn rcm(&self, rho: &Rho) -> commitment::NoteCommitTrapdoor { + pub(crate) fn rcm_v2(&self, rho: &Rho) -> commitment::NoteCommitTrapdoor { commitment::NoteCommitTrapdoor(to_scalar( PrfExpand::ORCHARD_RCM.with(&self.0, &rho.to_bytes()), )) } + + /// Quantum-recoverable rcm derivation per [ZIP 2005]. + /// + /// Binds rcm to all note fields for post-quantum commitment binding. This + /// implements $\mathsf{H}^{\mathsf{rcm},\mathsf{Orchard}}\_{\mathsf{rseed}}$: + /// + /// $$ + /// \mathsf{pre}\_{\mathsf{rcm}} = + /// [ \mathtt{0x0B} ] + /// \mathbin\Vert \mathsf{g}^\star\_{\mathsf{d}} + /// \mathbin\Vert \mathsf{pk}^\star\_{\mathsf{d}} + /// \mathbin\Vert \mathsf{I2LEOSP}\_{64}(\mathsf{v}) + /// \mathbin\Vert \rho + /// \mathbin\Vert \mathsf{I2LEOSP}\_{256}(\psi) + /// $$ + /// + /// $$ + /// \mathsf{rcm} = + /// \mathsf{ToScalar}^{\mathsf{Orchard}} + /// \left(\mathsf{PRF}^{\mathsf{expand}}\_{\mathsf{rseed}} + /// (\mathsf{pre}\_{\mathsf{rcm}})\right) + /// $$ + /// + /// [ZIP 2005]: https://zips.z.cash/zip-2005 + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] + pub(crate) fn rcm_v3( + &self, + rho: &Rho, + g_d: &NonIdentityPallasPoint, + pk_d: &NonIdentityPallasPoint, + value: u64, + psi: &pallas::Base, + ) -> commitment::NoteCommitTrapdoor { + let mut h = Blake2bParams::new() + .hash_length(64) + .personal(PRF_EXPAND_PERSONALIZATION) + .to_state(); + // rseed: raw bytes (32 bytes) + h.update(&self.0); + // domain separator: [0x0B] (1 byte, literal) + h.update(&[ZIP2005_ORCHARD_QR_RCM_DOMAIN_SEPARATOR]); + // g_d: LEBS2OSP_256(repr_P(g_d)) — compressed Pallas point (32 bytes) + h.update(&g_d.to_bytes()); + // pk_d: LEBS2OSP_256(repr_P(pk_d)) — compressed Pallas point (32 bytes) + h.update(&pk_d.to_bytes()); + // v: I2LEOSP_64(v) — unsigned 64-bit little-endian (8 bytes) + h.update(&value.to_le_bytes()); + // rho: LEBS2OSP_256(repr_P(rho)) — Pallas base field canonical repr (32 bytes) + h.update(&rho.0.to_repr()); + // psi: LEBS2OSP_256(repr_P(psi)) — Pallas base field canonical repr (32 bytes) + h.update(&psi.to_repr()); + + commitment::NoteCommitTrapdoor(to_scalar(*h.finalize().as_array())) + } } /// A discrete amount of funds received by an address. @@ -152,6 +247,8 @@ pub struct Note { rho: Rho, /// The seed randomness for various note components. rseed: RandomSeed, + /// The note plaintext version, determining rcm derivation strategy. + version: NoteVersion, } impl PartialEq for Note { @@ -184,12 +281,14 @@ impl Note { value: NoteValue, rho: Rho, rseed: RandomSeed, + version: NoteVersion, ) -> CtOption { let note = Note { recipient, value, rho, rseed, + version, }; CtOption::new(note, note.commitment_inner().is_some()) } @@ -204,10 +303,17 @@ impl Note { recipient: Address, value: NoteValue, rho: Rho, + version: NoteVersion, mut rng: impl RngCore, ) -> Self { loop { - let note = Note::from_parts(recipient, value, rho, RandomSeed::random(&mut rng, &rho)); + let note = Note::from_parts( + recipient, + value, + rho, + RandomSeed::random(&mut rng, &rho), + version, + ); if note.is_some().into() { break note.unwrap(); } @@ -223,6 +329,7 @@ impl Note { pub(crate) fn dummy( rng: &mut impl RngCore, rho: Option, + note_version: NoteVersion, ) -> (SpendingKey, FullViewingKey, Self) { let sk = SpendingKey::random(rng); let fvk: FullViewingKey = (&sk).into(); @@ -232,6 +339,7 @@ impl Note { recipient, NoteValue::ZERO, rho.unwrap_or_else(|| Rho::from_nf_old(Nullifier::dummy(rng))), + note_version, rng, ); @@ -263,6 +371,33 @@ impl Note { self.rho } + /// Returns the version of this note. + pub fn version(&self) -> NoteVersion { + self.version + } + + /// Derives the ψ value for this note. + pub(crate) fn psi(&self) -> pallas::Base { + self.rseed.psi(&self.rho()) + } + + /// Derives the note commitment trapdoor for this note. + pub(crate) fn rcm(&self) -> commitment::NoteCommitTrapdoor { + let rho = self.rho(); + + match self.version { + NoteVersion::V2 => self.rseed.rcm_v2(&rho), + NoteVersion::V3 => { + let g_d = self.recipient.g_d(); + let pk_d = self.recipient.pk_d().inner(); + let psi = self.rseed.psi(&rho); + + self.rseed + .rcm_v3(&rho, &g_d, &pk_d, self.value.inner(), &psi) + } + } + } + /// Derives the commitment to this note. /// /// Defined in [Zcash Protocol Spec § 3.2: Notes][notes]. @@ -284,25 +419,24 @@ impl Note { /// [notes]: https://zips.z.cash/protocol/nu5.pdf#notes fn commitment_inner(&self) -> CtOption { let g_d = self.recipient.g_d(); + let g_d_bytes = g_d.to_bytes(); + let pk_d = self.recipient.pk_d().inner(); + let pk_d_bytes = pk_d.to_bytes(); + let psi = self.psi(); NoteCommitment::derive( - g_d.to_bytes(), - self.recipient.pk_d().to_bytes(), + g_d_bytes, + pk_d_bytes, self.value, self.rho.0, - self.rseed.psi(&self.rho), - self.rseed.rcm(&self.rho), + psi, + self.rcm(), ) } /// Derives the nullifier for this note. pub fn nullifier(&self, fvk: &FullViewingKey) -> Nullifier { - Nullifier::derive( - fvk.nk(), - self.rho.0, - self.rseed.psi(&self.rho), - self.commitment(), - ) + Nullifier::derive(fvk.nk(), self.rho.0, self.psi(), self.commitment()) } } @@ -338,7 +472,7 @@ pub mod testing { address::testing::arb_address, note::nullifier::testing::arb_nullifier, value::NoteValue, }; - use super::{Note, RandomSeed, Rho}; + use super::{Note, NoteVersion, RandomSeed, Rho}; prop_compose! { /// Generate an arbitrary random seed @@ -348,8 +482,8 @@ pub mod testing { } prop_compose! { - /// Generate an action without authorization data. - pub fn arb_note(value: NoteValue)( + /// Generate an arbitrary note with the given plaintext version. + pub fn arb_note(value: NoteValue, version: NoteVersion)( recipient in arb_address(), rho in arb_nullifier().prop_map(Rho::from_nf_old), rseed in arb_rseed(), @@ -359,7 +493,96 @@ pub mod testing { value, rho, rseed, + version, } } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + keys::{FullViewingKey, Scope, SpendingKey}, + test_vectors::keys::TestVector, + }; + use ff::PrimeField; + use group::GroupEncoding; + + struct QrRcmDerivation { + rcm_old_repr: [u8; 32], + rcm_new_repr: [u8; 32], + cmx_old_bytes: [u8; 32], + cmx_qr_bytes: [u8; 32], + } + + fn qr_rcm_from_key_test_vector(tv: &TestVector) -> QrRcmDerivation { + let sk = SpendingKey::from_bytes(tv.sk).unwrap(); + let fvk = FullViewingKey::from(&sk); + let addr = fvk.address_at(0u32, Scope::External); + let rho = Rho::from_bytes(&tv.note_rho).unwrap(); + let rseed = RandomSeed::from_bytes(tv.note_rseed, &rho).unwrap(); + + let g_d = addr.g_d(); + let pk_d = addr.pk_d().inner(); + let g_d_bytes = g_d.to_bytes(); + let pk_d_bytes = pk_d.to_bytes(); + + let rcm_old = rseed.rcm_v2(&rho); + let psi = rseed.psi(&rho); + let rcm_new = rseed.rcm_v3(&rho, &g_d, &pk_d, tv.note_v, &psi); + + let rcm_old_repr = rcm_old.0.to_repr(); + let rcm_new_repr = rcm_new.0.to_repr(); + let rho_inner = rho.into_inner(); + let value = NoteValue::from_raw(tv.note_v); + + let cmx_old = + NoteCommitment::derive(g_d_bytes, pk_d_bytes, value, rho_inner, psi, rcm_old).unwrap(); + let cmx_old_bytes = ExtractedNoteCommitment::from(cmx_old).to_bytes(); + + let cmx_qr = + NoteCommitment::derive(g_d_bytes, pk_d_bytes, value, rho_inner, psi, rcm_new).unwrap(); + let cmx_qr_bytes = ExtractedNoteCommitment::from(cmx_qr).to_bytes(); + + QrRcmDerivation { + rcm_old_repr, + rcm_new_repr, + cmx_old_bytes, + cmx_qr_bytes, + } + } + + #[test] + fn qr_rcm_differs_from_old_rcm() { + let tv = &crate::test_vectors::keys::test_vectors()[0]; + let derived = qr_rcm_from_key_test_vector(tv); + + assert_ne!(derived.rcm_old_repr, derived.rcm_new_repr); + assert_eq!( + derived.cmx_old_bytes, tv.note_cmx, + "old cmx must match known test vector" + ); + assert_ne!(derived.cmx_old_bytes, derived.cmx_qr_bytes); + } + + #[test] + fn qr_rcm_verify_stored_vectors() { + for (i, key_tv) in crate::test_vectors::keys::test_vectors().iter().enumerate() { + let derived = qr_rcm_from_key_test_vector(key_tv); + + assert_eq!( + derived.rcm_new_repr, key_tv.note_qr_rcm, + "vector {i}: rcm_qr mismatch" + ); + assert_eq!( + derived.cmx_old_bytes, key_tv.note_cmx, + "vector {i}: cmx_old mismatch" + ); + assert_eq!( + derived.cmx_qr_bytes, key_tv.note_qr_cmx, + "vector {i}: cmx_qr mismatch" + ); + } + } +} diff --git a/src/note_encryption.rs b/src/note_encryption.rs index 8d138fa83..bcbd23a54 100644 --- a/src/note_encryption.rs +++ b/src/note_encryption.rs @@ -17,7 +17,7 @@ use crate::{ DiversifiedTransmissionKey, Diversifier, EphemeralPublicKey, EphemeralSecretKey, OutgoingViewingKey, PreparedEphemeralPublicKey, PreparedIncomingViewingKey, SharedSecret, }, - note::{ExtractedNoteCommitment, Nullifier, RandomSeed, Rho}, + note::{ExtractedNoteCommitment, NoteVersion, Nullifier, RandomSeed, Rho}, value::{NoteValue, ValueCommitment}, Address, Note, }; @@ -49,9 +49,10 @@ pub(crate) fn prf_ock_orchard( ) } -fn orchard_parse_note_plaintext_without_memo( - domain: &OrchardDomain, +fn parse_note_plaintext_without_memo( + rho: Rho, plaintext: &[u8], + note_version: NoteVersion, get_pk_d: F, ) -> Option<(Note, Address)> where @@ -59,33 +60,96 @@ where { assert!(plaintext.len() >= COMPACT_NOTE_SIZE); - // Check note plaintext version - if plaintext[0] != 0x02 { - return None; - } - // The unwraps below are guaranteed to succeed by the assertion above let diversifier = Diversifier::from_bytes(plaintext[1..12].try_into().unwrap()); let value = NoteValue::from_bytes(plaintext[12..20].try_into().unwrap()); let rseed = Option::from(RandomSeed::from_bytes( plaintext[20..COMPACT_NOTE_SIZE].try_into().unwrap(), - &domain.rho, + &rho, ))?; let pk_d = get_pk_d(&diversifier); let recipient = Address::from_parts(diversifier, pk_d); - let note = Option::from(Note::from_parts(recipient, value, domain.rho, rseed))?; + let note = Option::from(Note::from_parts(recipient, value, rho, rseed, note_version))?; Some((note, recipient)) } -/// Orchard-specific note encryption logic. +mod sealed { + /// Marker trait that prevents external `DomainVersion` implementations. + pub trait Sealed {} +} + +trait DomainPolicy { + fn note_version(&self, plaintext: &[u8]) -> Option; +} + +/// A sealed marker trait for note encryption domains with a fixed note plaintext version. +/// +/// This trait is sealed so that only this crate can define supported note encryption +/// domains. +pub trait DomainVersion: sealed::Sealed + Default { + /// The note plaintext version accepted by this domain during parsing and decryption. + const NOTE_VERSION: NoteVersion; +} + +impl DomainPolicy for V { + fn note_version(&self, plaintext: &[u8]) -> Option { + if plaintext.first().copied() == Some(V::NOTE_VERSION.lead_byte()) { + Some(V::NOTE_VERSION) + } else { + None + } + } +} + +/// Marker type for Orchard note encryption domains. +#[derive(Default, Debug)] +pub struct OrchardVersion; + +impl sealed::Sealed for OrchardVersion {} + +impl DomainVersion for OrchardVersion { + const NOTE_VERSION: NoteVersion = NoteVersion::V2; +} + +/// Marker type for Ironwood note encryption domains. +#[derive(Default, Debug)] +pub struct IronwoodVersion; + +impl sealed::Sealed for IronwoodVersion {} + +impl DomainVersion for IronwoodVersion { + const NOTE_VERSION: NoteVersion = NoteVersion::V3; +} + #[derive(Debug)] -pub struct OrchardDomain { +pub(crate) struct BundleDomainPolicy { + note_version: NoteVersion, +} + +impl DomainPolicy for BundleDomainPolicy { + fn note_version(&self, plaintext: &[u8]) -> Option { + let note_version = NoteVersion::from_lead_byte(*plaintext.first()?)?; + if note_version == self.note_version { + Some(note_version) + } else { + None + } + } +} + +/// Note encryption logic for a note plaintext version policy. +/// +/// The policy type `P` selects which note plaintext version is accepted during +/// parsing and decryption. Encryption uses the version recorded by the note. +#[derive(Debug)] +pub struct NoteEncryptionDomain

{ rho: Rho, + policy: P, } -impl memuse::DynamicUsage for OrchardDomain { +impl

memuse::DynamicUsage for NoteEncryptionDomain

{ fn dynamic_usage(&self) -> usize { self.rho.dynamic_usage() } @@ -95,26 +159,62 @@ impl memuse::DynamicUsage for OrchardDomain { } } -impl OrchardDomain { +impl NoteEncryptionDomain { + pub(crate) fn from_rho(rho: Rho) -> Self { + Self { + rho, + policy: V::default(), + } + } + /// Constructs a domain that can be used to trial-decrypt this action's output note. pub fn for_action(act: &Action) -> Self { - Self { rho: act.rho() } + Self::from_rho(act.rho()) } /// Constructs a domain that can be used to trial-decrypt a PCZT action's output note. pub fn for_pczt_action(act: &crate::pczt::Action) -> Self { - Self { - rho: Rho::from_nf_old(act.spend().nullifier), - } + Self::from_rho(Rho::from_nf_old(act.spend().nullifier)) } - /// Constructs a domain that can be used to trial-decrypt this action's output note. + /// Constructs a domain that can be used to trial-decrypt this compact action's output note. pub fn for_compact_action(act: &CompactAction) -> Self { - Self { rho: act.rho() } + Self::from_rho(act.rho()) + } +} + +/// Orchard-specific note encryption logic. +/// +/// This domain accepts only [`NoteVersion::V2`] note plaintexts, which use lead +/// byte `0x02`. +pub type OrchardDomain = NoteEncryptionDomain; + +/// Ironwood-specific note encryption logic. +/// +/// This domain is otherwise identical to [`OrchardDomain`], but accepts only +/// [`NoteVersion::V3`] note plaintexts, which use lead byte `0x03`. +pub type IronwoodDomain = NoteEncryptionDomain; + +/// Note encryption logic restricted to a single note plaintext version. +/// +/// This domain is used by public bundle helpers that are given the bundle's +/// [`NoteVersion`]. Trial decryption still happens once; after decryption +/// succeeds, the revealed note plaintext lead byte selects the note version, which is +/// enforced to match the expected one. +pub(crate) type BundleDomain = NoteEncryptionDomain; + +impl BundleDomain { + /// Constructs a domain that can be used to trial-decrypt this action's + /// output note as a note of `note_version`. + pub(crate) fn for_action(act: &Action, note_version: NoteVersion) -> Self { + Self { + rho: act.rho(), + policy: BundleDomainPolicy { note_version }, + } } } -impl Domain for OrchardDomain { +impl Domain for NoteEncryptionDomain

{ type EphemeralSecretKey = EphemeralSecretKey; type EphemeralPublicKey = EphemeralPublicKey; type PreparedEphemeralPublicKey = PreparedEphemeralPublicKey; @@ -169,7 +269,7 @@ impl Domain for OrchardDomain { fn note_plaintext_bytes(note: &Self::Note, memo: &Self::Memo) -> NotePlaintextBytes { let mut np = [0; NOTE_PLAINTEXT_SIZE]; - np[0] = 0x02; + np[0] = note.version().lead_byte(); np[1..12].copy_from_slice(note.recipient().diversifier().as_array()); np[12..20].copy_from_slice(¬e.value().to_bytes()); np[20..52].copy_from_slice(note.rseed().as_bytes()); @@ -213,7 +313,8 @@ impl Domain for OrchardDomain { ivk: &Self::IncomingViewingKey, plaintext: &[u8], ) -> Option<(Self::Note, Self::Recipient)> { - orchard_parse_note_plaintext_without_memo(self, plaintext, |diversifier| { + let note_version = self.policy.note_version(plaintext)?; + parse_note_plaintext_without_memo(self.rho, plaintext, note_version, |diversifier| { DiversifiedTransmissionKey::derive(ivk, diversifier) }) } @@ -223,7 +324,8 @@ impl Domain for OrchardDomain { pk_d: &Self::DiversifiedTransmissionKey, plaintext: &NotePlaintextBytes, ) -> Option<(Self::Note, Self::Recipient)> { - orchard_parse_note_plaintext_without_memo(self, &plaintext.0, |_| *pk_d) + let note_version = self.policy.note_version(&plaintext.0)?; + parse_note_plaintext_without_memo(self.rho, &plaintext.0, note_version, |_| *pk_d) } fn extract_memo(&self, plaintext: &NotePlaintextBytes) -> Self::Memo { @@ -242,25 +344,30 @@ impl Domain for OrchardDomain { } } -impl BatchDomain for OrchardDomain { +impl BatchDomain for NoteEncryptionDomain

{ fn batch_kdf<'a>( items: impl Iterator, &'a EphemeralKeyBytes)>, ) -> Vec> { - let (shared_secrets, ephemeral_keys): (Vec<_>, Vec<_>) = items.unzip(); - - SharedSecret::batch_to_affine(shared_secrets) - .zip(ephemeral_keys) - .map(|(secret, ephemeral_key)| { - secret.map(|dhsecret| SharedSecret::kdf_orchard_inner(dhsecret, ephemeral_key)) - }) - .collect() + batch_kdf(items) } } -/// Implementation of in-band secret distribution for Orchard bundles. -pub type OrchardNoteEncryption = zcash_note_encryption::NoteEncryption; +fn batch_kdf<'a>( + items: impl Iterator, &'a EphemeralKeyBytes)>, +) -> Vec> { + let (shared_secrets, ephemeral_keys): (Vec<_>, Vec<_>) = items.unzip(); -impl ShieldedOutput for Action { + SharedSecret::batch_to_affine(shared_secrets) + .zip(ephemeral_keys) + .map(|(secret, ephemeral_key)| { + secret.map(|dhsecret| SharedSecret::kdf_orchard_inner(dhsecret, ephemeral_key)) + }) + .collect() +} + +impl ShieldedOutput, ENC_CIPHERTEXT_SIZE> + for Action +{ fn ephemeral_key(&self) -> EphemeralKeyBytes { EphemeralKeyBytes(self.encrypted_note().epk_bytes) } @@ -274,7 +381,9 @@ impl ShieldedOutput for Action { } } -impl ShieldedOutput for crate::pczt::Action { +impl ShieldedOutput, ENC_CIPHERTEXT_SIZE> + for crate::pczt::Action +{ fn ephemeral_key(&self) -> EphemeralKeyBytes { EphemeralKeyBytes(self.output().encrypted_note().epk_bytes) } @@ -288,6 +397,41 @@ impl ShieldedOutput for crate::pczt::Action } } +impl ShieldedOutput, COMPACT_NOTE_SIZE> for CompactAction { + fn ephemeral_key(&self) -> EphemeralKeyBytes { + EphemeralKeyBytes(self.ephemeral_key.0) + } + + fn cmstar_bytes(&self) -> [u8; 32] { + self.cmx.to_bytes() + } + + fn enc_ciphertext(&self) -> &[u8; COMPACT_NOTE_SIZE] { + &self.enc_ciphertext + } +} + +/// Implementation of in-band secret distribution for Orchard bundles. +/// +/// This is the [`NoteEncryption`] instantiation for [`OrchardDomain`]. Encryption +/// behavior is shared with [`IronwoodNoteEncryption`]: the note plaintext lead +/// byte is selected from [`crate::Note::version`], while the domain type +/// controls which note plaintext versions are accepted during parsing and +/// decryption. +/// +/// [`NoteEncryption`]: zcash_note_encryption::NoteEncryption +pub type OrchardNoteEncryption = zcash_note_encryption::NoteEncryption; +/// Implementation of in-band secret distribution for Ironwood bundles. +/// +/// This is the [`NoteEncryption`] instantiation for [`IronwoodDomain`]. Encryption +/// behavior is shared with [`OrchardNoteEncryption`]: the note plaintext lead +/// byte is selected from [`crate::Note::version`], while the domain type +/// controls which note plaintext versions are accepted during parsing and +/// decryption. +/// +/// [`NoteEncryption`]: zcash_note_encryption::NoteEncryption +pub type IronwoodNoteEncryption = zcash_note_encryption::NoteEncryption; + /// A compact Action for light clients. #[derive(Clone)] pub struct CompactAction { @@ -308,7 +452,7 @@ impl From<&Action> for CompactAction { CompactAction { nullifier: *action.nullifier(), cmx: *action.cmx(), - ephemeral_key: action.ephemeral_key(), + ephemeral_key: EphemeralKeyBytes(action.encrypted_note().epk_bytes), enc_ciphertext: action.encrypted_note().enc_ciphertext[..52] .try_into() .unwrap(), @@ -316,20 +460,6 @@ impl From<&Action> for CompactAction { } } -impl ShieldedOutput for CompactAction { - fn ephemeral_key(&self) -> EphemeralKeyBytes { - EphemeralKeyBytes(self.ephemeral_key.0) - } - - fn cmstar_bytes(&self) -> [u8; 32] { - self.cmx.to_bytes() - } - - fn enc_ciphertext(&self) -> &[u8; COMPACT_NOTE_SIZE] { - &self.enc_ciphertext - } -} - impl CompactAction { /// Create a CompactAction from its constituent parts pub fn from_parts( @@ -370,7 +500,7 @@ pub mod testing { use crate::{ keys::OutgoingViewingKey, - note::{ExtractedNoteCommitment, Nullifier, RandomSeed, Rho}, + note::{ExtractedNoteCommitment, NoteVersion, Nullifier, RandomSeed, Rho}, value::NoteValue, Address, Note, }; @@ -398,7 +528,7 @@ pub mod testing { } } }; - let note = Note::from_parts(recipient, value, rho, rseed).unwrap(); + let note = Note::from_parts(recipient, value, rho, rseed, NoteVersion::V2).unwrap(); let encryptor = OrchardNoteEncryption::new(ovk, note, [0u8; 512]); let cmx = ExtractedNoteCommitment::from(note.commitment()); let ephemeral_key = OrchardDomain::epk_bytes(encryptor.epk()); @@ -420,23 +550,73 @@ pub mod testing { mod tests { use rand::rngs::OsRng; use zcash_note_encryption::{ - try_compact_note_decryption, try_note_decryption, try_output_recovery_with_ovk, + try_compact_note_decryption, try_note_decryption, try_output_recovery_with_ovk, Domain, EphemeralKeyBytes, }; - use super::{prf_ock_orchard, CompactAction, OrchardDomain, OrchardNoteEncryption}; + use super::{ + prf_ock_orchard, CompactAction, IronwoodDomain, IronwoodNoteEncryption, OrchardDomain, + OrchardNoteEncryption, + }; use crate::{ action::Action, keys::{ DiversifiedTransmissionKey, Diversifier, EphemeralSecretKey, IncomingViewingKey, - OutgoingViewingKey, PreparedIncomingViewingKey, + OutgoingViewingKey, PreparedIncomingViewingKey, Scope, SpendingKey, + }, + note::{ + ExtractedNoteCommitment, NoteVersion, Nullifier, RandomSeed, Rho, + TransmittedNoteCiphertext, }, - note::{ExtractedNoteCommitment, Nullifier, RandomSeed, Rho, TransmittedNoteCiphertext}, primitives::redpallas, - value::{NoteValue, ValueCommitment}, + value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Address, Note, }; + fn v3_encrypted_action() -> ( + Action<()>, + PreparedIncomingViewingKey, + Note, + Address, + [u8; 512], + ) { + let mut rng = OsRng; + let sk = SpendingKey::random(&mut rng); + let fvk = crate::keys::FullViewingKey::from(&sk); + let incoming_viewing_key = fvk.to_ivk(Scope::External); + let prepared_ivk = PreparedIncomingViewingKey::new(&incoming_viewing_key); + let recipient = fvk.address_at(0u32, Scope::External); + let nf_old = Nullifier::dummy(&mut rng); + let rho = Rho::from_nf_old(nf_old); + let note = Note::new( + recipient, + NoteValue::from_raw(5), + rho, + NoteVersion::V3, + &mut rng, + ); + let memo = [7u8; 512]; + let cv_net = ValueCommitment::derive(ValueSum::from_raw(5), ValueCommitTrapdoor::zero()); + let cmx = ExtractedNoteCommitment::from(note.commitment()); + let encryptor = IronwoodNoteEncryption::new(Some(fvk.to_ovk(Scope::External)), note, memo); + let encrypted_note = TransmittedNoteCiphertext { + epk_bytes: IronwoodDomain::epk_bytes(encryptor.epk()).0, + enc_ciphertext: encryptor.encrypt_note_plaintext(), + out_ciphertext: encryptor.encrypt_outgoing_plaintext(&cv_net, &cmx, &mut rng), + }; + let action = Action::from_parts( + nf_old, + redpallas::VerificationKey::dummy(), + cmx, + encrypted_note, + cv_net, + (), + ) + .expect("a dummy verification key is unlikely to be the identity"); + + (action, prepared_ivk, note, recipient, memo) + } + #[test] fn test_vectors() { let test_vectors = crate::test_vectors::note_encryption::test_vectors(); @@ -481,7 +661,8 @@ mod tests { assert_eq!(ock.as_ref(), tv.ock); let recipient = Address::from_parts(d, pk_d); - let note = Note::from_parts(recipient, value, rho, rseed).unwrap(); + let note_version = NoteVersion::V2; + let note = Note::from_parts(recipient, value, rho, rseed, note_version).unwrap(); assert_eq!(ExtractedNoteCommitment::from(note.commitment()), cmx); let action = Action::from_parts( @@ -505,7 +686,7 @@ mod tests { // (Tested first because it only requires immutable references.) // - let domain = OrchardDomain { rho }; + let domain = OrchardDomain::from_rho(rho); match try_note_decryption(&domain, &ivk, &action) { Some((decrypted_note, decrypted_to, decrypted_memo)) => { @@ -546,4 +727,85 @@ mod tests { ); } } + + #[test] + fn domains_accept_only_their_note_plaintext_versions() { + let mut rng = OsRng; + let sk = crate::keys::SpendingKey::random(&mut rng); + let fvk = crate::keys::FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, crate::keys::Scope::External); + let rho = Rho::from_nf_old(Nullifier::dummy(&mut rng)); + let memo = [0u8; 512]; + + let note_v2 = Note::new( + recipient, + NoteValue::from_raw(5), + rho, + NoteVersion::V2, + &mut rng, + ); + let note_v3 = Note::new( + recipient, + NoteValue::from_raw(5), + rho, + NoteVersion::V3, + &mut rng, + ); + let orchard_domain = OrchardDomain::from_rho(rho); + let ironwood_domain = IronwoodDomain::from_rho(rho); + + let np_v2 = OrchardDomain::note_plaintext_bytes(¬e_v2, &memo); + let np_v3 = IronwoodDomain::note_plaintext_bytes(¬e_v3, &memo); + let pk_d = recipient.pk_d(); + + assert_eq!( + orchard_domain + .parse_note_plaintext_without_memo_ovk(pk_d, &np_v2) + .map(|(note, _)| note), + Some(note_v2) + ); + assert_eq!( + ironwood_domain + .parse_note_plaintext_without_memo_ovk(pk_d, &np_v3) + .map(|(note, _)| note), + Some(note_v3) + ); + assert!(orchard_domain + .parse_note_plaintext_without_memo_ovk(pk_d, &np_v3) + .is_none()); + assert!(ironwood_domain + .parse_note_plaintext_without_memo_ovk(pk_d, &np_v2) + .is_none()); + } + + #[test] + fn ironwood_domain_decrypts_v3_encrypted_outputs() { + let (action, ivk, note, recipient, memo) = v3_encrypted_action(); + let domain = IronwoodDomain::for_action(&action); + + assert_eq!( + try_note_decryption(&domain, &ivk, &action), + Some((note, recipient, memo)) + ); + } + + #[test] + fn orchard_domain_rejects_v3_encrypted_outputs() { + let (action, ivk, _, _, _) = v3_encrypted_action(); + let domain = OrchardDomain::for_action(&action); + + assert!(try_note_decryption(&domain, &ivk, &action).is_none()); + } + + #[test] + fn ironwood_domain_decrypts_v3_compact_outputs() { + let (action, ivk, note, recipient, _) = v3_encrypted_action(); + let domain = IronwoodDomain::for_action(&action); + let compact = CompactAction::from(&action); + + assert_eq!( + try_compact_note_decryption(&domain, &ivk, &compact), + Some((note, recipient)) + ); + } } diff --git a/src/pczt.rs b/src/pczt.rs index 181268096..516f27ca5 100644 --- a/src/pczt.rs +++ b/src/pczt.rs @@ -11,13 +11,13 @@ use zcash_note_encryption::OutgoingCipherKey; use zip32::ChildIndex; use crate::{ - bundle::Flags, + bundle::{BundleVersion, Flags}, keys::{FullViewingKey, SpendingKey}, note::{ExtractedNoteCommitment, Nullifier, RandomSeed, Rho, TransmittedNoteCiphertext}, primitives::redpallas::{self, Binding, SpendAuth}, tree::MerklePath, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, - Address, Anchor, Proof, + Address, Anchor, NoteVersion, Proof, }; mod parse; @@ -64,6 +64,13 @@ pub struct Bundle { /// are consistent with these flags (i.e. are dummies as appropriate). pub(crate) flags: Flags, + /// The value pool and protocol version this bundle is encoded under. + /// + /// This is set by the Creator, and determines how the bundle's flags are interpreted and + /// which [`crate::Bundle`] the Transaction Extractor produces. The flags are always + /// consistent with this version (by parsing or construction). + pub(crate) bundle_version: BundleVersion, + /// The sum of the values of all `actions`. /// /// This is initialized by the Creator, and updated by the Constructor as spends or @@ -98,6 +105,16 @@ impl Bundle { pub fn actions_mut(&mut self) -> &mut [Action] { &mut self.actions } + + /// Returns the byte encoding of this bundle's flags under its own [`BundleVersion`]. + /// + /// This is infallible: a PCZT bundle is only ever constructed (by parsing or by the builder) + /// with flags that are representable under its version. + pub fn flag_byte(&self) -> u8 { + self.flags + .to_byte(self.bundle_version) + .expect("flags are validated against the bundle version at construction") + } } /// PCZT fields that are specific to producing an Orchard action within a transaction. @@ -182,6 +199,12 @@ pub struct Spend { /// - This is required by the Prover. pub(crate) fvk: Option, + /// The plaintext version of the note being spent. + /// + /// This is set by the Constructor, and is required by Verifiers and + /// Provers to reconstruct the note commitment. + pub(crate) note_version: NoteVersion, + /// A witness from the note to the bundle's anchor. /// /// - This is set by the Updater. @@ -218,6 +241,12 @@ pub struct Output { /// A commitment to the new note being created. pub(crate) cmx: ExtractedNoteCommitment, + /// The plaintext version of the new note being created. + /// + /// This is set by the Constructor, and is required by Verifiers and + /// Provers to reconstruct the note commitment. + pub(crate) note_version: NoteVersion, + /// The transmitted note ciphertext. /// /// This contains the following PCZT fields: @@ -232,7 +261,9 @@ pub struct Output { /// - This is required by the Prover. /// - The Signer can use `recipient` and `rseed` (if present) to verify that /// `enc_ciphertext` is correctly encrypted (and contains a note plaintext matching - /// the public commitments), and to confirm the value of the memo. + /// the public commitments), and to confirm the value of the memo. This does not apply + /// to the restricted builder's zero-valued output paired with a real spend, whose + /// `enc_ciphertext` is deliberately randomized; its note commitment remains verifiable. pub(crate) recipient: Option

, /// The value of the output. @@ -250,7 +281,9 @@ pub struct Output { /// - This is required by the Prover. /// - The Signer can use `recipient` and `rseed` (if present) to verify that /// `enc_ciphertext` is correctly encrypted (and contains a note plaintext matching - /// the public commitments), and to confirm the value of the memo. + /// the public commitments), and to confirm the value of the memo. This does not apply + /// to the restricted builder's zero-valued output paired with a real spend, whose + /// `enc_ciphertext` is deliberately randomized; its note commitment remains verifiable. pub(crate) rseed: Option, /// The `ock` value used to encrypt `out_ciphertext`. @@ -279,6 +312,7 @@ impl fmt::Debug for Output { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Output") .field("cmx", &self.cmx) + .field("note_version", &self.note_version) .field("encrypted_note", &self.encrypted_note) .field("recipient", &self.recipient) .field("value", &self.value) @@ -339,29 +373,96 @@ mod tests { use shardtree::{store::memory::MemoryShardStore, ShardTree}; use crate::{ - builder::{Builder, BundleType}, - circuit::ProvingKey, + builder::{Builder, BundleMetadata, BundleType}, + bundle::{BundleVersion, Flags}, + circuit::{OrchardCircuitVersion, ProvingKey, VerifyingKey}, constants::MERKLE_DEPTH_ORCHARD, keys::{FullViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, - note::{ExtractedNoteCommitment, RandomSeed, Rho}, - pczt::{ProverError, TxExtractorError, Zip32Derivation}, + note::{ExtractedNoteCommitment, NoteVersion, Nullifier, RandomSeed, Rho}, + pczt::{ + IoFinalizerError, ParseError, ProverError, SignerError, TxExtractorError, VerifyError, + Zip32Derivation, + }, primitives::redpallas::{self, SpendAuth}, - tree::{MerkleHashOrchard, EMPTY_ROOTS}, + tree::{MerkleHashOrchard, MerklePath, EMPTY_ROOTS}, value::NoteValue, Note, }; - /// Builds a minimal shielding-style pczt bundle, finalizes IO, and returns - /// it ready for `create_proof`. Used by identity-`rk` tests below. + /// Builds a cross-address-restricted pczt bundle with one real spend (15_000 at an + /// external address) and one wallet-controlled change output (5_000 at a different + /// wallet's internal address), without finalizing IO. + /// + /// Returns the bundle, its metadata, and the spend authorizing keys for the spend + /// and the change output respectively. + fn restricted_pczt_bundle( + mut rng: OsRng, + ) -> ( + super::Bundle, + BundleMetadata, + SpendAuthorizingKey, + SpendAuthorizingKey, + ) { + let spend_sk = SpendingKey::random(&mut rng); + let spend_fvk = FullViewingKey::from(&spend_sk); + let spend_recipient = spend_fvk.address_at(0u32, Scope::External); + let change_sk = SpendingKey::random(&mut rng); + let change_fvk = FullViewingKey::from(&change_sk); + let change_recipient = change_fvk.address_at(0u32, Scope::Internal); + let bundle_version = BundleVersion::orchard_v3(); + let note_version = bundle_version.note_version(); + + let rho = Rho::from_nf_old(Nullifier::dummy(&mut rng)); + let note = Note::new( + spend_recipient, + NoteValue::from_raw(15_000), + rho, + note_version, + &mut rng, + ); + let merkle_path = MerklePath::dummy(&mut rng); + let anchor = merkle_path.root(note.commitment().into()); + + let mut builder = Builder::new( + BundleType::DEFAULT, + bundle_version, + bundle_version.default_flags(), + anchor, + ) + .unwrap(); + builder.add_spend(spend_fvk, note, merkle_path).unwrap(); + builder + .add_change_output( + change_fvk, + None, + change_recipient, + NoteValue::from_raw(5_000), + [0u8; 512], + ) + .unwrap(); + + let (pczt_bundle, bundle_meta) = builder.build_for_pczt(&mut rng).unwrap(); + ( + pczt_bundle, + bundle_meta, + SpendAuthorizingKey::from(&spend_sk), + SpendAuthorizingKey::from(&change_sk), + ) + } + + /// Builds a minimal shielding-style pczt bundle, finalizes IO, and returns it ready for + /// tests that exercise `create_proof` and `extract`. fn minimal_finalized_pczt_bundle(mut rng: OsRng) -> super::Bundle { let sk = SpendingKey::random(&mut rng); let fvk = FullViewingKey::from(&sk); let recipient = fvk.address_at(0u32, Scope::External); - let mut builder = Builder::new( BundleType::DEFAULT, + BundleVersion::orchard_v2(), + BundleVersion::orchard_v2().default_flags(), EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), - ); + ) + .unwrap(); builder .add_output(None, recipient, NoteValue::from_raw(5000), [0u8; 512]) .unwrap(); @@ -372,6 +473,23 @@ mod tests { pczt_bundle } + fn ironwood_output_pczt_bundle(mut rng: OsRng) -> super::Bundle { + let sk = SpendingKey::random(&mut rng); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + let mut builder = Builder::new( + BundleType::DEFAULT, + BundleVersion::ironwood_v3(), + BundleVersion::ironwood_v3().default_flags(), + EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), + ) + .unwrap(); + builder + .add_output(None, recipient, NoteValue::from_raw(5000), [0u8; 512]) + .unwrap(); + builder.build_for_pczt(&mut rng).unwrap().0 + } + fn identity_rk() -> redpallas::VerificationKey { redpallas::VerificationKey::::try_from([0u8; 32]) .expect("plain redpallas accepts the identity encoding") @@ -379,7 +497,8 @@ mod tests { #[test] fn shielding_bundle() { - let pk = ProvingKey::build(); + let bundle_version = BundleVersion::orchard_v2(); + let pk = ProvingKey::build(bundle_version.circuit_version()); let mut rng = OsRng; let sk = SpendingKey::random(&mut rng); @@ -389,8 +508,11 @@ mod tests { // Run the Creator and Constructor roles. let mut builder = Builder::new( BundleType::DEFAULT, + bundle_version, + bundle_version.default_flags(), EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), - ); + ) + .unwrap(); builder .add_output(None, recipient, NoteValue::from_raw(5000), [0u8; 512]) .unwrap(); @@ -413,9 +535,179 @@ mod tests { bundle.apply_binding_signature(sighash, rng).unwrap(); } + #[test] + fn create_proof_uses_proving_key_circuit_version() { + let pk = ProvingKey::build(OrchardCircuitVersion::PostNu6_3); + let vk = VerifyingKey::build(OrchardCircuitVersion::PostNu6_3); + let rng = OsRng; + + let mut pczt_bundle = minimal_finalized_pczt_bundle(rng); + let sighash = [0; 32]; + // This is the load-bearing assertion: if PCZT proving still built FixedPostNu6_2 + // circuits unconditionally, `Proof::create` would reject them for this post-NU 6.3 key. + pczt_bundle.create_proof(&pk, rng).unwrap(); + + let bundle = pczt_bundle + .extract::() + .unwrap() + .unwrap() + .apply_binding_signature(sighash, rng) + .unwrap(); + + assert!(bundle.verify_proof(&vk).is_ok()); + } + + #[test] + fn qr_output_version_checks_note_commitment() { + let mut rng = OsRng; + let pk = ProvingKey::build(OrchardCircuitVersion::PostNu6_3); + let vk = VerifyingKey::build(OrchardCircuitVersion::PostNu6_3); + + let sk = SpendingKey::random(&mut rng); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + + let mut builder = Builder::new( + BundleType::DEFAULT, + BundleVersion::ironwood_v3(), + BundleVersion::ironwood_v3().default_flags(), + EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), + ) + .unwrap(); + builder + .add_output(None, recipient, NoteValue::from_raw(5000), [0u8; 512]) + .unwrap(); + let (mut pczt_bundle, bundle_meta) = builder.build_for_pczt(&mut rng).unwrap(); + let output_action_index = bundle_meta.output_action_index(0).unwrap(); + + let action = &pczt_bundle.actions()[output_action_index]; + assert_eq!(action.output.note_version(), &NoteVersion::V3); + action + .output + .verify_note_commitment(&action.spend) + .expect("V3 output version verifies the QR note commitment"); + + let sighash = [0; 32]; + pczt_bundle.finalize_io(sighash, rng).unwrap(); + pczt_bundle + .create_proof(&pk, rng) + .expect("V3 output version reconstructs the QR note for proving"); + + pczt_bundle.actions_mut()[output_action_index] + .output + .note_version = NoteVersion::V2; + let action = &pczt_bundle.actions()[output_action_index]; + assert!(matches!( + action.output.verify_note_commitment(&action.spend), + Err(VerifyError::InvalidExtractedNoteCommitment) + )); + pczt_bundle.create_proof(&pk, rng).unwrap(); + let bundle = pczt_bundle + .extract::() + .unwrap() + .unwrap() + .apply_binding_signature(sighash, rng) + .unwrap(); + assert!(bundle.verify_proof(&vk).is_err()); + } + + #[test] + fn qr_spend_version_checks_nullifier_and_proves() { + let pk = ProvingKey::build(OrchardCircuitVersion::PostNu6_3); + let mut rng = OsRng; + + let sk = SpendingKey::random(&mut rng); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + + let value = NoteValue::from_raw(15_000); + let note = { + let rho = Rho::from_bytes(&pallas::Base::random(&mut rng).to_repr()).unwrap(); + loop { + if let Some(note) = Note::from_parts( + recipient, + value, + rho, + RandomSeed::random(&mut rng, &rho), + NoteVersion::V3, + ) + .into_option() + { + break note; + } + } + }; + + let (anchor, merkle_path) = { + let cmx: ExtractedNoteCommitment = note.commitment().into(); + let leaf = MerkleHashOrchard::from_cmx(&cmx); + let mut tree: ShardTree, 32, 16> = + ShardTree::new(MemoryShardStore::empty(), 100); + tree.append( + leaf, + Retention::Checkpoint { + id: 0, + marking: Marking::Marked, + }, + ) + .unwrap(); + let root = tree.root_at_checkpoint_id(&0).unwrap().unwrap(); + let position = tree.max_leaf_position(None).unwrap().unwrap(); + let merkle_path = tree + .witness_at_checkpoint_id(position, &0) + .unwrap() + .unwrap(); + assert_eq!(root, merkle_path.root(MerkleHashOrchard::from_cmx(&cmx))); + (root.into(), merkle_path) + }; + + let bundle_version = BundleVersion::ironwood_v3(); + let mut builder = Builder::new( + BundleType::DEFAULT, + bundle_version, + bundle_version.default_flags(), + anchor, + ) + .unwrap(); + builder + .add_spend(fvk.clone(), note, merkle_path.into()) + .unwrap(); + builder + .add_output(None, recipient, NoteValue::from_raw(10_000), [0u8; 512]) + .unwrap(); + let (mut pczt_bundle, bundle_meta) = builder.build_for_pczt(&mut rng).unwrap(); + let spend_action_index = bundle_meta.spend_action_index(0).unwrap(); + + let action = &pczt_bundle.actions()[spend_action_index]; + assert_eq!(action.spend.note_version(), &NoteVersion::V3); + action + .spend + .verify_nullifier(None) + .expect("V3 spend version verifies the QR note nullifier"); + + pczt_bundle.finalize_io([0; 32], rng).unwrap(); + pczt_bundle + .create_proof(&pk, rng) + .expect("V3 spend version reconstructs the QR note for proving"); + + pczt_bundle.actions_mut()[spend_action_index] + .spend + .note_version = NoteVersion::V2; + let action = &pczt_bundle.actions()[spend_action_index]; + assert!(matches!( + action.spend.verify_nullifier(None), + Err(VerifyError::InvalidNullifier) + )); + assert!(matches!( + pczt_bundle.create_proof(&pk, rng), + Err(ProverError::RhoMismatch) + )); + } + #[test] fn shielded_bundle() { - let pk = ProvingKey::build(); + let bundle_version = BundleVersion::orchard_v2(); + let pk = ProvingKey::build(bundle_version.circuit_version()); let mut rng = OsRng; // Pretend we derived the spending key via ZIP 32. @@ -430,9 +722,14 @@ mod tests { let note = { let rho = Rho::from_bytes(&pallas::Base::random(&mut rng).to_repr()).unwrap(); loop { - if let Some(note) = - Note::from_parts(recipient, value, rho, RandomSeed::random(&mut rng, &rho)) - .into_option() + if let Some(note) = Note::from_parts( + recipient, + value, + rho, + RandomSeed::random(&mut rng, &rho), + bundle_version.note_version(), + ) + .into_option() { break note; } @@ -464,7 +761,14 @@ mod tests { }; // Run the Creator and Constructor roles. - let mut builder = Builder::new(BundleType::DEFAULT, anchor); + let bundle_version = BundleVersion::orchard_v2(); + let mut builder = Builder::new( + BundleType::DEFAULT, + bundle_version, + bundle_version.default_flags(), + anchor, + ) + .unwrap(); builder .add_spend(fvk.clone(), note, merkle_path.into()) .unwrap(); @@ -524,7 +828,7 @@ mod tests { #[test] fn create_proof_rejects_identity_rk() { - let pk = ProvingKey::build(); + let pk = ProvingKey::build(OrchardCircuitVersion::FixedPostNu6_2); let rng = OsRng; let mut pczt_bundle = minimal_finalized_pczt_bundle(rng); @@ -538,7 +842,7 @@ mod tests { #[test] fn extract_rejects_identity_rk() { - let pk = ProvingKey::build(); + let pk = ProvingKey::build(OrchardCircuitVersion::FixedPostNu6_2); let rng = OsRng; let mut pczt_bundle = minimal_finalized_pczt_bundle(rng); @@ -557,7 +861,7 @@ mod tests { #[test] fn extract_rejects_non_canonical_proof() { - let pk = ProvingKey::build(); + let pk = ProvingKey::build(OrchardCircuitVersion::FixedPostNu6_2); let rng = OsRng; let mut pczt_bundle = minimal_finalized_pczt_bundle(rng); @@ -578,4 +882,336 @@ mod tests { Err(TxExtractorError::NonCanonicalProofSize { .. }), )); } + + #[test] + fn parse_uses_bundle_version_for_flags() { + let anchor: crate::Anchor = EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(); + + // Bit 2 is reserved pre-NU6.3, and rejected for Orchard post-NU6.3 (which mandates + // the cross-address restriction); only Ironwood may set it. + for pr in [ + BundleVersion::orchard_insecure_v1(), + BundleVersion::orchard_v2(), + BundleVersion::orchard_v3(), + ] { + assert!(matches!( + super::Bundle::parse( + vec![], + 0b0000_0100, + pr, + (0, false), + anchor.to_bytes(), + None, + None, + ), + Err(ParseError::UnexpectedFlagBitsSet), + )); + } + + let parsed = super::Bundle::parse( + vec![], + 0b0000_0100, + BundleVersion::ironwood_v3(), + (0, false), + anchor.to_bytes(), + None, + None, + ) + .unwrap(); + + assert!(parsed.flags().cross_address_enabled()); + assert_eq!( + parsed.flags().to_byte(BundleVersion::ironwood_v3()), + Some(0b0000_0100) + ); + assert_eq!( + parsed.flags().to_byte(BundleVersion::orchard_v2()), + Some(0b0000_0000) + ); + + let restricted = super::Bundle::parse( + vec![], + 0b0000_0011, + BundleVersion::orchard_v3(), + (0, false), + anchor.to_bytes(), + None, + None, + ) + .unwrap(); + + assert!(!restricted.flags().cross_address_enabled()); + assert_eq!( + restricted.flags().to_byte(BundleVersion::orchard_v3()), + Some(0b0000_0011) + ); + assert_eq!( + restricted.flags().to_byte(BundleVersion::orchard_v2()), + None + ); + } + + #[test] + fn parse_preserves_note_versions() { + let bundle_version = BundleVersion::ironwood_v3(); + let pczt_bundle = ironwood_output_pczt_bundle(OsRng); + let flags = pczt_bundle.flags.to_byte(bundle_version).unwrap(); + let anchor = pczt_bundle.anchor.to_bytes(); + let actions = pczt_bundle.actions; + + let parsed = super::Bundle::parse( + actions, + flags, + bundle_version, + (5000, true), + anchor, + None, + None, + ) + .unwrap(); + let action = &parsed.actions()[0]; + + assert_eq!(action.spend().note_version(), &NoteVersion::V3); + assert_eq!(action.output().note_version(), &NoteVersion::V3); + } + + #[test] + fn parse_rejects_output_note_version_mismatch() { + let bundle_version = BundleVersion::ironwood_v3(); + let pczt_bundle = ironwood_output_pczt_bundle(OsRng); + let flags = pczt_bundle.flags.to_byte(bundle_version).unwrap(); + let anchor = pczt_bundle.anchor.to_bytes(); + let mut actions = pczt_bundle.actions; + actions[0].output.note_version = NoteVersion::V2; + + assert!(matches!( + super::Bundle::parse( + actions, + flags, + bundle_version, + (5000, true), + anchor, + None, + None, + ), + Err(ParseError::InvalidNoteVersion) + )); + } + + #[test] + fn create_proof_supports_cross_address_disabled_only_for_post_nu6_3() { + let rng = OsRng; + let sighash = [0; 32]; + + // Structural same-expanded-receiver violations are rejected before any key-capability + // check, for every circuit version. + for circuit_version in [ + OrchardCircuitVersion::FixedPostNu6_2, + OrchardCircuitVersion::PostNu6_3, + ] { + let pk = ProvingKey::build(circuit_version); + + let mut mismatched_pczt_bundle = minimal_finalized_pczt_bundle(rng); + mismatched_pczt_bundle.flags = Flags::CROSS_ADDRESS_DISABLED; + assert!(matches!( + mismatched_pczt_bundle.create_proof(&pk, rng), + Err(ProverError::DisallowedCrossAddressTransfer(_)), + )); + } + + let (mut pczt_bundle, bundle_meta, spend_ask, change_ask) = restricted_pczt_bundle(rng); + pczt_bundle.finalize_io(sighash, rng).unwrap(); + + // A pre-NU 6.3 proving key rejects the structurally-conforming restricted + // statement at the instance check, leaving the bundle unmodified. + let pk = ProvingKey::build(OrchardCircuitVersion::FixedPostNu6_2); + assert!(matches!( + pczt_bundle.create_proof(&pk, rng), + Err(ProverError::ProofFailed( + halo2_proofs::plonk::Error::InvalidInstances + )), + )); + assert!(pczt_bundle.zkproof.is_none()); + + // A post-NU 6.3 proving key proves the same statement, and the proof verifies + // in the extracted bundle under the post-NU 6.3 verifying key. + let pk = ProvingKey::build(OrchardCircuitVersion::PostNu6_3); + pczt_bundle.create_proof(&pk, rng).unwrap(); + + pczt_bundle.actions_mut()[bundle_meta.spend_action_index(0).unwrap()] + .sign(sighash, &spend_ask, rng) + .unwrap(); + pczt_bundle.actions_mut()[bundle_meta.output_action_index(0).unwrap()] + .sign(sighash, &change_ask, rng) + .unwrap(); + + let bundle = pczt_bundle + .extract::() + .unwrap() + .unwrap() + .apply_binding_signature(sighash, rng) + .unwrap(); + bundle + .verify_proof(&VerifyingKey::build(OrchardCircuitVersion::PostNu6_3)) + .unwrap(); + } + + #[test] + fn restricted_pczt_signing_flow() { + let rng = OsRng; + let (mut pczt_bundle, bundle_meta, spend_ask, change_ask) = restricted_pczt_bundle(rng); + + let sighash = [0; 32]; + pczt_bundle.finalize_io(sighash, rng).unwrap(); + pczt_bundle.verify_cross_address_restriction().unwrap(); + + let spend_action_index = bundle_meta.spend_action_index(0).unwrap(); + let change_action_index = bundle_meta.output_action_index(0).unwrap(); + assert_ne!(spend_action_index, change_action_index); + + // The fabricated change spend is wallet-controlled: it is signed through the + // normal Signer flow, and only by the matching spend authorizing key. + assert!(matches!( + pczt_bundle.actions_mut()[change_action_index].sign(sighash, &spend_ask, rng), + Err(SignerError::WrongSpendAuthorizingKey), + )); + pczt_bundle.actions_mut()[change_action_index] + .sign(sighash, &change_ask, rng) + .unwrap(); + pczt_bundle.actions_mut()[spend_action_index] + .sign(sighash, &spend_ask, rng) + .unwrap(); + + for action in pczt_bundle.actions() { + assert!(action.spend.spend_auth_sig.is_some()); + assert!(action.spend.dummy_sk.is_none()); + } + } + + #[test] + fn restricted_pczt_io_finalizer_signs_padding_dummy() { + let mut rng = OsRng; + let spend_sk = SpendingKey::random(&mut rng); + let spend_fvk = FullViewingKey::from(&spend_sk); + let spend_recipient = spend_fvk.address_at(0u32, Scope::External); + let note_version = NoteVersion::V2; + + let rho = Rho::from_nf_old(Nullifier::dummy(&mut rng)); + let note = Note::new( + spend_recipient, + NoteValue::from_raw(15_000), + rho, + note_version, + &mut rng, + ); + let merkle_path = MerklePath::dummy(&mut rng); + let anchor = merkle_path.root(note.commitment().into()); + + let bundle_version = BundleVersion::orchard_v3(); + let mut builder = Builder::new( + BundleType::DEFAULT, + bundle_version, + bundle_version.default_flags(), + anchor, + ) + .unwrap(); + builder.add_spend(spend_fvk, note, merkle_path).unwrap(); + + let (mut pczt_bundle, bundle_meta) = builder.build_for_pczt(&mut rng).unwrap(); + assert_eq!(pczt_bundle.actions().len(), 2); + + let spend_action_index = bundle_meta.spend_action_index(0).unwrap(); + let padding_action_index = 1 - spend_action_index; + assert!(pczt_bundle.actions()[padding_action_index] + .spend + .dummy_sk + .is_some()); + + let sighash = [0; 32]; + pczt_bundle.finalize_io(sighash, rng).unwrap(); + + // The IO Finalizer signed the padding dummy spend and cleared its `dummy_sk`; + // the real spend still needs its signature. + let padding_action = &pczt_bundle.actions()[padding_action_index]; + assert!(padding_action.spend.dummy_sk.is_none()); + assert!(padding_action.spend.spend_auth_sig.is_some()); + assert!(pczt_bundle.actions()[spend_action_index] + .spend + .spend_auth_sig + .is_none()); + + pczt_bundle.actions_mut()[spend_action_index] + .sign(sighash, &SpendAuthorizingKey::from(&spend_sk), rng) + .unwrap(); + } + + #[test] + fn finalize_io_rejects_cross_address_violation() { + let mut rng = OsRng; + let (mut pczt_bundle, _, _, _) = restricted_pczt_bundle(rng); + + let spend_recipient = pczt_bundle.actions()[0].spend.recipient.unwrap(); + let other_recipient = loop { + let fvk = FullViewingKey::from(&SpendingKey::random(&mut rng)); + let recipient = fvk.address_at(0u32, Scope::External); + if !spend_recipient.same_expanded_receiver(&recipient) { + break recipient; + } + }; + pczt_bundle.actions_mut()[0].output.recipient = Some(other_recipient); + + assert!(matches!( + pczt_bundle.finalize_io([0; 32], rng), + Err(IoFinalizerError::CrossAddressRestriction( + VerifyError::DisallowedCrossAddressTransfer + )), + )); + // The failed call left the bundle unmodified. + assert!(pczt_bundle.bsk.is_none()); + } + + #[test] + fn verify_cross_address_restriction_requires_recipients() { + let mut pczt_bundle = minimal_finalized_pczt_bundle(OsRng); + pczt_bundle.flags = Flags::CROSS_ADDRESS_DISABLED; + for action in pczt_bundle.actions_mut() { + action.output.recipient = action.spend.recipient; + } + pczt_bundle.verify_cross_address_restriction().unwrap(); + + let original = pczt_bundle.actions()[0].spend.recipient; + pczt_bundle.actions_mut()[0].spend.recipient = None; + assert!(matches!( + pczt_bundle.verify_cross_address_restriction(), + Err(VerifyError::MissingRecipient), + )); + + pczt_bundle.actions_mut()[0].spend.recipient = original; + pczt_bundle.actions_mut()[0].output.recipient = None; + assert!(matches!( + pczt_bundle.verify_cross_address_restriction(), + Err(VerifyError::MissingRecipient), + )); + } + + #[test] + fn extract_preserves_cross_address_disabled() { + let rng = OsRng; + + let mut pczt_bundle = minimal_finalized_pczt_bundle(rng); + pczt_bundle.zkproof = Some(crate::Proof::new(vec![ + 0; + crate::Proof::expected_proof_size( + pczt_bundle.actions.len() + ) + ])); + // Cross-address-disabled flags are only representable from NU6.3 onward, and the Orchard + // pool at NU6.3 mandates the restriction; that is the version under which an extracted + // bundle can legitimately carry these flags. + pczt_bundle.bundle_version = BundleVersion::orchard_v3(); + pczt_bundle.flags = Flags::CROSS_ADDRESS_DISABLED; + + let bundle = pczt_bundle.extract::().unwrap().unwrap(); + assert!(!bundle.flags().cross_address_enabled()); + } } diff --git a/src/pczt/io_finalizer.rs b/src/pczt/io_finalizer.rs index 73f7d00d8..c5da81e93 100644 --- a/src/pczt/io_finalizer.rs +++ b/src/pczt/io_finalizer.rs @@ -10,15 +10,28 @@ use crate::{ value::{ValueCommitTrapdoor, ValueCommitment}, }; -use super::SignerError; +use super::{SignerError, VerifyError}; impl super::Bundle { /// Finalizes the IO for this bundle. + /// + /// If the bundle disables cross-address transfers, the structural restriction is + /// first checked with [`Bundle::verify_cross_address_restriction`] (which requires + /// the spend and output `recipient` fields to be set); on failure the bundle is + /// left unmodified. + /// + /// [`Bundle::verify_cross_address_restriction`]: super::Bundle::verify_cross_address_restriction pub fn finalize_io( &mut self, sighash: [u8; 32], mut rng: R, ) -> Result<(), IoFinalizerError> { + // A bundle that disables cross-address transfers can never be proven or mined + // if any action violates the structural restriction; fail before mutating + // anything. + self.verify_cross_address_restriction() + .map_err(IoFinalizerError::CrossAddressRestriction)?; + // Compute the transaction binding signing key. let rcvs = self .actions @@ -64,6 +77,9 @@ impl super::Bundle { #[derive(Debug)] #[non_exhaustive] pub enum IoFinalizerError { + /// The bundle does not satisfy the cross-address restriction, or is missing the + /// `recipient` fields needed to check it. + CrossAddressRestriction(VerifyError), /// An error occurred while signing a dummy spend. DummySignature(SignerError), /// The IO Finalizer role requires all `rcv` fields to be set. @@ -76,6 +92,12 @@ pub enum IoFinalizerError { impl fmt::Display for IoFinalizerError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + IoFinalizerError::CrossAddressRestriction(e) => { + write!( + f, + "The bundle does not satisfy the cross-address restriction: {e}" + ) + } IoFinalizerError::DummySignature(e) => { write!(f, "An error occurred while signing a dummy spend: {e}") } diff --git a/src/pczt/parse.rs b/src/pczt/parse.rs index e8e06c10c..ade4226b4 100644 --- a/src/pczt/parse.rs +++ b/src/pczt/parse.rs @@ -12,9 +12,11 @@ use zip32::ChildIndex; use super::{Action, Bundle, Output, Spend, Zip32Derivation}; use crate::{ - bundle::Flags, + bundle::{BundleVersion, Flags}, keys::{FullViewingKey, SpendingKey}, - note::{ExtractedNoteCommitment, Nullifier, RandomSeed, Rho, TransmittedNoteCiphertext}, + note::{ + ExtractedNoteCommitment, NoteVersion, Nullifier, RandomSeed, Rho, TransmittedNoteCiphertext, + }, primitives::redpallas::{self, SpendAuth}, tree::{MerkleHashOrchard, MerklePath}, value::{NoteValue, Sign, ValueCommitTrapdoor, ValueCommitment, ValueSum}, @@ -23,16 +25,28 @@ use crate::{ impl Bundle { /// Parses a PCZT bundle from its component parts. + /// + /// See [`BundleVersion`] for the choice of `bundle_version`. + /// /// `value_sum` is represented as `(magnitude, is_negative)`. pub fn parse( actions: Vec, flags: u8, + bundle_version: BundleVersion, value_sum: (u64, bool), anchor: [u8; 32], zkproof: Option>, bsk: Option<[u8; 32]>, ) -> Result { - let flags = Flags::from_byte(flags).ok_or(ParseError::UnexpectedFlagBitsSet)?; + let flags = + Flags::from_byte(flags, bundle_version).ok_or(ParseError::UnexpectedFlagBitsSet)?; + + let note_version = bundle_version.note_version(); + for action in actions.iter() { + if *action.output.note_version() != note_version { + return Err(ParseError::InvalidNoteVersion); + } + } let value_sum = { let (magnitude, is_negative) = value_sum; @@ -60,6 +74,7 @@ impl Bundle { Ok(Self { actions, flags, + bundle_version, value_sum, anchor, zkproof, @@ -113,6 +128,7 @@ impl Spend { alpha: Option<[u8; 32]>, zip32_derivation: Option, dummy_sk: Option<[u8; 32]>, + note_version: NoteVersion, proprietary: BTreeMap>, ) -> Result { let nullifier = Nullifier::from_bytes(&nullifier) @@ -195,6 +211,7 @@ impl Spend { value, rho, rseed, + note_version, fvk, witness, alpha, @@ -221,6 +238,7 @@ impl Output { ock: Option<[u8; 32]>, zip32_derivation: Option, user_address: Option, + note_version: NoteVersion, proprietary: BTreeMap>, ) -> Result { let cmx = ExtractedNoteCommitment::from_bytes(&cmx) @@ -263,6 +281,7 @@ impl Output { Ok(Self { cmx, + note_version, encrypted_note, recipient, value, @@ -334,8 +353,11 @@ pub enum ParseError { InvalidZip32Derivation, /// `rho` must be provided whenever `rseed` is provided. MissingRho, - /// The provided `flags` field had unexpected bits set. + /// The provided `flags` field had unexpected bits set for the bundle's pool + /// restrictions. UnexpectedFlagBitsSet, + /// An invalid `note_version` was provided. + InvalidNoteVersion, } impl fmt::Display for ParseError { @@ -362,6 +384,7 @@ impl fmt::Display for ParseError { write!(f, "`rho` must be provided whenever `rseed` is provided") } ParseError::UnexpectedFlagBitsSet => write!(f, "`flags` field had unexpected bits set"), + ParseError::InvalidNoteVersion => write!(f, "invalid `note_version`"), } } } diff --git a/src/pczt/prover.rs b/src/pczt/prover.rs index 4716bc2d3..d46b33323 100644 --- a/src/pczt/prover.rs +++ b/src/pczt/prover.rs @@ -14,6 +14,27 @@ use crate::{ impl super::Bundle { /// Adds a proof to this PCZT bundle. + /// + /// The Action circuits are built for `pk`'s circuit version; the caller selects the + /// proving key matching the transaction format the PCZT targets. If the PCZT + /// bundle disables cross-address transfers, the key must be an + /// [`OrchardCircuitVersion::PostNu6_3`] proving key. + /// + /// # Errors + /// + /// Returns [`ProverError::DisallowedCrossAddressTransfer`] if the bundle + /// disables cross-address transfers, and any action's output + /// is addressed differently than its spent note. + /// + /// Returns [`ProverError::ProofFailed`] containing + /// [`plonk::Error::InvalidInstances`] if the bundle disables + /// cross-address transfers, and `pk` is not an + /// [`OrchardCircuitVersion::PostNu6_3`] proving key. + /// + /// Also returns an error if required Prover-role fields are missing or invalid, + /// or if proof creation fails. + /// + /// [`OrchardCircuitVersion::PostNu6_3`]: crate::circuit::OrchardCircuitVersion::PostNu6_3 pub fn create_proof( &mut self, pk: &ProvingKey, @@ -26,6 +47,18 @@ impl super::Bundle { return Ok(()); } + // Check the restriction structurally before synthesizing any circuit, for a + // clear error instead of an unsatisfiable-constraint failure. + self.verify_cross_address_restriction() + .map_err(|e| match e { + super::VerifyError::MissingRecipient => ProverError::MissingRecipient, + // `e` will normally be `VerifyError::DisallowedCrossAddressTransfer`, + // but `VerifyError` is `#[non_exhaustive]`. Any other error returned + // by `verify_cross_address_restriction` would by definition disallow + // a cross-address transfer. + e => ProverError::DisallowedCrossAddressTransfer(e), + })?; + let circuits = self .actions .iter() @@ -44,6 +77,7 @@ impl super::Bundle { action.spend.value.ok_or(ProverError::MissingValue)?, action.spend.rho.ok_or(ProverError::MissingRho)?, action.spend.rseed.ok_or(ProverError::MissingRandomSeed)?, + action.spend.note_version, ) .into_option() .ok_or(ProverError::InvalidSpendNote)?; @@ -65,6 +99,7 @@ impl super::Bundle { action.output.value.ok_or(ProverError::MissingValue)?, Rho::from_nf_old(action.spend.nullifier), action.output.rseed.ok_or(ProverError::MissingRandomSeed)?, + action.output.note_version, ) .into_option() .ok_or(ProverError::InvalidOutputNote)?; @@ -78,7 +113,7 @@ impl super::Bundle { .clone() .ok_or(ProverError::MissingValueCommitTrapdoor)?; - Circuit::from_action_context(spend, output_note, alpha, rcv) + Circuit::from_action_context(spend, output_note, alpha, rcv, pk.circuit_version()) .ok_or(ProverError::RhoMismatch) }) .collect::, ProverError>>()?; @@ -93,8 +128,7 @@ impl super::Bundle { action.spend.nullifier, action.spend.rk.clone(), action.output.cmx, - self.flags.spends_enabled(), - self.flags.outputs_enabled(), + self.flags, ) .ok_or(ProverError::IdentityRk) }) @@ -113,6 +147,9 @@ impl super::Bundle { #[derive(Debug)] #[non_exhaustive] pub enum ProverError { + /// An action's output is addressed differently than its spent note, but the bundle's pool + /// restrictions disable cross-address transfers. + DisallowedCrossAddressTransfer(super::VerifyError), /// The output note's components do not produce a valid note commitment. InvalidOutputNote, /// The spent note's components do not produce a valid note commitment. @@ -147,6 +184,14 @@ pub enum ProverError { impl fmt::Display for ProverError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + ProverError::DisallowedCrossAddressTransfer(e) => match e { + super::VerifyError::DisallowedCrossAddressTransfer => write!( + f, + "an action outputs to a different expanded receiver than it spends from, but the \ + bundle disables cross-address transfers" + ), + e => write!(f, "cross-address restriction verification failed: {e}"), + }, ProverError::InvalidOutputNote => write!(f, "output note is invalid"), ProverError::InvalidSpendNote => write!(f, "spent note is invalid"), ProverError::MissingFullViewingKey => { @@ -169,6 +214,14 @@ impl fmt::Display for ProverError { write!(f, "`rcv` must be set for the Prover role") } ProverError::MissingWitness => write!(f, "`witness` must be set for the Prover role"), + ProverError::ProofFailed(halo2_proofs::plonk::Error::InvalidInstances) => { + write!( + f, + "Failed to create proof: provided instances do not match the circuit, or \ + the cross-address restriction is not supported by the proving key's \ + circuit version", + ) + } ProverError::ProofFailed(e) => write!(f, "Failed to create proof: {e}"), ProverError::RhoMismatch => { write!(f, "output's `rho` does not match spent note's nullifier") diff --git a/src/pczt/signer.rs b/src/pczt/signer.rs index f31977710..d29ac9856 100644 --- a/src/pczt/signer.rs +++ b/src/pczt/signer.rs @@ -11,8 +11,10 @@ impl super::Action { /// Signs the Orchard spend with the given spend authorizing key. /// /// It is the caller's responsibility to perform any semantic validity checks on the - /// PCZT (for example, comfirming that the change amounts are correct) before calling - /// this method. + /// PCZT (for example, confirming that the change amounts are correct, and calling + /// [`Bundle::verify_cross_address_restriction`]) before applying signatures. + /// + /// [`Bundle::verify_cross_address_restriction`]: super::Bundle::verify_cross_address_restriction pub fn sign( &mut self, sighash: [u8; 32], @@ -38,8 +40,10 @@ impl super::Action { /// Applies the given signature to the Orchard spend, if valid. /// /// It is the caller's responsibility to perform any semantic validity checks on the - /// PCZT (for example, comfirming that the change amounts are correct) before calling - /// this method. + /// PCZT (for example, confirming that the change amounts are correct, and calling + /// [`Bundle::verify_cross_address_restriction`]) before applying signatures. + /// + /// [`Bundle::verify_cross_address_restriction`]: super::Bundle::verify_cross_address_restriction pub fn apply_signature( &mut self, sighash: [u8; 32], diff --git a/src/pczt/tx_extractor.rs b/src/pczt/tx_extractor.rs index 44913f74e..e71e5e37b 100644 --- a/src/pczt/tx_extractor.rs +++ b/src/pczt/tx_extractor.rs @@ -52,9 +52,11 @@ impl super::Bundle { }, )?; - // The proof comes straight from the (untrusted) PCZT, so reject it here if it is not - // the canonical size. This makes "an `Authorized` bundle always has a canonical proof" - // hold across the `Unbound` -> `Authorized` transition in `apply_binding_signature`. + // The proof comes straight from the (untrusted) PCZT, so reject + // non-canonical proof lengths here. This makes "an `Authorized` bundle + // always has a canonical proof" hold across the `Unbound` -> `Authorized` + // transition in `apply_binding_signature`. Circuit-key support for bundle + // flags is checked when proving or verifying. if let Some(bundle) = &bundle { crate::bundle::validate_proof_size( &bundle.authorization().proof, @@ -110,6 +112,7 @@ impl super::Bundle { value_balance, self.anchor, authorization, + self.bundle_version, )) } else { None @@ -141,6 +144,8 @@ pub enum TxExtractorError { /// The length of the proof that was provided. actual: usize, }, + /// The bundle's flags cannot be encoded under its value pool and protocol version. + UnrepresentableFlags, } impl From for TxExtractorError { @@ -158,6 +163,9 @@ impl From for TxExtractorError { crate::bundle::BundleError::NonCanonicalProofSize { expected, actual } => { TxExtractorError::NonCanonicalProofSize { expected, actual } } + crate::bundle::BundleError::UnrepresentableFlags => { + TxExtractorError::UnrepresentableFlags + } } } } @@ -190,6 +198,10 @@ impl fmt::Display for TxExtractorError { f, "Orchard `zkproof` has non-canonical length {actual}; expected {expected} bytes", ), + TxExtractorError::UnrepresentableFlags => write!( + f, + "Orchard bundle flags are not representable under its value pool and protocol version", + ), } } } diff --git a/src/pczt/verify.rs b/src/pczt/verify.rs index 49249a448..84075a0c7 100644 --- a/src/pczt/verify.rs +++ b/src/pczt/verify.rs @@ -7,6 +7,44 @@ use crate::{ Note, }; +impl super::Bundle { + /// If this bundle disables cross-address transfers, verifies that every action's + /// output is addressed to the same expanded receiver (`(g_d, pk_d)`) as its spent + /// note. This is a no-op for bundles that permit cross-address transfers. + /// + /// When the restriction applies, it requires `spend.recipient` and `output.recipient` + /// to be set on every action. Signers should always call this before signing. The + /// equivalent structural checks are also performed by [`Bundle::finalize_io`] and + /// `Bundle::create_proof`. + /// + /// The post-NU6.3 circuit supports enforcing the restriction; older circuit versions + /// do not. The prover and verifier APIs reject restricted bundles for those keys. + /// (That is not a security restriction; for security, the consensus verifier must use + /// the correct key for the epoch and pool.) + /// + /// [`Bundle::finalize_io`]: super::Bundle::finalize_io + pub fn verify_cross_address_restriction(&self) -> Result<(), VerifyError> { + if !self.flags.cross_address_enabled() { + for action in &self.actions { + let spend_recipient = action + .spend + .recipient + .ok_or(VerifyError::MissingRecipient)?; + let output_recipient = action + .output + .recipient + .ok_or(VerifyError::MissingRecipient)?; + + if !spend_recipient.same_expanded_receiver(&output_recipient) { + return Err(VerifyError::DisallowedCrossAddressTransfer); + } + } + } + + Ok(()) + } +} + impl super::Action { /// Verifies that the `cv_net` field is consistent with the note fields. /// @@ -73,6 +111,7 @@ impl super::Spend { self.value.ok_or(VerifyError::MissingValue)?, self.rho.ok_or(VerifyError::MissingRho)?, self.rseed.ok_or(VerifyError::MissingRandomSeed)?, + self.note_version, ) .into_option() .ok_or(VerifyError::InvalidSpendNote)?; @@ -131,6 +170,7 @@ impl super::Output { self.value.ok_or(VerifyError::MissingValue)?, Rho::from_nf_old(spend.nullifier), self.rseed.ok_or(VerifyError::MissingRandomSeed)?, + self.note_version, ) .into_option() .ok_or(VerifyError::InvalidOutputNote)?; @@ -147,6 +187,9 @@ impl super::Output { #[derive(Debug)] #[non_exhaustive] pub enum VerifyError { + /// An action's output is addressed differently than its spent note, but the bundle's pool + /// restriction disables cross-address transfers. + DisallowedCrossAddressTransfer, /// The output note's components do not produce the expected `cmx`. InvalidExtractedNoteCommitment, /// The spent note's components do not produce the expected `nullifier`. @@ -165,7 +208,7 @@ pub enum VerifyError { MissingFullViewingKey, /// `nullifier` verification requires `rseed` to be set. MissingRandomSeed, - /// `nullifier` verification requires `recipient` to be set. + /// Verification requires `recipient` to be set. MissingRecipient, /// `nullifier` verification requires `rho` to be set. MissingRho, @@ -182,6 +225,11 @@ pub enum VerifyError { impl fmt::Display for VerifyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + VerifyError::DisallowedCrossAddressTransfer => write!( + f, + "an action outputs to a different expanded receiver than it spends from, but the \ + bundle disables cross-address transfers" + ), VerifyError::InvalidExtractedNoteCommitment => { write!(f, "output note doesn't match `cmx`") } @@ -201,9 +249,7 @@ impl fmt::Display for VerifyError { VerifyError::MissingRandomSeed => { write!(f, "`rseed` missing for `nullifier` verification") } - VerifyError::MissingRecipient => { - write!(f, "`recipient` missing for `nullifier` verification") - } + VerifyError::MissingRecipient => write!(f, "`recipient` missing for verification"), VerifyError::MissingRho => write!(f, "`rho` missing for `nullifier` verification"), VerifyError::MissingSpendAuthRandomizer => { write!(f, "`alpha` missing for `rk` verification") diff --git a/src/test_vectors/keys.rs b/src/test_vectors/keys.rs index 5551a268d..636d8c601 100644 --- a/src/test_vectors/keys.rs +++ b/src/test_vectors/keys.rs @@ -21,11 +21,13 @@ pub(crate) struct TestVector { pub(crate) note_rho: [u8; 32], pub(crate) note_rseed: [u8; 32], pub(crate) note_cmx: [u8; 32], + pub(crate) note_qr_rcm: [u8; 32], + pub(crate) note_qr_cmx: [u8; 32], pub(crate) note_nf: [u8; 32], } pub(crate) fn test_vectors() -> Vec { - // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_key_components.py + // From https://github.com/zcash/zcash-test-vectors/blob/master/zcash_test_vectors/orchard/key_components.py vec![ TestVector { sk: [ @@ -112,6 +114,16 @@ pub(crate) fn test_vectors() -> Vec { 0x03, 0x7e, 0x0e, 0xcf, 0x68, 0x13, 0xb5, 0x1c, 0x81, 0xfe, 0x08, 0x5a, 0x7b, 0x78, 0x2f, 0x12, 0x42, 0x28, ], + note_qr_rcm: [ + 0x81, 0x66, 0xc5, 0xc7, 0x91, 0x23, 0xe9, 0xe2, 0x74, 0x8a, 0x1a, 0x2a, 0xaf, 0x76, + 0x24, 0xb9, 0xc0, 0xb6, 0x8d, 0x5d, 0x4a, 0xe1, 0x05, 0x5a, 0x56, 0x61, 0x25, 0x04, + 0x67, 0xde, 0xba, 0x31, + ], + note_qr_cmx: [ + 0x79, 0x5f, 0x62, 0xd8, 0x2b, 0xf3, 0x47, 0xbb, 0x98, 0xda, 0x93, 0x85, 0xdc, 0x90, + 0x34, 0x30, 0x53, 0x83, 0xfc, 0x01, 0x5a, 0x9a, 0xaa, 0xb1, 0x04, 0x6a, 0x91, 0xc8, + 0x7d, 0xda, 0x30, 0x14, + ], note_nf: [ 0x1b, 0x32, 0xed, 0xbb, 0xe4, 0xd1, 0x8f, 0x28, 0x87, 0x6d, 0xe2, 0x62, 0x51, 0x8a, 0xd3, 0x11, 0x22, 0x70, 0x1f, 0x8c, 0x0a, 0x52, 0xe9, 0x80, 0x47, 0xa3, 0x37, 0x87, @@ -203,6 +215,16 @@ pub(crate) fn test_vectors() -> Vec { 0x84, 0x69, 0x28, 0x48, 0xdc, 0xe2, 0x9b, 0xa4, 0xfe, 0xbd, 0x93, 0x20, 0x2b, 0x73, 0x05, 0xf9, 0x03, 0x00, ], + note_qr_rcm: [ + 0x63, 0xf7, 0x0e, 0x9f, 0x05, 0x6f, 0x2d, 0x7c, 0x86, 0x8c, 0x35, 0x22, 0x47, 0x25, + 0x83, 0xc1, 0x38, 0xe4, 0x30, 0xbc, 0x0a, 0x79, 0x98, 0x04, 0xb2, 0x23, 0x0f, 0x6e, + 0x58, 0x7d, 0x64, 0x2b, + ], + note_qr_cmx: [ + 0x47, 0xcc, 0x47, 0x49, 0xee, 0x0a, 0x1a, 0xbd, 0xe9, 0x9e, 0x9e, 0xcd, 0x09, 0x0d, + 0x90, 0x3d, 0x50, 0x61, 0xcb, 0xc0, 0x75, 0x01, 0xf3, 0xff, 0x25, 0x02, 0x71, 0x9d, + 0xb3, 0x0c, 0x15, 0x0b, + ], note_nf: [ 0x2c, 0xf0, 0x67, 0xbc, 0x21, 0xd6, 0x63, 0x20, 0xe5, 0x1b, 0x9f, 0xbd, 0xc8, 0xae, 0x03, 0x1c, 0x2c, 0x96, 0x37, 0x3d, 0xb4, 0x3b, 0x7b, 0x1a, 0x45, 0x05, 0x6c, 0x00, @@ -294,6 +316,16 @@ pub(crate) fn test_vectors() -> Vec { 0xa3, 0xc0, 0x53, 0xc3, 0x72, 0x0a, 0xd4, 0x9f, 0x40, 0xd2, 0x7c, 0x2d, 0xcc, 0xe3, 0x35, 0x00, 0x56, 0x16, ], + note_qr_rcm: [ + 0xcf, 0x37, 0xbe, 0xf8, 0x64, 0xfe, 0xff, 0x29, 0xc6, 0x4f, 0x57, 0xab, 0x3f, 0xf7, + 0x5a, 0x3c, 0x0a, 0x46, 0x05, 0x7d, 0x40, 0x51, 0x13, 0x28, 0xb2, 0x37, 0x27, 0x43, + 0x95, 0xd8, 0x02, 0x3e, + ], + note_qr_cmx: [ + 0x9c, 0xbb, 0x9f, 0x17, 0x43, 0xe7, 0x91, 0x15, 0x73, 0xce, 0xf4, 0x7a, 0x49, 0x74, + 0x7e, 0x34, 0x77, 0xde, 0xcd, 0x2e, 0x6b, 0xe7, 0xe3, 0xd5, 0xbd, 0x45, 0xf1, 0xf3, + 0x60, 0x7b, 0x0c, 0x25, + ], note_nf: [ 0x16, 0xfa, 0x2c, 0x34, 0x97, 0xfc, 0x09, 0xad, 0x90, 0xdd, 0x34, 0x92, 0x02, 0xa2, 0x4b, 0x69, 0x89, 0x2d, 0xc8, 0x06, 0x29, 0xb2, 0xd1, 0xbf, 0xeb, 0xaf, 0x41, 0x70, @@ -385,6 +417,16 @@ pub(crate) fn test_vectors() -> Vec { 0x3f, 0x8e, 0xda, 0x13, 0x13, 0xc3, 0x0a, 0xa2, 0x7d, 0xe9, 0x2e, 0x21, 0xa1, 0x08, 0x31, 0x6e, 0x82, 0x19, ], + note_qr_rcm: [ + 0x57, 0x38, 0xad, 0xf2, 0x3e, 0x7d, 0xce, 0x89, 0xa1, 0x90, 0xf9, 0x2f, 0xe4, 0x10, + 0xf5, 0xb6, 0x4f, 0x4c, 0x67, 0xb3, 0x0d, 0xd0, 0xb2, 0x34, 0x1d, 0xe7, 0xe8, 0x8d, + 0xda, 0x84, 0x3d, 0x2f, + ], + note_qr_cmx: [ + 0x0c, 0x65, 0xbc, 0x24, 0xfd, 0x81, 0xaa, 0xd3, 0x91, 0x05, 0x78, 0xbc, 0xfb, 0x27, + 0x19, 0x66, 0xf8, 0x26, 0xe4, 0x07, 0x5e, 0x7e, 0xac, 0x57, 0x6e, 0xdc, 0x08, 0x4e, + 0x36, 0xbd, 0x96, 0x39, + ], note_nf: [ 0x72, 0xd6, 0x30, 0x89, 0x60, 0x35, 0x1f, 0x7b, 0x26, 0xfa, 0x64, 0x60, 0x3f, 0xe4, 0xdf, 0xd8, 0x67, 0xbd, 0x5e, 0xb3, 0x67, 0xba, 0x2b, 0x7c, 0xa4, 0x91, 0xc9, 0x23, @@ -476,6 +518,16 @@ pub(crate) fn test_vectors() -> Vec { 0x37, 0xf2, 0xc1, 0x18, 0xd5, 0x21, 0x25, 0x62, 0x8d, 0x8a, 0x3f, 0x41, 0x2c, 0xe0, 0xe6, 0x53, 0x0e, 0x04, ], + note_qr_rcm: [ + 0x52, 0x99, 0xeb, 0x37, 0x8a, 0x30, 0xe7, 0x80, 0x99, 0x22, 0x53, 0x5c, 0xb9, 0x4e, + 0x7e, 0x2e, 0x46, 0xed, 0xf9, 0xc5, 0xab, 0xbf, 0x14, 0xfc, 0x3e, 0x0c, 0x00, 0x71, + 0x4f, 0x2e, 0xe3, 0x3f, + ], + note_qr_cmx: [ + 0x45, 0xca, 0x35, 0xfa, 0x3a, 0xce, 0x67, 0xcb, 0xb9, 0xec, 0xef, 0x31, 0xdb, 0x11, + 0xad, 0xb9, 0x82, 0x45, 0x31, 0x87, 0x95, 0x83, 0x92, 0x45, 0xc6, 0xe1, 0x40, 0x7f, + 0x1a, 0x8c, 0x82, 0x1b, + ], note_nf: [ 0xe6, 0x2b, 0x8e, 0xd8, 0x35, 0x40, 0x14, 0x6c, 0xd2, 0x3c, 0xac, 0x74, 0xee, 0xd7, 0xd7, 0x73, 0xd8, 0x02, 0x24, 0xa5, 0xaa, 0x30, 0xd6, 0x8e, 0x35, 0x57, 0x2e, 0xe8, @@ -567,6 +619,16 @@ pub(crate) fn test_vectors() -> Vec { 0x74, 0xf8, 0x5e, 0xa4, 0x8b, 0xa0, 0x7a, 0x4f, 0x92, 0xcc, 0xbd, 0x34, 0xfa, 0xa4, 0x2d, 0xfd, 0x49, 0x16, ], + note_qr_rcm: [ + 0xe3, 0xe5, 0x0e, 0x69, 0x35, 0x6d, 0x16, 0xdc, 0xcb, 0xaa, 0xea, 0x40, 0xa5, 0x5b, + 0x62, 0x51, 0x9b, 0xd5, 0x46, 0x67, 0x92, 0xe7, 0x74, 0x03, 0x11, 0xa7, 0xd6, 0xe4, + 0xef, 0x30, 0xc2, 0x31, + ], + note_qr_cmx: [ + 0x7c, 0x45, 0x8b, 0xaf, 0xa4, 0xf6, 0x57, 0xb0, 0x85, 0x23, 0x2a, 0x22, 0xef, 0x5e, + 0xc1, 0x13, 0xeb, 0x47, 0xca, 0x47, 0x2f, 0xac, 0x78, 0x61, 0x45, 0x6c, 0x77, 0x1b, + 0x8b, 0xc6, 0x01, 0x10, + ], note_nf: [ 0x4c, 0x99, 0xbf, 0xa8, 0xc2, 0x0d, 0xba, 0x59, 0xbb, 0x73, 0x47, 0xda, 0x16, 0xc4, 0x3b, 0x73, 0xc8, 0x87, 0x94, 0xc9, 0xeb, 0xcd, 0x0d, 0xd2, 0xb2, 0x5e, 0xe7, 0xbb, @@ -658,6 +720,16 @@ pub(crate) fn test_vectors() -> Vec { 0x0a, 0x90, 0x8d, 0xe7, 0xf0, 0x76, 0xec, 0xf8, 0x7f, 0x54, 0x1e, 0x0b, 0x7b, 0x48, 0xad, 0x4a, 0x26, 0x01, ], + note_qr_rcm: [ + 0xd7, 0xe1, 0xc3, 0x16, 0xf2, 0x04, 0x7e, 0x9b, 0x3a, 0x0c, 0x2f, 0xca, 0x8a, 0xfa, + 0x93, 0x87, 0xe5, 0x51, 0x07, 0x97, 0xfe, 0xae, 0x1f, 0xb1, 0xa2, 0x5e, 0xe5, 0x5d, + 0x14, 0x14, 0x06, 0x17, + ], + note_qr_cmx: [ + 0xc4, 0x32, 0xa3, 0xcc, 0x62, 0xc3, 0xf0, 0x54, 0x72, 0x6e, 0x3b, 0x5a, 0x71, 0x5b, + 0x28, 0x44, 0x02, 0x8f, 0xa4, 0x25, 0x03, 0xad, 0x14, 0x69, 0x5d, 0xa2, 0xa5, 0xfd, + 0xe6, 0xb3, 0xde, 0x02, + ], note_nf: [ 0x3b, 0x94, 0x8d, 0xb2, 0x16, 0x08, 0xe9, 0xac, 0xb2, 0x2a, 0x54, 0x17, 0xb9, 0x8c, 0x0d, 0xed, 0xd5, 0x27, 0xa9, 0x64, 0x87, 0x81, 0x4e, 0x64, 0x20, 0xcb, 0xff, 0x6e, @@ -749,6 +821,16 @@ pub(crate) fn test_vectors() -> Vec { 0x37, 0x08, 0x15, 0xa9, 0xd0, 0x37, 0x97, 0x3d, 0x85, 0xca, 0xc7, 0xea, 0x38, 0xb5, 0xa7, 0x16, 0xfa, 0x3b, ], + note_qr_rcm: [ + 0xb9, 0x28, 0xf8, 0x2f, 0x42, 0x6e, 0xcf, 0x4a, 0xb7, 0x22, 0x8d, 0x3a, 0x1d, 0x5d, + 0x26, 0x43, 0x27, 0x87, 0x03, 0x28, 0xc1, 0xe2, 0x64, 0x83, 0x04, 0x97, 0x9f, 0x91, + 0xea, 0xf3, 0xe4, 0x38, + ], + note_qr_cmx: [ + 0xa1, 0x42, 0xa5, 0xc4, 0x54, 0x68, 0x23, 0x7f, 0x61, 0x68, 0xa0, 0xc3, 0xd9, 0x50, + 0xe7, 0xd0, 0x3a, 0xf7, 0x0b, 0xac, 0x06, 0x2a, 0x82, 0x24, 0xae, 0xbf, 0x8c, 0xd7, + 0xa8, 0xe9, 0x6d, 0x1b, + ], note_nf: [ 0xac, 0xc2, 0xed, 0x2c, 0x7e, 0x3b, 0x19, 0x7e, 0x5c, 0xdb, 0x4a, 0x57, 0x63, 0x57, 0xd5, 0xf1, 0x35, 0x39, 0x16, 0x26, 0xc7, 0xa8, 0x25, 0xd1, 0x0a, 0xa2, 0x60, 0xae, @@ -840,6 +922,16 @@ pub(crate) fn test_vectors() -> Vec { 0xe4, 0x94, 0xea, 0x07, 0x2a, 0x2b, 0x86, 0x7b, 0x5f, 0x69, 0x43, 0x40, 0xc9, 0x6f, 0xc3, 0x70, 0xa9, 0x10, ], + note_qr_rcm: [ + 0x35, 0x80, 0xd9, 0x6f, 0x5b, 0xde, 0xe8, 0xca, 0x1c, 0x29, 0x6a, 0x45, 0x1f, 0x5d, + 0xbc, 0xfc, 0xb7, 0x51, 0x39, 0x2c, 0x7f, 0x56, 0x8d, 0x87, 0x47, 0x41, 0xe6, 0x13, + 0xd9, 0xba, 0xff, 0x01, + ], + note_qr_cmx: [ + 0x71, 0x8c, 0x60, 0xb6, 0xfd, 0xbc, 0x65, 0xeb, 0x1f, 0x66, 0x67, 0xc7, 0x22, 0x4f, + 0x0d, 0xef, 0x08, 0x9e, 0x03, 0xd7, 0x25, 0xbf, 0x7b, 0xb3, 0x66, 0x3a, 0x25, 0xf5, + 0xe5, 0xbe, 0xc9, 0x0d, + ], note_nf: [ 0xb0, 0xf1, 0x60, 0x2a, 0x2b, 0x1a, 0xf2, 0xfc, 0x55, 0xf1, 0x59, 0x50, 0xa6, 0x83, 0x83, 0x85, 0xe5, 0xe3, 0x9f, 0xec, 0xfd, 0x05, 0xcc, 0xec, 0x79, 0x9b, 0x75, 0xc6, @@ -931,6 +1023,16 @@ pub(crate) fn test_vectors() -> Vec { 0x8e, 0xed, 0x65, 0xc8, 0x8e, 0x67, 0x55, 0xda, 0xf1, 0x14, 0xd5, 0x54, 0xaf, 0x19, 0x67, 0xa7, 0xf4, 0x0a, ], + note_qr_rcm: [ + 0x51, 0x81, 0x52, 0x79, 0x92, 0x30, 0xb7, 0xbd, 0x9d, 0xbd, 0xe1, 0x0a, 0x86, 0x27, + 0xe8, 0x92, 0x89, 0x7a, 0x5a, 0x1f, 0xfb, 0x0b, 0x9e, 0x2b, 0xb0, 0xa0, 0xd5, 0x1b, + 0xc6, 0xec, 0x1e, 0x02, + ], + note_qr_cmx: [ + 0xa7, 0xb3, 0xeb, 0x43, 0x5c, 0x3c, 0x5c, 0x6e, 0xae, 0x4f, 0xb4, 0x5b, 0xaf, 0x52, + 0x0f, 0x79, 0x0e, 0xb3, 0x81, 0x64, 0x6d, 0x40, 0xce, 0x22, 0xf9, 0x66, 0xc9, 0x49, + 0xe8, 0xdb, 0x51, 0x28, + ], note_nf: [ 0x95, 0x64, 0x97, 0x28, 0x46, 0x5e, 0x68, 0x2a, 0xc0, 0x57, 0xad, 0x87, 0x62, 0x94, 0xd7, 0x00, 0xc2, 0x7f, 0xeb, 0xa2, 0xf7, 0x50, 0x92, 0x2f, 0x95, 0x51, 0x85, 0x70, diff --git a/src/tree.rs b/src/tree.rs index 6037596f5..da9c70388 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -62,8 +62,9 @@ impl Anchor { /// The anchor of the empty Orchard note commitment tree. /// /// This anchor does not correspond to any valid anchor for a spend, so it - /// may only be used for coinbase bundles or in circumstances where Orchard - /// functionality is not active. + /// may only be used for bundles without real spends — e.g. coinbase bundles, + /// where the pool's consensus rules permit them — or in circumstances where + /// Orchard functionality is not active. pub fn empty_tree() -> Anchor { Anchor(MerkleHashOrchard::empty_root(Level::from(MERKLE_DEPTH_ORCHARD as u8)).0) } diff --git a/tests/builder.rs b/tests/builder.rs index d4b9abe13..e19f2b511 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -3,22 +3,49 @@ use incrementalmerkletree::{Hashable, Marking, Retention}; use orchard::{ builder::{Builder, BundleType}, - bundle::{Authorized, Flags}, + bundle::{Authorized, BatchValidator, BundleVersion, Flags, TxVersion}, circuit::{OrchardCircuitVersion, ProvingKey, VerifyingKey}, keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, - note::ExtractedNoteCommitment, - note_encryption::OrchardDomain, - tree::MerkleHashOrchard, + note::{ExtractedNoteCommitment, NoteVersion}, + note_encryption::{IronwoodDomain, OrchardDomain}, + tree::{MerkleHashOrchard, MerklePath}, value::NoteValue, - Bundle, + Address, Bundle, }; use rand::rngs::OsRng; use shardtree::{store::memory::MemoryShardStore, ShardTree}; use zcash_note_encryption::try_note_decryption; -fn verify_bundle(bundle: &Bundle, vk: &VerifyingKey) { +/// Builds a single-leaf note commitment tree containing `cmx`, returning the tree +/// root and a witness for the leaf. +fn single_leaf_witness(cmx: &ExtractedNoteCommitment) -> (MerkleHashOrchard, MerklePath) { + let leaf = MerkleHashOrchard::from_cmx(cmx); + let mut tree: ShardTree, 32, 16> = + ShardTree::new(MemoryShardStore::empty(), 100); + tree.append( + leaf, + Retention::Checkpoint { + id: 0, + marking: Marking::Marked, + }, + ) + .unwrap(); + let root = tree.root_at_checkpoint_id(&0).unwrap().unwrap(); + let position = tree.max_leaf_position(None).unwrap().unwrap(); + let merkle_path = tree + .witness_at_checkpoint_id(position, &0) + .unwrap() + .unwrap(); + assert_eq!(root, merkle_path.root(leaf)); + (root, merkle_path.into()) +} + +fn verify_bundle(bundle: &Bundle, vk: &VerifyingKey, tx_version: TxVersion) { assert!(matches!(bundle.verify_proof(vk), Ok(()))); - let sighash: [u8; 32] = bundle.commitment().into(); + let sighash: [u8; 32] = bundle + .commitment(tx_version) + .expect("bundle flags are representable in this format") + .into(); let bvk = bundle.binding_validating_key(); for action in bundle.actions() { assert_eq!(action.rk().verify(&sighash, action.authorization()), Ok(())); @@ -29,11 +56,34 @@ fn verify_bundle(bundle: &Bundle, vk: &VerifyingKey) { ); } +/// The flags used by the output-only (shielding and coinbase) steps of these tests: spends +/// disabled, outputs enabled, cross-address transfers enabled. Every output-only bundle here +/// targets a pool that permits cross-address transfers (Orchard pre-NU6.3 and Ironwood). +const SHIELDING_FLAGS: Flags = Flags::SPENDS_DISABLED; + +/// Creates a builder of the given `bundle_version` and `bundle_type` over the +/// empty-tree anchor, with a single 5000-zat output to `recipient`. The builder disables +/// spends, since these helpers build output-only (shielding or coinbase) bundles. +fn output_only_builder( + bundle_version: BundleVersion, + bundle_type: BundleType, + recipient: Address, +) -> Builder { + let anchor = MerkleHashOrchard::empty_root(32.into()).into(); + let mut builder = Builder::new(bundle_type, bundle_version, SHIELDING_FLAGS, anchor) + .expect("shielding flags are valid for the bundle version"); + assert_eq!( + builder.add_output(None, recipient, NoteValue::from_raw(5000), [0u8; 512]), + Ok(()) + ); + builder +} + #[test] fn bundle_chain() { let mut rng = OsRng; - let pk = ProvingKey::build(); - let vk = VerifyingKey::build(); + let pk = ProvingKey::build(OrchardCircuitVersion::FixedPostNu6_2); + let vk = VerifyingKey::build(OrchardCircuitVersion::FixedPostNu6_2); let sk = SpendingKey::from_bytes([0; 32]).unwrap(); let fvk = FullViewingKey::from(&sk); @@ -41,21 +91,8 @@ fn bundle_chain() { // Create a shielding bundle. let shielding_bundle: Bundle<_, i64> = { - // Use the empty tree. - let anchor = MerkleHashOrchard::empty_root(32.into()).into(); - - let mut builder = Builder::new( - BundleType::Transactional { - flags: Flags::SPENDS_DISABLED, - bundle_required: false, - }, - anchor, - ); - let note_value = NoteValue::from_raw(5000); - assert_eq!( - builder.add_output(None, recipient, note_value, [0u8; 512]), - Ok(()) - ); + let builder = + output_only_builder(BundleVersion::orchard_v2(), BundleType::DEFAULT, recipient); let (unauthorized, bundle_meta) = builder.build(&mut rng).unwrap().unwrap(); assert_eq!( @@ -67,16 +104,19 @@ fn bundle_chain() { &fvk.to_ivk(Scope::External) ) .map(|(note, _, _)| note.value()), - Some(note_value) + Some(NoteValue::from_raw(5000)) ); - let sighash = unauthorized.commitment().into(); + let sighash = unauthorized + .commitment(TxVersion::V5) + .expect("bundle flags are representable in this format") + .into(); let proven = unauthorized.create_proof(&pk, &mut rng).unwrap(); proven.apply_signatures(rng, sighash, &[]).unwrap() }; // Verify the shielding bundle. - verify_bundle(&shielding_bundle, &vk); + verify_bundle(&shielding_bundle, &vk, TxVersion::V5); // Create a shielded bundle spending the previous output. let shielded_bundle: Bundle<_, i64> = { @@ -92,33 +132,25 @@ fn bundle_chain() { // Use the tree with a single leaf. let cmx: ExtractedNoteCommitment = note.commitment().into(); - let leaf = MerkleHashOrchard::from_cmx(&cmx); - let mut tree: ShardTree, 32, 16> = - ShardTree::new(MemoryShardStore::empty(), 100); - tree.append( - leaf, - Retention::Checkpoint { - id: 0, - marking: Marking::Marked, - }, + let (root, merkle_path) = single_leaf_witness(&cmx); + + let mut builder = Builder::new( + BundleType::DEFAULT, + BundleVersion::orchard_v2(), + BundleVersion::orchard_v2().default_flags(), + root.into(), ) .unwrap(); - let root = tree.root_at_checkpoint_id(&0).unwrap().unwrap(); - let position = tree.max_leaf_position(None).unwrap().unwrap(); - let merkle_path = tree - .witness_at_checkpoint_id(position, &0) - .unwrap() - .unwrap(); - assert_eq!(root, merkle_path.root(MerkleHashOrchard::from_cmx(&cmx))); - - let mut builder = Builder::new(BundleType::DEFAULT, root.into()); - assert_eq!(builder.add_spend(fvk, note, merkle_path.into()), Ok(())); + assert_eq!(builder.add_spend(fvk, note, merkle_path), Ok(())); assert_eq!( builder.add_output(None, recipient, NoteValue::from_raw(5000), [0u8; 512]), Ok(()) ); let (unauthorized, _) = builder.build(&mut rng).unwrap().unwrap(); - let sighash = unauthorized.commitment().into(); + let sighash = unauthorized + .commitment(TxVersion::V5) + .expect("bundle flags are representable in this format") + .into(); let proven = unauthorized.create_proof(&pk, &mut rng).unwrap(); proven .apply_signatures(rng, sighash, &[SpendAuthorizingKey::from(&sk)]) @@ -126,7 +158,7 @@ fn bundle_chain() { }; // Verify the shielded bundle. - verify_bundle(&shielded_bundle, &vk); + verify_bundle(&shielded_bundle, &vk, TxVersion::V5); } // A bundle built with the circuit version set to `InsecurePreNu6_2` produces a proof against @@ -135,33 +167,398 @@ fn bundle_chain() { #[test] fn builder_builds_for_insecure_circuit_version() { let mut rng = OsRng; - let insecure_pk = ProvingKey::build_for_version(OrchardCircuitVersion::InsecurePreNu6_2); - let insecure_vk = VerifyingKey::build_for_version(OrchardCircuitVersion::InsecurePreNu6_2); - let fixed_vk = VerifyingKey::build(); + let insecure_pk = ProvingKey::build(OrchardCircuitVersion::InsecurePreNu6_2); + let insecure_vk = VerifyingKey::build(OrchardCircuitVersion::InsecurePreNu6_2); + let fixed_vk = VerifyingKey::build(OrchardCircuitVersion::FixedPostNu6_2); let sk = SpendingKey::from_bytes([0; 32]).unwrap(); let fvk = FullViewingKey::from(&sk); let recipient = fvk.address_at(0u32, Scope::External); - let anchor = MerkleHashOrchard::empty_root(32.into()).into(); - let mut builder = Builder::new_for_version( - BundleType::Transactional { - flags: Flags::SPENDS_DISABLED, - bundle_required: false, - }, - anchor, - OrchardCircuitVersion::InsecurePreNu6_2, - ); - assert_eq!( - builder.add_output(None, recipient, NoteValue::from_raw(5000), [0u8; 512]), - Ok(()) + let builder = output_only_builder( + BundleVersion::orchard_insecure_v1(), + BundleType::DEFAULT, + recipient, ); let (unauthorized, _) = builder.build::(&mut rng).unwrap().unwrap(); - let sighash: [u8; 32] = unauthorized.commitment().into(); + let sighash: [u8; 32] = unauthorized + .commitment(TxVersion::V5) + .expect("bundle flags are representable in this format") + .into(); let proven = unauthorized.create_proof(&insecure_pk, &mut rng).unwrap(); let bundle = proven.apply_signatures(rng, sighash, &[]).unwrap(); assert!(matches!(bundle.verify_proof(&insecure_vk), Ok(()))); assert!(bundle.verify_proof(&fixed_vk).is_err()); } + +#[test] +fn builder_builds_for_post_nu6_3_circuit_version() { + let mut rng = OsRng; + let post_nu6_3_pk = ProvingKey::build(OrchardCircuitVersion::PostNu6_3); + let post_nu6_3_vk = VerifyingKey::build(OrchardCircuitVersion::PostNu6_3); + + let sk = SpendingKey::from_bytes([0; 32]).unwrap(); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + + let builder = output_only_builder(BundleVersion::ironwood_v3(), BundleType::DEFAULT, recipient); + + let (unauthorized, _) = builder.build::(&mut rng).unwrap().unwrap(); + assert_eq!( + unauthorized.circuit_version(), + OrchardCircuitVersion::PostNu6_3 + ); + + let sighash: [u8; 32] = unauthorized + .commitment(TxVersion::V6) + .expect("bundle flags are representable in this format") + .into(); + let proven = unauthorized.create_proof(&post_nu6_3_pk, &mut rng).unwrap(); + let bundle = proven.apply_signatures(rng, sighash, &[]).unwrap(); + + verify_bundle(&bundle, &post_nu6_3_vk, TxVersion::V6); +} + +#[test] +fn ironwood_builder_outputs_decrypt_with_ironwood_domain() { + let mut rng = OsRng; + let sk = SpendingKey::from_bytes([0; 32]).unwrap(); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + let ivk = PreparedIncomingViewingKey::new(&fvk.to_ivk(Scope::External)); + + let builder = output_only_builder(BundleVersion::ironwood_v3(), BundleType::DEFAULT, recipient); + let (bundle, bundle_meta) = builder.build::(&mut rng).unwrap().unwrap(); + let action = &bundle.actions()[bundle_meta + .output_action_index(0) + .expect("Output 0 can be found")]; + + let orchard_domain = OrchardDomain::for_action(action); + assert!(try_note_decryption(&orchard_domain, &ivk, action).is_none()); + + let ironwood_domain = IronwoodDomain::for_action(action); + let (note, decrypted_to, memo) = + try_note_decryption(&ironwood_domain, &ivk, action).expect("V3 output decrypts"); + + assert_eq!(note.version(), NoteVersion::V3); + assert_eq!(note.value(), NoteValue::from_raw(5000)); + assert_eq!(decrypted_to, recipient); + assert_eq!(memo, [0u8; 512]); +} + +#[test] +fn ironwood_bundle_helpers_decrypt_and_recover_outputs() { + let mut rng = OsRng; + let sk = SpendingKey::from_bytes([0; 32]).unwrap(); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + let ivk = fvk.to_ivk(Scope::External); + let ovk = fvk.to_ovk(Scope::External); + let bundle_version = BundleVersion::ironwood_v3(); + let anchor = MerkleHashOrchard::empty_root(32.into()).into(); + + let mut builder = Builder::new(BundleType::DEFAULT, bundle_version, SHIELDING_FLAGS, anchor) + .expect("shielding flags are valid for the bundle version"); + assert_eq!( + builder.add_output( + Some(ovk.clone()), + recipient, + NoteValue::from_raw(5000), + [0u8; 512], + ), + Ok(()) + ); + let (bundle, bundle_meta) = builder.build::(&mut rng).unwrap().unwrap(); + let action_idx = bundle_meta + .output_action_index(0) + .expect("Output 0 can be found"); + + let (note, decrypted_to, memo) = bundle + .decrypt_output_with_key(action_idx, &ivk) + .expect("V3 output decrypts through the bundle helper"); + assert_eq!(note.version(), NoteVersion::V3); + assert_eq!(note.value(), NoteValue::from_raw(5000)); + assert_eq!(decrypted_to, recipient); + assert_eq!(memo, [0u8; 512]); + + let decrypted = bundle.decrypt_outputs_with_keys(&[ivk]); + assert_eq!(decrypted.len(), 1); + assert_eq!(decrypted[0].0, action_idx); + assert_eq!(decrypted[0].2.version(), NoteVersion::V3); + assert_eq!(decrypted[0].2.value(), NoteValue::from_raw(5000)); + assert_eq!(decrypted[0].3, recipient); + assert_eq!(decrypted[0].4, [0u8; 512]); + + let (note, recovered_to, memo) = bundle + .recover_output_with_ovk(action_idx, &ovk) + .expect("V3 output recovers through the bundle helper"); + assert_eq!(note.version(), NoteVersion::V3); + assert_eq!(note.value(), NoteValue::from_raw(5000)); + assert_eq!(recovered_to, recipient); + assert_eq!(memo, [0u8; 512]); + + let recovered = bundle.recover_outputs_with_ovks(&[ovk]); + assert_eq!(recovered.len(), 1); + assert_eq!(recovered[0].0, action_idx); + assert_eq!(recovered[0].2.version(), NoteVersion::V3); + assert_eq!(recovered[0].2.value(), NoteValue::from_raw(5000)); + assert_eq!(recovered[0].3, recipient); + assert_eq!(recovered[0].4, [0u8; 512]); +} + +// Coinbase bundles disable nonzero-valued spends. From NU6.3, consensus requires +// nActionsOrchard = 0 in a v5+ coinbase transaction (v4, still valid after NU6.3, +// has no Orchard bundle). So a post-NU6.3 coinbase bundle built by this crate must +// be an Ironwood bundle. There the builder leaves cross-address enabled by default, +// and therefore ordinary outputs build normally. +#[test] +fn post_nu6_3_coinbase_bundle_proves_and_verifies() { + let mut rng = OsRng; + let post_nu6_3_pk = ProvingKey::build(OrchardCircuitVersion::PostNu6_3); + let post_nu6_3_vk = VerifyingKey::build(OrchardCircuitVersion::PostNu6_3); + + let sk = SpendingKey::from_bytes([0; 32]).unwrap(); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + + let builder = output_only_builder( + BundleVersion::ironwood_v3(), + BundleType::Coinbase, + recipient, + ); + + let (unauthorized, _) = builder.build::(&mut rng).unwrap().unwrap(); + assert_eq!(unauthorized.actions().len(), 1); + assert!(!unauthorized.flags().spends_enabled()); + assert!(unauthorized.flags().cross_address_enabled()); + + let sighash: [u8; 32] = unauthorized + .commitment(TxVersion::V6) + .expect("bundle flags are representable in this format") + .into(); + let proven = unauthorized.create_proof(&post_nu6_3_pk, &mut rng).unwrap(); + let bundle = proven.apply_signatures(rng, sighash, &[]).unwrap(); + + verify_bundle(&bundle, &post_nu6_3_vk, TxVersion::V6); +} + +// A post-NU 6.3 restricted bundle chain: an ordinary shielding bundle, followed by a bundle +// that disables cross-address transfers, withdraws part of the shielded value, +// and retains the rest as wallet-controlled change. +#[test] +fn post_nu6_3_restricted_bundle_chain() { + let mut rng = OsRng; + let post_nu6_3_pk = ProvingKey::build(OrchardCircuitVersion::PostNu6_3); + let post_nu6_3_vk = VerifyingKey::build(OrchardCircuitVersion::PostNu6_3); + let fixed_pk = ProvingKey::build(OrchardCircuitVersion::FixedPostNu6_2); + let fixed_vk = VerifyingKey::build(OrchardCircuitVersion::FixedPostNu6_2); + + let sk = SpendingKey::from_bytes([0; 32]).unwrap(); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + + let shielding_bundle: Bundle<_, i64> = { + let builder = + output_only_builder(BundleVersion::orchard_v2(), BundleType::DEFAULT, recipient); + + let (unauthorized, _) = builder.build(&mut rng).unwrap().unwrap(); + let sighash = unauthorized + .commitment(TxVersion::V5) + .expect("bundle flags are representable in this format") + .into(); + let proven = unauthorized.create_proof(&fixed_pk, &mut rng).unwrap(); + proven.apply_signatures(rng, sighash, &[]).unwrap() + }; + + verify_bundle(&shielding_bundle, &fixed_vk, TxVersion::V5); + + let change_addr = fvk.address_at(0u32, Scope::Internal); + let restricted_bundle: Bundle<_, i64> = { + let ivk = PreparedIncomingViewingKey::new(&fvk.to_ivk(Scope::External)); + let (note, _, _) = shielding_bundle + .actions() + .iter() + .find_map(|action| { + let domain = OrchardDomain::for_action(action); + try_note_decryption(&domain, &ivk, action) + }) + .unwrap(); + + let cmx: ExtractedNoteCommitment = note.commitment().into(); + let (root, merkle_path) = single_leaf_witness(&cmx); + + let mut builder = Builder::new( + BundleType::DEFAULT, + BundleVersion::orchard_v3(), + BundleVersion::orchard_v3().default_flags(), + root.into(), + ) + .unwrap(); + assert_eq!(builder.add_spend(fvk.clone(), note, merkle_path), Ok(())); + assert_eq!( + builder.add_change_output( + fvk.clone(), + Some(fvk.to_ovk(Scope::Internal)), + change_addr, + NoteValue::from_raw(3000), + [0u8; 512], + ), + Ok(()) + ); + let (unauthorized, bundle_meta) = builder.build(&mut rng).unwrap().unwrap(); + + assert_eq!(unauthorized.actions().len(), 2); + assert_ne!( + bundle_meta.spend_action_index(0), + bundle_meta.output_action_index(0) + ); + assert_eq!( + unauthorized + .decrypt_output_with_key( + bundle_meta + .output_action_index(0) + .expect("Output 0 can be found"), + &fvk.to_ivk(Scope::Internal), + ) + .map(|(note, recipient, _)| (note.value(), recipient)), + Some((NoteValue::from_raw(3000), change_addr)) + ); + + // The fabricated zero-valued output paired with the real spend is addressed to the spent + // note's own (external) receiver, but its ciphertext is randomized, so even the owning + // wallet's external ivk cannot trial-decrypt it -- which is what keeps the spend hidden + // from anyone (including a quantum adversary) who recovers that ivk from the address. + assert!(unauthorized + .decrypt_output_with_key( + bundle_meta + .spend_action_index(0) + .expect("Spend 0 can be found"), + &fvk.to_ivk(Scope::External), + ) + .is_none()); + + let sighash = unauthorized + .commitment(TxVersion::V5) + .expect("bundle flags are representable in this format") + .into(); + let proven = unauthorized.create_proof(&post_nu6_3_pk, &mut rng).unwrap(); + proven + .apply_signatures(rng, sighash, &[SpendAuthorizingKey::from(&sk)]) + .unwrap() + }; + + assert_eq!(restricted_bundle.value_balance(), &2000); + verify_bundle(&restricted_bundle, &post_nu6_3_vk, TxVersion::V5); + assert!(restricted_bundle.verify_proof(&fixed_vk).is_err()); + + let mut validator = BatchValidator::new(&post_nu6_3_vk); + validator + .add_bundle( + &restricted_bundle, + restricted_bundle + .commitment(TxVersion::V5) + .expect("bundle flags are representable in this format") + .into(), + ) + .unwrap(); + assert!(validator.validate(rng)); + + // A validator backed by a key that cannot constrain the cross-address restriction + // rejects the restricted bundle at insertion, rather than deferring the failure. + let mut validator = BatchValidator::new(&fixed_vk); + assert!(validator + .add_bundle( + &restricted_bundle, + restricted_bundle + .commitment(TxVersion::V5) + .expect("bundle flags are representable in this format") + .into(), + ) + .is_err()); +} + +// `BundleVersion::ironwood_v3()` is the post-NU6.3 Ironwood bundle version, which allows +// any choice of the `enableCrossAddress` flag. It shares the post-NU6.3 circuit with +// `BundleVersion::orchard_v3()`, and uses V3 note plaintexts. A transactional +// Ironwood bundle is therefore an ordinary spend+output bundle on the post-NU6.3 circuit +// whose NU6.3 flag byte sets bit 2. +#[test] +fn ironwood_post_nu6_3_unrestricted_bundle_proves_and_verifies() { + let mut rng = OsRng; + let post_nu6_3_pk = ProvingKey::build(BundleVersion::ironwood_v3().circuit_version()); + let post_nu6_3_vk = VerifyingKey::build(OrchardCircuitVersion::PostNu6_3); + + let sk = SpendingKey::from_bytes([0; 32]).unwrap(); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + + // Shield a note to spend (an unrestricted, output-only post-NU6.3 bundle). + let shielding_bundle: Bundle<_, i64> = { + let builder = + output_only_builder(BundleVersion::ironwood_v3(), BundleType::DEFAULT, recipient); + let (unauthorized, _) = builder.build(&mut rng).unwrap().unwrap(); + let sighash = unauthorized + .commitment(TxVersion::V6) + .expect("bundle flags are representable in this format") + .into(); + let proven = unauthorized.create_proof(&post_nu6_3_pk, &mut rng).unwrap(); + proven.apply_signatures(rng, sighash, &[]).unwrap() + }; + + let ivk = PreparedIncomingViewingKey::new(&fvk.to_ivk(Scope::External)); + let (note, _, _) = shielding_bundle + .actions() + .iter() + .find_map(|action| { + let orchard_domain = OrchardDomain::for_action(action); + assert!(try_note_decryption(&orchard_domain, &ivk, action).is_none()); + + let ironwood_domain = IronwoodDomain::for_action(action); + try_note_decryption(&ironwood_domain, &ivk, action) + }) + .unwrap(); + let cmx: ExtractedNoteCommitment = note.commitment().into(); + let (root, merkle_path) = single_leaf_witness(&cmx); + + // Spend the external-address note and send to a different (internal) address: a + // cross-address transfer, which Ironwood permits but post-NU6.3 Orchard would forbid. + let change_addr = fvk.address_at(0u32, Scope::Internal); + let mut builder = Builder::new( + BundleType::DEFAULT, + BundleVersion::ironwood_v3(), + BundleVersion::ironwood_v3().default_flags(), + root.into(), + ) + .unwrap(); + assert_eq!(builder.add_spend(fvk.clone(), note, merkle_path), Ok(())); + assert_eq!( + builder.add_output(None, change_addr, NoteValue::from_raw(5000), [0u8; 512]), + Ok(()) + ); + let (unauthorized, _) = builder.build(&mut rng).unwrap().unwrap(); + + assert_eq!( + unauthorized.circuit_version(), + OrchardCircuitVersion::PostNu6_3 + ); + // Cross-address transfers are enabled, so bit 2 of the NU6.3 flag byte is set. + assert!(unauthorized.flags().cross_address_enabled()); + let flag_byte = unauthorized + .flags() + .to_byte(BundleVersion::ironwood_v3()) + .expect("flags are representable under Ironwood"); + assert_eq!(flag_byte & 0b100, 0b100); + + let sighash = unauthorized + .commitment(TxVersion::V6) + .expect("bundle flags are representable in this format") + .into(); + let proven = unauthorized.create_proof(&post_nu6_3_pk, &mut rng).unwrap(); + let bundle = proven + .apply_signatures(rng, sighash, &[SpendAuthorizingKey::from(&sk)]) + .unwrap(); + + verify_bundle(&bundle, &post_nu6_3_vk, TxVersion::V6); +}