From c496f0f9369560ceea5ead6c5bdccf9165dd1702 Mon Sep 17 00:00:00 2001 From: YaoGalteland Date: Thu, 28 Mar 2024 15:04:15 +0100 Subject: [PATCH 1/3] add halo2_gadgets_optimized --- Cargo.lock | 21 + Cargo.toml | 1 + halo2_gadgets_optimized/CHANGELOG.md | 130 + halo2_gadgets_optimized/Cargo.toml | 85 + halo2_gadgets_optimized/README.md | 25 + halo2_gadgets_optimized/benches/poseidon.rs | 226 + halo2_gadgets_optimized/benches/primitives.rs | 68 + halo2_gadgets_optimized/benches/sha256.rs | 154 + .../proptest-regressions/constants/util.txt | 7 + halo2_gadgets_optimized/src/ecc.rs | 975 ++ halo2_gadgets_optimized/src/ecc/chip.rs | 644 + halo2_gadgets_optimized/src/ecc/chip/add.rs | 440 + .../src/ecc/chip/add_incomplete.rs | 192 + .../src/ecc/chip/constants.rs | 277 + halo2_gadgets_optimized/src/ecc/chip/mul.rs | 582 + .../src/ecc/chip/mul/complete.rs | 193 + .../src/ecc/chip/mul/incomplete.rs | 374 + .../src/ecc/chip/mul/overflow.rs | 208 + .../src/ecc/chip/mul_fixed.rs | 496 + .../src/ecc/chip/mul_fixed/base_field_elem.rs | 528 + .../src/ecc/chip/mul_fixed/full_width.rs | 316 + .../src/ecc/chip/mul_fixed/short.rs | 947 + .../src/ecc/chip/witness_point.rs | 211 + halo2_gadgets_optimized/src/lib.rs | 30 + halo2_gadgets_optimized/src/poseidon.rs | 296 + halo2_gadgets_optimized/src/poseidon/pow5.rs | 917 + .../src/poseidon/primitives.rs | 405 + .../src/poseidon/primitives/fp.rs | 1431 ++ .../src/poseidon/primitives/fq.rs | 1431 ++ .../src/poseidon/primitives/grain.rs | 195 + .../src/poseidon/primitives/mds.rs | 129 + .../src/poseidon/primitives/p128pow5t3.rs | 320 + .../src/poseidon/primitives/test_vectors.rs | 1261 ++ halo2_gadgets_optimized/src/sha256.rs | 166 + halo2_gadgets_optimized/src/sha256/table16.rs | 515 + .../src/sha256/table16/compression.rs | 1005 ++ .../table16/compression/compression_gates.rs | 443 + .../table16/compression/compression_util.rs | 991 ++ .../table16/compression/subregion_digest.rs | 102 + .../table16/compression/subregion_initial.rs | 156 + .../table16/compression/subregion_main.rs | 126 + .../src/sha256/table16/gates.rs | 125 + .../src/sha256/table16/message_schedule.rs | 455 + .../message_schedule/schedule_gates.rs | 395 + .../table16/message_schedule/schedule_util.rs | 181 + .../table16/message_schedule/subregion1.rs | 223 + .../table16/message_schedule/subregion2.rs | 487 + .../table16/message_schedule/subregion3.rs | 320 + .../src/sha256/table16/spread_table.rs | 454 + .../src/sha256/table16/util.rs | 117 + halo2_gadgets_optimized/src/sinsemilla.rs | 849 + .../src/sinsemilla/chip.rs | 342 + .../src/sinsemilla/chip/generator_table.rs | 175 + .../src/sinsemilla/chip/hash_to_point.rs | 572 + .../src/sinsemilla/merkle.rs | 400 + .../src/sinsemilla/merkle/chip.rs | 554 + .../src/sinsemilla/message.rs | 65 + .../src/sinsemilla/primitives.rs | 368 + .../src/sinsemilla/primitives/addition.rs | 73 + .../src/sinsemilla/primitives/sinsemilla_s.rs | 14344 ++++++++++++++++ halo2_gadgets_optimized/src/utilities.rs | 496 + .../src/utilities/cond_swap.rs | 624 + .../src/utilities/decompose_running_sum.rs | 389 + .../src/utilities/lookup_range_check.rs | 772 + halo2_proofs/src/circuit.rs | 130 +- 65 files changed, 39869 insertions(+), 60 deletions(-) create mode 100644 halo2_gadgets_optimized/CHANGELOG.md create mode 100644 halo2_gadgets_optimized/Cargo.toml create mode 100644 halo2_gadgets_optimized/README.md create mode 100644 halo2_gadgets_optimized/benches/poseidon.rs create mode 100644 halo2_gadgets_optimized/benches/primitives.rs create mode 100644 halo2_gadgets_optimized/benches/sha256.rs create mode 100644 halo2_gadgets_optimized/proptest-regressions/constants/util.txt create mode 100644 halo2_gadgets_optimized/src/ecc.rs create mode 100644 halo2_gadgets_optimized/src/ecc/chip.rs create mode 100644 halo2_gadgets_optimized/src/ecc/chip/add.rs create mode 100644 halo2_gadgets_optimized/src/ecc/chip/add_incomplete.rs create mode 100644 halo2_gadgets_optimized/src/ecc/chip/constants.rs create mode 100644 halo2_gadgets_optimized/src/ecc/chip/mul.rs create mode 100644 halo2_gadgets_optimized/src/ecc/chip/mul/complete.rs create mode 100644 halo2_gadgets_optimized/src/ecc/chip/mul/incomplete.rs create mode 100644 halo2_gadgets_optimized/src/ecc/chip/mul/overflow.rs create mode 100644 halo2_gadgets_optimized/src/ecc/chip/mul_fixed.rs create mode 100644 halo2_gadgets_optimized/src/ecc/chip/mul_fixed/base_field_elem.rs create mode 100644 halo2_gadgets_optimized/src/ecc/chip/mul_fixed/full_width.rs create mode 100644 halo2_gadgets_optimized/src/ecc/chip/mul_fixed/short.rs create mode 100644 halo2_gadgets_optimized/src/ecc/chip/witness_point.rs create mode 100644 halo2_gadgets_optimized/src/lib.rs create mode 100644 halo2_gadgets_optimized/src/poseidon.rs create mode 100644 halo2_gadgets_optimized/src/poseidon/pow5.rs create mode 100644 halo2_gadgets_optimized/src/poseidon/primitives.rs create mode 100644 halo2_gadgets_optimized/src/poseidon/primitives/fp.rs create mode 100644 halo2_gadgets_optimized/src/poseidon/primitives/fq.rs create mode 100644 halo2_gadgets_optimized/src/poseidon/primitives/grain.rs create mode 100644 halo2_gadgets_optimized/src/poseidon/primitives/mds.rs create mode 100644 halo2_gadgets_optimized/src/poseidon/primitives/p128pow5t3.rs create mode 100644 halo2_gadgets_optimized/src/poseidon/primitives/test_vectors.rs create mode 100644 halo2_gadgets_optimized/src/sha256.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16/compression.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16/compression/compression_gates.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16/compression/compression_util.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16/compression/subregion_digest.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16/compression/subregion_initial.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16/compression/subregion_main.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16/gates.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16/message_schedule.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16/message_schedule/schedule_gates.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16/message_schedule/schedule_util.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16/message_schedule/subregion1.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16/message_schedule/subregion2.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16/message_schedule/subregion3.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16/spread_table.rs create mode 100644 halo2_gadgets_optimized/src/sha256/table16/util.rs create mode 100644 halo2_gadgets_optimized/src/sinsemilla.rs create mode 100644 halo2_gadgets_optimized/src/sinsemilla/chip.rs create mode 100644 halo2_gadgets_optimized/src/sinsemilla/chip/generator_table.rs create mode 100644 halo2_gadgets_optimized/src/sinsemilla/chip/hash_to_point.rs create mode 100644 halo2_gadgets_optimized/src/sinsemilla/merkle.rs create mode 100644 halo2_gadgets_optimized/src/sinsemilla/merkle/chip.rs create mode 100644 halo2_gadgets_optimized/src/sinsemilla/message.rs create mode 100644 halo2_gadgets_optimized/src/sinsemilla/primitives.rs create mode 100644 halo2_gadgets_optimized/src/sinsemilla/primitives/addition.rs create mode 100644 halo2_gadgets_optimized/src/sinsemilla/primitives/sinsemilla_s.rs create mode 100644 halo2_gadgets_optimized/src/utilities.rs create mode 100644 halo2_gadgets_optimized/src/utilities/cond_swap.rs create mode 100644 halo2_gadgets_optimized/src/utilities/decompose_running_sum.rs create mode 100644 halo2_gadgets_optimized/src/utilities/lookup_range_check.rs diff --git a/Cargo.lock b/Cargo.lock index 2f8bc71dfb..9d8d67d3dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -864,6 +864,27 @@ dependencies = [ "uint", ] +[[package]] +name = "halo2_gadgets_optimized" +version = "0.3.0" +dependencies = [ + "arrayvec", + "bitvec", + "criterion", + "ff", + "group", + "halo2_proofs", + "inferno", + "lazy_static", + "pasta_curves", + "plotters", + "pprof", + "proptest", + "rand", + "subtle", + "uint", +] + [[package]] name = "halo2_legacy_pdqsort" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index b7878ae843..ec303a11ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,5 @@ members = [ "halo2", "halo2_gadgets", "halo2_proofs", + "halo2_gadgets_optimized", ] diff --git a/halo2_gadgets_optimized/CHANGELOG.md b/halo2_gadgets_optimized/CHANGELOG.md new file mode 100644 index 0000000000..26728c2218 --- /dev/null +++ b/halo2_gadgets_optimized/CHANGELOG.md @@ -0,0 +1,130 @@ +# Changelog +All notable changes to this project will be documented in this file. + +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] + +## [0.3.0] - 2023-03-21 +### Added +- `halo2_gadgets::poseidon::primitives::{Mds, generate_constants}` + +### Changed +- Migrated to `ff 0.13`, `group 0.13`, `pasta_curves 0.5` and `halo2_proofs 0.3`. +- APIs with `F: pasta_curves::arithmetic::FieldExt` bounds have been changed to + use `ff` traits directly. + +## [0.2.0] - 2022-06-23 +### Added +- `halo2_gadgets::utilities::RangeConstrained>::bitrange_of` + +### Changed +All APIs that represented witnessed values as `Option` now represent them as +`halo2_proofs::circuit::Value`. The core API changes are listed below. + +- Migrated to `halo2_proofs 0.2.0`. +- The following APIs now take `Value<_>` instead of `Option<_>`: + - `halo2_gadgets::ecc`: + - `EccInstructions::{witness_point, witness_point_non_id}` + - `EccInstructions::{witness_scalar_var, witness_scalar_fixed}` + - `ScalarVar::new` + - `ScalarFixed::new` + - `NonIdentityPoint::new` + - `Point::new` + - `halo2_gadgets::sinsemilla`: + - `SinsemillaInstructions::witness_message_piece` + - `MessagePiece::{from_field_elem, from_subpieces}` + - `halo2_gadgets::sinsemilla::merkle`: + - `MerklePath::construct` + - `halo2_gadgets::utilities`: + - `UtilitiesInstructions::load_private` + - `RangeConstrained::witness_short` + - `halo2_gadgets::utilities::cond_swap`: + - `CondSwapInstructions::swap` + - `halo2_gadgets::utilities::decompose_running_sum`: + - `RunningSumConfig::witness_decompose` + - `halo2_gadgets::utilities::lookup_range_check`: + - `LookupRangeCheckConfig::{witness_check, witness_short_check}` +- The following APIs now return `Value<_>` instead of `Option<_>`: + - `halo2_gadgets::ecc::chip`: + - `EccPoint::{point, is_identity}` + - `NonIdentityEccPoint::point` + - `halo2_gadgets::utilities`: + - `FieldValue::value` + - `Var::value` + - `RangeConstrained::value` +- `halo2_gadgets::sha256::BlockWord` is now a newtype wrapper around + `Value` instead of `Option`. + +### Removed +- `halo2_gadgets::utilities::RangeConstrained>::bitrange_of` + +## [0.1.0] - 2022-05-10 +### Added +- `halo2_gadgets::utilities`: + - `FieldValue` trait. + - `RangeConstrained` newtype wrapper. +- `halo2_gadgets::ecc`: + - `EccInstructions::witness_scalar_var` API to witness a full-width scalar + used in variable-base scalar multiplication. + - `EccInstructions::witness_scalar_fixed`, to witness a full-width scalar + used in fixed-base scalar multiplication. + - `EccInstructions::scalar_fixed_from_signed_short`, to construct a signed + short scalar used in fixed-base scalar multiplication from its magnitude and + sign. + - `BaseFitsInScalarInstructions` trait that can be implemented for a curve + whose base field fits into its scalar field. This provides a method + `scalar_var_from_base` that converts a base field element that exists as + a variable in the circuit, into a scalar to be used in variable-base + scalar multiplication. + - `ScalarFixed::new` + - `ScalarFixedShort::new` + - `ScalarVar::new` and `ScalarVar::from_base` gadget APIs. +- `halo2_gadgets::ecc::chip`: + - `ScalarVar` enum with `BaseFieldElem` and `FullWidth` variants. `FullWidth` + is unimplemented for `halo2_gadgets v0.1.0`. +- `halo2_gadgets::poseidon`: + - `primitives` (moved from `halo2_gadgets::primitives::poseidon`) +- `halo2_gadgets::sinsemilla`: + - `primitives` (moved from `halo2_gadgets::primitives::sinsemilla`) + - `MessagePiece::from_subpieces` + +### Changed +- `halo2_gadgets::ecc`: + - `EccInstructions::ScalarVar` is now treated as a full-width scalar, instead + of being restricted to a base field element. + - `EccInstructions::mul` now takes a `Self::ScalarVar` as argument, instead + of assuming that the scalar fits in a base field element `Self::Var`. + - `EccInstructions::mul_fixed` now takes a `Self::ScalarFixed` as argument, + instead of requiring that the chip always witness a new scalar. + - `EccInstructions::mul_fixed_short` now takes a `Self::ScalarFixedShort` as + argument, instead of the magnitude and sign directly. + - `FixedPoint::mul` now takes `ScalarFixed` instead of `Option`. + - `FixedPointShort::mul` now takes `ScalarFixedShort` instead of + `(EccChip::Var, EccChip::Var)`. +- `halo2_gadgets::ecc::chip`: + - `FixedPoint::u` now returns `Vec<[::Repr; H]>` + instead of `Vec<[[u8; 32]; H]>`. + - `ScalarKind` has been renamed to `FixedScalarKind`. +- `halo2_gadgets::sinsemilla`: + - `CommitDomain::{commit, short_commit}` now take the trapdoor `r` as an + `ecc::ScalarFixed` instead of `Option`. + - `merkle::MerklePath` can now be constructed with more or fewer than two + `MerkleChip`s. + +### Removed +- `halo2_gadgets::primitives` (use `halo2_gadgets::poseidon::primitives` or + `halo2_gadgets::sinsemilla::primitives` instead). + +## [0.1.0-beta.3] - 2022-04-06 +### Changed +- Migrated to `halo2_proofs 0.1.0-beta.4`. + +## [0.1.0-beta.2] - 2022-03-22 +### Changed +- Migrated to `halo2_proofs 0.1.0-beta.3`. + +## [0.1.0-beta.1] - 2022-02-14 +Initial release! diff --git a/halo2_gadgets_optimized/Cargo.toml b/halo2_gadgets_optimized/Cargo.toml new file mode 100644 index 0000000000..2593fa7b61 --- /dev/null +++ b/halo2_gadgets_optimized/Cargo.toml @@ -0,0 +1,85 @@ +[package] +name = "halo2_gadgets_optimized" +version = "0.3.0" +authors = [ + "Sean Bowe ", + "Jack Grigg ", + "Daira Hopwood ", + "Ying Tong Lai ", + "Kris Nuttycombe ", +] +edition = "2021" +rust-version = "1.60" +description = "Reusable gadgets and chip implementations for Halo 2" +license = "MIT OR Apache-2.0" +repository = "https://github.com/zcash/halo2" +readme = "README.md" +categories = ["cryptography"] +keywords = ["halo", "proofs", "zcash", "zkp", "zkSNARKs"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] + +[dependencies] +arrayvec = "0.7.0" +bitvec = "1" +ff = "0.13" +group = "0.13" +halo2_proofs = { version = "0.3", path = "../halo2_proofs", default-features = false } +lazy_static = "1" +pasta_curves = "0.5" +proptest = { version = "1.0.0", optional = true } +rand = "0.8" +subtle = "2.3" +uint = "0.9.2" # MSRV 1.56.1 + +# Developer tooling dependencies +plotters = { version = "0.3.0", default-features = false, optional = true } + +[dev-dependencies] +criterion = "0.3" +proptest = "1.0.0" + +[target.'cfg(unix)'.dev-dependencies] +inferno = ">=0.11, <0.11.5" # MSRV 1.59 +pprof = { version = "0.8", features = ["criterion", "flamegraph"] } # MSRV 1.56 + +[lib] +bench = false + +[features] +test-dev-graph = [ + "halo2_proofs/dev-graph", + "plotters", + "plotters/bitmap_backend", + "plotters/bitmap_encoder", + "plotters/ttf", +] +test-dependencies = ["proptest"] + +# In-development features +# See https://zcash.github.io/halo2/dev/features.html +beta = [ + "halo2_proofs/beta", +] +nightly = [ + "beta", + "halo2_proofs/nightly", + "unstable-sha256-gadget", +] +unstable-sha256-gadget = [] +# Add flags for in-development features above this line. + +[[bench]] +name = "primitives" +harness = false + +[[bench]] +name = "poseidon" +harness = false + +[[bench]] +name = "sha256" +harness = false +required-features = ["unstable-sha256-gadget"] diff --git a/halo2_gadgets_optimized/README.md b/halo2_gadgets_optimized/README.md new file mode 100644 index 0000000000..90a803c093 --- /dev/null +++ b/halo2_gadgets_optimized/README.md @@ -0,0 +1,25 @@ +# halo2_gadgets [![Crates.io](https://img.shields.io/crates/v/halo2_gadgets.svg)](https://crates.io/crates/halo2_gadgets) # + +Requires Rust 1.60+. + +## Documentation + +- [The Halo 2 Book](https://zcash.github.io/halo2/) +- [Crate documentation](https://docs.rs/halo2_gadgets) + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/halo2_gadgets_optimized/benches/poseidon.rs b/halo2_gadgets_optimized/benches/poseidon.rs new file mode 100644 index 0000000000..31f21d2795 --- /dev/null +++ b/halo2_gadgets_optimized/benches/poseidon.rs @@ -0,0 +1,226 @@ +use ff::Field; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + pasta::Fp, + plonk::{ + create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, + ConstraintSystem, Error, Instance, SingleVerifier, + }, + poly::commitment::Params, + transcript::{Blake2bRead, Blake2bWrite, Challenge255}, +}; +use pasta_curves::{pallas, vesta}; + +use halo2_gadgets::poseidon::{ + primitives::{self as poseidon, generate_constants, ConstantLength, Mds, Spec}, + Hash, Pow5Chip, Pow5Config, +}; +use std::convert::TryInto; +use std::marker::PhantomData; + +use criterion::{criterion_group, criterion_main, Criterion}; +use rand::rngs::OsRng; + +#[derive(Clone, Copy)] +struct HashCircuit +where + S: Spec + Clone + Copy, +{ + message: Value<[Fp; L]>, + _spec: PhantomData, +} + +#[derive(Debug, Clone)] +struct MyConfig { + input: [Column; L], + expected: Column, + poseidon_config: Pow5Config, +} + +impl Circuit + for HashCircuit +where + S: Spec + Copy + Clone, +{ + type Config = MyConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self { + message: Value::unknown(), + _spec: PhantomData, + } + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let state = (0..WIDTH).map(|_| meta.advice_column()).collect::>(); + let expected = meta.instance_column(); + meta.enable_equality(expected); + let partial_sbox = meta.advice_column(); + + let rc_a = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); + let rc_b = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); + + meta.enable_constant(rc_b[0]); + + Self::Config { + input: state[..RATE].try_into().unwrap(), + expected, + poseidon_config: Pow5Chip::configure::( + meta, + state.try_into().unwrap(), + partial_sbox, + rc_a.try_into().unwrap(), + rc_b.try_into().unwrap(), + ), + } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chip = Pow5Chip::construct(config.poseidon_config.clone()); + + let message = layouter.assign_region( + || "load message", + |mut region| { + let message_word = |i: usize| { + let value = self.message.map(|message_vals| message_vals[i]); + region.assign_advice( + || format!("load message_{}", i), + config.input[i], + 0, + || value, + ) + }; + + let message: Result, Error> = (0..L).map(message_word).collect(); + Ok(message?.try_into().unwrap()) + }, + )?; + + let hasher = Hash::<_, _, S, ConstantLength, WIDTH, RATE>::init( + chip, + layouter.namespace(|| "init"), + )?; + let output = hasher.hash(layouter.namespace(|| "hash"), message)?; + + layouter.constrain_instance(output.cell(), config.expected, 0) + } +} + +#[derive(Debug, Clone, Copy)] +struct MySpec; + +impl Spec for MySpec { + fn full_rounds() -> usize { + 8 + } + + fn partial_rounds() -> usize { + 56 + } + + fn sbox(val: Fp) -> Fp { + val.pow_vartime(&[5]) + } + + fn secure_mds() -> usize { + 0 + } + + fn constants() -> (Vec<[Fp; WIDTH]>, Mds, Mds) { + generate_constants::<_, Self, WIDTH, RATE>() + } +} + +const K: u32 = 7; + +fn bench_poseidon( + name: &str, + c: &mut Criterion, +) where + S: Spec + Copy + Clone, +{ + // Initialize the polynomial commitment parameters + let params: Params = Params::new(K); + + let empty_circuit = HashCircuit:: { + message: Value::unknown(), + _spec: PhantomData, + }; + + // Initialize the proving key + let vk = keygen_vk(¶ms, &empty_circuit).expect("keygen_vk should not fail"); + let pk = keygen_pk(¶ms, vk, &empty_circuit).expect("keygen_pk should not fail"); + + let prover_name = name.to_string() + "-prover"; + let verifier_name = name.to_string() + "-verifier"; + + let mut rng = OsRng; + let message = (0..L) + .map(|_| pallas::Base::random(rng)) + .collect::>() + .try_into() + .unwrap(); + let output = poseidon::Hash::<_, S, ConstantLength, WIDTH, RATE>::init().hash(message); + + let circuit = HashCircuit:: { + message: Value::known(message), + _spec: PhantomData, + }; + + c.bench_function(&prover_name, |b| { + b.iter(|| { + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + create_proof( + ¶ms, + &pk, + &[circuit], + &[&[&[output]]], + &mut rng, + &mut transcript, + ) + .expect("proof generation should not fail") + }) + }); + + // Create a proof + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + create_proof( + ¶ms, + &pk, + &[circuit], + &[&[&[output]]], + &mut rng, + &mut transcript, + ) + .expect("proof generation should not fail"); + let proof = transcript.finalize(); + + c.bench_function(&verifier_name, |b| { + b.iter(|| { + let strategy = SingleVerifier::new(¶ms); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + assert!(verify_proof( + ¶ms, + pk.get_vk(), + strategy, + &[&[&[output]]], + &mut transcript + ) + .is_ok()); + }); + }); +} + +fn criterion_benchmark(c: &mut Criterion) { + bench_poseidon::, 3, 2, 2>("WIDTH = 3, RATE = 2", c); + bench_poseidon::, 9, 8, 8>("WIDTH = 9, RATE = 8", c); + bench_poseidon::, 12, 11, 11>("WIDTH = 12, RATE = 11", c); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/halo2_gadgets_optimized/benches/primitives.rs b/halo2_gadgets_optimized/benches/primitives.rs new file mode 100644 index 0000000000..a6cb824ac7 --- /dev/null +++ b/halo2_gadgets_optimized/benches/primitives.rs @@ -0,0 +1,68 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use ff::Field; +use halo2_gadgets::{ + poseidon::primitives::{self as poseidon, ConstantLength, P128Pow5T3}, + sinsemilla::primitives as sinsemilla, +}; + +use pasta_curves::pallas; +#[cfg(unix)] +use pprof::criterion::{Output, PProfProfiler}; +use rand::{rngs::OsRng, Rng}; + +fn bench_primitives(c: &mut Criterion) { + let mut rng = OsRng; + + { + let mut group = c.benchmark_group("Poseidon"); + + let message = [pallas::Base::random(rng), pallas::Base::random(rng)]; + + group.bench_function("2-to-1", |b| { + b.iter(|| { + poseidon::Hash::<_, P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash(message) + }) + }); + } + + { + let mut group = c.benchmark_group("Sinsemilla"); + + let hasher = sinsemilla::HashDomain::new("hasher"); + let committer = sinsemilla::CommitDomain::new("committer"); + let bits: Vec = (0..1086).map(|_| rng.gen()).collect(); + let r = pallas::Scalar::random(rng); + + // Benchmark the input sizes we use in Orchard: + // - 510 bits for Commit^ivk + // - 520 bits for MerkleCRH + // - 1086 bits for NoteCommit + for size in [510, 520, 1086] { + group.bench_function(BenchmarkId::new("hash-to-point", size), |b| { + b.iter(|| hasher.hash_to_point(bits[..size].iter().cloned())) + }); + + group.bench_function(BenchmarkId::new("hash", size), |b| { + b.iter(|| hasher.hash(bits[..size].iter().cloned())) + }); + + group.bench_function(BenchmarkId::new("commit", size), |b| { + b.iter(|| committer.commit(bits[..size].iter().cloned(), &r)) + }); + + group.bench_function(BenchmarkId::new("short-commit", size), |b| { + b.iter(|| committer.commit(bits[..size].iter().cloned(), &r)) + }); + } + } +} + +#[cfg(unix)] +criterion_group! { + name = benches; + config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); + targets = bench_primitives +} +#[cfg(not(unix))] +criterion_group!(benches, bench_primitives); +criterion_main!(benches); diff --git a/halo2_gadgets_optimized/benches/sha256.rs b/halo2_gadgets_optimized/benches/sha256.rs new file mode 100644 index 0000000000..ffb4165a70 --- /dev/null +++ b/halo2_gadgets_optimized/benches/sha256.rs @@ -0,0 +1,154 @@ +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + pasta::{pallas, EqAffine}, + plonk::{ + create_proof, keygen_pk, keygen_vk, verify_proof, Circuit, ConstraintSystem, Error, + SingleVerifier, + }, + poly::commitment::Params, + transcript::{Blake2bRead, Blake2bWrite, Challenge255}, +}; +use rand::rngs::OsRng; + +use criterion::{criterion_group, criterion_main, Criterion}; +use std::{ + fs::{create_dir_all, File}, + io::{prelude::*, BufReader}, + path::Path, +}; + +use halo2_gadgets::sha256::{BlockWord, Sha256, Table16Chip, Table16Config, BLOCK_SIZE}; + +#[allow(dead_code)] +fn bench(name: &str, k: u32, c: &mut Criterion) { + #[derive(Default)] + struct MyCircuit {} + + impl Circuit for MyCircuit { + type Config = Table16Config; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + Table16Chip::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + Table16Chip::load(config.clone(), &mut layouter)?; + let table16_chip = Table16Chip::construct(config); + + // Test vector: "abc" + let test_input = [ + BlockWord(Value::known(0b01100001011000100110001110000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000011000)), + ]; + + // Create a message of length 31 blocks + let mut input = Vec::with_capacity(31 * BLOCK_SIZE); + for _ in 0..31 { + input.extend_from_slice(&test_input); + } + + Sha256::digest(table16_chip, layouter.namespace(|| "'abc' * 2"), &input)?; + + Ok(()) + } + } + + // Create parent directory for assets + create_dir_all("./benches/sha256_assets").expect("Failed to create sha256_assets directory"); + + // Initialize the polynomial commitment parameters + let params_path = Path::new("./benches/sha256_assets/sha256_params"); + if File::open(params_path).is_err() { + let params: Params = Params::new(k); + let mut buf = Vec::new(); + + params.write(&mut buf).expect("Failed to write params"); + let mut file = File::create(params_path).expect("Failed to create sha256_params"); + + file.write_all(&buf[..]) + .expect("Failed to write params to file"); + } + + let params_fs = File::open(params_path).expect("couldn't load sha256_params"); + let params: Params = + Params::read::<_>(&mut BufReader::new(params_fs)).expect("Failed to read params"); + + let empty_circuit: MyCircuit = MyCircuit {}; + + // Initialize the proving key + let vk = keygen_vk(¶ms, &empty_circuit).expect("keygen_vk should not fail"); + let pk = keygen_pk(¶ms, vk, &empty_circuit).expect("keygen_pk should not fail"); + + let circuit: MyCircuit = MyCircuit {}; + + let verifier_name = name.to_string() + "-verifier"; + + // Benchmark proof creation + /* + let prover_name = name.to_string() + "-prover"; + c.bench_function(&prover_name, |b| { + b.iter(|| { + let mut transcript = Blake2bWrite::init(vec![]); + create_proof(¶ms, &pk, &[circuit], &[&[]], OsRng, &mut transcript) + .expect("proof generation should not fail"); + }); + }); + */ + + // Create a proof + let proof_path = Path::new("./benches/sha256_assets/sha256_proof"); + if File::open(proof_path).is_err() { + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + create_proof(¶ms, &pk, &[circuit], &[&[]], OsRng, &mut transcript) + .expect("proof generation should not fail"); + let proof: Vec = transcript.finalize(); + let mut file = File::create(proof_path).expect("Failed to create sha256_proof"); + file.write_all(&proof[..]).expect("Failed to write proof"); + } + + let mut proof_fs = File::open(proof_path).expect("couldn't load sha256_proof"); + let mut proof = Vec::::new(); + proof_fs + .read_to_end(&mut proof) + .expect("Couldn't read proof"); + + c.bench_function(&verifier_name, |b| { + b.iter(|| { + let strategy = SingleVerifier::new(¶ms); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + assert!(verify_proof(¶ms, pk.get_vk(), strategy, &[&[]], &mut transcript).is_ok()); + }); + }); +} + +#[allow(dead_code)] +fn criterion_benchmark(c: &mut Criterion) { + bench("sha256", 17, c); + // bench("sha256", 20, c); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/halo2_gadgets_optimized/proptest-regressions/constants/util.txt b/halo2_gadgets_optimized/proptest-regressions/constants/util.txt new file mode 100644 index 0000000000..9e49b54616 --- /dev/null +++ b/halo2_gadgets_optimized/proptest-regressions/constants/util.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 251d6e9f7ad2f5cd8679dec6b69aa9c879baae8742791b19669c136aef12deac # shrinks to scalar = 0x0000000000000000000000000000000000000000000000000000000000000000, window_num_bits = 6 diff --git a/halo2_gadgets_optimized/src/ecc.rs b/halo2_gadgets_optimized/src/ecc.rs new file mode 100644 index 0000000000..87af93d505 --- /dev/null +++ b/halo2_gadgets_optimized/src/ecc.rs @@ -0,0 +1,975 @@ +//! Elliptic curve operations. + +use std::fmt::Debug; + +use halo2_proofs::{ + arithmetic::CurveAffine, + circuit::{AssignedCell, Chip, Layouter, Value}, + plonk::Error, +}; + +use crate::utilities::UtilitiesInstructions; + +pub mod chip; + +/// The set of circuit instructions required to use the ECC gadgets. +pub trait EccInstructions: +Chip + UtilitiesInstructions + Clone + Debug + Eq +{ + /// Variable representing a scalar used in variable-base scalar mul. + /// + /// This type is treated as a full-width scalar. However, if `Self` implements + /// [`BaseFitsInScalarInstructions`] then this may also be constructed from an element + /// of the base field. + type ScalarVar: Clone + Debug; + /// Variable representing a full-width element of the elliptic curve's + /// scalar field, to be used for fixed-base scalar mul. + type ScalarFixed: Clone + Debug; + /// Variable representing a signed short element of the elliptic curve's + /// scalar field, to be used for fixed-base scalar mul. + /// + /// A `ScalarFixedShort` must be in the range [-(2^64 - 1), 2^64 - 1]. + type ScalarFixedShort: Clone + Debug; + /// Variable representing an elliptic curve point. + type Point: From + Clone + Debug; + /// Variable representing a non-identity elliptic curve point. + type NonIdentityPoint: Clone + Debug; + /// Variable representing the affine short Weierstrass x-coordinate of an + /// elliptic curve point. + type X: Clone + Debug; + /// Enumeration of the set of fixed bases to be used in scalar mul. + /// TODO: When associated consts can be used as const generics, introduce + /// `Self::NUM_WINDOWS`, `Self::NUM_WINDOWS_BASE_FIELD`, `Self::NUM_WINDOWS_SHORT` + /// and use them to differentiate `FixedPoints` types. + type FixedPoints: FixedPoints; + + /// Constrains point `a` to be equal in value to point `b`. + fn constrain_equal( + &self, + layouter: &mut impl Layouter, + a: &Self::Point, + b: &Self::Point, + ) -> Result<(), Error>; + + /// Witnesses the given point as a private input to the circuit. + /// This allows the point to be the identity, mapped to (0, 0) in + /// affine coordinates. + fn witness_point( + &self, + layouter: &mut impl Layouter, + value: Value, + ) -> Result; + + /// Witnesses the given constant point as a private input to the circuit. + /// This allows the point to be the identity, mapped to (0, 0) in + /// affine coordinates. + fn witness_point_from_constant( + &self, + layouter: &mut impl Layouter, + value: C, + ) -> Result; + + /// Witnesses the given point as a private input to the circuit. + /// This returns an error if the point is the identity. + fn witness_point_non_id( + &self, + layouter: &mut impl Layouter, + value: Value, + ) -> Result; + + /// Witnesses a full-width scalar to be used in variable-base multiplication. + fn witness_scalar_var( + &self, + layouter: &mut impl Layouter, + value: Value, + ) -> Result; + + /// Witnesses a full-width scalar to be used in fixed-base multiplication. + fn witness_scalar_fixed( + &self, + layouter: &mut impl Layouter, + value: Value, + ) -> Result; + + /// Converts a magnitude and sign that exists as variables in the circuit into a + /// signed short scalar to be used in fixed-base scalar multiplication. + fn scalar_fixed_from_signed_short( + &self, + layouter: &mut impl Layouter, + magnitude_sign: (Self::Var, Self::Var), + ) -> Result; + + /// Extracts the x-coordinate of a point. + fn extract_p + Clone>(point: &Point) -> Self::X; + + /// Performs incomplete point addition, returning `a + b`. + /// + /// This returns an error in exceptional cases. + fn add_incomplete( + &self, + layouter: &mut impl Layouter, + a: &Self::NonIdentityPoint, + b: &Self::NonIdentityPoint, + ) -> Result; + + /// Performs complete point addition, returning `a + b`. + fn add + Clone, B: Into + Clone>( + &self, + layouter: &mut impl Layouter, + a: &A, + b: &B, + ) -> Result; + + /// Performs variable-base sign-scalar multiplication, returning `[sign] point` + /// `sign` must be in {-1, 1}. + fn mul_sign( + &self, + layouter: &mut impl Layouter, + sign: &AssignedCell, + point: &Self::Point, + ) -> Result; + + /// Performs variable-base scalar multiplication, returning `[scalar] base`. + fn mul( + &self, + layouter: &mut impl Layouter, + scalar: &Self::ScalarVar, + base: &Self::NonIdentityPoint, + ) -> Result<(Self::Point, Self::ScalarVar), Error>; + + /// Performs fixed-base scalar multiplication using a full-width scalar, returning `[scalar] base`. + fn mul_fixed( + &self, + layouter: &mut impl Layouter, + scalar: &Self::ScalarFixed, + base: &>::FullScalar, + ) -> Result<(Self::Point, Self::ScalarFixed), Error>; + + /// Performs fixed-base scalar multiplication using a short signed scalar, returning + /// `[scalar] base`. + fn mul_fixed_short( + &self, + layouter: &mut impl Layouter, + scalar: &Self::ScalarFixedShort, + base: &>::ShortScalar, + ) -> Result<(Self::Point, Self::ScalarFixedShort), Error>; + + /// Performs fixed-base scalar multiplication using a base field element as the scalar. + /// In the current implementation, this base field element must be output from another + /// instruction. + fn mul_fixed_base_field_elem( + &self, + layouter: &mut impl Layouter, + base_field_elem: Self::Var, + base: &>::Base, + ) -> Result; +} + +/// Instructions that can be implemented for a curve whose base field fits into +/// its scalar field. +pub trait BaseFitsInScalarInstructions: EccInstructions { + /// Converts a base field element that exists as a variable in the circuit + /// into a scalar to be used in variable-base scalar multiplication. + fn scalar_var_from_base( + &self, + layouter: &mut impl Layouter, + base: &Self::Var, + ) -> Result; +} + +/// Defines the fixed points for a given instantiation of the ECC chip. +pub trait FixedPoints: Debug + Eq + Clone { + /// Fixed points that can be used with full-width scalar multiplication. + type FullScalar: Debug + Eq + Clone; + /// Fixed points that can be used with short scalar multiplication. + type ShortScalar: Debug + Eq + Clone; + /// Fixed points that can be multiplied by base field elements. + type Base: Debug + Eq + Clone; +} + +/// An integer representing an element of the scalar field for a specific elliptic curve. +#[derive(Debug)] +pub struct ScalarVar> { + chip: EccChip, + inner: EccChip::ScalarVar, +} + +impl> ScalarVar { + /// Witnesses the given full-width scalar. + /// + /// Depending on the `EccChip` implementation, this may either witness the scalar + /// immediately, or delay witnessing until its first use in [`NonIdentityPoint::mul`]. + pub fn new( + chip: EccChip, + mut layouter: impl Layouter, + value: Value, + ) -> Result { + let scalar = chip.witness_scalar_var(&mut layouter, value); + scalar.map(|inner| ScalarVar { chip, inner }) + } +} + +impl> ScalarVar { + /// Constructs a scalar from an existing base-field element. + pub fn from_base( + chip: EccChip, + mut layouter: impl Layouter, + base: &EccChip::Var, + ) -> Result { + let scalar = chip.scalar_var_from_base(&mut layouter, base); + scalar.map(|inner| ScalarVar { chip, inner }) + } +} + +/// An integer representing an element of the scalar field for a specific elliptic curve, +/// for [`FixedPoint`] scalar multiplication. +#[derive(Debug)] +pub struct ScalarFixed> { + chip: EccChip, + inner: EccChip::ScalarFixed, +} + +impl> ScalarFixed { + /// Witnesses the given full-width scalar. + /// + /// Depending on the `EccChip` implementation, this may either witness the scalar + /// immediately, or delay witnessing until its first use in [`FixedPoint::mul`]. + pub fn new( + chip: EccChip, + mut layouter: impl Layouter, + value: Value, + ) -> Result { + let scalar = chip.witness_scalar_fixed(&mut layouter, value); + scalar.map(|inner| ScalarFixed { chip, inner }) + } +} + +/// A signed short (64-bit) integer represented as an element of the scalar field for a +/// specific elliptic curve, to be used for [`FixedPointShort`] scalar multiplication. +#[derive(Debug)] +pub struct ScalarFixedShort> { + chip: EccChip, + inner: EccChip::ScalarFixedShort, +} + +impl> ScalarFixedShort { + /// Converts the given signed short scalar. + /// + /// `magnitude_sign` must be a tuple of two circuit-assigned values: + /// - An unsigned integer of at most 64 bits. + /// - A sign value that is either 1 or -1. + /// + /// Depending on the `EccChip` implementation, the scalar may either be constrained + /// immediately by this constructor, or lazily constrained when it is first used in + /// [`FixedPointShort::mul`]. + pub fn new( + chip: EccChip, + mut layouter: impl Layouter, + magnitude_sign: (EccChip::Var, EccChip::Var), + ) -> Result { + let scalar = chip.scalar_fixed_from_signed_short(&mut layouter, magnitude_sign); + scalar.map(|inner| ScalarFixedShort { chip, inner }) + } +} + +/// A point on a specific elliptic curve that is guaranteed to not be the identity. +#[derive(Copy, Clone, Debug)] +pub struct NonIdentityPoint> { + chip: EccChip, + inner: EccChip::NonIdentityPoint, +} + +impl> NonIdentityPoint { + /// Constructs a new point with the given value. + pub fn new( + chip: EccChip, + mut layouter: impl Layouter, + value: Value, + ) -> Result { + let point = chip.witness_point_non_id(&mut layouter, value); + point.map(|inner| NonIdentityPoint { chip, inner }) + } + + /// Constrains this point to be equal in value to another point. + pub fn constrain_equal> + Clone>( + &self, + mut layouter: impl Layouter, + other: &Other, + ) -> Result<(), Error> { + let other: Point = (other.clone()).into(); + self.chip.constrain_equal( + &mut layouter, + &Point::::from(self.clone()).inner, + &other.inner, + ) + } + + /// Returns the inner point. + pub fn inner(&self) -> &EccChip::NonIdentityPoint { + &self.inner + } + + /// Extracts the x-coordinate of a point. + pub fn extract_p(&self) -> X { + X::from_inner(self.chip.clone(), EccChip::extract_p(&self.inner)) + } + + /// Wraps the given point (obtained directly from an instruction) in a gadget. + pub fn from_inner(chip: EccChip, inner: EccChip::NonIdentityPoint) -> Self { + NonIdentityPoint { chip, inner } + } + + /// Returns `self + other` using complete addition. + pub fn add> + Clone>( + &self, + mut layouter: impl Layouter, + other: &Other, + ) -> Result, Error> { + let other: Point = (other.clone()).into(); + + assert_eq!(self.chip, other.chip); + self.chip + .add(&mut layouter, &self.inner, &other.inner) + .map(|inner| Point { + chip: self.chip.clone(), + inner, + }) + } + + /// Returns `self + other` using incomplete addition. + /// The arguments are type-constrained not to be the identity point, + /// and since exceptional cases return an Error, the result also cannot + /// be the identity point. + pub fn add_incomplete( + &self, + mut layouter: impl Layouter, + other: &Self, + ) -> Result { + assert_eq!(self.chip, other.chip); + self.chip + .add_incomplete(&mut layouter, &self.inner, &other.inner) + .map(|inner| NonIdentityPoint { + chip: self.chip.clone(), + inner, + }) + } + + /// Returns `[by] self`. + #[allow(clippy::type_complexity)] + pub fn mul( + &self, + mut layouter: impl Layouter, + by: ScalarVar, + ) -> Result<(Point, ScalarVar), Error> { + assert_eq!(self.chip, by.chip); + self.chip + .mul(&mut layouter, &by.inner, &self.inner.clone()) + .map(|(point, scalar)| { + ( + Point { + chip: self.chip.clone(), + inner: point, + }, + ScalarVar { + chip: self.chip.clone(), + inner: scalar, + }, + ) + }) + } +} + +impl + Clone + Debug + Eq> +From> for Point +{ + fn from(non_id_point: NonIdentityPoint) -> Self { + Self { + chip: non_id_point.chip, + inner: non_id_point.inner.into(), + } + } +} + +/// A point on a specific elliptic curve. +#[derive(Copy, Clone, Debug)] +pub struct Point + Clone + Debug + Eq> { + chip: EccChip, + inner: EccChip::Point, +} + +impl + Clone + Debug + Eq> Point { + /// Constructs a new point with the given value. + pub fn new( + chip: EccChip, + mut layouter: impl Layouter, + value: Value, + ) -> Result { + let point = chip.witness_point(&mut layouter, value); + point.map(|inner| Point { chip, inner }) + } + + /// Constructs a new point with the given fixed value. + pub fn new_from_constant( + chip: EccChip, + mut layouter: impl Layouter, + value: C, + ) -> Result { + let point = chip.witness_point_from_constant(&mut layouter, value); + point.map(|inner| Point { chip, inner }) + } + + /// Constrains this point to be equal in value to another point. + pub fn constrain_equal> + Clone>( + &self, + mut layouter: impl Layouter, + other: &Other, + ) -> Result<(), Error> { + let other: Point = (other.clone()).into(); + self.chip + .constrain_equal(&mut layouter, &self.inner, &other.inner) + } + + /// Returns the inner point. + pub fn inner(&self) -> &EccChip::Point { + &self.inner + } + + /// Extracts the x-coordinate of a point. + pub fn extract_p(&self) -> X { + X::from_inner(self.chip.clone(), EccChip::extract_p(&self.inner)) + } + + /// Wraps the given point (obtained directly from an instruction) in a gadget. + pub fn from_inner(chip: EccChip, inner: EccChip::Point) -> Self { + Point { chip, inner } + } + + /// Returns `self + other` using complete addition. + pub fn add> + Clone>( + &self, + mut layouter: impl Layouter, + other: &Other, + ) -> Result, Error> { + let other: Point = (other.clone()).into(); + + assert_eq!(self.chip, other.chip); + self.chip + .add(&mut layouter, &self.inner, &other.inner) + .map(|inner| Point { + chip: self.chip.clone(), + inner, + }) + } + + /// Returns `[sign] self`. + /// `sign` must be in {-1, 1}. + pub fn mul_sign( + &self, + mut layouter: impl Layouter, + sign: &AssignedCell, + ) -> Result, Error> { + self.chip + .mul_sign(&mut layouter, sign, &self.inner) + .map(|point| Point { + chip: self.chip.clone(), + inner: point, + }) + } +} + +/// The affine short Weierstrass x-coordinate of a point on a specific elliptic curve. +#[derive(Debug)] +pub struct X> { + inner: EccChip::X, +} + +impl> X { + /// Wraps the given x-coordinate (obtained directly from an instruction) in a gadget. + pub fn from_inner(chip: EccChip, inner: EccChip::X) -> Self { + let _ = chip; // unused + X { inner } + } + + /// Returns the inner x-coordinate. + pub fn inner(&self) -> &EccChip::X { + &self.inner + } +} + +/// Precomputed multiples of a fixed point, for full-width scalar multiplication. +/// +/// Fixing the curve point enables window tables to be baked into the circuit, making +/// scalar multiplication more efficient. These window tables are tuned to full-width +/// scalar multiplication. +#[derive(Clone, Debug)] +pub struct FixedPoint> { + chip: EccChip, + inner: >::FullScalar, +} + +/// Precomputed multiples of a fixed point, that can be multiplied by base-field elements. +/// +/// Fixing the curve point enables window tables to be baked into the circuit, making +/// scalar multiplication more efficient. These window tables are tuned to scalar +/// multiplication by base-field elements. +#[derive(Clone, Debug)] +pub struct FixedPointBaseField> { + chip: EccChip, + inner: >::Base, +} + +/// Precomputed multiples of a fixed point, for short signed scalar multiplication. +#[derive(Clone, Debug)] +pub struct FixedPointShort> { + chip: EccChip, + inner: >::ShortScalar, +} + +impl> FixedPoint { + #[allow(clippy::type_complexity)] + /// Returns `[by] self`. + pub fn mul( + &self, + mut layouter: impl Layouter, + by: ScalarFixed, + ) -> Result<(Point, ScalarFixed), Error> { + assert_eq!(self.chip, by.chip); + self.chip + .mul_fixed(&mut layouter, &by.inner, &self.inner) + .map(|(point, scalar)| { + ( + Point { + chip: self.chip.clone(), + inner: point, + }, + ScalarFixed { + chip: self.chip.clone(), + inner: scalar, + }, + ) + }) + } + + /// Wraps the given fixed base (obtained directly from an instruction) in a gadget. + pub fn from_inner( + chip: EccChip, + inner: >::FullScalar, + ) -> Self { + Self { chip, inner } + } +} + +impl> FixedPointBaseField { + #[allow(clippy::type_complexity)] + /// Returns `[by] self`. + pub fn mul( + &self, + mut layouter: impl Layouter, + by: EccChip::Var, + ) -> Result, Error> { + self.chip + .mul_fixed_base_field_elem(&mut layouter, by, &self.inner) + .map(|inner| Point { + chip: self.chip.clone(), + inner, + }) + } + + /// Wraps the given fixed base (obtained directly from an instruction) in a gadget. + pub fn from_inner( + chip: EccChip, + inner: >::Base, + ) -> Self { + Self { chip, inner } + } +} + +impl> FixedPointShort { + #[allow(clippy::type_complexity)] + /// Returns `[by] self`. + pub fn mul( + &self, + mut layouter: impl Layouter, + by: ScalarFixedShort, + ) -> Result<(Point, ScalarFixedShort), Error> { + assert_eq!(self.chip, by.chip); + self.chip + .mul_fixed_short(&mut layouter, &by.inner, &self.inner) + .map(|(point, scalar)| { + ( + Point { + chip: self.chip.clone(), + inner: point, + }, + ScalarFixedShort { + chip: self.chip.clone(), + inner: scalar, + }, + ) + }) + } + + /// Wraps the given fixed base (obtained directly from an instruction) in a gadget. + pub fn from_inner( + chip: EccChip, + inner: >::ShortScalar, + ) -> Self { + Self { chip, inner } + } +} + +#[cfg(test)] +pub(crate) mod tests { + use ff::PrimeField; + use group::{prime::PrimeCurveAffine, Curve, Group}; + + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + dev::MockProver, + plonk::{Circuit, ConstraintSystem, Error}, + }; + use lazy_static::lazy_static; + use pasta_curves::pallas; + + use super::{ + chip::{ + find_zs_and_us, BaseFieldElem, EccChip, EccConfig, FixedPoint, FullScalar, ShortScalar, + H, NUM_WINDOWS, NUM_WINDOWS_SHORT, + }, + FixedPoints, + }; + use crate::utilities::lookup_range_check::LookupRangeCheckConfig; + + #[derive(Debug, Eq, PartialEq, Clone)] + pub(crate) struct TestFixedBases; + #[derive(Debug, Eq, PartialEq, Clone)] + pub(crate) struct FullWidth(pallas::Affine, &'static [(u64, [pallas::Base; H])]); + #[derive(Debug, Eq, PartialEq, Clone)] + pub(crate) struct BaseField; + #[derive(Debug, Eq, PartialEq, Clone)] + pub(crate) struct Short; + + lazy_static! { + static ref BASE: pallas::Affine = pallas::Point::generator().to_affine(); + static ref ZS_AND_US: Vec<(u64, [pallas::Base; H])> = + find_zs_and_us(*BASE, NUM_WINDOWS).unwrap(); + static ref ZS_AND_US_SHORT: Vec<(u64, [pallas::Base; H])> = + find_zs_and_us(*BASE, NUM_WINDOWS_SHORT).unwrap(); + } + + impl FullWidth { + pub(crate) fn from_pallas_generator() -> Self { + FullWidth(*BASE, &ZS_AND_US) + } + + pub(crate) fn from_parts( + base: pallas::Affine, + zs_and_us: &'static [(u64, [pallas::Base; H])], + ) -> Self { + FullWidth(base, zs_and_us) + } + } + + impl FixedPoint for FullWidth { + type FixedScalarKind = FullScalar; + + fn generator(&self) -> pallas::Affine { + self.0 + } + + fn u(&self) -> Vec<[[u8; 32]; H]> { + self.1 + .iter() + .map(|(_, us)| { + [ + us[0].to_repr(), + us[1].to_repr(), + us[2].to_repr(), + us[3].to_repr(), + us[4].to_repr(), + us[5].to_repr(), + us[6].to_repr(), + us[7].to_repr(), + ] + }) + .collect() + } + + fn z(&self) -> Vec { + self.1.iter().map(|(z, _)| *z).collect() + } + } + + impl FixedPoint for BaseField { + type FixedScalarKind = BaseFieldElem; + + fn generator(&self) -> pallas::Affine { + *BASE + } + + fn u(&self) -> Vec<[[u8; 32]; H]> { + ZS_AND_US + .iter() + .map(|(_, us)| { + [ + us[0].to_repr(), + us[1].to_repr(), + us[2].to_repr(), + us[3].to_repr(), + us[4].to_repr(), + us[5].to_repr(), + us[6].to_repr(), + us[7].to_repr(), + ] + }) + .collect() + } + + fn z(&self) -> Vec { + ZS_AND_US.iter().map(|(z, _)| *z).collect() + } + } + + impl FixedPoint for Short { + type FixedScalarKind = ShortScalar; + + fn generator(&self) -> pallas::Affine { + *BASE + } + + fn u(&self) -> Vec<[[u8; 32]; H]> { + ZS_AND_US_SHORT + .iter() + .map(|(_, us)| { + [ + us[0].to_repr(), + us[1].to_repr(), + us[2].to_repr(), + us[3].to_repr(), + us[4].to_repr(), + us[5].to_repr(), + us[6].to_repr(), + us[7].to_repr(), + ] + }) + .collect() + } + + fn z(&self) -> Vec { + ZS_AND_US_SHORT.iter().map(|(z, _)| *z).collect() + } + } + + impl FixedPoints for TestFixedBases { + type FullScalar = FullWidth; + type ShortScalar = Short; + type Base = BaseField; + } + + struct MyCircuit { + test_errors: bool, + } + + #[allow(non_snake_case)] + impl Circuit for MyCircuit { + type Config = EccConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + MyCircuit { test_errors: false } + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let advices = [ + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + ]; + let lookup_table = meta.lookup_table_column(); + let table_range_check_tag = meta.lookup_table_column(); + let lagrange_coeffs = [ + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + ]; + // Shared fixed column for loading constants + let constants = meta.fixed_column(); + meta.enable_constant(constants); + + let range_check = LookupRangeCheckConfig::configure( + meta, + advices[9], + lookup_table, + table_range_check_tag, + ); + EccChip::::configure(meta, advices, lagrange_coeffs, range_check) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chip = EccChip::construct(config.clone()); + + // Load 10-bit lookup table. In the Action circuit, this will be + // provided by the Sinsemilla chip. + config.lookup_config.load(&mut layouter)?; + + // Generate a random non-identity point P + let p_val = pallas::Point::random(rand::rngs::OsRng).to_affine(); // P + let p = super::NonIdentityPoint::new( + chip.clone(), + layouter.namespace(|| "P"), + Value::known(p_val), + )?; + let p_neg = -p_val; + let p_neg = super::NonIdentityPoint::new( + chip.clone(), + layouter.namespace(|| "-P"), + Value::known(p_neg), + )?; + + // Generate a random non-identity point Q + let q_val = pallas::Point::random(rand::rngs::OsRng).to_affine(); // Q + let q = super::NonIdentityPoint::new( + chip.clone(), + layouter.namespace(|| "Q"), + Value::known(q_val), + )?; + + // Make sure P and Q are not the same point. + assert_ne!(p_val, q_val); + + // Test that we can witness the identity as a point, but not as a non-identity point. + { + let _ = super::Point::new( + chip.clone(), + layouter.namespace(|| "identity"), + Value::known(pallas::Affine::identity()), + )?; + + super::NonIdentityPoint::new( + chip.clone(), + layouter.namespace(|| "identity"), + Value::known(pallas::Affine::identity()), + ) + .expect_err("Trying to witness the identity should return an error"); + } + + // Test witness non-identity point + { + super::chip::witness_point::tests::test_witness_non_id( + chip.clone(), + layouter.namespace(|| "witness non-identity point"), + ) + } + + // Test complete addition + { + super::chip::add::tests::test_add( + chip.clone(), + layouter.namespace(|| "complete addition"), + p_val, + &p, + q_val, + &q, + &p_neg, + )?; + } + + // Test incomplete addition + { + super::chip::add_incomplete::tests::test_add_incomplete( + chip.clone(), + layouter.namespace(|| "incomplete addition"), + p_val, + &p, + q_val, + &q, + &p_neg, + self.test_errors, + )?; + } + + // Test variable-base scalar multiplication + { + super::chip::mul::tests::test_mul( + chip.clone(), + layouter.namespace(|| "variable-base scalar mul"), + &p, + p_val, + )?; + } + + // Test variable-base sign-scalar multiplication + { + super::chip::mul_fixed::short::tests::test_mul_sign( + chip.clone(), + layouter.namespace(|| "variable-base sign-scalar mul"), + )?; + } + + // Test full-width fixed-base scalar multiplication + { + super::chip::mul_fixed::full_width::tests::test_mul_fixed( + chip.clone(), + layouter.namespace(|| "full-width fixed-base scalar mul"), + )?; + } + + // Test signed short fixed-base scalar multiplication + { + super::chip::mul_fixed::short::tests::test_mul_fixed_short( + chip.clone(), + layouter.namespace(|| "signed short fixed-base scalar mul"), + )?; + } + + // Test fixed-base scalar multiplication with a base field element + { + super::chip::mul_fixed::base_field_elem::tests::test_mul_fixed_base_field( + chip, + layouter.namespace(|| "fixed-base scalar mul with base field element"), + )?; + } + + Ok(()) + } + } + + #[test] + fn ecc_chip() { + let k = 13; + let circuit = MyCircuit { test_errors: true }; + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())) + } + + #[cfg(feature = "test-dev-graph")] + #[test] + fn print_ecc_chip() { + use plotters::prelude::*; + + let root = BitMapBackend::new("ecc-chip-layout.png", (1024, 7680)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root.titled("Ecc Chip Layout", ("sans-serif", 60)).unwrap(); + + let circuit = MyCircuit { test_errors: false }; + halo2_proofs::dev::CircuitLayout::default() + .render(13, &circuit, &root) + .unwrap(); + } +} \ No newline at end of file diff --git a/halo2_gadgets_optimized/src/ecc/chip.rs b/halo2_gadgets_optimized/src/ecc/chip.rs new file mode 100644 index 0000000000..8036b08338 --- /dev/null +++ b/halo2_gadgets_optimized/src/ecc/chip.rs @@ -0,0 +1,644 @@ +//! Chip implementations for the ECC gadgets. + +use super::{BaseFitsInScalarInstructions, EccInstructions, FixedPoints}; +use crate::{ + sinsemilla::primitives as sinsemilla, + utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions}, +}; +use arrayvec::ArrayVec; + +use ff::PrimeField; +use group::prime::PrimeCurveAffine; +use halo2_proofs::{ + circuit::{AssignedCell, Chip, Layouter, Value}, + plonk::{Advice, Assigned, Column, ConstraintSystem, Error, Fixed}, +}; +use pasta_curves::{arithmetic::CurveAffine, pallas}; + +use std::convert::TryInto; + +pub(super) mod add; +pub(super) mod add_incomplete; +pub mod constants; +pub(super) mod mul; +pub(super) mod mul_fixed; +pub(super) mod witness_point; + +pub use constants::*; + +// Exposed for Sinsemilla. +pub(crate) use mul::incomplete::DoubleAndAdd; + +/// A curve point represented in affine (x, y) coordinates, or the +/// identity represented as (0, 0). +/// Each coordinate is assigned to a cell. +#[derive(Clone, Debug)] +pub struct EccPoint { + /// x-coordinate + /// + /// Stored as an `Assigned` to enable batching inversions. + x: AssignedCell, pallas::Base>, + /// y-coordinate + /// + /// Stored as an `Assigned` to enable batching inversions. + y: AssignedCell, pallas::Base>, +} + +impl EccPoint { + /// Constructs a point from its coordinates, without checking they are on the curve. + /// + /// This is an internal API that we only use where we know we have a valid curve point. + pub(crate) fn from_coordinates_unchecked( + x: AssignedCell, pallas::Base>, + y: AssignedCell, pallas::Base>, + ) -> Self { + EccPoint { x, y } + } + + /// Returns the value of this curve point, if known. + pub fn point(&self) -> Value { + self.x.value().zip(self.y.value()).map(|(x, y)| { + if x.is_zero_vartime() && y.is_zero_vartime() { + pallas::Affine::identity() + } else { + pallas::Affine::from_xy(x.evaluate(), y.evaluate()).unwrap() + } + }) + } + /// The cell containing the affine short-Weierstrass x-coordinate, + /// or 0 for the zero point. + pub fn x(&self) -> AssignedCell { + self.x.clone().evaluate() + } + /// The cell containing the affine short-Weierstrass y-coordinate, + /// or 0 for the zero point. + pub fn y(&self) -> AssignedCell { + self.y.clone().evaluate() + } + + #[cfg(test)] + fn is_identity(&self) -> Value { + self.x.value().map(|x| x.is_zero_vartime()) + } +} + +/// A non-identity point represented in affine (x, y) coordinates. +/// Each coordinate is assigned to a cell. +#[derive(Clone, Debug)] +pub struct NonIdentityEccPoint { + /// x-coordinate + /// + /// Stored as an `Assigned` to enable batching inversions. + x: AssignedCell, pallas::Base>, + /// y-coordinate + /// + /// Stored as an `Assigned` to enable batching inversions. + y: AssignedCell, pallas::Base>, +} + +impl NonIdentityEccPoint { + /// Constructs a point from its coordinates, without checking they are on the curve. + /// + /// This is an internal API that we only use where we know we have a valid non-identity + /// curve point. + pub(crate) fn from_coordinates_unchecked( + x: AssignedCell, pallas::Base>, + y: AssignedCell, pallas::Base>, + ) -> Self { + NonIdentityEccPoint { x, y } + } + + /// Returns the value of this curve point, if known. + pub fn point(&self) -> Value { + self.x.value().zip(self.y.value()).map(|(x, y)| { + assert!(!x.is_zero_vartime() && !y.is_zero_vartime()); + pallas::Affine::from_xy(x.evaluate(), y.evaluate()).unwrap() + }) + } + /// The cell containing the affine short-Weierstrass x-coordinate. + pub fn x(&self) -> AssignedCell { + self.x.clone().evaluate() + } + /// The cell containing the affine short-Weierstrass y-coordinate. + pub fn y(&self) -> AssignedCell { + self.y.clone().evaluate() + } +} + +impl From for EccPoint { + fn from(non_id_point: NonIdentityEccPoint) -> Self { + Self { + x: non_id_point.x, + y: non_id_point.y, + } + } +} + +/// Configuration for [`EccChip`]. +#[derive(Clone, Debug, Eq, PartialEq)] +#[allow(non_snake_case)] +pub struct EccConfig> { + /// Advice columns needed by instructions in the ECC chip. + pub advices: [Column; 10], + + /// Incomplete addition + add_incomplete: add_incomplete::Config, + + /// Complete addition + add: add::Config, + + /// Variable-base scalar multiplication + mul: mul::Config, + + /// Fixed-base full-width scalar multiplication + mul_fixed_full: mul_fixed::full_width::Config, + /// Fixed-base signed short scalar multiplication + mul_fixed_short: mul_fixed::short::Config, + /// Fixed-base mul using a base field element as a scalar + mul_fixed_base_field: mul_fixed::base_field_elem::Config, + + /// Witness point + witness_point: witness_point::Config, + + /// Lookup range check using 10-bit lookup table + pub lookup_config: LookupRangeCheckConfig, +} + +/// A trait representing the kind of scalar used with a particular `FixedPoint`. +/// +/// This trait exists because of limitations around const generics. +pub trait FixedScalarKind { + /// The number of windows that this scalar kind requires. + const NUM_WINDOWS: usize; +} + +/// Type marker representing a full-width scalar for use in fixed-base scalar +/// multiplication. +#[derive(Debug)] +pub enum FullScalar {} +impl FixedScalarKind for FullScalar { + const NUM_WINDOWS: usize = NUM_WINDOWS; +} + +/// Type marker representing a signed 64-bit scalar for use in fixed-base scalar +/// multiplication. +#[derive(Debug)] +pub enum ShortScalar {} +impl FixedScalarKind for ShortScalar { + const NUM_WINDOWS: usize = NUM_WINDOWS_SHORT; +} + +/// Type marker representing a base field element being used as a scalar in fixed-base +/// scalar multiplication. +#[derive(Debug)] +pub enum BaseFieldElem {} +impl FixedScalarKind for BaseFieldElem { + const NUM_WINDOWS: usize = NUM_WINDOWS; +} + +/// Returns information about a fixed point that is required by [`EccChip`]. +/// +/// For each window required by `Self::FixedScalarKind`, $z$ is a field element such that for +/// each point $(x, y)$ in the window: +/// - $z + y = u^2$ (some square in the field); and +/// - $z - y$ is not a square. +/// +/// TODO: When associated consts can be used as const generics, introduce a +/// `const NUM_WINDOWS: usize` associated const, and return `NUM_WINDOWS`-sized +/// arrays instead of `Vec`s. +pub trait FixedPoint: std::fmt::Debug + Eq + Clone { + /// The kind of scalar that this fixed point can be multiplied by. + type FixedScalarKind: FixedScalarKind; + + /// Returns the generator for this fixed point. + fn generator(&self) -> C; + + /// Returns the $u$ values for this fixed point. + fn u(&self) -> Vec<[::Repr; H]>; + + /// Returns the $z$ value for this fixed point. + fn z(&self) -> Vec; + + /// Returns the Lagrange coefficients for this fixed point. + fn lagrange_coeffs(&self) -> Vec<[C::Base; H]> { + compute_lagrange_coeffs(self.generator(), Self::FixedScalarKind::NUM_WINDOWS) + } +} + +/// An [`EccInstructions`] chip that uses 10 advice columns. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EccChip> { + config: EccConfig, +} + +impl> Chip for EccChip { + type Config = EccConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} + +impl> UtilitiesInstructions +for EccChip +{ + type Var = AssignedCell; +} + +impl> EccChip { + /// Reconstructs this chip from the given config. + pub fn construct(config: >::Config) -> Self { + Self { config } + } + + /// # Side effects + /// + /// All columns in `advices` will be equality-enabled. + #[allow(non_snake_case)] + pub fn configure( + meta: &mut ConstraintSystem, + advices: [Column; 10], + lagrange_coeffs: [Column; 8], + range_check: LookupRangeCheckConfig, + ) -> >::Config { + // Create witness point gate + let witness_point = witness_point::Config::configure(meta, advices[0], advices[1]); + // Create incomplete point addition gate + let add_incomplete = + add_incomplete::Config::configure(meta, advices[0], advices[1], advices[2], advices[3]); + + // Create complete point addition gate + let add = add::Config::configure( + meta, advices[0], advices[1], advices[2], advices[3], advices[4], advices[5], + advices[6], advices[7], advices[8], + ); + + // Create variable-base scalar mul gates + let mul = mul::Config::configure(meta, add, range_check, advices); + + // Create config that is shared across short, base-field, and full-width + // fixed-base scalar mul. + let mul_fixed = mul_fixed::Config::::configure( + meta, + lagrange_coeffs, + advices[4], + advices[5], + add, + add_incomplete, + ); + + // Create gate that is only used in full-width fixed-base scalar mul. + let mul_fixed_full = + mul_fixed::full_width::Config::::configure(meta, mul_fixed.clone()); + + // Create gate that is only used in short fixed-base scalar mul. + let mul_fixed_short = + mul_fixed::short::Config::::configure(meta, mul_fixed.clone()); + + // Create gate that is only used in fixed-base mul using a base field element. + let mul_fixed_base_field = mul_fixed::base_field_elem::Config::::configure( + meta, + advices[6..9].try_into().unwrap(), + range_check, + mul_fixed, + ); + + EccConfig { + advices, + add_incomplete, + add, + mul, + mul_fixed_full, + mul_fixed_short, + mul_fixed_base_field, + witness_point, + lookup_config: range_check, + } + } +} + +/// A full-width scalar used for fixed-base scalar multiplication. +/// This is decomposed into 85 3-bit windows in little-endian order, +/// i.e. `windows` = [k_0, k_1, ..., k_84] (for a 255-bit scalar) +/// where `scalar = k_0 + k_1 * (2^3) + ... + k_84 * (2^3)^84` and +/// each `k_i` is in the range [0..2^3). +#[derive(Clone, Debug)] +pub struct EccScalarFixed { + value: Value, + /// The circuit-assigned windows representing this scalar, or `None` if the scalar has + /// not been used yet. + windows: Option, { NUM_WINDOWS }>>, +} + +// TODO: Make V a `u64` +type MagnitudeCell = AssignedCell; +// TODO: Make V an enum Sign { Positive, Negative } +type SignCell = AssignedCell; +type MagnitudeSign = (MagnitudeCell, SignCell); + +/// A signed short scalar used for fixed-base scalar multiplication. +/// A short scalar must have magnitude in the range [0..2^64), with +/// a sign of either 1 or -1. +/// This is decomposed into 3-bit windows in little-endian order +/// using a running sum `z`, where z_{i+1} = (z_i - a_i) / (2^3) +/// for element α = a_0 + (2^3) a_1 + ... + (2^{3(n-1)}) a_{n-1}. +/// Each `a_i` is in the range [0..2^3). +/// +/// `windows` = [k_0, k_1, ..., k_21] (for a 64-bit magnitude) +/// where `scalar = k_0 + k_1 * (2^3) + ... + k_84 * (2^3)^84` and +/// each `k_i` is in the range [0..2^3). +/// k_21 must be a single bit, i.e. 0 or 1. +#[derive(Clone, Debug)] +pub struct EccScalarFixedShort { + magnitude: MagnitudeCell, + sign: SignCell, + /// The circuit-assigned running sum constraining this signed short scalar, or `None` + /// if the scalar has not been used yet. + running_sum: + Option, { NUM_WINDOWS_SHORT + 1 }>>, +} + +/// A base field element used for fixed-base scalar multiplication. +/// This is decomposed into 3-bit windows in little-endian order +/// using a running sum `z`, where z_{i+1} = (z_i - a_i) / (2^3) +/// for element α = a_0 + (2^3) a_1 + ... + (2^{3(n-1)}) a_{n-1}. +/// Each `a_i` is in the range [0..2^3). +/// +/// `running_sum` = [z_0, ..., z_85], where we expect z_85 = 0. +/// Since z_0 is initialized as the scalar α, we store it as +/// `base_field_elem`. +#[derive(Clone, Debug)] +struct EccBaseFieldElemFixed { + base_field_elem: AssignedCell, + running_sum: ArrayVec, { NUM_WINDOWS + 1 }>, +} + +impl EccBaseFieldElemFixed { + #![allow(dead_code)] + fn base_field_elem(&self) -> AssignedCell { + self.base_field_elem.clone() + } +} + +/// An enumeration of the possible types of scalars used in variable-base +/// multiplication. +#[derive(Clone, Debug)] +pub enum ScalarVar { + /// An element of the elliptic curve's base field, that is used as a scalar + /// in variable-base scalar mul. + /// + /// It is not true in general that a scalar field element fits in a curve's + /// base field, and in particular it is untrue for the Pallas curve, whose + /// scalar field `Fq` is larger than its base field `Fp`. + /// + /// However, the only use of variable-base scalar mul in the Orchard protocol + /// is in deriving diversified addresses `[ivk] g_d`, and `ivk` is guaranteed + /// to be in the base field of the curve. (See non-normative notes in + /// [4.2.3 Orchard Key Components][orchardkeycomponents].) + /// + /// [orchardkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents + BaseFieldElem(AssignedCell), + /// A full-width scalar. This is unimplemented for halo2_gadgets v0.1.0. + FullWidth, +} + +impl> EccInstructions for EccChip + where + >::Base: + FixedPoint, + >::FullScalar: + FixedPoint, + >::ShortScalar: + FixedPoint, +{ + type ScalarFixed = EccScalarFixed; + type ScalarFixedShort = EccScalarFixedShort; + type ScalarVar = ScalarVar; + type Point = EccPoint; + type NonIdentityPoint = NonIdentityEccPoint; + type X = AssignedCell; + type FixedPoints = Fixed; + + fn constrain_equal( + &self, + layouter: &mut impl Layouter, + a: &Self::Point, + b: &Self::Point, + ) -> Result<(), Error> { + layouter.assign_region( + || "constrain equal", + |mut region| { + // Constrain x-coordinates + region.constrain_equal(a.x().cell(), b.x().cell())?; + // Constrain x-coordinates + region.constrain_equal(a.y().cell(), b.y().cell()) + }, + ) + } + + fn witness_point( + &self, + layouter: &mut impl Layouter, + value: Value, + ) -> Result { + let config = self.config().witness_point; + layouter.assign_region( + || "witness point", + |mut region| config.point(value, 0, &mut region), + ) + } + + fn witness_point_from_constant( + &self, + layouter: &mut impl Layouter, + value: pallas::Affine, + ) -> Result { + let config = self.config().witness_point; + layouter.assign_region( + || "witness point (constant)", + |mut region| config.constant_point(value, 0, &mut region), + ) + } + + fn witness_point_non_id( + &self, + layouter: &mut impl Layouter, + value: Value, + ) -> Result { + let config = self.config().witness_point; + layouter.assign_region( + || "witness non-identity point", + |mut region| config.point_non_id(value, 0, &mut region), + ) + } + + fn witness_scalar_var( + &self, + _layouter: &mut impl Layouter, + _value: Value, + ) -> Result { + // This is unimplemented for halo2_gadgets v0.1.0. + todo!() + } + + fn witness_scalar_fixed( + &self, + _layouter: &mut impl Layouter, + value: Value, + ) -> Result { + Ok(EccScalarFixed { + value, + // This chip uses lazy witnessing. + windows: None, + }) + } + + fn scalar_fixed_from_signed_short( + &self, + _layouter: &mut impl Layouter, + (magnitude, sign): MagnitudeSign, + ) -> Result { + Ok(EccScalarFixedShort { + magnitude, + sign, + // This chip uses lazy constraining. + running_sum: None, + }) + } + + fn extract_p + Clone>(point: &Point) -> Self::X { + let point: EccPoint = (point.clone()).into(); + point.x() + } + + fn add_incomplete( + &self, + layouter: &mut impl Layouter, + a: &Self::NonIdentityPoint, + b: &Self::NonIdentityPoint, + ) -> Result { + let config = self.config().add_incomplete; + layouter.assign_region( + || "incomplete point addition", + |mut region| config.assign_region(a, b, 0, &mut region), + ) + } + + fn add + Clone, B: Into + Clone>( + &self, + layouter: &mut impl Layouter, + a: &A, + b: &B, + ) -> Result { + let config = self.config().add; + layouter.assign_region( + || "complete point addition", + |mut region| { + config.assign_region(&(a.clone()).into(), &(b.clone()).into(), 0, &mut region) + }, + ) + } + + /// Performs variable-base sign-scalar multiplication, returning `[sign] point` + /// `sign` must be in {-1, 1}. + fn mul_sign( + &self, + layouter: &mut impl Layouter, + sign: &AssignedCell, + point: &Self::Point, + ) -> Result { + // Multiply point by sign, using the same gate as mul_fixed::short. + // This also constrains sign to be in {-1, 1}. + let config_short = self.config().mul_fixed_short.clone(); + config_short.assign_scalar_sign( + layouter.namespace(|| "variable-base sign-scalar mul"), + sign, + point, + ) + } + + fn mul( + &self, + layouter: &mut impl Layouter, + scalar: &Self::ScalarVar, + base: &Self::NonIdentityPoint, + ) -> Result<(Self::Point, Self::ScalarVar), Error> { + let config = self.config().mul; + match scalar { + ScalarVar::BaseFieldElem(scalar) => config.assign( + layouter.namespace(|| "variable-base scalar mul"), + scalar.clone(), + base, + ), + ScalarVar::FullWidth => { + todo!() + } + } + } + + fn mul_fixed( + &self, + layouter: &mut impl Layouter, + scalar: &Self::ScalarFixed, + base: &>::FullScalar, + ) -> Result<(Self::Point, Self::ScalarFixed), Error> { + let config = self.config().mul_fixed_full.clone(); + config.assign( + layouter.namespace(|| format!("fixed-base mul of {:?}", base)), + scalar, + base, + ) + } + + fn mul_fixed_short( + &self, + layouter: &mut impl Layouter, + scalar: &Self::ScalarFixedShort, + base: &>::ShortScalar, + ) -> Result<(Self::Point, Self::ScalarFixedShort), Error> { + let config = self.config().mul_fixed_short.clone(); + config.assign( + layouter.namespace(|| format!("short fixed-base mul of {:?}", base)), + scalar, + base, + ) + } + + fn mul_fixed_base_field_elem( + &self, + layouter: &mut impl Layouter, + base_field_elem: AssignedCell, + base: &>::Base, + ) -> Result { + let config = self.config().mul_fixed_base_field.clone(); + config.assign( + layouter.namespace(|| format!("base-field elem fixed-base mul of {:?}", base)), + base_field_elem, + base, + ) + } +} + +impl> BaseFitsInScalarInstructions +for EccChip + where + >::Base: + FixedPoint, + >::FullScalar: + FixedPoint, + >::ShortScalar: + FixedPoint, +{ + fn scalar_var_from_base( + &self, + _layouter: &mut impl Layouter, + base: &Self::Var, + ) -> Result { + Ok(ScalarVar::BaseFieldElem(base.clone())) + } +} \ No newline at end of file diff --git a/halo2_gadgets_optimized/src/ecc/chip/add.rs b/halo2_gadgets_optimized/src/ecc/chip/add.rs new file mode 100644 index 0000000000..8932b1282b --- /dev/null +++ b/halo2_gadgets_optimized/src/ecc/chip/add.rs @@ -0,0 +1,440 @@ +use super::EccPoint; + +use group::ff::PrimeField; +use halo2_proofs::{ + circuit::Region, + plonk::{Advice, Assigned, Column, ConstraintSystem, Constraints, Error, Expression, Selector}, + poly::Rotation, +}; +use pasta_curves::pallas; + +use std::collections::HashSet; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Config { + q_add: Selector, + // lambda + lambda: Column, + // x-coordinate of P in P + Q = R + pub x_p: Column, + // y-coordinate of P in P + Q = R + pub y_p: Column, + // x-coordinate of Q or R in P + Q = R + pub x_qr: Column, + // y-coordinate of Q or R in P + Q = R + pub y_qr: Column, + // α = inv0(x_q - x_p) + alpha: Column, + // β = inv0(x_p) + beta: Column, + // γ = inv0(x_q) + gamma: Column, + // δ = inv0(y_p + y_q) if x_q = x_p, 0 otherwise + delta: Column, +} + +impl Config { + #[allow(clippy::too_many_arguments)] + pub(super) fn configure( + meta: &mut ConstraintSystem, + x_p: Column, + y_p: Column, + x_qr: Column, + y_qr: Column, + lambda: Column, + alpha: Column, + beta: Column, + gamma: Column, + delta: Column, + ) -> Self { + meta.enable_equality(x_p); + meta.enable_equality(y_p); + meta.enable_equality(x_qr); + meta.enable_equality(y_qr); + + let config = Self { + q_add: meta.selector(), + x_p, + y_p, + x_qr, + y_qr, + lambda, + alpha, + beta, + gamma, + delta, + }; + + config.create_gate(meta); + + config + } + + pub(crate) fn output_columns(&self) -> HashSet> { + [self.x_qr, self.y_qr].into_iter().collect() + } + + fn create_gate(&self, meta: &mut ConstraintSystem) { + // https://p.z.cash/halo2-0.1:ecc-complete-addition + meta.create_gate("complete addition", |meta| { + let q_add = meta.query_selector(self.q_add); + let x_p = meta.query_advice(self.x_p, Rotation::cur()); + let y_p = meta.query_advice(self.y_p, Rotation::cur()); + let x_q = meta.query_advice(self.x_qr, Rotation::cur()); + let y_q = meta.query_advice(self.y_qr, Rotation::cur()); + let x_r = meta.query_advice(self.x_qr, Rotation::next()); + let y_r = meta.query_advice(self.y_qr, Rotation::next()); + let lambda = meta.query_advice(self.lambda, Rotation::cur()); + + // α = inv0(x_q - x_p) + let alpha = meta.query_advice(self.alpha, Rotation::cur()); + // β = inv0(x_p) + let beta = meta.query_advice(self.beta, Rotation::cur()); + // γ = inv0(x_q) + let gamma = meta.query_advice(self.gamma, Rotation::cur()); + // δ = inv0(y_p + y_q) if x_q = x_p, 0 otherwise + let delta = meta.query_advice(self.delta, Rotation::cur()); + + // Useful composite expressions + // (x_q − x_p) + let x_q_minus_x_p = x_q.clone() - x_p.clone(); + // (x_p - x_r) + let x_p_minus_x_r = x_p.clone() - x_r.clone(); + // (y_q + y_p) + let y_q_plus_y_p = y_q.clone() + y_p.clone(); + // α ⋅(x_q - x_p) + let if_alpha = x_q_minus_x_p.clone() * alpha; + // β ⋅ x_p + let if_beta = x_p.clone() * beta; + // γ ⋅ x_q + let if_gamma = x_q.clone() * gamma; + // δ ⋅(y_q + y_p) + let if_delta = y_q_plus_y_p.clone() * delta; + + // Useful constants + let one = Expression::Constant(pallas::Base::one()); + let two = Expression::Constant(pallas::Base::from(2)); + let three = Expression::Constant(pallas::Base::from(3)); + + // (x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_p)) = 0 + let poly1 = { + let y_q_minus_y_p = y_q.clone() - y_p.clone(); // (y_q − y_p) + let incomplete = x_q_minus_x_p.clone() * lambda.clone() - y_q_minus_y_p; // (x_q − x_p)⋅λ − (y_q−y_p) + + // q_add ⋅(x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_p)) + x_q_minus_x_p.clone() * incomplete + }; + + // (1 - (x_q - x_p)⋅α)⋅(2y_p ⋅λ - 3x_p^2) = 0 + let poly2 = { + let three_x_p_sq = three * x_p.clone().square(); // 3x_p^2 + let two_y_p = two * y_p.clone(); // 2y_p + let tangent_line = two_y_p * lambda.clone() - three_x_p_sq; // (2y_p ⋅λ - 3x_p^2) + + // q_add ⋅(1 - (x_q - x_p)⋅α)⋅(2y_p ⋅λ - 3x_p^2) + (one.clone() - if_alpha.clone()) * tangent_line + }; + + // (λ^2 - x_p - x_q - x_r) + let nonexceptional_x_r = + lambda.clone().square() - x_p.clone() - x_q.clone() - x_r.clone(); + // (λ ⋅(x_p - x_r) - y_p - y_r) + let nonexceptional_y_r = lambda * x_p_minus_x_r - y_p.clone() - y_r.clone(); + + // x_p⋅x_q⋅(x_q - x_p)⋅(λ^2 - x_p - x_q - x_r) = 0 + let poly3a = + x_p.clone() * x_q.clone() * x_q_minus_x_p.clone() * nonexceptional_x_r.clone(); + + // x_p⋅x_q⋅(x_q - x_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0 + let poly3b = x_p.clone() * x_q.clone() * x_q_minus_x_p * nonexceptional_y_r.clone(); + + // x_p⋅x_q⋅(y_q + y_p)⋅(λ^2 - x_p - x_q - x_r) = 0 + let poly3c = x_p.clone() * x_q.clone() * y_q_plus_y_p.clone() * nonexceptional_x_r; + + // x_p⋅x_q⋅(y_q + y_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0 + let poly3d = x_p.clone() * x_q.clone() * y_q_plus_y_p * nonexceptional_y_r; + + // (1 - x_p * β) * (x_r - x_q) = 0 + let poly4a = (one.clone() - if_beta.clone()) * (x_r.clone() - x_q); + + // (1 - x_p * β) * (y_r - y_q) = 0 + let poly4b = (one.clone() - if_beta) * (y_r.clone() - y_q); + + // (1 - x_q * γ) * (x_r - x_p) = 0 + let poly5a = (one.clone() - if_gamma.clone()) * (x_r.clone() - x_p); + + // (1 - x_q * γ) * (y_r - y_p) = 0 + let poly5b = (one.clone() - if_gamma) * (y_r.clone() - y_p); + + // ((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * x_r + let poly6a = (one.clone() - if_alpha.clone() - if_delta.clone()) * x_r; + + // ((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * y_r + let poly6b = (one - if_alpha - if_delta) * y_r; + + Constraints::with_selector( + q_add, + [ + ("1", poly1), + ("2", poly2), + ("3a", poly3a), + ("3b", poly3b), + ("3c", poly3c), + ("3d", poly3d), + ("4a", poly4a), + ("4b", poly4b), + ("5a", poly5a), + ("5b", poly5b), + ("6a", poly6a), + ("6b", poly6b), + ], + ) + }); + } + + pub(super) fn assign_region( + &self, + p: &EccPoint, + q: &EccPoint, + offset: usize, + region: &mut Region<'_, pallas::Base>, + ) -> Result { + // Enable `q_add` selector + self.q_add.enable(region, offset)?; + + // Copy point `p` into `x_p`, `y_p` columns + p.x.copy_advice(|| "x_p", region, self.x_p, offset)?; + p.y.copy_advice(|| "y_p", region, self.y_p, offset)?; + + // Copy point `q` into `x_qr`, `y_qr` columns + q.x.copy_advice(|| "x_q", region, self.x_qr, offset)?; + q.y.copy_advice(|| "y_q", region, self.y_qr, offset)?; + + let (x_p, y_p) = (p.x.value(), p.y.value()); + let (x_q, y_q) = (q.x.value(), q.y.value()); + + // Assign α = inv0(x_q - x_p) + let alpha = (x_q - x_p).invert(); + region.assign_advice(|| "α", self.alpha, offset, || alpha)?; + + // Assign β = inv0(x_p) + let beta = x_p.invert(); + region.assign_advice(|| "β", self.beta, offset, || beta)?; + + // Assign γ = inv0(x_q) + let gamma = x_q.invert(); + region.assign_advice(|| "γ", self.gamma, offset, || gamma)?; + + // Assign δ = inv0(y_q + y_p) if x_q = x_p, 0 otherwise + let delta = x_p + .zip(x_q) + .zip(y_p) + .zip(y_q) + .map(|(((x_p, x_q), y_p), y_q)| { + if x_q == x_p { + (y_q + y_p).invert() + } else { + Assigned::Zero + } + }); + region.assign_advice(|| "δ", self.delta, offset, || delta)?; + + #[allow(clippy::collapsible_else_if)] + // Assign lambda + let lambda = + x_p.zip(y_p) + .zip(x_q) + .zip(y_q) + .zip(alpha) + .map(|((((x_p, y_p), x_q), y_q), alpha)| { + if x_q != x_p { + // λ = (y_q - y_p)/(x_q - x_p) + // Here, alpha = inv0(x_q - x_p), which suffices since we + // know that x_q != x_p in this branch. + (y_q - y_p) * alpha + } else { + if !y_p.is_zero_vartime() { + // 3(x_p)^2 + let three_x_p_sq = x_p.square() * pallas::Base::from(3); + // 1 / 2(y_p) + let inv_two_y_p = y_p.invert() * pallas::Base::TWO_INV; + // λ = 3(x_p)^2 / 2(y_p) + three_x_p_sq * inv_two_y_p + } else { + Assigned::Zero + } + } + }); + region.assign_advice(|| "λ", self.lambda, offset, || lambda)?; + + // Calculate (x_r, y_r) + let r = + x_p.zip(y_p) + .zip(x_q) + .zip(y_q) + .zip(lambda) + .map(|((((x_p, y_p), x_q), y_q), lambda)| { + { + if x_p.is_zero_vartime() { + // 0 + Q = Q + (*x_q, *y_q) + } else if x_q.is_zero_vartime() { + // P + 0 = P + (*x_p, *y_p) + } else if (x_q == x_p) && (*y_q == -y_p) { + // P + (-P) maps to (0,0) + (Assigned::Zero, Assigned::Zero) + } else { + // x_r = λ^2 - x_p - x_q + let x_r = lambda.square() - x_p - x_q; + // y_r = λ(x_p - x_r) - y_p + let y_r = lambda * (x_p - x_r) - y_p; + (x_r, y_r) + } + } + }); + + // Assign x_r + let x_r = r.map(|r| r.0); + let x_r_cell = region.assign_advice(|| "x_r", self.x_qr, offset + 1, || x_r)?; + + // Assign y_r + let y_r = r.map(|r| r.1); + let y_r_cell = region.assign_advice(|| "y_r", self.y_qr, offset + 1, || y_r)?; + + let result = EccPoint::from_coordinates_unchecked(x_r_cell, y_r_cell); + + #[cfg(test)] + // Check that the correct sum is obtained. + { + use group::Curve; + + let p = p.point(); + let q = q.point(); + let real_sum = p.zip(q).map(|(p, q)| p + q); + let result = result.point(); + + real_sum + .zip(result) + .assert_if_known(|(real_sum, result)| &real_sum.to_affine() == result); + } + + Ok(result) + } +} + +#[cfg(test)] +pub mod tests { + use group::{prime::PrimeCurveAffine, Curve}; + use halo2_proofs::{ + circuit::{Layouter, Value}, + plonk::Error, + }; + use pasta_curves::{arithmetic::CurveExt, pallas}; + + use crate::ecc::{chip::EccPoint, EccInstructions, NonIdentityPoint}; + + #[allow(clippy::too_many_arguments)] + pub fn test_add< + EccChip: EccInstructions + Clone + Eq + std::fmt::Debug, + >( + chip: EccChip, + mut layouter: impl Layouter, + p_val: pallas::Affine, + p: &NonIdentityPoint, + q_val: pallas::Affine, + q: &NonIdentityPoint, + p_neg: &NonIdentityPoint, + ) -> Result<(), Error> { + // Make sure P and Q are not the same point. + assert_ne!(p_val, q_val); + + // Check complete addition P + (-P) + let zero = { + let result = p.add(layouter.namespace(|| "P + (-P)"), p_neg)?; + result + .inner() + .is_identity() + .assert_if_known(|is_identity| *is_identity); + result + }; + + // Check complete addition 𝒪 + 𝒪 + { + let result = zero.add(layouter.namespace(|| "𝒪 + 𝒪"), &zero)?; + result.constrain_equal(layouter.namespace(|| "𝒪 + 𝒪 = 𝒪"), &zero)?; + } + + // Check P + Q + { + let result = p.add(layouter.namespace(|| "P + Q"), q)?; + let witnessed_result = NonIdentityPoint::new( + chip.clone(), + layouter.namespace(|| "witnessed P + Q"), + Value::known((p_val + q_val).to_affine()), + )?; + result.constrain_equal(layouter.namespace(|| "constrain P + Q"), &witnessed_result)?; + } + + // P + P + { + let result = p.add(layouter.namespace(|| "P + P"), p)?; + let witnessed_result = NonIdentityPoint::new( + chip.clone(), + layouter.namespace(|| "witnessed P + P"), + Value::known((p_val + p_val).to_affine()), + )?; + result.constrain_equal(layouter.namespace(|| "constrain P + P"), &witnessed_result)?; + } + + // P + 𝒪 + { + let result = p.add(layouter.namespace(|| "P + 𝒪"), &zero)?; + result.constrain_equal(layouter.namespace(|| "P + 𝒪 = P"), p)?; + } + + // 𝒪 + P + { + let result = zero.add(layouter.namespace(|| "𝒪 + P"), p)?; + result.constrain_equal(layouter.namespace(|| "𝒪 + P = P"), p)?; + } + + // (x, y) + (ζx, y) should behave like normal P + Q. + let endo_p = p_val.to_curve().endo(); + let endo_p = NonIdentityPoint::new( + chip.clone(), + layouter.namespace(|| "endo(P)"), + Value::known(endo_p.to_affine()), + )?; + p.add(layouter.namespace(|| "P + endo(P)"), &endo_p)?; + + // (x, y) + (ζx, -y) should also behave like normal P + Q. + let endo_p_neg = (-p_val).to_curve().endo(); + let endo_p_neg = NonIdentityPoint::new( + chip.clone(), + layouter.namespace(|| "endo(-P)"), + Value::known(endo_p_neg.to_affine()), + )?; + p.add(layouter.namespace(|| "P + endo(-P)"), &endo_p_neg)?; + + // (x, y) + ((ζ^2)x, y) + let endo_2_p = p_val.to_curve().endo().endo(); + let endo_2_p = NonIdentityPoint::new( + chip.clone(), + layouter.namespace(|| "endo^2(P)"), + Value::known(endo_2_p.to_affine()), + )?; + p.add(layouter.namespace(|| "P + endo^2(P)"), &endo_2_p)?; + + // (x, y) + ((ζ^2)x, -y) + let endo_2_p_neg = (-p_val).to_curve().endo().endo(); + let endo_2_p_neg = NonIdentityPoint::new( + chip, + layouter.namespace(|| "endo^2(-P)"), + Value::known(endo_2_p_neg.to_affine()), + )?; + p.add(layouter.namespace(|| "P + endo^2(-P)"), &endo_2_p_neg)?; + + Ok(()) + } +} diff --git a/halo2_gadgets_optimized/src/ecc/chip/add_incomplete.rs b/halo2_gadgets_optimized/src/ecc/chip/add_incomplete.rs new file mode 100644 index 0000000000..597dfb1bc5 --- /dev/null +++ b/halo2_gadgets_optimized/src/ecc/chip/add_incomplete.rs @@ -0,0 +1,192 @@ +use std::collections::HashSet; + +use super::NonIdentityEccPoint; +use halo2_proofs::{ + circuit::Region, + plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, + poly::Rotation, +}; +use pasta_curves::pallas; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Config { + q_add_incomplete: Selector, + // x-coordinate of P in P + Q = R + pub x_p: Column, + // y-coordinate of P in P + Q = R + pub y_p: Column, + // x-coordinate of Q or R in P + Q = R + pub x_qr: Column, + // y-coordinate of Q or R in P + Q = R + pub y_qr: Column, +} + +impl Config { + pub(super) fn configure( + meta: &mut ConstraintSystem, + x_p: Column, + y_p: Column, + x_qr: Column, + y_qr: Column, + ) -> Self { + meta.enable_equality(x_p); + meta.enable_equality(y_p); + meta.enable_equality(x_qr); + meta.enable_equality(y_qr); + + let config = Self { + q_add_incomplete: meta.selector(), + x_p, + y_p, + x_qr, + y_qr, + }; + + config.create_gate(meta); + + config + } + + pub(crate) fn advice_columns(&self) -> HashSet> { + [self.x_p, self.y_p, self.x_qr, self.y_qr] + .into_iter() + .collect() + } + + fn create_gate(&self, meta: &mut ConstraintSystem) { + // https://p.z.cash/halo2-0.1:ecc-incomplete-addition + meta.create_gate("incomplete addition", |meta| { + let q_add_incomplete = meta.query_selector(self.q_add_incomplete); + let x_p = meta.query_advice(self.x_p, Rotation::cur()); + let y_p = meta.query_advice(self.y_p, Rotation::cur()); + let x_q = meta.query_advice(self.x_qr, Rotation::cur()); + let y_q = meta.query_advice(self.y_qr, Rotation::cur()); + let x_r = meta.query_advice(self.x_qr, Rotation::next()); + let y_r = meta.query_advice(self.y_qr, Rotation::next()); + + // (x_r + x_q + x_p)⋅(x_p − x_q)^2 − (y_p − y_q)^2 = 0 + let poly1 = { + (x_r.clone() + x_q.clone() + x_p.clone()) + * (x_p.clone() - x_q.clone()) + * (x_p.clone() - x_q.clone()) + - (y_p.clone() - y_q.clone()).square() + }; + + // (y_r + y_q)(x_p − x_q) − (y_p − y_q)(x_q − x_r) = 0 + let poly2 = (y_r + y_q.clone()) * (x_p - x_q.clone()) - (y_p - y_q) * (x_q - x_r); + + Constraints::with_selector(q_add_incomplete, [("x_r", poly1), ("y_r", poly2)]) + }); + } + + pub(super) fn assign_region( + &self, + p: &NonIdentityEccPoint, + q: &NonIdentityEccPoint, + offset: usize, + region: &mut Region<'_, pallas::Base>, + ) -> Result { + // Enable `q_add_incomplete` selector + self.q_add_incomplete.enable(region, offset)?; + + // Handle exceptional cases + let (x_p, y_p) = (p.x.value(), p.y.value()); + let (x_q, y_q) = (q.x.value(), q.y.value()); + x_p.zip(y_p) + .zip(x_q) + .zip(y_q) + .error_if_known_and(|(((x_p, y_p), x_q), y_q)| { + // P is point at infinity + (x_p.is_zero_vartime() && y_p.is_zero_vartime()) + // Q is point at infinity + || (x_q.is_zero_vartime() && y_q.is_zero_vartime()) + // x_p = x_q + || (x_p == x_q) + })?; + + // Copy point `p` into `x_p`, `y_p` columns + p.x.copy_advice(|| "x_p", region, self.x_p, offset)?; + p.y.copy_advice(|| "y_p", region, self.y_p, offset)?; + + // Copy point `q` into `x_qr`, `y_qr` columns + q.x.copy_advice(|| "x_q", region, self.x_qr, offset)?; + q.y.copy_advice(|| "y_q", region, self.y_qr, offset)?; + + // Compute the sum `P + Q = R` + let r = x_p + .zip(y_p) + .zip(x_q) + .zip(y_q) + .map(|(((x_p, y_p), x_q), y_q)| { + { + // λ = (y_q - y_p)/(x_q - x_p) + let lambda = (y_q - y_p) * (x_q - x_p).invert(); + // x_r = λ^2 - x_p - x_q + let x_r = lambda.square() - x_p - x_q; + // y_r = λ(x_p - x_r) - y_p + let y_r = lambda * (x_p - x_r) - y_p; + (x_r, y_r) + } + }); + + // Assign the sum to `x_qr`, `y_qr` columns in the next row + let x_r = r.map(|r| r.0); + let x_r_var = region.assign_advice(|| "x_r", self.x_qr, offset + 1, || x_r)?; + + let y_r = r.map(|r| r.1); + let y_r_var = region.assign_advice(|| "y_r", self.y_qr, offset + 1, || y_r)?; + + let result = NonIdentityEccPoint::from_coordinates_unchecked(x_r_var, y_r_var); + + Ok(result) + } +} + +#[cfg(test)] +pub mod tests { + use group::Curve; + use halo2_proofs::{ + circuit::{Layouter, Value}, + plonk::Error, + }; + use pasta_curves::pallas; + + use crate::ecc::{EccInstructions, NonIdentityPoint}; + + #[allow(clippy::too_many_arguments)] + pub fn test_add_incomplete< + EccChip: EccInstructions + Clone + Eq + std::fmt::Debug, + >( + chip: EccChip, + mut layouter: impl Layouter, + p_val: pallas::Affine, + p: &NonIdentityPoint, + q_val: pallas::Affine, + q: &NonIdentityPoint, + p_neg: &NonIdentityPoint, + test_errors: bool, + ) -> Result<(), Error> { + // P + Q + { + let result = p.add_incomplete(layouter.namespace(|| "P + Q"), q)?; + let witnessed_result = NonIdentityPoint::new( + chip, + layouter.namespace(|| "witnessed P + Q"), + Value::known((p_val + q_val).to_affine()), + )?; + result.constrain_equal(layouter.namespace(|| "constrain P + Q"), &witnessed_result)?; + } + + if test_errors { + // P + P should return an error + p.add_incomplete(layouter.namespace(|| "P + P"), p) + .expect_err("P + P should return an error"); + + // P + (-P) should return an error + p.add_incomplete(layouter.namespace(|| "P + (-P)"), p_neg) + .expect_err("P + (-P) should return an error"); + } + + Ok(()) + } +} diff --git a/halo2_gadgets_optimized/src/ecc/chip/constants.rs b/halo2_gadgets_optimized/src/ecc/chip/constants.rs new file mode 100644 index 0000000000..e543d718db --- /dev/null +++ b/halo2_gadgets_optimized/src/ecc/chip/constants.rs @@ -0,0 +1,277 @@ +//! Constants required for the ECC chip. + +use arrayvec::ArrayVec; +use group::{ + ff::{Field, PrimeField}, + Curve, +}; +use halo2_proofs::arithmetic::lagrange_interpolate; +use pasta_curves::{arithmetic::CurveAffine, pallas}; + +/// Window size for fixed-base scalar multiplication +pub const FIXED_BASE_WINDOW_SIZE: usize = 3; + +/// $2^{`FIXED_BASE_WINDOW_SIZE`}$ +pub const H: usize = 1 << FIXED_BASE_WINDOW_SIZE; + +/// Number of windows for a full-width scalar +pub const NUM_WINDOWS: usize = + (pallas::Scalar::NUM_BITS as usize + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE; + +/// Number of windows for a short signed scalar +pub const NUM_WINDOWS_SHORT: usize = + (L_SCALAR_SHORT + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE; + +/// $\ell_\mathsf{value}$ +/// Number of bits in an unsigned short scalar. +pub(crate) const L_SCALAR_SHORT: usize = 64; + +/// The Pallas scalar field modulus is $q = 2^{254} + \mathsf{t_q}$. +/// +pub(crate) const T_Q: u128 = 45560315531506369815346746415080538113; + +/// The Pallas base field modulus is $p = 2^{254} + \mathsf{t_p}$. +/// +pub(crate) const T_P: u128 = 45560315531419706090280762371685220353; + +/// For each fixed base, we calculate its scalar multiples in three-bit windows. +/// Each window will have $2^3 = 8$ points. The tables are computed as described in +/// [the Halo 2 book](https://zcash.github.io/halo2/design/gadgets/ecc/fixed-base-scalar-mul.html#load-fixed-base). +fn compute_window_table(base: C, num_windows: usize) -> Vec<[C; H]> { + let mut window_table: Vec<[C; H]> = Vec::with_capacity(num_windows); + + // Generate window table entries for all windows but the last. + // For these first `num_windows - 1` windows, we compute the multiple [(k+2)*(2^3)^w]B. + // Here, w ranges from [0..`num_windows - 1`) + for w in 0..(num_windows - 1) { + window_table.push( + (0..H) + .map(|k| { + // scalar = (k+2)*(8^w) + let scalar = C::Scalar::from(k as u64 + 2) + * C::Scalar::from(H as u64).pow(&[w as u64, 0, 0, 0]); + (base * scalar).to_affine() + }) + .collect::>() + .into_inner() + .unwrap(), + ); + } + + // Generate window table entries for the last window, w = `num_windows - 1`. + // For the last window, we compute [k * (2^3)^w - sum]B, where sum is defined + // as sum = \sum_{j = 0}^{`num_windows - 2`} 2^{3j+1} + let sum = (0..(num_windows - 1)).fold(C::Scalar::ZERO, |acc, j| { + acc + C::Scalar::from(2).pow(&[FIXED_BASE_WINDOW_SIZE as u64 * j as u64 + 1, 0, 0, 0]) + }); + window_table.push( + (0..H) + .map(|k| { + // scalar = k * (2^3)^w - sum, where w = `num_windows - 1` + let scalar = C::Scalar::from(k as u64) + * C::Scalar::from(H as u64).pow(&[(num_windows - 1) as u64, 0, 0, 0]) + - sum; + (base * scalar).to_affine() + }) + .collect::>() + .into_inner() + .unwrap(), + ); + + window_table +} + +/// For each window, we interpolate the $x$-coordinate. +/// Here, we pre-compute and store the coefficients of the interpolation polynomial. +pub fn compute_lagrange_coeffs(base: C, num_windows: usize) -> Vec<[C::Base; H]> { + // We are interpolating over the 3-bit window, k \in [0..8) + let points: Vec<_> = (0..H).map(|i| C::Base::from(i as u64)).collect(); + + let window_table = compute_window_table(base, num_windows); + + window_table + .iter() + .map(|window_points| { + let x_window_points: Vec<_> = window_points + .iter() + .map(|point| *point.coordinates().unwrap().x()) + .collect(); + lagrange_interpolate(&points, &x_window_points) + .into_iter() + .collect::>() + .into_inner() + .unwrap() + }) + .collect() +} + +/// For each window, $z$ is a field element such that for each point $(x, y)$ in the window: +/// - $z + y = u^2$ (some square in the field); and +/// - $z - y$ is not a square. +/// If successful, return a vector of `(z: u64, us: [C::Base; H])` for each window. +/// +/// This function was used to generate the `z`s and `u`s for the Orchard fixed +/// bases. The outputs of this function have been stored as constants, and it +/// is not called anywhere in this codebase. However, we keep this function here +/// as a utility for those who wish to use it with different parameters. +pub fn find_zs_and_us( + base: C, + num_windows: usize, +) -> Option> { + // Closure to find z and u's for one window + let find_z_and_us = |window_points: &[C]| { + assert_eq!(H, window_points.len()); + + let ys: Vec<_> = window_points + .iter() + .map(|point| *point.coordinates().unwrap().y()) + .collect(); + (0..(1000 * (1 << (2 * H)))).find_map(|z| { + ys.iter() + .map(|&y| { + if (-y + C::Base::from(z)).sqrt().is_none().into() { + (y + C::Base::from(z)).sqrt().into() + } else { + None + } + }) + .collect::>>() + .map(|us| (z, us.into_inner().unwrap())) + }) + }; + + let window_table = compute_window_table(base, num_windows); + window_table + .iter() + .map(|window_points| find_z_and_us(window_points)) + .collect() +} + +/// Test that the z-values and u-values satisfy the conditions: +/// 1. z + y = u^2, +/// 2. z - y is not a square +/// for the y-coordinate of each fixed-base multiple in each window. +#[cfg(any(test, feature = "test-dependencies"))] +#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] +pub fn test_zs_and_us(base: C, z: &[u64], u: &[[[u8; 32]; H]], num_windows: usize) { + let window_table = compute_window_table(base, num_windows); + + for ((u, z), window_points) in u.iter().zip(z.iter()).zip(window_table) { + for (u, point) in u.iter().zip(window_points.iter()) { + let y = *point.coordinates().unwrap().y(); + let mut u_repr = ::Repr::default(); + u_repr.as_mut().copy_from_slice(u); + let u = C::Base::from_repr(u_repr).unwrap(); + assert_eq!(C::Base::from(*z) + y, u * u); // allow either square root + assert!(bool::from((C::Base::from(*z) - y).sqrt().is_none())); + } + } +} + +/// Test that Lagrange interpolation coefficients reproduce the correct x-coordinate +/// for each fixed-base multiple in each window. +#[cfg(any(test, feature = "test-dependencies"))] +#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] +pub fn test_lagrange_coeffs(base: C, num_windows: usize) { + /// Evaluate y = f(x) given the coefficients of f(x) + fn evaluate(x: u8, coeffs: &[C::Base]) -> C::Base { + let x = C::Base::from(x as u64); + coeffs + .iter() + .rev() + .cloned() + .reduce(|acc, coeff| acc * x + coeff) + .unwrap_or(C::Base::ZERO) + } + + let lagrange_coeffs = compute_lagrange_coeffs(base, num_windows); + + // Check first 84 windows, i.e. `k_0, k_1, ..., k_83` + for (idx, coeffs) in lagrange_coeffs[0..(num_windows - 1)].iter().enumerate() { + // Test each three-bit chunk in this window. + for bits in 0..(H as u8) { + { + // Interpolate the x-coordinate using this window's coefficients + let interpolated_x = evaluate::(bits, coeffs); + + // Compute the actual x-coordinate of the multiple [(k+2)*(8^w)]B. + let point = base + * C::Scalar::from(bits as u64 + 2) + * C::Scalar::from(H as u64).pow(&[idx as u64, 0, 0, 0]); + let x = *point.to_affine().coordinates().unwrap().x(); + + // Check that the interpolated x-coordinate matches the actual one. + assert_eq!(x, interpolated_x); + } + } + } + + // Check last window. + for bits in 0..(H as u8) { + // Interpolate the x-coordinate using the last window's coefficients + let interpolated_x = evaluate::(bits, &lagrange_coeffs[num_windows - 1]); + + // Compute the actual x-coordinate of the multiple [k * (8^84) - offset]B, + // where offset = \sum_{j = 0}^{83} 2^{3j+1} + let offset = (0..(num_windows - 1)).fold(C::Scalar::ZERO, |acc, w| { + acc + C::Scalar::from(2).pow(&[FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1, 0, 0, 0]) + }); + let scalar = C::Scalar::from(bits as u64) + * C::Scalar::from(H as u64).pow(&[(num_windows - 1) as u64, 0, 0, 0]) + - offset; + let point = base * scalar; + let x = *point.to_affine().coordinates().unwrap().x(); + + // Check that the interpolated x-coordinate matches the actual one. + assert_eq!(x, interpolated_x); + } +} + +#[cfg(test)] +mod tests { + use ff::FromUniformBytes; + use group::{ff::Field, Curve, Group}; + use pasta_curves::{arithmetic::CurveAffine, pallas}; + use proptest::prelude::*; + + use super::{compute_window_table, find_zs_and_us, test_lagrange_coeffs, H, NUM_WINDOWS}; + + prop_compose! { + /// Generate an arbitrary Pallas point. + pub fn arb_point()(bytes in prop::array::uniform32(0u8..)) -> pallas::Point { + // Instead of rejecting out-of-range bytes, let's reduce them. + let mut buf = [0; 64]; + buf[..32].copy_from_slice(&bytes); + let scalar = pallas::Scalar::from_uniform_bytes(&buf); + pallas::Point::generator() * scalar + } + } + + proptest! { + #[test] + fn lagrange_coeffs( + base in arb_point(), + ) { + test_lagrange_coeffs(base.to_affine(), NUM_WINDOWS); + } + } + + #[test] + fn zs_and_us() { + let base = pallas::Point::random(rand::rngs::OsRng); + let (z, u): (Vec, Vec<[pallas::Base; H]>) = + find_zs_and_us(base.to_affine(), NUM_WINDOWS) + .unwrap() + .into_iter() + .unzip(); + let window_table = compute_window_table(base.to_affine(), NUM_WINDOWS); + + for ((u, z), window_points) in u.iter().zip(z.iter()).zip(window_table) { + for (u, point) in u.iter().zip(window_points.iter()) { + let y = *point.coordinates().unwrap().y(); + assert_eq!(pallas::Base::from(*z) + y, u * u); // allow either square root + assert!(bool::from((pallas::Base::from(*z) - y).sqrt().is_none())); + } + } + } +} diff --git a/halo2_gadgets_optimized/src/ecc/chip/mul.rs b/halo2_gadgets_optimized/src/ecc/chip/mul.rs new file mode 100644 index 0000000000..8e857ae442 --- /dev/null +++ b/halo2_gadgets_optimized/src/ecc/chip/mul.rs @@ -0,0 +1,582 @@ +use super::{add, EccPoint, NonIdentityEccPoint, ScalarVar, T_Q}; +use crate::{ + sinsemilla::primitives as sinsemilla, + utilities::{bool_check, lookup_range_check::LookupRangeCheckConfig, ternary}, +}; +use std::{ + convert::TryInto, + ops::{Deref, Range}, +}; + +use ff::{Field, PrimeField}; +use halo2_proofs::{ + circuit::{AssignedCell, Layouter, Region, Value}, + plonk::{Advice, Assigned, Column, ConstraintSystem, Constraints, Error, Selector}, + poly::Rotation, +}; +use uint::construct_uint; + +use pasta_curves::pallas; + +mod complete; +pub(super) mod incomplete; +mod overflow; + +/// Number of bits for which complete addition needs to be used in variable-base +/// scalar multiplication +const NUM_COMPLETE_BITS: usize = 3; + +// Bits used in incomplete addition. k_{254} to k_{4} inclusive +const INCOMPLETE_LEN: usize = pallas::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS; + +// Bits k_{254} to k_{4} inclusive are used in incomplete addition. +// The `hi` half is k_{254} to k_{130} inclusive (length 125 bits). +// (It is a coincidence that k_{130} matches the boundary of the +// overflow check described in [the book](https://zcash.github.io/halo2/design/gadgets/ecc/var-base-scalar-mul.html#overflow-check).) +const INCOMPLETE_HI_RANGE: Range = 0..INCOMPLETE_HI_LEN; +const INCOMPLETE_HI_LEN: usize = INCOMPLETE_LEN / 2; + +// Bits k_{254} to k_{4} inclusive are used in incomplete addition. +// The `lo` half is k_{129} to k_{4} inclusive (length 126 bits). +const INCOMPLETE_LO_RANGE: Range = INCOMPLETE_HI_LEN..INCOMPLETE_LEN; +const INCOMPLETE_LO_LEN: usize = INCOMPLETE_LEN - INCOMPLETE_HI_LEN; + +// Bits k_{3} to k_{1} inclusive are used in complete addition. +// Bit k_{0} is handled separately. +const COMPLETE_RANGE: Range = INCOMPLETE_LEN..(INCOMPLETE_LEN + NUM_COMPLETE_BITS); + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Config { + // Selector used to check switching logic on LSB + q_mul_lsb: Selector, + // Configuration used in complete addition + add_config: add::Config, + // Configuration used for `hi` bits of the scalar + hi_config: incomplete::Config, + // Configuration used for `lo` bits of the scalar + lo_config: incomplete::Config, + // Configuration used for complete addition part of double-and-add algorithm + complete_config: complete::Config, + // Configuration used to check for overflow + overflow_config: overflow::Config, +} + +impl Config { + pub(super) fn configure( + meta: &mut ConstraintSystem, + add_config: add::Config, + lookup_config: LookupRangeCheckConfig, + advices: [Column; 10], + ) -> Self { + let hi_config = incomplete::Config::configure( + meta, advices[9], advices[3], advices[0], advices[1], advices[4], advices[5], + ); + let lo_config = incomplete::Config::configure( + meta, advices[6], advices[7], advices[0], advices[1], advices[8], advices[2], + ); + let complete_config = complete::Config::configure(meta, advices[9], add_config); + let overflow_config = + overflow::Config::configure(meta, lookup_config, advices[6..9].try_into().unwrap()); + + let config = Self { + q_mul_lsb: meta.selector(), + add_config, + hi_config, + lo_config, + complete_config, + overflow_config, + }; + + config.create_gate(meta); + + assert_eq!( + config.hi_config.double_and_add.x_p, config.lo_config.double_and_add.x_p, + "x_p is shared across hi and lo halves." + ); + assert_eq!( + config.hi_config.y_p, config.lo_config.y_p, + "y_p is shared across hi and lo halves." + ); + + // For both hi_config and lo_config: + // z and lambda1 are assigned on the same row as the add_config output. + // Therefore, z and lambda1 must not overlap with add_config.x_qr, add_config.y_qr. + let add_config_outputs = config.add_config.output_columns(); + { + assert!( + !add_config_outputs.contains(&config.hi_config.z), + "incomplete config z cannot overlap with complete addition columns." + ); + assert!( + !add_config_outputs.contains(&config.hi_config.double_and_add.lambda_1), + "incomplete config lambda1 cannot overlap with complete addition columns." + ); + } + { + assert!( + !add_config_outputs.contains(&config.lo_config.z), + "incomplete config z cannot overlap with complete addition columns." + ); + assert!( + !add_config_outputs.contains(&config.lo_config.double_and_add.lambda_1), + "incomplete config lambda1 cannot overlap with complete addition columns." + ); + } + + config + } + + fn create_gate(&self, meta: &mut ConstraintSystem) { + // If `lsb` is 0, (x, y) = (x_p, -y_p). If `lsb` is 1, (x, y) = (0,0). + // https://p.z.cash/halo2-0.1:ecc-var-mul-lsb-gate?partial + meta.create_gate("LSB check", |meta| { + let q_mul_lsb = meta.query_selector(self.q_mul_lsb); + + let z_1 = meta.query_advice(self.complete_config.z_complete, Rotation::cur()); + let z_0 = meta.query_advice(self.complete_config.z_complete, Rotation::next()); + let x_p = meta.query_advice(self.add_config.x_p, Rotation::cur()); + let y_p = meta.query_advice(self.add_config.y_p, Rotation::cur()); + let base_x = meta.query_advice(self.add_config.x_p, Rotation::next()); + let base_y = meta.query_advice(self.add_config.y_p, Rotation::next()); + + // z_0 = 2 * z_1 + k_0 + // => k_0 = z_0 - 2 * z_1 + let lsb = z_0 - z_1 * pallas::Base::from(2); + + let bool_check = bool_check(lsb.clone()); + + // `lsb` = 0 => (x_p, y_p) = (x, -y) + // `lsb` = 1 => (x_p, y_p) = (0,0) + let lsb_x = ternary(lsb.clone(), x_p.clone(), x_p - base_x); + let lsb_y = ternary(lsb, y_p.clone(), y_p + base_y); + + Constraints::with_selector( + q_mul_lsb, + [ + ("bool_check", bool_check), + ("lsb_x", lsb_x), + ("lsb_y", lsb_y), + ], + ) + }); + } + + pub(super) fn assign( + &self, + mut layouter: impl Layouter, + alpha: AssignedCell, + base: &NonIdentityEccPoint, + ) -> Result<(EccPoint, ScalarVar), Error> { + let (result, zs): (EccPoint, Vec>) = layouter.assign_region( + || "variable-base scalar mul", + |mut region| { + let offset = 0; + + // Case `base` into an `EccPoint` for later use. + let base_point: EccPoint = base.clone().into(); + + // Decompose `k = alpha + t_q` bitwise (big-endian bit order). + let bits = decompose_for_scalar_mul(alpha.value()); + + // Define ranges for each part of the algorithm. + let bits_incomplete_hi = &bits[INCOMPLETE_HI_RANGE]; + let bits_incomplete_lo = &bits[INCOMPLETE_LO_RANGE]; + let lsb = bits[pallas::Scalar::NUM_BITS as usize - 1]; + + // Initialize the accumulator `acc = [2]base` using complete addition. + let acc = + self.add_config + .assign_region(&base_point, &base_point, offset, &mut region)?; + + // Increase the offset by 1 after complete addition. + let offset = offset + 1; + + // Initialize the running sum for scalar decomposition to zero. + // + // `incomplete::Config::double_and_add` will copy this cell directly into + // itself. This is fine because we are just assigning the same value to + // the same cell twice, and then applying an equality constraint between + // the cell and itself (which the permutation argument treats as a no-op). + let z_init = Z(region.assign_advice_from_constant( + || "z_init = 0", + self.hi_config.z, + offset, + pallas::Base::zero(), + )?); + + // Double-and-add (incomplete addition) for the `hi` half of the scalar decomposition + let (x_a, y_a, zs_incomplete_hi) = self.hi_config.double_and_add( + &mut region, + offset, + base, + bits_incomplete_hi, + (X(acc.x), Y(acc.y), z_init.clone()), + )?; + + // Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition + let z = zs_incomplete_hi.last().expect("should not be empty"); + let (x_a, y_a, zs_incomplete_lo) = self.lo_config.double_and_add( + &mut region, + offset, + base, + bits_incomplete_lo, + (x_a, y_a, z.clone()), + )?; + + // Move from incomplete addition to complete addition. + // Inside incomplete::double_and_add, the offset was increased once after initialization + // of the running sum. + // Then, the final assignment of double-and-add was made on row + offset + 1. + // Outside of incomplete addition, we must account for these offset increases by adding + // 2 to the incomplete addition length. + assert!(INCOMPLETE_LO_RANGE.len() >= INCOMPLETE_HI_RANGE.len()); + let offset = offset + INCOMPLETE_LO_RANGE.len() + 2; + + // Complete addition + let (acc, zs_complete) = { + let z = zs_incomplete_lo.last().expect("should not be empty"); + // Bits used in complete addition. k_{3} to k_{1} inclusive + // The LSB k_{0} is handled separately. + let bits_complete = &bits[COMPLETE_RANGE]; + self.complete_config.assign_region( + &mut region, + offset, + bits_complete, + &base_point, + x_a, + y_a, + z.clone(), + )? + }; + + // Each iteration of the complete addition uses two rows. + let offset = offset + COMPLETE_RANGE.len() * 2; + + // Process the least significant bit + let z_1 = zs_complete.last().unwrap().clone(); + let (result, z_0) = self.process_lsb(&mut region, offset, base, acc, z_1, lsb)?; + + #[cfg(test)] + // Check that the correct multiple is obtained. + { + use group::Curve; + + let base = base.point(); + let alpha = alpha + .value() + .map(|alpha| pallas::Scalar::from_repr(alpha.to_repr()).unwrap()); + let real_mul = base.zip(alpha).map(|(base, alpha)| base * alpha); + let result = result.point(); + + real_mul + .zip(result) + .assert_if_known(|(real_mul, result)| &real_mul.to_affine() == result); + } + + let zs = { + let mut zs = std::iter::empty() + .chain(Some(z_init)) + .chain(zs_incomplete_hi.into_iter()) + .chain(zs_incomplete_lo.into_iter()) + .chain(zs_complete.into_iter()) + .chain(Some(z_0)) + .collect::>(); + assert_eq!(zs.len(), pallas::Scalar::NUM_BITS as usize + 1); + + // This reverses zs to give us [z_0, z_1, ..., z_{254}, z_{255}]. + zs.reverse(); + zs + }; + + Ok((result, zs)) + }, + )?; + + self.overflow_config.overflow_check( + layouter.namespace(|| "overflow check"), + alpha.clone(), + &zs, + )?; + + Ok((result, ScalarVar::BaseFieldElem(alpha))) + } + + /// Processes the final scalar bit `k_0`. + /// + /// Assumptions for this sub-region: + /// - `acc_x` and `acc_y` are assigned in row `offset` by the previous complete + /// addition. They will be copied into themselves. + /// - `z_1 is assigned in row `offset` by the mul::complete region assignment. We only + /// use its value here. + /// + /// `x_p` and `y_p` are assigned here, and then copied into themselves by the complete + /// addition subregion. + /// + /// ```text + /// | x_p | y_p | acc_x | acc_y | complete addition | z_1 | q_mul_lsb = 1 + /// |base_x|base_y| res_x | res_y | | | | | | z_0 | + /// ``` + /// + /// [Specification](https://p.z.cash/halo2-0.1:ecc-var-mul-lsb-gate?partial). + fn process_lsb( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + base: &NonIdentityEccPoint, + acc: EccPoint, + z_1: Z, + lsb: Value, + ) -> Result<(EccPoint, Z), Error> { + // Enforce switching logic on LSB using a custom gate + self.q_mul_lsb.enable(region, offset)?; + + // z_1 has been assigned at (z_complete, offset). + // Assign z_0 = 2⋅z_1 + k_0 + let z_0 = { + let z_0_val = z_1.value().zip(lsb).map(|(z_1, lsb)| { + let lsb = pallas::Base::from(lsb as u64); + z_1 * pallas::Base::from(2) + lsb + }); + let z_0_cell = region.assign_advice( + || "z_0", + self.complete_config.z_complete, + offset + 1, + || z_0_val, + )?; + + Z(z_0_cell) + }; + + // Copy in `base_x`, `base_y` to use in the LSB gate + base.x() + .copy_advice(|| "copy base_x", region, self.add_config.x_p, offset + 1)?; + base.y() + .copy_advice(|| "copy base_y", region, self.add_config.y_p, offset + 1)?; + + // If `lsb` is 0, return `Acc + (-P)`. If `lsb` is 1, simply return `Acc + 0`. + let x = lsb.and_then(|lsb| { + if !lsb { + base.x.value().cloned() + } else { + Value::known(Assigned::Zero) + } + }); + + let y = lsb.and_then(|lsb| { + if !lsb { + -base.y.value() + } else { + Value::known(Assigned::Zero) + } + }); + + let x_cell = region.assign_advice(|| "x", self.add_config.x_p, offset, || x)?; + let y_cell = region.assign_advice(|| "y", self.add_config.y_p, offset, || y)?; + + let p = EccPoint::from_coordinates_unchecked(x_cell, y_cell); + + // Return the result of the final complete addition as `[scalar]B` + let result = self.add_config.assign_region(&p, &acc, offset, region)?; + + Ok((result, z_0)) + } +} + +#[derive(Clone, Debug)] +// `x`-coordinate of the accumulator. +struct X(AssignedCell, F>); +impl Deref for X { + type Target = AssignedCell, F>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Clone, Debug)] +// `y`-coordinate of the accumulator. +struct Y(AssignedCell, F>); +impl Deref for Y { + type Target = AssignedCell, F>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Clone, Debug)] +// Cumulative sum `z` used to decompose the scalar. +struct Z(AssignedCell); +impl Deref for Z { + type Target = AssignedCell; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// https://p.z.cash/halo2-0.1:ecc-var-mul-witness-scalar?partial +#[allow(clippy::assign_op_pattern)] +#[allow(clippy::ptr_offset_with_cast)] +fn decompose_for_scalar_mul(scalar: Value<&pallas::Base>) -> Vec> { + construct_uint! { + struct U256(4); + } + + let bitstring = scalar.map(|scalar| { + // We use `k = scalar + t_q` in the double-and-add algorithm, where + // the scalar field `F_q = 2^254 + t_q`. + // Note that the addition `scalar + t_q` is not reduced. + // + let scalar = U256::from_little_endian(&scalar.to_repr()); + let t_q = U256::from_little_endian(&T_Q.to_le_bytes()); + let k = scalar + t_q; + + // Little-endian bit representation of `k`. + let bitstring = { + let mut le_bytes = [0u8; 32]; + k.to_little_endian(&mut le_bytes); + le_bytes + .into_iter() + .flat_map(|byte| (0..8).map(move |shift| (byte >> shift) % 2 == 1)) + }; + + // Take the first 255 bits. + bitstring + .take(pallas::Scalar::NUM_BITS as usize) + .collect::>() + }); + + // Transpose. + let mut bitstring = bitstring.transpose_vec(pallas::Scalar::NUM_BITS as usize); + // Reverse to get the big-endian bit representation. + bitstring.reverse(); + bitstring +} + +#[cfg(test)] +pub mod tests { + use group::{ + ff::{Field, PrimeField}, + Curve, + }; + use halo2_proofs::{ + circuit::{Chip, Layouter, Value}, + plonk::Error, + }; + use pasta_curves::pallas; + use rand::rngs::OsRng; + + use crate::{ + ecc::{ + chip::{EccChip, EccPoint}, + tests::TestFixedBases, + EccInstructions, NonIdentityPoint, Point, ScalarVar, + }, + utilities::UtilitiesInstructions, + }; + + pub(crate) fn test_mul( + chip: EccChip, + mut layouter: impl Layouter, + p: &NonIdentityPoint>, + p_val: pallas::Affine, + ) -> Result<(), Error> { + let column = chip.config().advices[0]; + + fn constrain_equal_non_id< + EccChip: EccInstructions + Clone + Eq + std::fmt::Debug, + >( + chip: EccChip, + mut layouter: impl Layouter, + base_val: pallas::Affine, + scalar_val: pallas::Base, + result: Point, + ) -> Result<(), Error> { + // Move scalar from base field into scalar field (which always fits + // for Pallas). + let scalar = pallas::Scalar::from_repr(scalar_val.to_repr()).unwrap(); + let expected = NonIdentityPoint::new( + chip, + layouter.namespace(|| "expected point"), + Value::known((base_val * scalar).to_affine()), + )?; + result.constrain_equal(layouter.namespace(|| "constrain result"), &expected) + } + + // [a]B + { + let scalar_val = pallas::Base::random(OsRng); + let (result, _) = { + let scalar = chip.load_private( + layouter.namespace(|| "random scalar"), + column, + Value::known(scalar_val), + )?; + let scalar = ScalarVar::from_base( + chip.clone(), + layouter.namespace(|| "ScalarVar from_base"), + &scalar, + )?; + p.mul(layouter.namespace(|| "random [a]B"), scalar)? + }; + constrain_equal_non_id( + chip.clone(), + layouter.namespace(|| "random [a]B"), + p_val, + scalar_val, + result, + )?; + } + + // [0]B should return (0,0) since variable-base scalar multiplication + // uses complete addition for the final bits of the scalar. + { + let scalar_val = pallas::Base::zero(); + let (result, _) = { + let scalar = chip.load_private( + layouter.namespace(|| "zero"), + column, + Value::known(scalar_val), + )?; + let scalar = ScalarVar::from_base( + chip.clone(), + layouter.namespace(|| "ScalarVar from_base"), + &scalar, + )?; + p.mul(layouter.namespace(|| "[0]B"), scalar)? + }; + result + .inner() + .is_identity() + .assert_if_known(|is_identity| *is_identity); + } + + // [-1]B (the largest possible base field element) + { + let scalar_val = -pallas::Base::one(); + let (result, _) = { + let scalar = chip.load_private( + layouter.namespace(|| "-1"), + column, + Value::known(scalar_val), + )?; + let scalar = ScalarVar::from_base( + chip.clone(), + layouter.namespace(|| "ScalarVar from_base"), + &scalar, + )?; + p.mul(layouter.namespace(|| "[-1]B"), scalar)? + }; + constrain_equal_non_id( + chip, + layouter.namespace(|| "[-1]B"), + p_val, + scalar_val, + result, + )?; + } + + Ok(()) + } +} diff --git a/halo2_gadgets_optimized/src/ecc/chip/mul/complete.rs b/halo2_gadgets_optimized/src/ecc/chip/mul/complete.rs new file mode 100644 index 0000000000..1795ddc62b --- /dev/null +++ b/halo2_gadgets_optimized/src/ecc/chip/mul/complete.rs @@ -0,0 +1,193 @@ +use super::super::{add, EccPoint}; +use super::{COMPLETE_RANGE, X, Y, Z}; +use crate::utilities::{bool_check, ternary}; + +use halo2_proofs::{ + circuit::{Region, Value}, + plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Expression, Selector}, + poly::Rotation, +}; + +use pasta_curves::pallas; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Config { + // Selector used to constrain the cells used in complete addition. + q_mul_decompose_var: Selector, + // Advice column used to decompose scalar in complete addition. + pub z_complete: Column, + // Configuration used in complete addition + add_config: add::Config, +} + +impl Config { + pub(super) fn configure( + meta: &mut ConstraintSystem, + z_complete: Column, + add_config: add::Config, + ) -> Self { + meta.enable_equality(z_complete); + + let config = Self { + q_mul_decompose_var: meta.selector(), + z_complete, + add_config, + }; + + config.create_gate(meta); + + config + } + + /// Gate used to check scalar decomposition is correct. + /// This is used to check the bits used in complete addition, since the incomplete + /// addition gate (controlled by `q_mul`) already checks scalar decomposition for + /// the other bits. + fn create_gate(&self, meta: &mut ConstraintSystem) { + // | y_p | z_complete | + // -------------------- + // | y_p | z_{i + 1} | + // | | base_y | + // | | z_i | + // https://p.z.cash/halo2-0.1:ecc-var-mul-complete-gate + meta.create_gate( + "Decompose scalar for complete bits of variable-base mul", + |meta| { + let q_mul_decompose_var = meta.query_selector(self.q_mul_decompose_var); + // z_{i + 1} + let z_prev = meta.query_advice(self.z_complete, Rotation::prev()); + // z_i + let z_next = meta.query_advice(self.z_complete, Rotation::next()); + + // k_{i} = z_{i} - 2⋅z_{i+1} + let k = z_next - Expression::Constant(pallas::Base::from(2)) * z_prev; + // (k_i) ⋅ (1 - k_i) = 0 + let bool_check = bool_check(k.clone()); + + // base_y + let base_y = meta.query_advice(self.z_complete, Rotation::cur()); + // y_p + let y_p = meta.query_advice(self.add_config.y_p, Rotation::prev()); + + // k_i = 0 => y_p = -base_y + // k_i = 1 => y_p = base_y + let y_switch = ternary(k, base_y.clone() - y_p.clone(), base_y + y_p); + + Constraints::with_selector( + q_mul_decompose_var, + [("bool_check", bool_check), ("y_switch", y_switch)], + ) + }, + ); + } + + #[allow(clippy::type_complexity)] + #[allow(non_snake_case)] + #[allow(clippy::too_many_arguments)] + pub(super) fn assign_region( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + bits: &[Value], + base: &EccPoint, + x_a: X, + y_a: Y, + z: Z, + ) -> Result<(EccPoint, Vec>), Error> { + // Make sure we have the correct number of bits for the complete addition + // part of variable-base scalar mul. + assert_eq!(bits.len(), COMPLETE_RANGE.len()); + + // Enable selectors for complete range + for row in 0..COMPLETE_RANGE.len() { + // Each iteration uses 2 rows (two complete additions) + let row = 2 * row; + // Check scalar decomposition for each iteration. Since the gate enabled by + // `q_mul_decompose_var` queries the previous row, we enable the selector on + // `row + offset + 1` (instead of `row + offset`). + self.q_mul_decompose_var.enable(region, row + offset + 1)?; + } + + // Use x_a, y_a output from incomplete addition + let mut acc = EccPoint::from_coordinates_unchecked(x_a.0, y_a.0); + + // Copy running sum `z` from incomplete addition + let mut z = { + let z = z.copy_advice( + || "Copy `z` running sum from incomplete addition", + region, + self.z_complete, + offset, + )?; + Z(z) + }; + + // Store interstitial running sum `z`s in vector + let mut zs: Vec> = Vec::with_capacity(bits.len()); + + // Complete addition + for (iter, k) in bits.iter().enumerate() { + // Each iteration uses 2 rows (two complete additions) + let row = 2 * iter; + + // | x_p | y_p | x_qr | y_qr | z_complete | + // ------------------------------------------ + // | U_x | U_y | acc_x | acc_y | z_{i + 1} | row + offset + // |acc_x|acc_y|acc+U_x|acc+U_y| base_y | + // | | | res_x | res_y | z_i | + + // Update `z`. + z = { + // z_next = z_cur * 2 + k_next + let z_val = z.value() * Value::known(pallas::Base::from(2)) + + k.map(|k| pallas::Base::from(k as u64)); + let z_cell = + region.assign_advice(|| "z", self.z_complete, row + offset + 2, || z_val)?; + Z(z_cell) + }; + zs.push(z.clone()); + + // Assign `y_p` for complete addition. + let y_p = { + let base_y = base.y.copy_advice( + || "Copy `base.y`", + region, + self.z_complete, + row + offset + 1, + )?; + + // If the bit is set, use `y`; if the bit is not set, use `-y` + let y_p = + base_y + .value() + .cloned() + .zip(k.as_ref()) + .map(|(base_y, k)| if !k { -base_y } else { base_y }); + + // Assign the conditionally-negated y coordinate into the cell it will be + // used from by both the complete addition gate, and the decomposition and + // conditional negation gate. + // + // The complete addition gate will copy this cell onto itself. This is + // fine because we are just assigning the same value to the same cell + // twice, and then applying an equality constraint between the cell and + // itself (which the permutation argument treats as a no-op). + region.assign_advice(|| "y_p", self.add_config.y_p, row + offset, || y_p)? + }; + + // U = P if the bit is set; U = -P is the bit is not set. + let U = EccPoint::from_coordinates_unchecked(base.x.clone(), y_p); + + // Acc + U + let tmp_acc = self + .add_config + .assign_region(&U, &acc, row + offset, region)?; + + // Acc + U + Acc + acc = self + .add_config + .assign_region(&acc, &tmp_acc, row + offset + 1, region)?; + } + Ok((acc, zs)) + } +} diff --git a/halo2_gadgets_optimized/src/ecc/chip/mul/incomplete.rs b/halo2_gadgets_optimized/src/ecc/chip/mul/incomplete.rs new file mode 100644 index 0000000000..8baa115f7b --- /dev/null +++ b/halo2_gadgets_optimized/src/ecc/chip/mul/incomplete.rs @@ -0,0 +1,374 @@ +use super::super::NonIdentityEccPoint; +use super::{X, Y, Z}; +use crate::utilities::bool_check; + +use group::ff::PrimeField; +use halo2_proofs::{ + circuit::{Region, Value}, + plonk::{ + Advice, Column, ConstraintSystem, Constraints, Error, Expression, Selector, VirtualCells, + }, + poly::Rotation, +}; +use pasta_curves::pallas; + +/// A helper struct for implementing single-row double-and-add using incomplete addition. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) struct DoubleAndAdd { + // x-coordinate of the accumulator in each double-and-add iteration. + pub(crate) x_a: Column, + // x-coordinate of the point being added in each double-and-add iteration. + pub(crate) x_p: Column, + // lambda1 in each double-and-add iteration. + pub(crate) lambda_1: Column, + // lambda2 in each double-and-add iteration. + pub(crate) lambda_2: Column, +} + +impl DoubleAndAdd { + /// Derives the expression `x_r = lambda_1^2 - x_a - x_p`. + pub(crate) fn x_r( + &self, + meta: &mut VirtualCells, + rotation: Rotation, + ) -> Expression { + let x_a = meta.query_advice(self.x_a, rotation); + let x_p = meta.query_advice(self.x_p, rotation); + let lambda_1 = meta.query_advice(self.lambda_1, rotation); + lambda_1.square() - x_a - x_p + } + + /// Derives the expression `Y_A = (lambda_1 + lambda_2) * (x_a - x_r)`. + /// + /// Note that this is missing the factor of `1/2`; the Sinsemilla constraints factor + /// it out, so we leave it up to the caller to handle it. + #[allow(non_snake_case)] + pub(crate) fn Y_A( + &self, + meta: &mut VirtualCells, + rotation: Rotation, + ) -> Expression { + let x_a = meta.query_advice(self.x_a, rotation); + let lambda_1 = meta.query_advice(self.lambda_1, rotation); + let lambda_2 = meta.query_advice(self.lambda_2, rotation); + (lambda_1 + lambda_2) * (x_a - self.x_r(meta, rotation)) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) struct Config { + // Selector constraining the first row of incomplete addition. + pub(super) q_mul_1: Selector, + // Selector constraining the main loop of incomplete addition. + pub(super) q_mul_2: Selector, + // Selector constraining the last row of incomplete addition. + pub(super) q_mul_3: Selector, + // Cumulative sum used to decompose the scalar. + pub(super) z: Column, + // Logic specific to merged double-and-add. + pub(super) double_and_add: DoubleAndAdd, + // y-coordinate of the point being added in each double-and-add iteration. + pub(super) y_p: Column, +} + +impl Config { + pub(super) fn configure( + meta: &mut ConstraintSystem, + z: Column, + x_a: Column, + x_p: Column, + y_p: Column, + lambda_1: Column, + lambda_2: Column, + ) -> Self { + meta.enable_equality(z); + meta.enable_equality(lambda_1); + + let config = Self { + q_mul_1: meta.selector(), + q_mul_2: meta.selector(), + q_mul_3: meta.selector(), + z, + double_and_add: DoubleAndAdd { + x_a, + x_p, + lambda_1, + lambda_2, + }, + y_p, + }; + + config.create_gate(meta); + + config + } + + // Gate for incomplete addition part of variable-base scalar multiplication. + fn create_gate(&self, meta: &mut ConstraintSystem) { + // Closure to compute x_{R,i} = λ_{1,i}^2 - x_{A,i} - x_{P,i} + let x_r = |meta: &mut VirtualCells, rotation: Rotation| { + self.double_and_add.x_r(meta, rotation) + }; + + // Closure to compute y_{A,i} = (λ_{1,i} + λ_{2,i}) * (x_{A,i} - x_{R,i}) / 2 + let y_a = |meta: &mut VirtualCells, rotation: Rotation| { + self.double_and_add.Y_A(meta, rotation) * pallas::Base::TWO_INV + }; + + // Constraints used for q_mul_{2, 3} == 1 + // https://p.z.cash/halo2-0.1:ecc-var-mul-incomplete-main-loop?partial + // https://p.z.cash/halo2-0.1:ecc-var-mul-incomplete-last-row?partial + let for_loop = |meta: &mut VirtualCells, + y_a_next: Expression| { + let one = Expression::Constant(pallas::Base::one()); + + // z_i + let z_cur = meta.query_advice(self.z, Rotation::cur()); + // z_{i+1} + let z_prev = meta.query_advice(self.z, Rotation::prev()); + // x_{A,i} + let x_a_cur = meta.query_advice(self.double_and_add.x_a, Rotation::cur()); + // x_{A,i-1} + let x_a_next = meta.query_advice(self.double_and_add.x_a, Rotation::next()); + // x_{P,i} + let x_p_cur = meta.query_advice(self.double_and_add.x_p, Rotation::cur()); + // y_{P,i} + let y_p_cur = meta.query_advice(self.y_p, Rotation::cur()); + // λ_{1,i} + let lambda1_cur = meta.query_advice(self.double_and_add.lambda_1, Rotation::cur()); + // λ_{2,i} + let lambda2_cur = meta.query_advice(self.double_and_add.lambda_2, Rotation::cur()); + + let y_a_cur = y_a(meta, Rotation::cur()); + + // The current bit in the scalar decomposition, k_i = z_i - 2⋅z_{i+1}. + // Recall that we assigned the cumulative variable `z_i` in descending order, + // i from n down to 0. So z_{i+1} corresponds to the `z_prev` query. + let k = z_cur - z_prev * pallas::Base::from(2); + // Check booleanity of decomposition. + let bool_check = bool_check(k.clone()); + + // λ_{1,i}⋅(x_{A,i} − x_{P,i}) − y_{A,i} + (2k_i - 1) y_{P,i} = 0 + let gradient_1 = lambda1_cur * (x_a_cur.clone() - x_p_cur) - y_a_cur.clone() + + (k * pallas::Base::from(2) - one) * y_p_cur; + + // λ_{2,i}^2 − x_{A,i-1} − x_{R,i} − x_{A,i} = 0 + let secant_line = lambda2_cur.clone().square() + - x_a_next.clone() + - x_r(meta, Rotation::cur()) + - x_a_cur.clone(); + + // λ_{2,i}⋅(x_{A,i} − x_{A,i-1}) − y_{A,i} − y_{A,i-1} = 0 + let gradient_2 = lambda2_cur * (x_a_cur - x_a_next) - y_a_cur - y_a_next; + + std::iter::empty() + .chain(Some(("bool_check", bool_check))) + .chain(Some(("gradient_1", gradient_1))) + .chain(Some(("secant_line", secant_line))) + .chain(Some(("gradient_2", gradient_2))) + }; + + // q_mul_1 == 1 checks + // https://p.z.cash/halo2-0.1:ecc-var-mul-incomplete-first-row + meta.create_gate("q_mul_1 == 1 checks", |meta| { + let q_mul_1 = meta.query_selector(self.q_mul_1); + + let y_a_next = y_a(meta, Rotation::next()); + let y_a_witnessed = meta.query_advice(self.double_and_add.lambda_1, Rotation::cur()); + Constraints::with_selector(q_mul_1, Some(("init y_a", y_a_witnessed - y_a_next))) + }); + + // q_mul_2 == 1 checks + // https://p.z.cash/halo2-0.1:ecc-var-mul-incomplete-main-loop?partial + meta.create_gate("q_mul_2 == 1 checks", |meta| { + let q_mul_2 = meta.query_selector(self.q_mul_2); + + let y_a_next = y_a(meta, Rotation::next()); + + // x_{P,i} + let x_p_cur = meta.query_advice(self.double_and_add.x_p, Rotation::cur()); + // x_{P,i-1} + let x_p_next = meta.query_advice(self.double_and_add.x_p, Rotation::next()); + // y_{P,i} + let y_p_cur = meta.query_advice(self.y_p, Rotation::cur()); + // y_{P,i-1} + let y_p_next = meta.query_advice(self.y_p, Rotation::next()); + + // The base used in double-and-add remains constant. We check that its + // x- and y- coordinates are the same throughout. + let x_p_check = x_p_cur - x_p_next; + let y_p_check = y_p_cur - y_p_next; + + Constraints::with_selector( + q_mul_2, + std::iter::empty() + .chain(Some(("x_p_check", x_p_check))) + .chain(Some(("y_p_check", y_p_check))) + .chain(for_loop(meta, y_a_next)), + ) + }); + + // q_mul_3 == 1 checks + // https://p.z.cash/halo2-0.1:ecc-var-mul-incomplete-last-row?partial + meta.create_gate("q_mul_3 == 1 checks", |meta| { + let q_mul_3 = meta.query_selector(self.q_mul_3); + let y_a_final = meta.query_advice(self.double_and_add.lambda_1, Rotation::next()); + Constraints::with_selector(q_mul_3, for_loop(meta, y_a_final)) + }); + } + + /// We perform incomplete addition on all but the last three bits of the + /// decomposed scalar. + /// We split the bits in the incomplete addition range into "hi" and "lo" + /// halves and process them side by side, using the same rows but with + /// non-overlapping columns. The base is never the identity point even at + /// the boundary between halves. + /// Returns (x, y, z). + #[allow(clippy::type_complexity)] + pub(super) fn double_and_add( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + base: &NonIdentityEccPoint, + bits: &[Value], + acc: (X, Y, Z), + ) -> Result<(X, Y, Vec>), Error> { + // Check that we have the correct number of bits for this double-and-add. + assert_eq!(bits.len(), NUM_BITS); + + // Handle exceptional cases + let (x_p, y_p) = (base.x.value().cloned(), base.y.value().cloned()); + let (x_a, y_a) = (acc.0.value().cloned(), acc.1.value().cloned()); + + x_a.zip(y_a) + .zip(x_p.zip(y_p)) + .error_if_known_and(|((x_a, y_a), (x_p, y_p))| { + // A is point at infinity + (x_p.is_zero_vartime() && y_p.is_zero_vartime()) + // Q is point at infinity + || (x_a.is_zero_vartime() && y_a.is_zero_vartime()) + // x_p = x_a + || (x_p == x_a) + })?; + + // Set q_mul values + { + // q_mul_1 = 1 on offset 0 + self.q_mul_1.enable(region, offset)?; + + let offset = offset + 1; + // q_mul_2 = 1 on all rows after offset 0, excluding the last row. + for idx in 0..(NUM_BITS - 1) { + self.q_mul_2.enable(region, offset + idx)?; + } + + // q_mul_3 = 1 on the last row. + self.q_mul_3.enable(region, offset + NUM_BITS - 1)?; + } + + // Initialise double-and-add + let (mut x_a, mut y_a, mut z) = { + // Initialise the running `z` sum for the scalar bits. + let z = acc.2.copy_advice(|| "starting z", region, self.z, offset)?; + + // Initialise acc + let x_a = acc.0.copy_advice( + || "starting x_a", + region, + self.double_and_add.x_a, + offset + 1, + )?; + let y_a = acc.1.copy_advice( + || "starting y_a", + region, + self.double_and_add.lambda_1, + offset, + )?; + + (x_a, y_a.value().cloned(), z) + }; + + // Increase offset by 1; we used row 0 for initializing `z`. + let offset = offset + 1; + + // Initialise vector to store all interstitial `z` running sum values. + let mut zs: Vec> = Vec::with_capacity(bits.len()); + + // Incomplete addition + for (row, k) in bits.iter().enumerate() { + // z_{i} = 2 * z_{i+1} + k_i + // https://p.z.cash/halo2-0.1:ecc-var-mul-witness-scalar?partial + let z_val = z + .value() + .zip(k.as_ref()) + .map(|(z_val, k)| pallas::Base::from(2) * z_val + pallas::Base::from(*k as u64)); + z = region.assign_advice(|| "z", self.z, row + offset, || z_val)?; + zs.push(Z(z.clone())); + + // Assign `x_p`, `y_p` + region.assign_advice(|| "x_p", self.double_and_add.x_p, row + offset, || x_p)?; + region.assign_advice(|| "y_p", self.y_p, row + offset, || y_p)?; + + // If the bit is set, use `y`; if the bit is not set, use `-y` + let y_p = y_p + .zip(k.as_ref()) + .map(|(y_p, k)| if !k { -y_p } else { y_p }); + + // Compute and assign λ1⋅(x_A − x_P) = y_A − y_P + let lambda1 = y_a + .zip(y_p) + .zip(x_a.value()) + .zip(x_p) + .map(|(((y_a, y_p), x_a), x_p)| (y_a - y_p) * (x_a - x_p).invert()); + region.assign_advice( + || "lambda1", + self.double_and_add.lambda_1, + row + offset, + || lambda1, + )?; + + // x_R = λ1^2 - x_A - x_P + let x_r = lambda1 + .zip(x_a.value()) + .zip(x_p) + .map(|((lambda1, x_a), x_p)| lambda1.square() - x_a - x_p); + + // λ2 = (2(y_A) / (x_A - x_R)) - λ1 + let lambda2 = + lambda1 + .zip(y_a) + .zip(x_a.value()) + .zip(x_r) + .map(|(((lambda1, y_a), x_a), x_r)| { + y_a * pallas::Base::from(2) * (x_a - x_r).invert() - lambda1 + }); + region.assign_advice( + || "lambda2", + self.double_and_add.lambda_2, + row + offset, + || lambda2, + )?; + + // Compute and assign `x_a` for the next row + let x_a_new = lambda2.square() - x_a.value() - x_r; + y_a = lambda2 * (x_a.value() - x_a_new) - y_a; + let x_a_val = x_a_new; + x_a = region.assign_advice( + || "x_a", + self.double_and_add.x_a, + row + offset + 1, + || x_a_val, + )?; + } + + // Witness final y_a + let y_a = region.assign_advice( + || "y_a", + self.double_and_add.lambda_1, + offset + NUM_BITS, + || y_a, + )?; + + Ok((X(x_a), Y(y_a), zs)) + } +} diff --git a/halo2_gadgets_optimized/src/ecc/chip/mul/overflow.rs b/halo2_gadgets_optimized/src/ecc/chip/mul/overflow.rs new file mode 100644 index 0000000000..12101ae82e --- /dev/null +++ b/halo2_gadgets_optimized/src/ecc/chip/mul/overflow.rs @@ -0,0 +1,208 @@ +use super::{T_Q, Z}; +use crate::{ + sinsemilla::primitives as sinsemilla, utilities::lookup_range_check::LookupRangeCheckConfig, +}; + +use group::ff::PrimeField; +use halo2_proofs::circuit::AssignedCell; +use halo2_proofs::{ + circuit::Layouter, + plonk::{Advice, Assigned, Column, ConstraintSystem, Constraints, Error, Expression, Selector}, + poly::Rotation, +}; +use pasta_curves::pallas; + +use std::iter; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Config { + // Selector to check z_0 = alpha + t_q (mod p) + q_mul_overflow: Selector, + // 10-bit lookup table + lookup_config: LookupRangeCheckConfig, + // Advice columns + advices: [Column; 3], +} + +impl Config { + pub(super) fn configure( + meta: &mut ConstraintSystem, + lookup_config: LookupRangeCheckConfig, + advices: [Column; 3], + ) -> Self { + for advice in advices.iter() { + meta.enable_equality(*advice); + } + + let config = Self { + q_mul_overflow: meta.selector(), + lookup_config, + advices, + }; + + config.create_gate(meta); + + config + } + + fn create_gate(&self, meta: &mut ConstraintSystem) { + // https://p.z.cash/halo2-0.1:ecc-var-mul-overflow + meta.create_gate("overflow checks", |meta| { + let q_mul_overflow = meta.query_selector(self.q_mul_overflow); + + // Constant expressions + let one = Expression::Constant(pallas::Base::one()); + let two_pow_124 = Expression::Constant(pallas::Base::from_u128(1 << 124)); + let two_pow_130 = + two_pow_124.clone() * Expression::Constant(pallas::Base::from_u128(1 << 6)); + + let z_0 = meta.query_advice(self.advices[0], Rotation::prev()); + let z_130 = meta.query_advice(self.advices[0], Rotation::cur()); + let eta = meta.query_advice(self.advices[0], Rotation::next()); + + let k_254 = meta.query_advice(self.advices[1], Rotation::prev()); + let alpha = meta.query_advice(self.advices[1], Rotation::cur()); + + // s_minus_lo_130 = s - sum_{i = 0}^{129} 2^i ⋅ s_i + let s_minus_lo_130 = meta.query_advice(self.advices[1], Rotation::next()); + + let s = meta.query_advice(self.advices[2], Rotation::cur()); + let s_check = s - (alpha.clone() + k_254.clone() * two_pow_130); + + // q = 2^254 + t_q is the Pallas scalar field modulus. + // We cast t_q into the base field to check alpha + t_q (mod p). + let t_q = Expression::Constant(pallas::Base::from_u128(T_Q)); + + // z_0 - alpha - t_q = 0 (mod p) + let recovery = z_0 - alpha - t_q; + + // k_254 * (z_130 - 2^124) = 0 + let lo_zero = k_254.clone() * (z_130.clone() - two_pow_124); + + // k_254 * s_minus_lo_130 = 0 + let s_minus_lo_130_check = k_254.clone() * s_minus_lo_130.clone(); + + // (1 - k_254) * (1 - z_130 * eta) * s_minus_lo_130 = 0 + let canonicity = (one.clone() - k_254) * (one - z_130 * eta) * s_minus_lo_130; + + Constraints::with_selector( + q_mul_overflow, + iter::empty() + .chain(Some(("s_check", s_check))) + .chain(Some(("recovery", recovery))) + .chain(Some(("lo_zero", lo_zero))) + .chain(Some(("s_minus_lo_130_check", s_minus_lo_130_check))) + .chain(Some(("canonicity", canonicity))), + ) + }); + } + + pub(super) fn overflow_check( + &self, + mut layouter: impl Layouter, + alpha: AssignedCell, + zs: &[Z], // [z_0, z_1, ..., z_{254}, z_{255}] + ) -> Result<(), Error> { + // s = alpha + k_254 ⋅ 2^130 is witnessed here, and then copied into + // the decomposition as well as the overflow check gate. + // In the overflow check gate, we check that s is properly derived + // from alpha and k_254. + let s = { + let k_254 = zs[254].clone(); + let s_val = alpha + .value() + .zip(k_254.value()) + .map(|(alpha, k_254)| alpha + k_254 * pallas::Base::from_u128(1 << 65).square()); + + layouter.assign_region( + || "s = alpha + k_254 ⋅ 2^130", + |mut region| { + region.assign_advice( + || "s = alpha + k_254 ⋅ 2^130", + self.advices[0], + 0, + || s_val, + ) + }, + )? + }; + + // Subtract the first 130 low bits of s = alpha + k_254 ⋅ 2^130 + // using thirteen 10-bit lookups, s_{0..=129} + let s_minus_lo_130 = + self.s_minus_lo_130(layouter.namespace(|| "decompose s_{0..=129}"), s.clone())?; + + layouter.assign_region( + || "overflow check", + |mut region| { + let offset = 0; + + // Enable overflow check gate + self.q_mul_overflow.enable(&mut region, offset + 1)?; + + // Copy `z_0` + zs[0].copy_advice(|| "copy z_0", &mut region, self.advices[0], offset)?; + + // Copy `z_130` + zs[130].copy_advice(|| "copy z_130", &mut region, self.advices[0], offset + 1)?; + + // Witness η = inv0(z_130), where inv0(x) = 0 if x = 0, 1/x otherwise + { + let eta = zs[130].value().map(|z_130| Assigned::from(z_130).invert()); + region.assign_advice( + || "η = inv0(z_130)", + self.advices[0], + offset + 2, + || eta, + )?; + } + + // Copy `k_254` = z_254 + zs[254].copy_advice(|| "copy k_254", &mut region, self.advices[1], offset)?; + + // Copy original alpha + alpha.copy_advice( + || "copy original alpha", + &mut region, + self.advices[1], + offset + 1, + )?; + + // Copy weighted sum of the decomposition of s = alpha + k_254 ⋅ 2^130. + s_minus_lo_130.copy_advice( + || "copy s_minus_lo_130", + &mut region, + self.advices[1], + offset + 2, + )?; + + // Copy witnessed s to check that it was properly derived from alpha and k_254. + s.copy_advice(|| "copy s", &mut region, self.advices[2], offset + 1)?; + + Ok(()) + }, + )?; + + Ok(()) + } + + fn s_minus_lo_130( + &self, + mut layouter: impl Layouter, + s: AssignedCell, + ) -> Result, Error> { + // Number of k-bit words we can use in the lookup decomposition. + let num_words = 130 / sinsemilla::K; + assert!(num_words * sinsemilla::K == 130); + + // Decompose the low 130 bits of `s` using thirteen 10-bit lookups. + let zs = self.lookup_config.copy_check( + layouter.namespace(|| "Decompose low 130 bits of s"), + s, + num_words, + false, + )?; + // (s - (2^0 s_0 + 2^1 s_1 + ... + 2^129 s_129)) / 2^130 + Ok(zs[zs.len() - 1].clone()) + } +} diff --git a/halo2_gadgets_optimized/src/ecc/chip/mul_fixed.rs b/halo2_gadgets_optimized/src/ecc/chip/mul_fixed.rs new file mode 100644 index 0000000000..d0781056b8 --- /dev/null +++ b/halo2_gadgets_optimized/src/ecc/chip/mul_fixed.rs @@ -0,0 +1,496 @@ +use super::{ + add, add_incomplete, EccBaseFieldElemFixed, EccScalarFixed, EccScalarFixedShort, FixedPoint, + NonIdentityEccPoint, FIXED_BASE_WINDOW_SIZE, H, +}; +use crate::utilities::decompose_running_sum::RunningSumConfig; + +use std::marker::PhantomData; + +use group::{ + ff::{Field, PrimeField, PrimeFieldBits}, + Curve, +}; +use halo2_proofs::{ + circuit::{AssignedCell, Region, Value}, + plonk::{ + Advice, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector, + VirtualCells, + }, + poly::Rotation, +}; +use lazy_static::lazy_static; +use pasta_curves::{arithmetic::CurveAffine, pallas}; + +pub mod base_field_elem; +pub mod full_width; +pub mod short; + +lazy_static! { + static ref TWO_SCALAR: pallas::Scalar = pallas::Scalar::from(2); + // H = 2^3 (3-bit window) + static ref H_SCALAR: pallas::Scalar = pallas::Scalar::from(H as u64); + static ref H_BASE: pallas::Base = pallas::Base::from(H as u64); +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Config> { + running_sum_config: RunningSumConfig, + // The fixed Lagrange interpolation coefficients for `x_p`. + lagrange_coeffs: [Column; H], + // The fixed `z` for each window such that `y + z = u^2`. + fixed_z: Column, + // Decomposition of an `n-1`-bit scalar into `k`-bit windows: + // a = a_0 + 2^k(a_1) + 2^{2k}(a_2) + ... + 2^{(n-1)k}(a_{n-1}) + window: Column, + // y-coordinate of accumulator (only used in the final row). + u: Column, + // Configuration for `add` + add_config: add::Config, + // Configuration for `add_incomplete` + add_incomplete_config: add_incomplete::Config, + _marker: PhantomData, +} + +impl> Config { + #[allow(clippy::too_many_arguments)] + pub(super) fn configure( + meta: &mut ConstraintSystem, + lagrange_coeffs: [Column; H], + window: Column, + u: Column, + add_config: add::Config, + add_incomplete_config: add_incomplete::Config, + ) -> Self { + meta.enable_equality(window); + meta.enable_equality(u); + + let q_running_sum = meta.selector(); + let running_sum_config = RunningSumConfig::configure(meta, q_running_sum, window); + + let config = Self { + running_sum_config, + lagrange_coeffs, + fixed_z: meta.fixed_column(), + window, + u, + add_config, + add_incomplete_config, + _marker: PhantomData, + }; + + // Check relationships between `add_config` and `add_incomplete_config`. + assert_eq!( + config.add_config.x_p, config.add_incomplete_config.x_p, + "add and add_incomplete are used internally in mul_fixed." + ); + assert_eq!( + config.add_config.y_p, config.add_incomplete_config.y_p, + "add and add_incomplete are used internally in mul_fixed." + ); + for advice in [config.window, config.u].iter() { + assert_ne!( + *advice, config.add_config.x_qr, + "Do not overlap with output columns of add." + ); + assert_ne!( + *advice, config.add_config.y_qr, + "Do not overlap with output columns of add." + ); + } + + config.running_sum_coords_gate(meta); + + config + } + + /// Check that each window in the running sum decomposition uses the correct y_p + /// and interpolated x_p. + /// + /// This gate is used both in the mul_fixed::base_field_elem and mul_fixed::short + /// helpers, which decompose the scalar using a running sum. + /// + /// This gate is not used in the mul_fixed::full_width helper, since the full-width + /// scalar is witnessed directly as three-bit windows instead of being decomposed + /// via a running sum. + fn running_sum_coords_gate(&self, meta: &mut ConstraintSystem) { + meta.create_gate("Running sum coordinates check", |meta| { + let q_mul_fixed_running_sum = + meta.query_selector(self.running_sum_config.q_range_check()); + + let z_cur = meta.query_advice(self.window, Rotation::cur()); + let z_next = meta.query_advice(self.window, Rotation::next()); + + // z_{i+1} = (z_i - a_i) / 2^3 + // => a_i = z_i - z_{i+1} * 2^3 + let word = z_cur - z_next * pallas::Base::from(H as u64); + + Constraints::with_selector(q_mul_fixed_running_sum, self.coords_check(meta, word)) + }); + } + + /// [Specification](https://p.z.cash/halo2-0.1:ecc-fixed-mul-coordinates). + #[allow(clippy::op_ref)] + fn coords_check( + &self, + meta: &mut VirtualCells<'_, pallas::Base>, + window: Expression, + ) -> Vec<(&'static str, Expression)> { + let y_p = meta.query_advice(self.add_config.y_p, Rotation::cur()); + let x_p = meta.query_advice(self.add_config.x_p, Rotation::cur()); + let z = meta.query_fixed(self.fixed_z); + let u = meta.query_advice(self.u, Rotation::cur()); + + let window_pow: Vec> = (0..H) + .map(|pow| { + (0..pow).fold(Expression::Constant(pallas::Base::one()), |acc, _| { + acc * window.clone() + }) + }) + .collect(); + + let interpolated_x = window_pow.iter().zip(self.lagrange_coeffs.iter()).fold( + Expression::Constant(pallas::Base::zero()), + |acc, (window_pow, coeff)| acc + (window_pow.clone() * meta.query_fixed(*coeff)), + ); + + // Check interpolation of x-coordinate + let x_check = interpolated_x - x_p.clone(); + // Check that `y + z = u^2`, where `z` is fixed and `u`, `y` are witnessed + let y_check = u.square() - y_p.clone() - z; + // Check that (x, y) is on the curve + let on_curve = + y_p.square() - x_p.clone().square() * x_p - Expression::Constant(pallas::Affine::b()); + + vec![ + ("check x", x_check), + ("check y", y_check), + ("on-curve", on_curve), + ] + } + + #[allow(clippy::type_complexity)] + fn assign_region_inner, const NUM_WINDOWS: usize>( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + scalar: &ScalarFixed, + base: &F, + coords_check_toggle: Selector, + ) -> Result<(NonIdentityEccPoint, NonIdentityEccPoint), Error> { + // Assign fixed columns for given fixed base + self.assign_fixed_constants::(region, offset, base, coords_check_toggle)?; + + // Initialize accumulator + let acc = self.initialize_accumulator::(region, offset, base, scalar)?; + + // Process all windows excluding least and most significant windows + let acc = self.add_incomplete::(region, offset, acc, base, scalar)?; + + // Process most significant window + let mul_b = self.process_msb::(region, offset, base, scalar)?; + + Ok((acc, mul_b)) + } + + /// [Specification](https://p.z.cash/halo2-0.1:ecc-fixed-mul-load-base). + fn assign_fixed_constants, const NUM_WINDOWS: usize>( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + base: &F, + coords_check_toggle: Selector, + ) -> Result<(), Error> { + let mut constants = None; + let build_constants = || { + let lagrange_coeffs = base.lagrange_coeffs(); + assert_eq!(lagrange_coeffs.len(), NUM_WINDOWS); + + let z = base.z(); + assert_eq!(z.len(), NUM_WINDOWS); + + (lagrange_coeffs, z) + }; + + // Assign fixed columns for given fixed base + for window in 0..NUM_WINDOWS { + coords_check_toggle.enable(region, window + offset)?; + + // Assign x-coordinate Lagrange interpolation coefficients + for k in 0..H { + region.assign_fixed( + || { + format!( + "Lagrange interpolation coeff for window: {:?}, k: {:?}", + window, k + ) + }, + self.lagrange_coeffs[k], + window + offset, + || { + if constants.as_ref().is_none() { + constants = Some(build_constants()); + } + let lagrange_coeffs = &constants.as_ref().unwrap().0; + Value::known(lagrange_coeffs[window][k]) + }, + )?; + } + + // Assign z-values for each window + region.assign_fixed( + || format!("z-value for window: {:?}", window), + self.fixed_z, + window + offset, + || { + let z = &constants.as_ref().unwrap().1; + Value::known(pallas::Base::from(z[window])) + }, + )?; + } + + Ok(()) + } + + /// Assigns the values used to process a window. + fn process_window, const NUM_WINDOWS: usize>( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + w: usize, + k_usize: Value, + window_scalar: Value, + base: &F, + ) -> Result { + let base_value = base.generator(); + let base_u = base.u(); + assert_eq!(base_u.len(), NUM_WINDOWS); + + // Compute [window_scalar]B + let mul_b = { + let mul_b = window_scalar.map(|scalar| base_value * scalar); + let mul_b = mul_b.map(|mul_b| mul_b.to_affine().coordinates().unwrap()); + + let x = mul_b.map(|mul_b| { + let x = *mul_b.x(); + assert!(x != pallas::Base::zero()); + x.into() + }); + let x = region.assign_advice( + || format!("mul_b_x, window {}", w), + self.add_config.x_p, + offset + w, + || x, + )?; + + let y = mul_b.map(|mul_b| { + let y = *mul_b.y(); + assert!(y != pallas::Base::zero()); + y.into() + }); + let y = region.assign_advice( + || format!("mul_b_y, window {}", w), + self.add_config.y_p, + offset + w, + || y, + )?; + + NonIdentityEccPoint::from_coordinates_unchecked(x, y) + }; + + // Assign u = (y_p + z_w).sqrt() + let u_val = k_usize.map(|k| pallas::Base::from_repr(base_u[w][k]).unwrap()); + region.assign_advice(|| "u", self.u, offset + w, || u_val)?; + + Ok(mul_b) + } + + fn initialize_accumulator, const NUM_WINDOWS: usize>( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + base: &F, + scalar: &ScalarFixed, + ) -> Result { + // Recall that the message at each window `w` is represented as + // `m_w = [(k_w + 2) ⋅ 8^w]B`. + // When `w = 0`, we have `m_0 = [(k_0 + 2)]B`. + let w = 0; + let k0 = scalar.windows_field()[0]; + let k0_usize = scalar.windows_usize()[0]; + self.process_lower_bits::<_, NUM_WINDOWS>(region, offset, w, k0, k0_usize, base) + } + + fn add_incomplete, const NUM_WINDOWS: usize>( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + mut acc: NonIdentityEccPoint, + base: &F, + scalar: &ScalarFixed, + ) -> Result { + let scalar_windows_field = scalar.windows_field(); + let scalar_windows_usize = scalar.windows_usize(); + assert_eq!(scalar_windows_field.len(), NUM_WINDOWS); + + for (w, (k, k_usize)) in scalar_windows_field + .into_iter() + .zip(scalar_windows_usize) + .enumerate() + // The MSB is processed separately. + .take(NUM_WINDOWS - 1) + // Skip k_0 (already processed). + .skip(1) + { + // Compute [(k_w + 2) ⋅ 8^w]B + // + // This assigns the coordinates of the returned point into the input cells for + // the incomplete addition gate, which will then copy them into themselves. + let mul_b = + self.process_lower_bits::<_, NUM_WINDOWS>(region, offset, w, k, k_usize, base)?; + + // Add to the accumulator. + // + // After the first loop, the accumulator will already be in the input cells + // for the incomplete addition gate, and will be copied into themselves. + acc = self + .add_incomplete_config + .assign_region(&mul_b, &acc, offset + w, region)?; + } + Ok(acc) + } + + /// Assigns the values used to process a window that does not contain the MSB. + fn process_lower_bits, const NUM_WINDOWS: usize>( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + w: usize, + k: Value, + k_usize: Value, + base: &F, + ) -> Result { + // `scalar = [(k_w + 2) ⋅ 8^w] + let scalar = k.map(|k| (k + *TWO_SCALAR) * (*H_SCALAR).pow(&[w as u64, 0, 0, 0])); + + self.process_window::<_, NUM_WINDOWS>(region, offset, w, k_usize, scalar, base) + } + + /// Assigns the values used to process the window containing the MSB. + fn process_msb, const NUM_WINDOWS: usize>( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + base: &F, + scalar: &ScalarFixed, + ) -> Result { + let k_usize = scalar.windows_usize()[NUM_WINDOWS - 1]; + + // offset_acc = \sum_{j = 0}^{NUM_WINDOWS - 2} 2^{FIXED_BASE_WINDOW_SIZE*j + 1} + let offset_acc = (0..(NUM_WINDOWS - 1)).fold(pallas::Scalar::zero(), |acc, w| { + acc + (*TWO_SCALAR).pow(&[FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1, 0, 0, 0]) + }); + + // `scalar = [k * 8^(NUM_WINDOWS - 1) - offset_acc]`. + let scalar = scalar.windows_field()[scalar.windows_field().len() - 1] + .map(|k| k * (*H_SCALAR).pow(&[(NUM_WINDOWS - 1) as u64, 0, 0, 0]) - offset_acc); + + self.process_window::<_, NUM_WINDOWS>( + region, + offset, + NUM_WINDOWS - 1, + k_usize, + scalar, + base, + ) + } +} + +enum ScalarFixed { + FullWidth(EccScalarFixed), + Short(EccScalarFixedShort), + BaseFieldElem(EccBaseFieldElemFixed), +} + +impl From<&EccScalarFixed> for ScalarFixed { + fn from(scalar_fixed: &EccScalarFixed) -> Self { + Self::FullWidth(scalar_fixed.clone()) + } +} + +impl From<&EccScalarFixedShort> for ScalarFixed { + fn from(scalar_fixed: &EccScalarFixedShort) -> Self { + Self::Short(scalar_fixed.clone()) + } +} + +impl From<&EccBaseFieldElemFixed> for ScalarFixed { + fn from(base_field_elem: &EccBaseFieldElemFixed) -> Self { + Self::BaseFieldElem(base_field_elem.clone()) + } +} + +impl ScalarFixed { + /// The scalar decomposition was done in the base field. For computation + /// outside the circuit, we now convert them back into the scalar field. + /// + /// This function does not require that the base field fits inside the scalar field, + /// because the window size fits into either field. + fn windows_field(&self) -> Vec> { + let running_sum_to_windows = |zs: Vec>| { + (0..(zs.len() - 1)) + .map(|idx| { + let z_cur = zs[idx].value(); + let z_next = zs[idx + 1].value(); + let word = z_cur - z_next * Value::known(*H_BASE); + // This assumes that the endianness of the encodings of pallas::Base + // and pallas::Scalar are the same. They happen to be, but we need to + // be careful if this is generalised. + word.map(|word| pallas::Scalar::from_repr(word.to_repr()).unwrap()) + }) + .collect::>() + }; + match self { + Self::BaseFieldElem(scalar) => running_sum_to_windows(scalar.running_sum.to_vec()), + Self::Short(scalar) => running_sum_to_windows( + scalar + .running_sum + .as_ref() + .expect("EccScalarFixedShort has been constrained") + .to_vec(), + ), + Self::FullWidth(scalar) => scalar + .windows + .as_ref() + .expect("EccScalarFixed has been witnessed") + .iter() + .map(|bits| { + // This assumes that the endianness of the encodings of pallas::Base + // and pallas::Scalar are the same. They happen to be, but we need to + // be careful if this is generalised. + bits.value() + .map(|value| pallas::Scalar::from_repr(value.to_repr()).unwrap()) + }) + .collect::>(), + } + } + + /// The scalar decomposition is guaranteed to be in three-bit windows, so we construct + /// `usize` indices from the lowest three bits of each window field element for + /// convenient indexing into `u`-values. + fn windows_usize(&self) -> Vec> { + self.windows_field() + .iter() + .map(|window| { + window.map(|window| { + window + .to_le_bits() + .iter() + .by_vals() + .take(FIXED_BASE_WINDOW_SIZE) + .rev() + .fold(0, |acc, b| 2 * acc + usize::from(b)) + }) + }) + .collect::>() + } +} diff --git a/halo2_gadgets_optimized/src/ecc/chip/mul_fixed/base_field_elem.rs b/halo2_gadgets_optimized/src/ecc/chip/mul_fixed/base_field_elem.rs new file mode 100644 index 0000000000..9a2b0c76ce --- /dev/null +++ b/halo2_gadgets_optimized/src/ecc/chip/mul_fixed/base_field_elem.rs @@ -0,0 +1,528 @@ +use super::super::{EccBaseFieldElemFixed, EccPoint, FixedPoints, NUM_WINDOWS, T_P}; +use super::H_BASE; + +use crate::utilities::bool_check; +use crate::{ + sinsemilla::primitives as sinsemilla, + utilities::{bitrange_subset, lookup_range_check::LookupRangeCheckConfig, range_check}, +}; + +use group::ff::PrimeField; +use halo2_proofs::{ + circuit::{AssignedCell, Layouter}, + plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Expression, Selector}, + poly::Rotation, +}; +use pasta_curves::pallas; + +use std::convert::TryInto; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Config> { + q_mul_fixed_base_field: Selector, + canon_advices: [Column; 3], + lookup_config: LookupRangeCheckConfig, + super_config: super::Config, +} + +impl> Config { + pub(crate) fn configure( + meta: &mut ConstraintSystem, + canon_advices: [Column; 3], + lookup_config: LookupRangeCheckConfig, + super_config: super::Config, + ) -> Self { + for advice in canon_advices.iter() { + meta.enable_equality(*advice); + } + + let config = Self { + q_mul_fixed_base_field: meta.selector(), + canon_advices, + lookup_config, + super_config, + }; + + let add_incomplete_advices = config.super_config.add_incomplete_config.advice_columns(); + for canon_advice in config.canon_advices.iter() { + assert!( + !add_incomplete_advices.contains(canon_advice), + "Deconflict canon_advice columns with incomplete addition columns." + ); + } + + config.create_gate(meta); + + config + } + + fn create_gate(&self, meta: &mut ConstraintSystem) { + // Check that the base field element is canonical. + // https://p.z.cash/halo2-0.1:ecc-fixed-mul-base-canonicity + meta.create_gate("Canonicity checks", |meta| { + let q_mul_fixed_base_field = meta.query_selector(self.q_mul_fixed_base_field); + + let alpha = meta.query_advice(self.canon_advices[0], Rotation::prev()); + // The last three bits of α. + let z_84_alpha = meta.query_advice(self.canon_advices[2], Rotation::prev()); + + // Decompose α into three pieces, in little-endian order: + // α = α_0 (252 bits) || α_1 (2 bits) || α_2 (1 bit). + // + // α_0 is derived, not witnessed. + let alpha_0 = { + let two_pow_252 = pallas::Base::from_u128(1 << 126).square(); + alpha - (z_84_alpha.clone() * two_pow_252) + }; + let alpha_1 = meta.query_advice(self.canon_advices[1], Rotation::cur()); + let alpha_2 = meta.query_advice(self.canon_advices[2], Rotation::cur()); + + let alpha_0_prime = meta.query_advice(self.canon_advices[0], Rotation::cur()); + let z_13_alpha_0_prime = meta.query_advice(self.canon_advices[0], Rotation::next()); + let z_44_alpha = meta.query_advice(self.canon_advices[1], Rotation::next()); + let z_43_alpha = meta.query_advice(self.canon_advices[2], Rotation::next()); + + let decomposition_checks = { + // Range-constrain α_1 to be 2 bits + let alpha_1_range_check = range_check(alpha_1.clone(), 1 << 2); + // Boolean-constrain α_2 + let alpha_2_range_check = bool_check(alpha_2.clone()); + // Check that α_1 + 2^2 α_2 = z_84_alpha + let z_84_alpha_check = z_84_alpha.clone() + - (alpha_1.clone() + alpha_2.clone() * pallas::Base::from(1 << 2)); + + std::iter::empty() + .chain(Some(("alpha_1_range_check", alpha_1_range_check))) + .chain(Some(("alpha_2_range_check", alpha_2_range_check))) + .chain(Some(("z_84_alpha_check", z_84_alpha_check))) + }; + + // Check α_0_prime = α_0 + 2^130 - t_p + let alpha_0_prime_check = { + let two_pow_130 = Expression::Constant(pallas::Base::from_u128(1 << 65).square()); + let t_p = Expression::Constant(pallas::Base::from_u128(T_P)); + alpha_0_prime - (alpha_0 + two_pow_130 - t_p) + }; + + // We want to enforce canonicity of a 255-bit base field element, α. + // That is, we want to check that 0 ≤ α < p, where p is Pallas base + // field modulus p = 2^254 + t_p + // = 2^254 + 45560315531419706090280762371685220353. + // Note that t_p < 2^130. + // + // α has been decomposed into three pieces in little-endian order: + // α = α_0 (252 bits) || α_1 (2 bits) || α_2 (1 bit). + // = α_0 + 2^252 α_1 + 2^254 α_2. + // + // If the MSB α_2 = 1, then: + // - α_2 = 1 => α_1 = 0, and + // - α_2 = 1 => α_0 < t_p. To enforce this: + // - α_2 = 1 => 0 ≤ α_0 < 2^130 + // - alpha_0_hi_120 = 0 (constrain α_0 to be 132 bits) + // - a_43 = 0 or 1 (constrain α_0[130..=131] to be 0) + // - α_2 = 1 => 0 ≤ α_0 + 2^130 - t_p < 2^130 + // => 13 ten-bit lookups of α_0 + 2^130 - t_p + // => z_13_alpha_0_prime = 0 + // + let canon_checks = { + // alpha_0_hi_120 = z_44 - 2^120 z_84 + let alpha_0_hi_120 = { + let two_pow_120 = + Expression::Constant(pallas::Base::from_u128(1 << 60).square()); + z_44_alpha.clone() - z_84_alpha * two_pow_120 + }; + // a_43 = z_43 - (2^3)z_44 + let a_43 = z_43_alpha - z_44_alpha * *H_BASE; + + std::iter::empty() + .chain(Some(("MSB = 1 => alpha_1 = 0", alpha_2.clone() * alpha_1))) + .chain(Some(( + "MSB = 1 => alpha_0_hi_120 = 0", + alpha_2.clone() * alpha_0_hi_120, + ))) + .chain(Some(( + "MSB = 1 => a_43 = 0 or 1", + alpha_2.clone() * bool_check(a_43), + ))) + .chain(Some(( + "MSB = 1 => z_13_alpha_0_prime = 0", + alpha_2 * z_13_alpha_0_prime, + ))) + }; + + Constraints::with_selector( + q_mul_fixed_base_field, + canon_checks + .chain(decomposition_checks) + .chain(Some(("alpha_0_prime check", alpha_0_prime_check))), + ) + }); + } + + pub fn assign( + &self, + mut layouter: impl Layouter, + scalar: AssignedCell, + base: &>::Base, + ) -> Result + where + >::Base: super::super::FixedPoint, + { + let (scalar, acc, mul_b) = layouter.assign_region( + || "Base-field elem fixed-base mul (incomplete addition)", + |mut region| { + let offset = 0; + + // Decompose scalar + let scalar = { + let running_sum = self.super_config.running_sum_config.copy_decompose( + &mut region, + offset, + scalar.clone(), + true, + pallas::Base::NUM_BITS as usize, + NUM_WINDOWS, + )?; + EccBaseFieldElemFixed { + base_field_elem: running_sum[0].clone(), + running_sum: (*running_sum).as_slice().try_into().unwrap(), + } + }; + + let (acc, mul_b) = self.super_config.assign_region_inner::<_, NUM_WINDOWS>( + &mut region, + offset, + &(&scalar).into(), + base, + self.super_config.running_sum_config.q_range_check(), + )?; + + Ok((scalar, acc, mul_b)) + }, + )?; + + // Add to the accumulator and return the final result as `[scalar]B`. + let result = layouter.assign_region( + || "Base-field elem fixed-base mul (complete addition)", + |mut region| { + self.super_config.add_config.assign_region( + &mul_b.clone().into(), + &acc.clone().into(), + 0, + &mut region, + ) + }, + )?; + + #[cfg(test)] + // Check that the correct multiple is obtained. + { + use super::super::FixedPoint; + use group::Curve; + + let scalar = &scalar + .base_field_elem() + .value() + .map(|scalar| pallas::Scalar::from_repr(scalar.to_repr()).unwrap()); + let real_mul = scalar.map(|scalar| base.generator() * scalar); + let result = result.point(); + + real_mul + .zip(result) + .assert_if_known(|(real_mul, result)| &real_mul.to_affine() == result); + } + + // We want to enforce canonicity of a 255-bit base field element, α. + // That is, we want to check that 0 ≤ α < p, where p is Pallas base + // field modulus p = 2^254 + t_p + // = 2^254 + 45560315531419706090280762371685220353. + // Note that t_p < 2^130. + // + // α has been decomposed into three pieces in little-endian order: + // α = α_0 (252 bits) || α_1 (2 bits) || α_2 (1 bit). + // = α_0 + 2^252 α_1 + 2^254 α_2. + // + // If the MSB α_2 = 1, then: + // - α_2 = 1 => α_1 = 0, and + // - α_2 = 1 => α_0 < t_p. To enforce this: + // - α_2 = 1 => 0 ≤ α_0 < 2^130 + // => 13 ten-bit lookups of α_0 + // - α_2 = 1 => 0 ≤ α_0 + 2^130 - t_p < 2^130 + // => 13 ten-bit lookups of α_0 + 2^130 - t_p + // => z_13_alpha_0_prime = 0 + // + let (alpha, running_sum) = (scalar.base_field_elem, &scalar.running_sum); + let z_43_alpha = running_sum[43].clone(); + let z_44_alpha = running_sum[44].clone(); + let z_84_alpha = running_sum[84].clone(); + + // α_0 = α - z_84_alpha * 2^252 + let alpha_0 = alpha + .value() + .zip(z_84_alpha.value()) + .map(|(alpha, z_84_alpha)| { + let two_pow_252 = pallas::Base::from_u128(1 << 126).square(); + alpha - z_84_alpha * two_pow_252 + }); + + let (alpha_0_prime, z_13_alpha_0_prime) = { + // alpha_0_prime = alpha + 2^130 - t_p. + let alpha_0_prime = alpha_0.map(|alpha_0| { + let two_pow_130 = pallas::Base::from_u128(1 << 65).square(); + let t_p = pallas::Base::from_u128(T_P); + alpha_0 + two_pow_130 - t_p + }); + let zs = self.lookup_config.witness_check( + layouter.namespace(|| "Lookup range check alpha_0 + 2^130 - t_p"), + alpha_0_prime, + 13, + false, + )?; + let alpha_0_prime = zs[0].clone(); + + (alpha_0_prime, zs[13].clone()) + }; + + layouter.assign_region( + || "Canonicity checks", + |mut region| { + // Activate canonicity check gate + self.q_mul_fixed_base_field.enable(&mut region, 1)?; + + // Offset 0 + { + let offset = 0; + + // Copy α + alpha.copy_advice(|| "Copy α", &mut region, self.canon_advices[0], offset)?; + + // z_84_alpha = the top three bits of alpha. + z_84_alpha.copy_advice( + || "Copy z_84_alpha", + &mut region, + self.canon_advices[2], + offset, + )?; + } + + // Offset 1 + { + let offset = 1; + // Copy alpha_0_prime = alpha_0 + 2^130 - t_p. + // We constrain this in the custom gate to be derived correctly. + alpha_0_prime.copy_advice( + || "Copy α_0 + 2^130 - t_p", + &mut region, + self.canon_advices[0], + offset, + )?; + + // Decompose α into three pieces, + // α = α_0 (252 bits) || α_1 (2 bits) || α_2 (1 bit). + // We only need to witness α_1 and α_2. α_0 is derived in the gate. + // Witness α_1 = α[252..=253] + let alpha_1 = alpha.value().map(|alpha| bitrange_subset(alpha, 252..254)); + region.assign_advice( + || "α_1 = α[252..=253]", + self.canon_advices[1], + offset, + || alpha_1, + )?; + + // Witness the MSB α_2 = α[254] + let alpha_2 = alpha.value().map(|alpha| bitrange_subset(alpha, 254..255)); + region.assign_advice( + || "α_2 = α[254]", + self.canon_advices[2], + offset, + || alpha_2, + )?; + } + + // Offset 2 + { + let offset = 2; + // Copy z_13_alpha_0_prime + z_13_alpha_0_prime.copy_advice( + || "Copy z_13_alpha_0_prime", + &mut region, + self.canon_advices[0], + offset, + )?; + + // Copy z_44_alpha + z_44_alpha.copy_advice( + || "Copy z_44_alpha", + &mut region, + self.canon_advices[1], + offset, + )?; + + // Copy z_43_alpha + z_43_alpha.copy_advice( + || "Copy z_43_alpha", + &mut region, + self.canon_advices[2], + offset, + )?; + } + + Ok(()) + }, + )?; + + Ok(result) + } +} + +#[cfg(test)] +pub mod tests { + use group::{ + ff::{Field, PrimeField}, + Curve, + }; + use halo2_proofs::{ + circuit::{Chip, Layouter, Value}, + plonk::Error, + }; + use pasta_curves::pallas; + use rand::rngs::OsRng; + + use crate::{ + ecc::{ + chip::{EccChip, FixedPoint, H}, + tests::{BaseField, TestFixedBases}, + FixedPointBaseField, NonIdentityPoint, Point, + }, + utilities::UtilitiesInstructions, + }; + + pub(crate) fn test_mul_fixed_base_field( + chip: EccChip, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + test_single_base( + chip.clone(), + layouter.namespace(|| "base_field_elem"), + FixedPointBaseField::from_inner(chip, BaseField), + BaseField.generator(), + ) + } + + #[allow(clippy::op_ref)] + fn test_single_base( + chip: EccChip, + mut layouter: impl Layouter, + base: FixedPointBaseField>, + base_val: pallas::Affine, + ) -> Result<(), Error> { + let rng = OsRng; + + let column = chip.config().advices[0]; + + fn constrain_equal_non_id( + chip: EccChip, + mut layouter: impl Layouter, + base_val: pallas::Affine, + scalar_val: pallas::Base, + result: Point>, + ) -> Result<(), Error> { + // Move scalar from base field into scalar field (which always fits for Pallas). + let scalar = pallas::Scalar::from_repr(scalar_val.to_repr()).unwrap(); + let expected = NonIdentityPoint::new( + chip, + layouter.namespace(|| "expected point"), + Value::known((base_val * scalar).to_affine()), + )?; + result.constrain_equal(layouter.namespace(|| "constrain result"), &expected) + } + + // [a]B + { + let scalar_fixed = pallas::Base::random(rng); + let result = { + let scalar_fixed = chip.load_private( + layouter.namespace(|| "random base field element"), + column, + Value::known(scalar_fixed), + )?; + base.mul(layouter.namespace(|| "random [a]B"), scalar_fixed)? + }; + constrain_equal_non_id( + chip.clone(), + layouter.namespace(|| "random [a]B"), + base_val, + scalar_fixed, + result, + )?; + } + + // There is a single canonical sequence of window values for which a doubling occurs on the last step: + // 1333333333333333333333333333333333333333333333333333333333333333333333333333333333334 in octal. + // (There is another *non-canonical* sequence + // 5333333333333333333333333333333333333333332711161673731021062440252244051273333333333 in octal.) + { + let h = pallas::Base::from(H as u64); + let scalar_fixed = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334" + .chars() + .fold(pallas::Base::zero(), |acc, c| { + acc * &h + &pallas::Base::from(c.to_digit(8).unwrap() as u64) + }); + let result = { + let scalar_fixed = chip.load_private( + layouter.namespace(|| "mul with double"), + column, + Value::known(scalar_fixed), + )?; + base.mul(layouter.namespace(|| "mul with double"), scalar_fixed)? + }; + constrain_equal_non_id( + chip.clone(), + layouter.namespace(|| "mul with double"), + base_val, + scalar_fixed, + result, + )?; + } + + // [0]B should return (0,0) since it uses complete addition + // on the last step. + { + let scalar_fixed = pallas::Base::zero(); + let result = { + let scalar_fixed = chip.load_private( + layouter.namespace(|| "zero"), + column, + Value::known(scalar_fixed), + )?; + base.mul(layouter.namespace(|| "mul by zero"), scalar_fixed)? + }; + result + .inner() + .is_identity() + .assert_if_known(|is_identity| *is_identity); + } + + // [-1]B is the largest base field element + { + let scalar_fixed = -pallas::Base::one(); + let result = { + let scalar_fixed = chip.load_private( + layouter.namespace(|| "-1"), + column, + Value::known(scalar_fixed), + )?; + base.mul(layouter.namespace(|| "mul by -1"), scalar_fixed)? + }; + constrain_equal_non_id( + chip, + layouter.namespace(|| "mul by -1"), + base_val, + scalar_fixed, + result, + )?; + } + + Ok(()) + } +} diff --git a/halo2_gadgets_optimized/src/ecc/chip/mul_fixed/full_width.rs b/halo2_gadgets_optimized/src/ecc/chip/mul_fixed/full_width.rs new file mode 100644 index 0000000000..886f86bbba --- /dev/null +++ b/halo2_gadgets_optimized/src/ecc/chip/mul_fixed/full_width.rs @@ -0,0 +1,316 @@ +use super::super::{EccPoint, EccScalarFixed, FixedPoints, FIXED_BASE_WINDOW_SIZE, H, NUM_WINDOWS}; + +use crate::utilities::{decompose_word, range_check}; +use arrayvec::ArrayVec; +use ff::PrimeField; +use halo2_proofs::{ + circuit::{AssignedCell, Layouter, Region, Value}, + plonk::{ConstraintSystem, Constraints, Error, Selector}, + poly::Rotation, +}; +use pasta_curves::pallas; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Config> { + q_mul_fixed_full: Selector, + super_config: super::Config, +} + +impl> Config { + pub(crate) fn configure( + meta: &mut ConstraintSystem, + super_config: super::Config, + ) -> Self { + let config = Self { + q_mul_fixed_full: meta.selector(), + super_config, + }; + + config.create_gate(meta); + + config + } + + fn create_gate(&self, meta: &mut ConstraintSystem) { + // Check that each window `k` is within 3 bits + // https://p.z.cash/halo2-0.1:ecc-fixed-mul-full-word + meta.create_gate("Full-width fixed-base scalar mul", |meta| { + let q_mul_fixed_full = meta.query_selector(self.q_mul_fixed_full); + let window = meta.query_advice(self.super_config.window, Rotation::cur()); + + Constraints::with_selector( + q_mul_fixed_full, + self.super_config + .coords_check(meta, window.clone()) + .into_iter() + // Constrain each window to a 3-bit value: + // window * (1 - window) * ... * (7 - window) + .chain(Some(("window range check", range_check(window, H)))), + ) + }); + } + + /// Witnesses the given scalar as `NUM_WINDOWS` 3-bit windows. + /// + /// The scalar is allowed to be non-canonical. + fn witness( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + scalar: Value, + ) -> Result { + let windows = self.decompose_scalar_fixed::<{ pallas::Scalar::NUM_BITS as usize }>( + scalar, offset, region, + )?; + + Ok(EccScalarFixed { + value: scalar, + windows: Some(windows), + }) + } + + /// Witnesses the given scalar as `NUM_WINDOWS` 3-bit windows. + /// + /// The scalar is allowed to be non-canonical. + fn decompose_scalar_fixed( + &self, + scalar: Value, + offset: usize, + region: &mut Region<'_, pallas::Base>, + ) -> Result, NUM_WINDOWS>, Error> { + // Enable `q_mul_fixed_full` selector + for idx in 0..NUM_WINDOWS { + self.q_mul_fixed_full.enable(region, offset + idx)?; + } + + // Decompose scalar into `k-bit` windows + let scalar_windows: Value> = scalar.map(|scalar| { + decompose_word::(&scalar, SCALAR_NUM_BITS, FIXED_BASE_WINDOW_SIZE) + }); + + // Transpose `Value>` into `Vec>`. + let scalar_windows = scalar_windows + .map(|windows| { + windows + .into_iter() + .map(|window| pallas::Base::from(window as u64)) + }) + .transpose_vec(NUM_WINDOWS); + + // Store the scalar decomposition + let mut windows: ArrayVec, NUM_WINDOWS> = + ArrayVec::new(); + for (idx, window) in scalar_windows.into_iter().enumerate() { + let window_cell = region.assign_advice( + || format!("k[{:?}]", offset + idx), + self.super_config.window, + offset + idx, + || window, + )?; + windows.push(window_cell); + } + + Ok(windows) + } + + pub fn assign( + &self, + mut layouter: impl Layouter, + scalar: &EccScalarFixed, + base: &>::FullScalar, + ) -> Result<(EccPoint, EccScalarFixed), Error> + where + >::FullScalar: + super::super::FixedPoint, + { + let (scalar, acc, mul_b) = layouter.assign_region( + || "Full-width fixed-base mul (incomplete addition)", + |mut region| { + let offset = 0; + + // Lazily witness the scalar. + let scalar = match scalar.windows { + None => self.witness(&mut region, offset, scalar.value), + Some(_) => todo!("unimplemented for halo2_gadgets v0.1.0"), + }?; + + let (acc, mul_b) = self.super_config.assign_region_inner::<_, NUM_WINDOWS>( + &mut region, + offset, + &(&scalar).into(), + base, + self.q_mul_fixed_full, + )?; + + Ok((scalar, acc, mul_b)) + }, + )?; + + // Add to the accumulator and return the final result as `[scalar]B`. + let result = layouter.assign_region( + || "Full-width fixed-base mul (last window, complete addition)", + |mut region| { + self.super_config.add_config.assign_region( + &mul_b.clone().into(), + &acc.clone().into(), + 0, + &mut region, + ) + }, + )?; + + #[cfg(test)] + // Check that the correct multiple is obtained. + { + use super::super::FixedPoint; + use group::Curve; + + let real_mul = scalar.value.map(|scalar| base.generator() * scalar); + let result = result.point(); + + real_mul + .zip(result) + .assert_if_known(|(real_mul, result)| &real_mul.to_affine() == result); + } + + Ok((result, scalar)) + } +} + +#[cfg(test)] +pub mod tests { + use group::{ff::Field, Curve}; + use halo2_proofs::{ + circuit::{Layouter, Value}, + plonk::Error, + }; + use pasta_curves::pallas; + use rand::rngs::OsRng; + + use crate::ecc::{ + chip::{EccChip, FixedPoint as _, H}, + tests::{FullWidth, TestFixedBases}, + FixedPoint, NonIdentityPoint, Point, ScalarFixed, + }; + + pub(crate) fn test_mul_fixed( + chip: EccChip, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let test_base = FullWidth::from_pallas_generator(); + test_single_base( + chip.clone(), + layouter.namespace(|| "full_width"), + FixedPoint::from_inner(chip, test_base.clone()), + test_base.generator(), + )?; + + Ok(()) + } + + #[allow(clippy::op_ref)] + fn test_single_base( + chip: EccChip, + mut layouter: impl Layouter, + base: FixedPoint>, + base_val: pallas::Affine, + ) -> Result<(), Error> { + fn constrain_equal_non_id( + chip: EccChip, + mut layouter: impl Layouter, + base_val: pallas::Affine, + scalar_val: pallas::Scalar, + result: Point>, + ) -> Result<(), Error> { + let expected = NonIdentityPoint::new( + chip, + layouter.namespace(|| "expected point"), + Value::known((base_val * scalar_val).to_affine()), + )?; + result.constrain_equal(layouter.namespace(|| "constrain result"), &expected) + } + + // [a]B + { + let scalar_fixed = pallas::Scalar::random(OsRng); + let by = ScalarFixed::new( + chip.clone(), + layouter.namespace(|| "random a"), + Value::known(scalar_fixed), + )?; + + let (result, _) = base.mul(layouter.namespace(|| "random [a]B"), by)?; + constrain_equal_non_id( + chip.clone(), + layouter.namespace(|| "random [a]B"), + base_val, + scalar_fixed, + result, + )?; + } + + // There is a single canonical sequence of window values for which a doubling occurs on the last step: + // 1333333333333333333333333333333333333333333333333333333333333333333333333333333333334 in octal. + // (There is another *non-canonical* sequence + // 5333333333333333333333333333333333333333332711161673731021062440252244051273333333333 in octal.) + { + const LAST_DOUBLING: &str = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334"; + let h = pallas::Scalar::from(H as u64); + let scalar_fixed = LAST_DOUBLING + .chars() + .fold(pallas::Scalar::zero(), |acc, c| { + acc * &h + &pallas::Scalar::from(c.to_digit(8).unwrap() as u64) + }); + let by = ScalarFixed::new( + chip.clone(), + layouter.namespace(|| LAST_DOUBLING), + Value::known(scalar_fixed), + )?; + let (result, _) = base.mul(layouter.namespace(|| "mul with double"), by)?; + + constrain_equal_non_id( + chip.clone(), + layouter.namespace(|| "mul with double"), + base_val, + scalar_fixed, + result, + )?; + } + + // [0]B should return (0,0) since it uses complete addition + // on the last step. + { + let scalar_fixed = pallas::Scalar::zero(); + let zero = ScalarFixed::new( + chip.clone(), + layouter.namespace(|| "0"), + Value::known(scalar_fixed), + )?; + let (result, _) = base.mul(layouter.namespace(|| "mul by zero"), zero)?; + result + .inner() + .is_identity() + .assert_if_known(|is_identity| *is_identity); + } + + // [-1]B is the largest scalar field element. + { + let scalar_fixed = -pallas::Scalar::one(); + let neg_1 = ScalarFixed::new( + chip.clone(), + layouter.namespace(|| "-1"), + Value::known(scalar_fixed), + )?; + let (result, _) = base.mul(layouter.namespace(|| "mul by -1"), neg_1)?; + constrain_equal_non_id( + chip, + layouter.namespace(|| "mul by -1"), + base_val, + scalar_fixed, + result, + )?; + } + + Ok(()) + } +} diff --git a/halo2_gadgets_optimized/src/ecc/chip/mul_fixed/short.rs b/halo2_gadgets_optimized/src/ecc/chip/mul_fixed/short.rs new file mode 100644 index 0000000000..09a2a8d6df --- /dev/null +++ b/halo2_gadgets_optimized/src/ecc/chip/mul_fixed/short.rs @@ -0,0 +1,947 @@ +use std::convert::TryInto; + +use super::super::{EccPoint, EccScalarFixedShort, FixedPoints, L_SCALAR_SHORT, NUM_WINDOWS_SHORT}; +use crate::{ecc::chip::MagnitudeSign, utilities::bool_check}; + +use halo2_proofs::{ + circuit::{AssignedCell, Layouter, Region}, + plonk::{ConstraintSystem, Constraints, Error, Expression, Selector}, + poly::Rotation, +}; +use pasta_curves::pallas; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Config> { + // Selector used for fixed-base scalar mul with short signed exponent. + q_mul_fixed_short: Selector, + super_config: super::Config, +} + +impl> Config { + pub(crate) fn configure( + meta: &mut ConstraintSystem, + super_config: super::Config, + ) -> Self { + let config = Self { + q_mul_fixed_short: meta.selector(), + super_config, + }; + + config.create_gate(meta); + + config + } + + fn create_gate(&self, meta: &mut ConstraintSystem) { + // Gate contains the following constraints: + // - https://p.z.cash/halo2-0.1:ecc-fixed-mul-short-msb + // - https://p.z.cash/halo2-0.1:ecc-fixed-mul-short-conditional-neg + meta.create_gate("Short fixed-base mul gate", |meta| { + let q_mul_fixed_short = meta.query_selector(self.q_mul_fixed_short); + let y_p = meta.query_advice(self.super_config.add_config.y_p, Rotation::cur()); + let y_a = meta.query_advice(self.super_config.add_config.y_qr, Rotation::cur()); + // z_21 = k_21 + let last_window = meta.query_advice(self.super_config.u, Rotation::cur()); + let sign = meta.query_advice(self.super_config.window, Rotation::cur()); + + let one = Expression::Constant(pallas::Base::one()); + + // Check that last window is either 0 or 1. + let last_window_check = bool_check(last_window); + // Check that sign is either 1 or -1. + let sign_check = sign.clone().square() - one; + + // `(x_a, y_a)` is the result of `[m]B`, where `m` is the magnitude. + // We conditionally negate this result using `y_p = y_a * s`, where `s` is the sign. + + // Check that the final `y_p = y_a` or `y_p = -y_a` + // + // This constraint is redundant / unnecessary, because `sign` is constrained + // to -1 or 1 by `sign_check`, and `negation_check` therefore permits a strict + // subset of the cases that this constraint permits. + let y_check = (y_p.clone() - y_a.clone()) * (y_p.clone() + y_a.clone()); + + // Check that the correct sign is witnessed s.t. sign * y_p = y_a + let negation_check = sign * y_p - y_a; + + Constraints::with_selector( + q_mul_fixed_short, + [ + ("last_window_check", last_window_check), + ("sign_check", sign_check), + ("y_check", y_check), + ("negation_check", negation_check), + ], + ) + }); + } + + /// Constraints `magnitude` to be at most 66 bits. + /// + /// The final window is separately constrained to be a single bit, which completes the + /// 64-bit range constraint. + fn decompose( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + magnitude_sign: MagnitudeSign, + ) -> Result { + let (magnitude, sign) = magnitude_sign; + + // Decompose magnitude + let running_sum = self.super_config.running_sum_config.copy_decompose( + region, + offset, + magnitude.clone(), + true, + L_SCALAR_SHORT, + NUM_WINDOWS_SHORT, + )?; + + Ok(EccScalarFixedShort { + magnitude, + sign, + running_sum: Some((*running_sum).as_slice().try_into().unwrap()), + }) + } + + pub fn assign( + &self, + mut layouter: impl Layouter, + scalar: &EccScalarFixedShort, + base: &>::ShortScalar, + ) -> Result<(EccPoint, EccScalarFixedShort), Error> + where + >::ShortScalar: + super::super::FixedPoint, + { + let (scalar, acc, mul_b) = layouter.assign_region( + || "Short fixed-base mul (incomplete addition)", + |mut region| { + let offset = 0; + + // Decompose the scalar + let scalar = match scalar.running_sum { + None => self.decompose( + &mut region, + offset, + (scalar.magnitude.clone(), scalar.sign.clone()), + ), + Some(_) => todo!("unimplemented for halo2_gadgets v0.1.0"), + }?; + + let (acc, mul_b) = self + .super_config + .assign_region_inner::<_, NUM_WINDOWS_SHORT>( + &mut region, + offset, + &(&scalar).into(), + base, + self.super_config.running_sum_config.q_range_check(), + )?; + + Ok((scalar, acc, mul_b)) + }, + )?; + + // Last window + let result = layouter.assign_region( + || "Short fixed-base mul (most significant word)", + |mut region| { + let offset = 0; + // Add to the cumulative sum to get `[magnitude]B`. + let magnitude_mul = self.super_config.add_config.assign_region( + &mul_b.clone().into(), + &acc.clone().into(), + offset, + &mut region, + )?; + + // Increase offset by 1 after complete addition + let offset = offset + 1; + + // Copy sign to `window` column + let sign = scalar.sign.copy_advice( + || "sign", + &mut region, + self.super_config.window, + offset, + )?; + + // Copy last window to `u` column. + // (Although the last window is not a `u` value; we are copying it into the `u` + // column because there is an available cell there.) + let z_21 = scalar.running_sum.as_ref().unwrap()[21].clone(); + z_21.copy_advice(|| "last_window", &mut region, self.super_config.u, offset)?; + + // Conditionally negate `y`-coordinate + let y_val = sign.value().and_then(|sign| { + if sign == &-pallas::Base::one() { + -magnitude_mul.y.value() + } else { + magnitude_mul.y.value().cloned() + } + }); + + // Enable mul_fixed_short selector on final row + self.q_mul_fixed_short.enable(&mut region, offset)?; + + // Assign final `y` to `y_p` column and return final point + let y_var = region.assign_advice( + || "y_var", + self.super_config.add_config.y_p, + offset, + || y_val, + )?; + + Ok(EccPoint::from_coordinates_unchecked(magnitude_mul.x, y_var)) + }, + )?; + + #[cfg(test)] + // Check that the correct multiple is obtained. + // This inlined test is only done for valid 64-bit magnitudes + // and valid +/- 1 signs. + // Invalid values result in constraint failures which are + // tested at the circuit-level. + { + use super::super::FixedPoint; + use group::{ff::PrimeField, Curve}; + + scalar + .magnitude + .value() + .zip(scalar.sign.value()) + .zip(result.point()) + .assert_if_known(|((magnitude, sign), result)| { + let magnitude_is_valid = + magnitude <= &&pallas::Base::from(0xFFFF_FFFF_FFFF_FFFFu64); + let sign_is_valid = sign.square() == pallas::Base::one(); + // Only check the result if the magnitude and sign are valid. + !(magnitude_is_valid && sign_is_valid) || { + let scalar = { + // Move magnitude from base field into scalar field (which always fits + // for Pallas). + let magnitude = pallas::Scalar::from_repr(magnitude.to_repr()).unwrap(); + + let sign = if sign == &&pallas::Base::one() { + pallas::Scalar::one() + } else { + -pallas::Scalar::one() + }; + + magnitude * sign + }; + let real_mul = base.generator() * scalar; + + &real_mul.to_affine() == result + } + }); + } + + Ok((result, scalar)) + } + + /// Multiply the point by sign, using the q_mul_fixed_short gate. + /// Constraints `sign` in {-1, 1} + pub fn assign_scalar_sign( + &self, + mut layouter: impl Layouter, + sign: &AssignedCell, + point: &EccPoint, + ) -> Result { + let signed_point = layouter.assign_region( + || "Signed point", + |mut region| { + let offset = 0; + + // Enable mul_fixed_short selector to check the sign logic. + self.q_mul_fixed_short.enable(&mut region, offset)?; + + // Set "last window" to 0 (this field is irrelevant here). + region.assign_advice_from_constant( + || "u=0", + self.super_config.u, + offset, + pallas::Base::zero(), + )?; + + // Copy sign to `window` column + sign.copy_advice(|| "sign", &mut region, self.super_config.window, offset)?; + + // Assign the input y-coordinate. + point.y.copy_advice( + || "unsigned y", + &mut region, + self.super_config.add_config.y_qr, + offset, + )?; + + // Conditionally negate y-coordinate according to the value of sign + let signed_y_val = sign.value().and_then(|sign| { + if sign == &-pallas::Base::one() { + -point.y.value() + } else { + point.y.value().cloned() + } + }); + + // Assign the output signed y-coordinate. + let signed_y = region.assign_advice( + || "signed y", + self.super_config.add_config.y_p, + offset, + || signed_y_val, + )?; + + Ok(EccPoint { + x: point.x.clone(), + y: signed_y, + }) + }, + )?; + + Ok(signed_point) + } +} + +#[cfg(test)] +pub mod tests { + use group::{ff::PrimeField, Curve, Group}; + use halo2_proofs::{ + arithmetic::CurveAffine, + circuit::{AssignedCell, Chip, Layouter, Value}, + plonk::{Any, Error}, + }; + use pasta_curves::pallas; + + use crate::{ + ecc::{ + chip::{EccChip, FixedPoint, MagnitudeSign}, + tests::{Short, TestFixedBases}, + FixedPointShort, NonIdentityPoint, Point, ScalarFixedShort, + }, + utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions}, + }; + + #[allow(clippy::op_ref)] + pub(crate) fn test_mul_fixed_short( + chip: EccChip, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // test_short + let base_val = Short.generator(); + let test_short = FixedPointShort::from_inner(chip.clone(), Short); + + fn load_magnitude_sign( + chip: EccChip, + mut layouter: impl Layouter, + magnitude: pallas::Base, + sign: pallas::Base, + ) -> Result { + let column = chip.config().advices[0]; + let magnitude = chip.load_private( + layouter.namespace(|| "magnitude"), + column, + Value::known(magnitude), + )?; + let sign = + chip.load_private(layouter.namespace(|| "sign"), column, Value::known(sign))?; + + Ok((magnitude, sign)) + } + + fn constrain_equal_non_id( + chip: EccChip, + mut layouter: impl Layouter, + base_val: pallas::Affine, + scalar_val: pallas::Scalar, + result: Point>, + ) -> Result<(), Error> { + let expected = NonIdentityPoint::new( + chip, + layouter.namespace(|| "expected point"), + Value::known((base_val * scalar_val).to_affine()), + )?; + result.constrain_equal(layouter.namespace(|| "constrain result"), &expected) + } + + let magnitude_signs = [ + ("random [a]B", pallas::Base::from(rand::random::()), { + let mut random_sign = pallas::Base::one(); + if rand::random::() { + random_sign = -random_sign; + } + random_sign + }), + ( + "[2^64 - 1]B", + pallas::Base::from(0xFFFF_FFFF_FFFF_FFFFu64), + pallas::Base::one(), + ), + ( + "-[2^64 - 1]B", + pallas::Base::from(0xFFFF_FFFF_FFFF_FFFFu64), + -pallas::Base::one(), + ), + // There is a single canonical sequence of window values for which a doubling occurs on the last step: + // 1333333333333333333334 in octal. + // [0xB6DB_6DB6_DB6D_B6DC] B + ( + "mul_with_double", + pallas::Base::from(0xB6DB_6DB6_DB6D_B6DCu64), + pallas::Base::one(), + ), + ( + "mul_with_double negative", + pallas::Base::from(0xB6DB_6DB6_DB6D_B6DCu64), + -pallas::Base::one(), + ), + ]; + + for (name, magnitude, sign) in magnitude_signs.iter() { + let (result, _) = { + let magnitude_sign = load_magnitude_sign( + chip.clone(), + layouter.namespace(|| *name), + *magnitude, + *sign, + )?; + let by = ScalarFixedShort::new( + chip.clone(), + layouter.namespace(|| "signed short scalar"), + magnitude_sign, + )?; + test_short.mul(layouter.namespace(|| *name), by)? + }; + // Move from base field into scalar field + let scalar = { + let magnitude = pallas::Scalar::from_repr(magnitude.to_repr()).unwrap(); + let sign = if *sign == pallas::Base::one() { + pallas::Scalar::one() + } else { + -pallas::Scalar::one() + }; + magnitude * sign + }; + constrain_equal_non_id( + chip.clone(), + layouter.namespace(|| *name), + base_val, + scalar, + result, + )?; + } + + let zero_magnitude_signs = [ + ("mul by +zero", pallas::Base::zero(), pallas::Base::one()), + ("mul by -zero", pallas::Base::zero(), -pallas::Base::one()), + ]; + + for (name, magnitude, sign) in zero_magnitude_signs.iter() { + let (result, _) = { + let magnitude_sign = load_magnitude_sign( + chip.clone(), + layouter.namespace(|| *name), + *magnitude, + *sign, + )?; + let by = ScalarFixedShort::new( + chip.clone(), + layouter.namespace(|| "signed short scalar"), + magnitude_sign, + )?; + test_short.mul(layouter.namespace(|| *name), by)? + }; + result + .inner() + .is_identity() + .assert_if_known(|is_identity| *is_identity); + } + + Ok(()) + } + + #[test] + fn invalid_magnitude_sign() { + use crate::{ + ecc::chip::{EccConfig, FixedPoint}, + utilities::UtilitiesInstructions, + }; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + dev::{FailureLocation, MockProver, VerifyFailure}, + plonk::{Circuit, ConstraintSystem, Error}, + }; + + #[derive(Default)] + struct MyCircuit { + magnitude: Value, + sign: Value, + // For test checking + magnitude_error: Value, + } + + impl UtilitiesInstructions for MyCircuit { + type Var = AssignedCell; + } + + impl Circuit for MyCircuit { + type Config = EccConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let advices = [ + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + ]; + let lookup_table = meta.lookup_table_column(); + let table_range_check_tag = meta.lookup_table_column(); + let lagrange_coeffs = [ + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + ]; + + // Shared fixed column for loading constants + let constants = meta.fixed_column(); + meta.enable_constant(constants); + + let range_check = LookupRangeCheckConfig::configure( + meta, + advices[9], + lookup_table, + table_range_check_tag, + ); + EccChip::::configure(meta, advices, lagrange_coeffs, range_check) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let column = config.advices[0]; + + let short_config = config.mul_fixed_short.clone(); + let magnitude_sign = { + let magnitude = self.load_private( + layouter.namespace(|| "load magnitude"), + column, + self.magnitude, + )?; + let sign = + self.load_private(layouter.namespace(|| "load sign"), column, self.sign)?; + ScalarFixedShort::new( + EccChip::construct(config), + layouter.namespace(|| "signed short scalar"), + (magnitude, sign), + )? + }; + + short_config.assign(layouter, &magnitude_sign.inner, &Short)?; + + Ok(()) + } + } + + // Copied from halo2_proofs::dev::util + fn format_value(v: pallas::Base) -> String { + use ff::Field; + if v.is_zero_vartime() { + "0".into() + } else if v == pallas::Base::one() { + "1".into() + } else if v == -pallas::Base::one() { + "-1".into() + } else { + // Format value as hex. + let s = format!("{:?}", v); + // Remove leading zeroes. + let s = s.strip_prefix("0x").unwrap(); + let s = s.trim_start_matches('0'); + format!("0x{}", s) + } + } + + // Magnitude larger than 64 bits should fail + { + let circuits = [ + // 2^64 + MyCircuit { + magnitude: Value::known(pallas::Base::from_u128(1 << 64)), + sign: Value::known(pallas::Base::one()), + magnitude_error: Value::known(pallas::Base::from(1 << 1)), + }, + // -2^64 + MyCircuit { + magnitude: Value::known(pallas::Base::from_u128(1 << 64)), + sign: Value::known(-pallas::Base::one()), + magnitude_error: Value::known(pallas::Base::from(1 << 1)), + }, + // 2^66 + MyCircuit { + magnitude: Value::known(pallas::Base::from_u128(1 << 66)), + sign: Value::known(pallas::Base::one()), + magnitude_error: Value::known(pallas::Base::from(1 << 3)), + }, + // -2^66 + MyCircuit { + magnitude: Value::known(pallas::Base::from_u128(1 << 66)), + sign: Value::known(-pallas::Base::one()), + magnitude_error: Value::known(pallas::Base::from(1 << 3)), + }, + // 2^254 + MyCircuit { + magnitude: Value::known(pallas::Base::from_u128(1 << 127).square()), + sign: Value::known(pallas::Base::one()), + magnitude_error: Value::known( + pallas::Base::from_u128(1 << 95).square() * pallas::Base::from(2), + ), + }, + // -2^254 + MyCircuit { + magnitude: Value::known(pallas::Base::from_u128(1 << 127).square()), + sign: Value::known(-pallas::Base::one()), + magnitude_error: Value::known( + pallas::Base::from_u128(1 << 95).square() * pallas::Base::from(2), + ), + }, + ]; + + for circuit in circuits.iter() { + let prover = MockProver::::run(11, circuit, vec![]).unwrap(); + circuit.magnitude_error.assert_if_known(|magnitude_error| { + assert_eq!( + prover.verify(), + Err(vec![ + VerifyFailure::ConstraintNotSatisfied { + constraint: ( + (17, "Short fixed-base mul gate").into(), + 0, + "last_window_check", + ) + .into(), + location: FailureLocation::InRegion { + region: (3, "Short fixed-base mul (most significant word)") + .into(), + offset: 1, + }, + cell_values: vec![( + ((Any::Advice, 5).into(), 0).into(), + format_value(*magnitude_error), + )], + }, + VerifyFailure::Permutation { + column: (Any::Fixed, 10).into(), + location: FailureLocation::OutsideRegion { row: 0 }, + }, + VerifyFailure::Permutation { + column: (Any::Advice, 4).into(), + location: FailureLocation::InRegion { + region: (2, "Short fixed-base mul (incomplete addition)") + .into(), + offset: 22, + }, + }, + ]) + ); + true + }); + } + } + + // Sign that is not +/- 1 should fail + { + let magnitude_u64 = rand::random::(); + let circuit = MyCircuit { + magnitude: Value::known(pallas::Base::from(magnitude_u64)), + sign: Value::known(pallas::Base::zero()), + magnitude_error: Value::unknown(), + }; + + let negation_check_y = { + *(Short.generator() * pallas::Scalar::from(magnitude_u64)) + .to_affine() + .coordinates() + .unwrap() + .y() + }; + + let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); + assert_eq!( + prover.verify(), + Err(vec![ + VerifyFailure::ConstraintNotSatisfied { + constraint: ((17, "Short fixed-base mul gate").into(), 1, "sign_check") + .into(), + location: FailureLocation::InRegion { + region: (3, "Short fixed-base mul (most significant word)").into(), + offset: 1, + }, + cell_values: vec![(((Any::Advice, 4).into(), 0).into(), "0".to_string())], + }, + VerifyFailure::ConstraintNotSatisfied { + constraint: ( + (17, "Short fixed-base mul gate").into(), + 3, + "negation_check" + ) + .into(), + location: FailureLocation::InRegion { + region: (3, "Short fixed-base mul (most significant word)").into(), + offset: 1, + }, + cell_values: vec![ + ( + ((Any::Advice, 1).into(), 0).into(), + format_value(negation_check_y), + ), + ( + ((Any::Advice, 3).into(), 0).into(), + format_value(negation_check_y), + ), + (((Any::Advice, 4).into(), 0).into(), "0".to_string()), + ], + } + ]) + ); + } + } + + pub(crate) fn test_mul_sign( + chip: EccChip, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // Generate a random non-identity point P + let p_val = pallas::Point::random(rand::rngs::OsRng).to_affine(); + let p = Point::new( + chip.clone(), + layouter.namespace(|| "P"), + Value::known(p_val), + )?; + + // Create -P + let p_neg_val = -p_val; + let p_neg = Point::new( + chip.clone(), + layouter.namespace(|| "-P"), + Value::known(p_neg_val), + )?; + + // Create the identity point + let identity = Point::new( + chip.clone(), + layouter.namespace(|| "identity"), + Value::known(pallas::Point::identity().to_affine()), + )?; + + // Create -1 and 1 scalars + let pos_sign = chip.load_private( + layouter.namespace(|| "positive sign"), + chip.config().advices[0], + Value::known(pallas::Base::one()), + )?; + let neg_sign = chip.load_private( + layouter.namespace(|| "negative sign"), + chip.config().advices[1], + Value::known(-pallas::Base::one()), + )?; + + // [1] P == P + { + let result = p.mul_sign(layouter.namespace(|| "[1] P"), &pos_sign)?; + result.constrain_equal(layouter.namespace(|| "constrain [1] P"), &p)?; + } + + // [-1] P == -P + { + let result = p.mul_sign(layouter.namespace(|| "[1] P"), &neg_sign)?; + result.constrain_equal(layouter.namespace(|| "constrain [1] P"), &p_neg)?; + } + + // [1] 0 == 0 + { + let result = identity.mul_sign(layouter.namespace(|| "[1] O"), &pos_sign)?; + result.constrain_equal(layouter.namespace(|| "constrain [1] 0"), &identity)?; + } + + // [-1] 0 == 0 + { + let result = identity.mul_sign(layouter.namespace(|| "[-1] O"), &neg_sign)?; + result.constrain_equal(layouter.namespace(|| "constrain [1] 0"), &identity)?; + } + + Ok(()) + } + + #[test] + fn invalid_sign_in_mul_sign() { + use crate::{ecc::chip::EccConfig, utilities::UtilitiesInstructions}; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + dev::{FailureLocation, MockProver, VerifyFailure}, + plonk::{Circuit, ConstraintSystem, Error}, + }; + + #[derive(Default)] + struct MyCircuit { + base: Value, + sign: Value, + } + + impl UtilitiesInstructions for MyCircuit { + type Var = AssignedCell; + } + + impl Circuit for MyCircuit { + type Config = EccConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let advices = [ + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + ]; + let lookup_table = meta.lookup_table_column(); + let table_range_check_tag = meta.lookup_table_column(); + let lagrange_coeffs = [ + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + ]; + + // Shared fixed column for loading constants + let constants = meta.fixed_column(); + meta.enable_constant(constants); + + let range_check = LookupRangeCheckConfig::configure( + meta, + advices[9], + lookup_table, + table_range_check_tag, + ); + EccChip::::configure(meta, advices, lagrange_coeffs, range_check) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chip = EccChip::construct(config.clone()); + + let column = config.advices[0]; + + //let short_config = config.mul_fixed_short.clone(); + let base = Point::new(chip, layouter.namespace(|| "load base"), self.base)?; + + let sign = + self.load_private(layouter.namespace(|| "load sign"), column, self.sign)?; + + base.mul_sign(layouter.namespace(|| "[sign] base"), &sign)?; + + Ok(()) + } + } + + // Copied from halo2_proofs::dev::util + fn format_value(v: pallas::Base) -> String { + use ff::Field; + if v.is_zero_vartime() { + "0".into() + } else if v == pallas::Base::one() { + "1".into() + } else if v == -pallas::Base::one() { + "-1".into() + } else { + // Format value as hex. + let s = format!("{:?}", v); + // Remove leading zeroes. + let s = s.strip_prefix("0x").unwrap(); + let s = s.trim_start_matches('0'); + format!("0x{}", s) + } + } + + // Sign that is not +/- 1 should fail + // Generate a random non-identity point + let point = pallas::Point::random(rand::rngs::OsRng); + let circuit = MyCircuit { + base: Value::known(point.to_affine()), + sign: Value::known(pallas::Base::zero()), + }; + + let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); + assert_eq!( + prover.verify(), + Err(vec![ + VerifyFailure::ConstraintNotSatisfied { + constraint: ((17, "Short fixed-base mul gate").into(), 1, "sign_check").into(), + location: FailureLocation::InRegion { + region: (2, "Signed point").into(), + offset: 0, + }, + cell_values: vec![(((Any::Advice, 4).into(), 0).into(), "0".to_string())], + }, + VerifyFailure::ConstraintNotSatisfied { + constraint: ( + (17, "Short fixed-base mul gate").into(), + 3, + "negation_check" + ) + .into(), + location: FailureLocation::InRegion { + region: (2, "Signed point").into(), + offset: 0, + }, + cell_values: vec![ + ( + ((Any::Advice, 1).into(), 0).into(), + format_value(*point.to_affine().coordinates().unwrap().y()), + ), + ( + ((Any::Advice, 3).into(), 0).into(), + format_value(*point.to_affine().coordinates().unwrap().y()), + ), + (((Any::Advice, 4).into(), 0).into(), "0".to_string()), + ], + } + ]) + ); + } +} \ No newline at end of file diff --git a/halo2_gadgets_optimized/src/ecc/chip/witness_point.rs b/halo2_gadgets_optimized/src/ecc/chip/witness_point.rs new file mode 100644 index 0000000000..bb21bbb511 --- /dev/null +++ b/halo2_gadgets_optimized/src/ecc/chip/witness_point.rs @@ -0,0 +1,211 @@ +use super::{EccPoint, NonIdentityEccPoint}; + +use group::prime::PrimeCurveAffine; + +use halo2_proofs::{ + circuit::{AssignedCell, Region, Value}, + plonk::{ + Advice, Assigned, Column, ConstraintSystem, Constraints, Error, Expression, Selector, + VirtualCells, + }, + poly::Rotation, +}; +use pasta_curves::{arithmetic::CurveAffine, pallas}; + +type Coordinates = ( + AssignedCell, pallas::Base>, + AssignedCell, pallas::Base>, +); + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Config { + q_point: Selector, + q_point_non_id: Selector, + // x-coordinate + pub x: Column, + // y-coordinate + pub y: Column, +} + +impl Config { + pub(super) fn configure( + meta: &mut ConstraintSystem, + x: Column, + y: Column, + ) -> Self { + let config = Self { + q_point: meta.selector(), + q_point_non_id: meta.selector(), + x, + y, + }; + + config.create_gate(meta); + + config + } + + fn create_gate(&self, meta: &mut ConstraintSystem) { + let curve_eqn = |meta: &mut VirtualCells| { + let x = meta.query_advice(self.x, Rotation::cur()); + let y = meta.query_advice(self.y, Rotation::cur()); + + // y^2 = x^3 + b + y.square() - (x.clone().square() * x) - Expression::Constant(pallas::Affine::b()) + }; + + // https://p.z.cash/halo2-0.1:ecc-witness-point + meta.create_gate("witness point", |meta| { + // Check that the point being witnessed is either: + // - the identity, which is mapped to (0, 0) in affine coordinates; or + // - a valid curve point y^2 = x^3 + b, where b = 5 in the Pallas equation + + let q_point = meta.query_selector(self.q_point); + let x = meta.query_advice(self.x, Rotation::cur()); + let y = meta.query_advice(self.y, Rotation::cur()); + + // We can't use `Constraints::with_selector` because that creates constraints + // of the form `q_point * (x * curve_eqn)`, but this was implemented without + // parentheses, and thus evaluates as `(q_point * x) * curve_eqn`, which is + // structurally different in the pinned verifying key. + [ + ("x == 0 v on_curve", q_point.clone() * x * curve_eqn(meta)), + ("y == 0 v on_curve", q_point * y * curve_eqn(meta)), + ] + }); + + // https://p.z.cash/halo2-0.1:ecc-witness-non-identity-point + meta.create_gate("witness non-identity point", |meta| { + // Check that the point being witnessed is a valid curve point y^2 = x^3 + b, + // where b = 5 in the Pallas equation + + let q_point_non_id = meta.query_selector(self.q_point_non_id); + + Constraints::with_selector(q_point_non_id, Some(("on_curve", curve_eqn(meta)))) + }); + } + + fn assign_xy( + &self, + value: Value<(Assigned, Assigned)>, + offset: usize, + region: &mut Region<'_, pallas::Base>, + ) -> Result { + // Assign `x` value + let x_val = value.map(|value| value.0); + let x_var = region.assign_advice(|| "x", self.x, offset, || x_val)?; + + // Assign `y` value + let y_val = value.map(|value| value.1); + let y_var = region.assign_advice(|| "y", self.y, offset, || y_val)?; + + Ok((x_var, y_var)) + } + + fn assign_xy_from_constant( + &self, + value: (Assigned, Assigned), + offset: usize, + region: &mut Region<'_, pallas::Base>, + ) -> Result { + // Assign `x` value + let x_var = region.assign_advice_from_constant(|| "x", self.x, offset, value.0)?; + + // Assign `y` value + let y_var = region.assign_advice_from_constant(|| "y", self.y, offset, value.1)?; + + Ok((x_var, y_var)) + } + + /// Assigns a point that can be the identity. + pub(super) fn point( + &self, + value: Value, + offset: usize, + region: &mut Region<'_, pallas::Base>, + ) -> Result { + // Enable `q_point` selector + self.q_point.enable(region, offset)?; + + let value = value.map(|value| { + // Map the identity to (0, 0). + if value == pallas::Affine::identity() { + (Assigned::Zero, Assigned::Zero) + } else { + let value = value.coordinates().unwrap(); + (value.x().into(), value.y().into()) + } + }); + + self.assign_xy(value, offset, region) + .map(|(x, y)| EccPoint::from_coordinates_unchecked(x, y)) + } + + /// Assigns a constant point that can be the identity. + pub(super) fn constant_point( + &self, + value: pallas::Affine, + offset: usize, + region: &mut Region<'_, pallas::Base>, + ) -> Result { + // Enable `q_point` selector + self.q_point.enable(region, offset)?; + + let value = if value == pallas::Affine::identity() { + // Map the identity to (0, 0). + (Assigned::Zero, Assigned::Zero) + } else { + let value = value.coordinates().unwrap(); + (value.x().into(), value.y().into()) + }; + + self.assign_xy_from_constant(value, offset, region) + .map(|(x, y)| EccPoint::from_coordinates_unchecked(x, y)) + } + + /// Assigns a non-identity point. + pub(super) fn point_non_id( + &self, + value: Value, + offset: usize, + region: &mut Region<'_, pallas::Base>, + ) -> Result { + // Enable `q_point_non_id` selector + self.q_point_non_id.enable(region, offset)?; + + // Return an error if the point is the identity. + value.error_if_known_and(|value| value == &pallas::Affine::identity())?; + + let value = value.map(|value| { + let value = value.coordinates().unwrap(); + (value.x().into(), value.y().into()) + }); + + self.assign_xy(value, offset, region) + .map(|(x, y)| NonIdentityEccPoint::from_coordinates_unchecked(x, y)) + } +} + +#[cfg(test)] +pub mod tests { + use halo2_proofs::circuit::Layouter; + use pasta_curves::pallas; + + use super::*; + use crate::ecc::{EccInstructions, NonIdentityPoint}; + + pub fn test_witness_non_id< + EccChip: EccInstructions + Clone + Eq + std::fmt::Debug, + >( + chip: EccChip, + mut layouter: impl Layouter, + ) { + // Witnessing the identity should return an error. + NonIdentityPoint::new( + chip, + layouter.namespace(|| "witness identity"), + Value::known(pallas::Affine::identity()), + ) + .expect_err("witnessing 𝒪 should return an error"); + } +} \ No newline at end of file diff --git a/halo2_gadgets_optimized/src/lib.rs b/halo2_gadgets_optimized/src/lib.rs new file mode 100644 index 0000000000..2ac2623a99 --- /dev/null +++ b/halo2_gadgets_optimized/src/lib.rs @@ -0,0 +1,30 @@ +//! This crate provides various common gadgets and chips for use with `halo2_proofs`. +//! +//! # Gadgets +//! +//! Gadgets are an abstraction for writing reusable and interoperable circuit logic. They +//! do not create any circuit constraints or assignments themselves, instead interacting +//! with the circuit through a defined "instruction set". A circuit developer uses gadgets +//! by instantiating them with a particular choice of chip. +//! +//! # Chips +//! +//! Chips implement the low-level circuit constraints. The same instructions may be +//! implemented by multiple chips, enabling different performance trade-offs to be made. +//! Chips can be highly optimised by their developers, as long as they conform to the +//! defined instructions. + +#![cfg_attr(docsrs, feature(doc_cfg))] +// Catch documentation errors caused by code changes. +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(missing_debug_implementations)] +#![deny(missing_docs)] +#![deny(unsafe_code)] + +pub mod ecc; +pub mod poseidon; +#[cfg(feature = "unstable-sha256-gadget")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable-sha256-gadget")))] +pub mod sha256; +pub mod sinsemilla; +pub mod utilities; diff --git a/halo2_gadgets_optimized/src/poseidon.rs b/halo2_gadgets_optimized/src/poseidon.rs new file mode 100644 index 0000000000..77736c8677 --- /dev/null +++ b/halo2_gadgets_optimized/src/poseidon.rs @@ -0,0 +1,296 @@ +//! The Poseidon algebraic hash function. + +use std::convert::TryInto; +use std::fmt; +use std::marker::PhantomData; + +use group::ff::{Field, PrimeField}; +use halo2_proofs::{ + circuit::{AssignedCell, Chip, Layouter}, + plonk::Error, +}; + +mod pow5; +pub use pow5::{Pow5Chip, Pow5Config, StateWord}; + +pub mod primitives; +use primitives::{Absorbing, ConstantLength, Domain, Spec, SpongeMode, Squeezing, State}; + +/// A word from the padded input to a Poseidon sponge. +#[derive(Clone, Debug)] +pub enum PaddedWord { + /// A message word provided by the prover. + Message(AssignedCell), + /// A padding word, that will be fixed in the circuit parameters. + Padding(F), +} + +/// The set of circuit instructions required to use the Poseidon permutation. +pub trait PoseidonInstructions, const T: usize, const RATE: usize>: + Chip +{ + /// Variable representing the word over which the Poseidon permutation operates. + type Word: Clone + fmt::Debug + From> + Into>; + + /// Applies the Poseidon permutation to the given state. + fn permute( + &self, + layouter: &mut impl Layouter, + initial_state: &State, + ) -> Result, Error>; +} + +/// The set of circuit instructions required to use the [`Sponge`] and [`Hash`] gadgets. +/// +/// [`Hash`]: self::Hash +pub trait PoseidonSpongeInstructions< + F: Field, + S: Spec, + D: Domain, + const T: usize, + const RATE: usize, +>: PoseidonInstructions +{ + /// Returns the initial empty state for the given domain. + fn initial_state(&self, layouter: &mut impl Layouter) + -> Result, Error>; + + /// Adds the given input to the state. + fn add_input( + &self, + layouter: &mut impl Layouter, + initial_state: &State, + input: &Absorbing, RATE>, + ) -> Result, Error>; + + /// Extracts sponge output from the given state. + fn get_output(state: &State) -> Squeezing; +} + +/// A word over which the Poseidon permutation operates. +#[derive(Debug)] +pub struct Word< + F: Field, + PoseidonChip: PoseidonInstructions, + S: Spec, + const T: usize, + const RATE: usize, +> { + inner: PoseidonChip::Word, +} + +impl< + F: Field, + PoseidonChip: PoseidonInstructions, + S: Spec, + const T: usize, + const RATE: usize, + > Word +{ + /// The word contained in this gadget. + pub fn inner(&self) -> PoseidonChip::Word { + self.inner.clone() + } + + /// Construct a [`Word`] gadget from the inner word. + pub fn from_inner(inner: PoseidonChip::Word) -> Self { + Self { inner } + } +} + +fn poseidon_sponge< + F: Field, + PoseidonChip: PoseidonSpongeInstructions, + S: Spec, + D: Domain, + const T: usize, + const RATE: usize, +>( + chip: &PoseidonChip, + mut layouter: impl Layouter, + state: &mut State, + input: Option<&Absorbing, RATE>>, +) -> Result, Error> { + if let Some(input) = input { + *state = chip.add_input(&mut layouter, state, input)?; + } + *state = chip.permute(&mut layouter, state)?; + Ok(PoseidonChip::get_output(state)) +} + +/// A Poseidon sponge. +#[derive(Debug)] +pub struct Sponge< + F: Field, + PoseidonChip: PoseidonSpongeInstructions, + S: Spec, + M: SpongeMode, + D: Domain, + const T: usize, + const RATE: usize, +> { + chip: PoseidonChip, + mode: M, + state: State, + _marker: PhantomData, +} + +impl< + F: Field, + PoseidonChip: PoseidonSpongeInstructions, + S: Spec, + D: Domain, + const T: usize, + const RATE: usize, + > Sponge, RATE>, D, T, RATE> +{ + /// Constructs a new duplex sponge for the given Poseidon specification. + pub fn new(chip: PoseidonChip, mut layouter: impl Layouter) -> Result { + chip.initial_state(&mut layouter).map(|state| Sponge { + chip, + mode: Absorbing( + (0..RATE) + .map(|_| None) + .collect::>() + .try_into() + .unwrap(), + ), + state, + _marker: PhantomData::default(), + }) + } + + /// Absorbs an element into the sponge. + pub fn absorb( + &mut self, + mut layouter: impl Layouter, + value: PaddedWord, + ) -> Result<(), Error> { + for entry in self.mode.0.iter_mut() { + if entry.is_none() { + *entry = Some(value); + return Ok(()); + } + } + + // We've already absorbed as many elements as we can + let _ = poseidon_sponge( + &self.chip, + layouter.namespace(|| "PoseidonSponge"), + &mut self.state, + Some(&self.mode), + )?; + self.mode = Absorbing::init_with(value); + + Ok(()) + } + + /// Transitions the sponge into its squeezing state. + #[allow(clippy::type_complexity)] + pub fn finish_absorbing( + mut self, + mut layouter: impl Layouter, + ) -> Result, D, T, RATE>, Error> + { + let mode = poseidon_sponge( + &self.chip, + layouter.namespace(|| "PoseidonSponge"), + &mut self.state, + Some(&self.mode), + )?; + + Ok(Sponge { + chip: self.chip, + mode, + state: self.state, + _marker: PhantomData::default(), + }) + } +} + +impl< + F: Field, + PoseidonChip: PoseidonSpongeInstructions, + S: Spec, + D: Domain, + const T: usize, + const RATE: usize, + > Sponge, D, T, RATE> +{ + /// Squeezes an element from the sponge. + pub fn squeeze(&mut self, mut layouter: impl Layouter) -> Result, Error> { + loop { + for entry in self.mode.0.iter_mut() { + if let Some(inner) = entry.take() { + return Ok(inner.into()); + } + } + + // We've already squeezed out all available elements + self.mode = poseidon_sponge( + &self.chip, + layouter.namespace(|| "PoseidonSponge"), + &mut self.state, + None, + )?; + } + } +} + +/// A Poseidon hash function, built around a sponge. +#[derive(Debug)] +pub struct Hash< + F: Field, + PoseidonChip: PoseidonSpongeInstructions, + S: Spec, + D: Domain, + const T: usize, + const RATE: usize, +> { + sponge: Sponge, RATE>, D, T, RATE>, +} + +impl< + F: Field, + PoseidonChip: PoseidonSpongeInstructions, + S: Spec, + D: Domain, + const T: usize, + const RATE: usize, + > Hash +{ + /// Initializes a new hasher. + pub fn init(chip: PoseidonChip, layouter: impl Layouter) -> Result { + Sponge::new(chip, layouter).map(|sponge| Hash { sponge }) + } +} + +impl< + F: PrimeField, + PoseidonChip: PoseidonSpongeInstructions, T, RATE>, + S: Spec, + const T: usize, + const RATE: usize, + const L: usize, + > Hash, T, RATE> +{ + /// Hashes the given input. + pub fn hash( + mut self, + mut layouter: impl Layouter, + message: [AssignedCell; L], + ) -> Result, Error> { + for (i, value) in message + .into_iter() + .map(PaddedWord::Message) + .chain( as Domain>::padding(L).map(PaddedWord::Padding)) + .enumerate() + { + self.sponge + .absorb(layouter.namespace(|| format!("absorb_{}", i)), value)?; + } + self.sponge + .finish_absorbing(layouter.namespace(|| "finish absorbing"))? + .squeeze(layouter.namespace(|| "squeeze")) + } +} diff --git a/halo2_gadgets_optimized/src/poseidon/pow5.rs b/halo2_gadgets_optimized/src/poseidon/pow5.rs new file mode 100644 index 0000000000..704bda1daa --- /dev/null +++ b/halo2_gadgets_optimized/src/poseidon/pow5.rs @@ -0,0 +1,917 @@ +use std::convert::TryInto; +use std::iter; + +use group::ff::Field; +use halo2_proofs::{ + circuit::{AssignedCell, Cell, Chip, Layouter, Region, Value}, + plonk::{ + Advice, Any, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector, + }, + poly::Rotation, +}; + +use super::{ + primitives::{Absorbing, Domain, Mds, Spec, Squeezing, State}, + PaddedWord, PoseidonInstructions, PoseidonSpongeInstructions, +}; +use crate::utilities::Var; + +/// Configuration for a [`Pow5Chip`]. +#[derive(Clone, Debug)] +pub struct Pow5Config { + pub(crate) state: [Column; WIDTH], + partial_sbox: Column, + rc_a: [Column; WIDTH], + rc_b: [Column; WIDTH], + s_full: Selector, + s_partial: Selector, + s_pad_and_add: Selector, + + half_full_rounds: usize, + half_partial_rounds: usize, + alpha: [u64; 4], + round_constants: Vec<[F; WIDTH]>, + m_reg: Mds, +} + +/// A Poseidon chip using an $x^5$ S-Box. +/// +/// The chip is implemented using a single round per row for full rounds, and two rounds +/// per row for partial rounds. +#[derive(Debug)] +pub struct Pow5Chip { + config: Pow5Config, +} + +impl Pow5Chip { + /// Configures this chip for use in a circuit. + /// + /// # Side-effects + /// + /// All columns in `state` will be equality-enabled. + // + // TODO: Does the rate need to be hard-coded here, or only the width? It probably + // needs to be known wherever we implement the hashing gadget, but it isn't strictly + // necessary for the permutation. + pub fn configure>( + meta: &mut ConstraintSystem, + state: [Column; WIDTH], + partial_sbox: Column, + rc_a: [Column; WIDTH], + rc_b: [Column; WIDTH], + ) -> Pow5Config { + assert_eq!(RATE, WIDTH - 1); + // Generate constants for the Poseidon permutation. + // This gadget requires R_F and R_P to be even. + assert!(S::full_rounds() & 1 == 0); + assert!(S::partial_rounds() & 1 == 0); + let half_full_rounds = S::full_rounds() / 2; + let half_partial_rounds = S::partial_rounds() / 2; + let (round_constants, m_reg, m_inv) = S::constants(); + + // This allows state words to be initialized (by constraining them equal to fixed + // values), and used in a permutation from an arbitrary region. rc_a is used in + // every permutation round, while rc_b is empty in the initial and final full + // rounds, so we use rc_b as "scratch space" for fixed values (enabling potential + // layouter optimisations). + for column in iter::empty() + .chain(state.iter().cloned().map(Column::::from)) + .chain(rc_b.iter().cloned().map(Column::::from)) + { + meta.enable_equality(column); + } + + let s_full = meta.selector(); + let s_partial = meta.selector(); + let s_pad_and_add = meta.selector(); + + let alpha = [5, 0, 0, 0]; + let pow_5 = |v: Expression| { + let v2 = v.clone() * v.clone(); + v2.clone() * v2 * v + }; + + meta.create_gate("full round", |meta| { + let s_full = meta.query_selector(s_full); + + Constraints::with_selector( + s_full, + (0..WIDTH) + .map(|next_idx| { + let state_next = meta.query_advice(state[next_idx], Rotation::next()); + let expr = (0..WIDTH) + .map(|idx| { + let state_cur = meta.query_advice(state[idx], Rotation::cur()); + let rc_a = meta.query_fixed(rc_a[idx]); + pow_5(state_cur + rc_a) * m_reg[next_idx][idx] + }) + .reduce(|acc, term| acc + term) + .expect("WIDTH > 0"); + expr - state_next + }) + .collect::>(), + ) + }); + + meta.create_gate("partial rounds", |meta| { + let cur_0 = meta.query_advice(state[0], Rotation::cur()); + let mid_0 = meta.query_advice(partial_sbox, Rotation::cur()); + + let rc_a0 = meta.query_fixed(rc_a[0]); + let rc_b0 = meta.query_fixed(rc_b[0]); + + let s_partial = meta.query_selector(s_partial); + + use halo2_proofs::plonk::VirtualCells; + let mid = |idx: usize, meta: &mut VirtualCells| { + let mid = mid_0.clone() * m_reg[idx][0]; + (1..WIDTH).fold(mid, |acc, cur_idx| { + let cur = meta.query_advice(state[cur_idx], Rotation::cur()); + let rc_a = meta.query_fixed(rc_a[cur_idx]); + acc + (cur + rc_a) * m_reg[idx][cur_idx] + }) + }; + + let next = |idx: usize, meta: &mut VirtualCells| { + (0..WIDTH) + .map(|next_idx| { + let next = meta.query_advice(state[next_idx], Rotation::next()); + next * m_inv[idx][next_idx] + }) + .reduce(|acc, next| acc + next) + .expect("WIDTH > 0") + }; + + let partial_round_linear = |idx: usize, meta: &mut VirtualCells| { + let rc_b = meta.query_fixed(rc_b[idx]); + mid(idx, meta) + rc_b - next(idx, meta) + }; + + Constraints::with_selector( + s_partial, + std::iter::empty() + // state[0] round a + .chain(Some(pow_5(cur_0 + rc_a0) - mid_0.clone())) + // state[0] round b + .chain(Some(pow_5(mid(0, meta) + rc_b0) - next(0, meta))) + .chain((1..WIDTH).map(|idx| partial_round_linear(idx, meta))) + .collect::>(), + ) + }); + + meta.create_gate("pad-and-add", |meta| { + let initial_state_rate = meta.query_advice(state[RATE], Rotation::prev()); + let output_state_rate = meta.query_advice(state[RATE], Rotation::next()); + + let s_pad_and_add = meta.query_selector(s_pad_and_add); + + let pad_and_add = |idx: usize| { + let initial_state = meta.query_advice(state[idx], Rotation::prev()); + let input = meta.query_advice(state[idx], Rotation::cur()); + let output_state = meta.query_advice(state[idx], Rotation::next()); + + // We pad the input by storing the required padding in fixed columns and + // then constraining the corresponding input columns to be equal to it. + initial_state + input - output_state + }; + + Constraints::with_selector( + s_pad_and_add, + (0..RATE) + .map(pad_and_add) + // The capacity element is never altered by the input. + .chain(Some(initial_state_rate - output_state_rate)) + .collect::>(), + ) + }); + + Pow5Config { + state, + partial_sbox, + rc_a, + rc_b, + s_full, + s_partial, + s_pad_and_add, + half_full_rounds, + half_partial_rounds, + alpha, + round_constants, + m_reg, + } + } + + /// Construct a [`Pow5Chip`]. + pub fn construct(config: Pow5Config) -> Self { + Pow5Chip { config } + } +} + +impl Chip for Pow5Chip { + type Config = Pow5Config; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} + +impl, const WIDTH: usize, const RATE: usize> + PoseidonInstructions for Pow5Chip +{ + type Word = StateWord; + + fn permute( + &self, + layouter: &mut impl Layouter, + initial_state: &State, + ) -> Result, Error> { + let config = self.config(); + + layouter.assign_region( + || "permute state", + |mut region| { + // Load the initial state into this region. + let state = Pow5State::load(&mut region, config, initial_state)?; + + let state = (0..config.half_full_rounds).fold(Ok(state), |res, r| { + res.and_then(|state| state.full_round(&mut region, config, r, r)) + })?; + + let state = (0..config.half_partial_rounds).fold(Ok(state), |res, r| { + res.and_then(|state| { + state.partial_round( + &mut region, + config, + config.half_full_rounds + 2 * r, + config.half_full_rounds + r, + ) + }) + })?; + + let state = (0..config.half_full_rounds).fold(Ok(state), |res, r| { + res.and_then(|state| { + state.full_round( + &mut region, + config, + config.half_full_rounds + 2 * config.half_partial_rounds + r, + config.half_full_rounds + config.half_partial_rounds + r, + ) + }) + })?; + + Ok(state.0) + }, + ) + } +} + +impl< + F: Field, + S: Spec, + D: Domain, + const WIDTH: usize, + const RATE: usize, + > PoseidonSpongeInstructions for Pow5Chip +{ + fn initial_state( + &self, + layouter: &mut impl Layouter, + ) -> Result, Error> { + let config = self.config(); + let state = layouter.assign_region( + || format!("initial state for domain {}", D::name()), + |mut region| { + let mut state = Vec::with_capacity(WIDTH); + let mut load_state_word = |i: usize, value: F| -> Result<_, Error> { + let var = region.assign_advice_from_constant( + || format!("state_{}", i), + config.state[i], + 0, + value, + )?; + state.push(StateWord(var)); + + Ok(()) + }; + + for i in 0..RATE { + load_state_word(i, F::ZERO)?; + } + load_state_word(RATE, D::initial_capacity_element())?; + + Ok(state) + }, + )?; + + Ok(state.try_into().unwrap()) + } + + fn add_input( + &self, + layouter: &mut impl Layouter, + initial_state: &State, + input: &Absorbing, RATE>, + ) -> Result, Error> { + let config = self.config(); + layouter.assign_region( + || format!("add input for domain {}", D::name()), + |mut region| { + config.s_pad_and_add.enable(&mut region, 1)?; + + // Load the initial state into this region. + let load_state_word = |i: usize| { + initial_state[i] + .0 + .copy_advice( + || format!("load state_{}", i), + &mut region, + config.state[i], + 0, + ) + .map(StateWord) + }; + let initial_state: Result, Error> = + (0..WIDTH).map(load_state_word).collect(); + let initial_state = initial_state?; + + // Load the input into this region. + let load_input_word = |i: usize| { + let (cell, value) = match input.0[i].clone() { + Some(PaddedWord::Message(word)) => (word.cell(), word.value().copied()), + Some(PaddedWord::Padding(padding_value)) => { + let cell = region + .assign_fixed( + || format!("load pad_{}", i), + config.rc_b[i], + 1, + || Value::known(padding_value), + )? + .cell(); + (cell, Value::known(padding_value)) + } + _ => panic!("Input is not padded"), + }; + let var = region.assign_advice( + || format!("load input_{}", i), + config.state[i], + 1, + || value, + )?; + region.constrain_equal(cell, var.cell())?; + + Ok(StateWord(var)) + }; + let input: Result, Error> = (0..RATE).map(load_input_word).collect(); + let input = input?; + + // Constrain the output. + let constrain_output_word = |i: usize| { + let value = initial_state[i].0.value().copied() + + input + .get(i) + .map(|word| word.0.value().cloned()) + // The capacity element is never altered by the input. + .unwrap_or_else(|| Value::known(F::ZERO)); + region + .assign_advice( + || format!("load output_{}", i), + config.state[i], + 2, + || value, + ) + .map(StateWord) + }; + + let output: Result, Error> = (0..WIDTH).map(constrain_output_word).collect(); + output.map(|output| output.try_into().unwrap()) + }, + ) + } + + fn get_output(state: &State) -> Squeezing { + Squeezing( + state[..RATE] + .iter() + .map(|word| Some(word.clone())) + .collect::>() + .try_into() + .unwrap(), + ) + } +} + +/// A word in the Poseidon state. +#[derive(Clone, Debug)] +pub struct StateWord(AssignedCell); + +impl From> for AssignedCell { + fn from(state_word: StateWord) -> AssignedCell { + state_word.0 + } +} + +impl From> for StateWord { + fn from(cell_value: AssignedCell) -> StateWord { + StateWord(cell_value) + } +} + +impl Var for StateWord { + fn cell(&self) -> Cell { + self.0.cell() + } + + fn value(&self) -> Value { + self.0.value().cloned() + } +} + +#[derive(Debug)] +struct Pow5State([StateWord; WIDTH]); + +impl Pow5State { + fn full_round( + self, + region: &mut Region, + config: &Pow5Config, + round: usize, + offset: usize, + ) -> Result { + Self::round(region, config, round, offset, config.s_full, |_| { + let q = self.0.iter().enumerate().map(|(idx, word)| { + word.0 + .value() + .map(|v| *v + config.round_constants[round][idx]) + }); + let r: Value> = q.map(|q| q.map(|q| q.pow(&config.alpha))).collect(); + let m = &config.m_reg; + let state = m.iter().map(|m_i| { + r.as_ref().map(|r| { + r.iter() + .enumerate() + .fold(F::ZERO, |acc, (j, r_j)| acc + m_i[j] * r_j) + }) + }); + + Ok((round + 1, state.collect::>().try_into().unwrap())) + }) + } + + fn partial_round( + self, + region: &mut Region, + config: &Pow5Config, + round: usize, + offset: usize, + ) -> Result { + Self::round(region, config, round, offset, config.s_partial, |region| { + let m = &config.m_reg; + let p: Value> = self.0.iter().map(|word| word.0.value().cloned()).collect(); + + let r: Value> = p.map(|p| { + let r_0 = (p[0] + config.round_constants[round][0]).pow(&config.alpha); + let r_i = p[1..] + .iter() + .enumerate() + .map(|(i, p_i)| *p_i + config.round_constants[round][i + 1]); + std::iter::empty().chain(Some(r_0)).chain(r_i).collect() + }); + + region.assign_advice( + || format!("round_{} partial_sbox", round), + config.partial_sbox, + offset, + || r.as_ref().map(|r| r[0]), + )?; + + let p_mid: Value> = m + .iter() + .map(|m_i| { + r.as_ref().map(|r| { + m_i.iter() + .zip(r.iter()) + .fold(F::ZERO, |acc, (m_ij, r_j)| acc + *m_ij * r_j) + }) + }) + .collect(); + + // Load the second round constants. + let mut load_round_constant = |i: usize| { + region.assign_fixed( + || format!("round_{} rc_{}", round + 1, i), + config.rc_b[i], + offset, + || Value::known(config.round_constants[round + 1][i]), + ) + }; + for i in 0..WIDTH { + load_round_constant(i)?; + } + + let r_mid: Value> = p_mid.map(|p| { + let r_0 = (p[0] + config.round_constants[round + 1][0]).pow(&config.alpha); + let r_i = p[1..] + .iter() + .enumerate() + .map(|(i, p_i)| *p_i + config.round_constants[round + 1][i + 1]); + std::iter::empty().chain(Some(r_0)).chain(r_i).collect() + }); + + let state: Vec> = m + .iter() + .map(|m_i| { + r_mid.as_ref().map(|r| { + m_i.iter() + .zip(r.iter()) + .fold(F::ZERO, |acc, (m_ij, r_j)| acc + *m_ij * r_j) + }) + }) + .collect(); + + Ok((round + 2, state.try_into().unwrap())) + }) + } + + fn load( + region: &mut Region, + config: &Pow5Config, + initial_state: &State, WIDTH>, + ) -> Result { + let load_state_word = |i: usize| { + initial_state[i] + .0 + .copy_advice(|| format!("load state_{}", i), region, config.state[i], 0) + .map(StateWord) + }; + + let state: Result, _> = (0..WIDTH).map(load_state_word).collect(); + state.map(|state| Pow5State(state.try_into().unwrap())) + } + + fn round( + region: &mut Region, + config: &Pow5Config, + round: usize, + offset: usize, + round_gate: Selector, + round_fn: impl FnOnce(&mut Region) -> Result<(usize, [Value; WIDTH]), Error>, + ) -> Result { + // Enable the required gate. + round_gate.enable(region, offset)?; + + // Load the round constants. + let mut load_round_constant = |i: usize| { + region.assign_fixed( + || format!("round_{} rc_{}", round, i), + config.rc_a[i], + offset, + || Value::known(config.round_constants[round][i]), + ) + }; + for i in 0..WIDTH { + load_round_constant(i)?; + } + + // Compute the next round's state. + let (next_round, next_state) = round_fn(region)?; + + let next_state_word = |i: usize| { + let value = next_state[i]; + let var = region.assign_advice( + || format!("round_{} state_{}", next_round, i), + config.state[i], + offset + 1, + || value, + )?; + Ok(StateWord(var)) + }; + + let next_state: Result, _> = (0..WIDTH).map(next_state_word).collect(); + next_state.map(|next_state| Pow5State(next_state.try_into().unwrap())) + } +} + +#[cfg(test)] +mod tests { + use group::ff::{Field, PrimeField}; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + dev::MockProver, + pasta::Fp, + plonk::{self, Circuit, ConstraintSystem, Error, SingleVerifier}, + poly::commitment::Params, + transcript::{Blake2bRead, Blake2bWrite, Challenge255}, + }; + use pasta_curves::{pallas, EqAffine}; + use rand::rngs::OsRng; + + use super::{PoseidonInstructions, Pow5Chip, Pow5Config, StateWord}; + use crate::poseidon::{ + primitives::{self as poseidon, ConstantLength, P128Pow5T3 as OrchardNullifier, Spec}, + Hash, + }; + use std::convert::TryInto; + use std::marker::PhantomData; + + struct PermuteCircuit, const WIDTH: usize, const RATE: usize>( + PhantomData, + ); + + impl, const WIDTH: usize, const RATE: usize> Circuit + for PermuteCircuit + { + type Config = Pow5Config; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + PermuteCircuit::(PhantomData) + } + + fn configure(meta: &mut ConstraintSystem) -> Pow5Config { + let state = (0..WIDTH).map(|_| meta.advice_column()).collect::>(); + let partial_sbox = meta.advice_column(); + + let rc_a = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); + let rc_b = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); + + Pow5Chip::configure::( + meta, + state.try_into().unwrap(), + partial_sbox, + rc_a.try_into().unwrap(), + rc_b.try_into().unwrap(), + ) + } + + fn synthesize( + &self, + config: Pow5Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let initial_state = layouter.assign_region( + || "prepare initial state", + |mut region| { + let state_word = |i: usize| { + let value = Value::known(Fp::from(i as u64)); + let var = region.assign_advice( + || format!("load state_{}", i), + config.state[i], + 0, + || value, + )?; + Ok(StateWord(var)) + }; + + let state: Result, Error> = (0..WIDTH).map(state_word).collect(); + Ok(state?.try_into().unwrap()) + }, + )?; + + let chip = Pow5Chip::construct(config.clone()); + let final_state = as PoseidonInstructions< + Fp, + S, + WIDTH, + RATE, + >>::permute(&chip, &mut layouter, &initial_state)?; + + // For the purpose of this test, compute the real final state inline. + let mut expected_final_state = (0..WIDTH) + .map(|idx| Fp::from(idx as u64)) + .collect::>() + .try_into() + .unwrap(); + let (round_constants, mds, _) = S::constants(); + poseidon::permute::<_, S, WIDTH, RATE>( + &mut expected_final_state, + &mds, + &round_constants, + ); + + layouter.assign_region( + || "constrain final state", + |mut region| { + let mut final_state_word = |i: usize| { + let var = region.assign_advice( + || format!("load final_state_{}", i), + config.state[i], + 0, + || Value::known(expected_final_state[i]), + )?; + region.constrain_equal(final_state[i].0.cell(), var.cell()) + }; + + for i in 0..(WIDTH) { + final_state_word(i)?; + } + + Ok(()) + }, + ) + } + } + + #[test] + fn poseidon_permute() { + let k = 6; + let circuit = PermuteCircuit::(PhantomData); + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())) + } + + struct HashCircuit< + S: Spec, + const WIDTH: usize, + const RATE: usize, + const L: usize, + > { + message: Value<[Fp; L]>, + // For the purpose of this test, witness the result. + // TODO: Move this into an instance column. + output: Value, + _spec: PhantomData, + } + + impl, const WIDTH: usize, const RATE: usize, const L: usize> + Circuit for HashCircuit + { + type Config = Pow5Config; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self { + message: Value::unknown(), + output: Value::unknown(), + _spec: PhantomData, + } + } + + fn configure(meta: &mut ConstraintSystem) -> Pow5Config { + let state = (0..WIDTH).map(|_| meta.advice_column()).collect::>(); + let partial_sbox = meta.advice_column(); + + let rc_a = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); + let rc_b = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); + + meta.enable_constant(rc_b[0]); + + Pow5Chip::configure::( + meta, + state.try_into().unwrap(), + partial_sbox, + rc_a.try_into().unwrap(), + rc_b.try_into().unwrap(), + ) + } + + fn synthesize( + &self, + config: Pow5Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chip = Pow5Chip::construct(config.clone()); + + let message = layouter.assign_region( + || "load message", + |mut region| { + let message_word = |i: usize| { + let value = self.message.map(|message_vals| message_vals[i]); + region.assign_advice( + || format!("load message_{}", i), + config.state[i], + 0, + || value, + ) + }; + + let message: Result, Error> = (0..L).map(message_word).collect(); + Ok(message?.try_into().unwrap()) + }, + )?; + + let hasher = Hash::<_, _, S, ConstantLength, WIDTH, RATE>::init( + chip, + layouter.namespace(|| "init"), + )?; + let output = hasher.hash(layouter.namespace(|| "hash"), message)?; + + layouter.assign_region( + || "constrain output", + |mut region| { + let expected_var = region.assign_advice( + || "load output", + config.state[0], + 0, + || self.output, + )?; + region.constrain_equal(output.cell(), expected_var.cell()) + }, + ) + } + } + + #[test] + fn poseidon_hash() { + let rng = OsRng; + + let message = [Fp::random(rng), Fp::random(rng)]; + let output = + poseidon::Hash::<_, OrchardNullifier, ConstantLength<2>, 3, 2>::init().hash(message); + + let k = 6; + let circuit = HashCircuit:: { + message: Value::known(message), + output: Value::known(output), + _spec: PhantomData, + }; + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())) + } + + #[test] + fn poseidon_hash_longer_input() { + let rng = OsRng; + + let message = [Fp::random(rng), Fp::random(rng), Fp::random(rng)]; + let output = + poseidon::Hash::<_, OrchardNullifier, ConstantLength<3>, 3, 2>::init().hash(message); + + let k = 7; + let circuit = HashCircuit:: { + message: Value::known(message), + output: Value::known(output), + _spec: PhantomData, + }; + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + + let params = Params::new(k); + let vk = plonk::keygen_vk(¶ms, &circuit).unwrap(); + let pk = plonk::keygen_pk(¶ms, vk, &circuit).unwrap(); + + let mut transcript = Blake2bWrite::<_, EqAffine, _>::init(vec![]); + plonk::create_proof( + ¶ms, + &pk, + &[circuit], + &[&[]], + &mut OsRng, + &mut transcript, + ) + .unwrap(); + let proof = transcript.finalize(); + + let strategy = SingleVerifier::new(¶ms); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + assert!( + plonk::verify_proof(¶ms, pk.get_vk(), strategy, &[&[]], &mut transcript).is_ok() + ); + } + + #[test] + fn hash_test_vectors() { + for tv in crate::poseidon::primitives::test_vectors::fp::hash() { + let message = [ + pallas::Base::from_repr(tv.input[0]).unwrap(), + pallas::Base::from_repr(tv.input[1]).unwrap(), + ]; + let output = poseidon::Hash::<_, OrchardNullifier, ConstantLength<2>, 3, 2>::init() + .hash(message); + + let k = 6; + let circuit = HashCircuit:: { + message: Value::known(message), + output: Value::known(output), + _spec: PhantomData, + }; + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + } + } + + #[cfg(feature = "test-dev-graph")] + #[test] + fn print_poseidon_chip() { + use plotters::prelude::*; + + let root = BitMapBackend::new("poseidon-chip-layout.png", (1024, 768)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root + .titled("Poseidon Chip Layout", ("sans-serif", 60)) + .unwrap(); + + let circuit = HashCircuit:: { + message: Value::unknown(), + output: Value::unknown(), + _spec: PhantomData, + }; + halo2_proofs::dev::CircuitLayout::default() + .render(6, &circuit, &root) + .unwrap(); + } +} diff --git a/halo2_gadgets_optimized/src/poseidon/primitives.rs b/halo2_gadgets_optimized/src/poseidon/primitives.rs new file mode 100644 index 0000000000..6c8d133dc2 --- /dev/null +++ b/halo2_gadgets_optimized/src/poseidon/primitives.rs @@ -0,0 +1,405 @@ +//! The Poseidon algebraic hash function. + +use std::convert::TryInto; +use std::fmt; +use std::iter; +use std::marker::PhantomData; + +use group::ff::{Field, FromUniformBytes, PrimeField}; + +pub(crate) mod fp; +pub(crate) mod fq; +pub(crate) mod grain; +pub(crate) mod mds; + +#[cfg(test)] +pub(crate) mod test_vectors; + +mod p128pow5t3; +pub use p128pow5t3::P128Pow5T3; + +use grain::SboxType; + +/// The type used to hold permutation state. +pub(crate) type State = [F; T]; + +/// The type used to hold sponge rate. +pub(crate) type SpongeRate = [Option; RATE]; + +/// The type used to hold the MDS matrix and its inverse. +pub type Mds = [[F; T]; T]; + +/// A specification for a Poseidon permutation. +pub trait Spec: fmt::Debug { + /// The number of full rounds for this specification. + /// + /// This must be an even number. + fn full_rounds() -> usize; + + /// The number of partial rounds for this specification. + fn partial_rounds() -> usize; + + /// The S-box for this specification. + fn sbox(val: F) -> F; + + /// Side-loaded index of the first correct and secure MDS that will be generated by + /// the reference implementation. + /// + /// This is used by the default implementation of [`Spec::constants`]. If you are + /// hard-coding the constants, you may leave this unimplemented. + fn secure_mds() -> usize; + + /// Generates `(round_constants, mds, mds^-1)` corresponding to this specification. + fn constants() -> (Vec<[F; T]>, Mds, Mds); +} + +/// Generates `(round_constants, mds, mds^-1)` corresponding to this specification. +pub fn generate_constants< + F: FromUniformBytes<64> + Ord, + S: Spec, + const T: usize, + const RATE: usize, +>() -> (Vec<[F; T]>, Mds, Mds) { + let r_f = S::full_rounds(); + let r_p = S::partial_rounds(); + + let mut grain = grain::Grain::new(SboxType::Pow, T as u16, r_f as u16, r_p as u16); + + let round_constants = (0..(r_f + r_p)) + .map(|_| { + let mut rc_row = [F::ZERO; T]; + for (rc, value) in rc_row + .iter_mut() + .zip((0..T).map(|_| grain.next_field_element())) + { + *rc = value; + } + rc_row + }) + .collect(); + + let (mds, mds_inv) = mds::generate_mds::(&mut grain, S::secure_mds()); + + (round_constants, mds, mds_inv) +} + +/// Runs the Poseidon permutation on the given state. +pub(crate) fn permute, const T: usize, const RATE: usize>( + state: &mut State, + mds: &Mds, + round_constants: &[[F; T]], +) { + let r_f = S::full_rounds() / 2; + let r_p = S::partial_rounds(); + + let apply_mds = |state: &mut State| { + let mut new_state = [F::ZERO; T]; + // Matrix multiplication + #[allow(clippy::needless_range_loop)] + for i in 0..T { + for j in 0..T { + new_state[i] += mds[i][j] * state[j]; + } + } + *state = new_state; + }; + + let full_round = |state: &mut State, rcs: &[F; T]| { + for (word, rc) in state.iter_mut().zip(rcs.iter()) { + *word = S::sbox(*word + rc); + } + apply_mds(state); + }; + + let part_round = |state: &mut State, rcs: &[F; T]| { + for (word, rc) in state.iter_mut().zip(rcs.iter()) { + *word += rc; + } + // In a partial round, the S-box is only applied to the first state word. + state[0] = S::sbox(state[0]); + apply_mds(state); + }; + + iter::empty() + .chain(iter::repeat(&full_round as &dyn Fn(&mut State, &[F; T])).take(r_f)) + .chain(iter::repeat(&part_round as &dyn Fn(&mut State, &[F; T])).take(r_p)) + .chain(iter::repeat(&full_round as &dyn Fn(&mut State, &[F; T])).take(r_f)) + .zip(round_constants.iter()) + .fold(state, |state, (round, rcs)| { + round(state, rcs); + state + }); +} + +fn poseidon_sponge, const T: usize, const RATE: usize>( + state: &mut State, + input: Option<&Absorbing>, + mds_matrix: &Mds, + round_constants: &[[F; T]], +) -> Squeezing { + if let Some(Absorbing(input)) = input { + // `Iterator::zip` short-circuits when one iterator completes, so this will only + // mutate the rate portion of the state. + for (word, value) in state.iter_mut().zip(input.iter()) { + *word += value.expect("poseidon_sponge is called with a padded input"); + } + } + + permute::(state, mds_matrix, round_constants); + + let mut output = [None; RATE]; + for (word, value) in output.iter_mut().zip(state.iter()) { + *word = Some(*value); + } + Squeezing(output) +} + +mod private { + pub trait SealedSpongeMode {} + impl SealedSpongeMode for super::Absorbing {} + impl SealedSpongeMode for super::Squeezing {} +} + +/// The state of the `Sponge`. +pub trait SpongeMode: private::SealedSpongeMode {} + +/// The absorbing state of the `Sponge`. +#[derive(Debug)] +pub struct Absorbing(pub(crate) SpongeRate); + +/// The squeezing state of the `Sponge`. +#[derive(Debug)] +pub struct Squeezing(pub(crate) SpongeRate); + +impl SpongeMode for Absorbing {} +impl SpongeMode for Squeezing {} + +impl Absorbing { + pub(crate) fn init_with(val: F) -> Self { + Self( + iter::once(Some(val)) + .chain((1..RATE).map(|_| None)) + .collect::>() + .try_into() + .unwrap(), + ) + } +} + +/// A Poseidon sponge. +pub(crate) struct Sponge< + F: Field, + S: Spec, + M: SpongeMode, + const T: usize, + const RATE: usize, +> { + mode: M, + state: State, + mds_matrix: Mds, + round_constants: Vec<[F; T]>, + _marker: PhantomData, +} + +impl, const T: usize, const RATE: usize> + Sponge, T, RATE> +{ + /// Constructs a new sponge for the given Poseidon specification. + pub(crate) fn new(initial_capacity_element: F) -> Self { + let (round_constants, mds_matrix, _) = S::constants(); + + let mode = Absorbing([None; RATE]); + let mut state = [F::ZERO; T]; + state[RATE] = initial_capacity_element; + + Sponge { + mode, + state, + mds_matrix, + round_constants, + _marker: PhantomData::default(), + } + } + + /// Absorbs an element into the sponge. + pub(crate) fn absorb(&mut self, value: F) { + for entry in self.mode.0.iter_mut() { + if entry.is_none() { + *entry = Some(value); + return; + } + } + + // We've already absorbed as many elements as we can + let _ = poseidon_sponge::( + &mut self.state, + Some(&self.mode), + &self.mds_matrix, + &self.round_constants, + ); + self.mode = Absorbing::init_with(value); + } + + /// Transitions the sponge into its squeezing state. + pub(crate) fn finish_absorbing(mut self) -> Sponge, T, RATE> { + let mode = poseidon_sponge::( + &mut self.state, + Some(&self.mode), + &self.mds_matrix, + &self.round_constants, + ); + + Sponge { + mode, + state: self.state, + mds_matrix: self.mds_matrix, + round_constants: self.round_constants, + _marker: PhantomData::default(), + } + } +} + +impl, const T: usize, const RATE: usize> + Sponge, T, RATE> +{ + /// Squeezes an element from the sponge. + pub(crate) fn squeeze(&mut self) -> F { + loop { + for entry in self.mode.0.iter_mut() { + if let Some(e) = entry.take() { + return e; + } + } + + // We've already squeezed out all available elements + self.mode = poseidon_sponge::( + &mut self.state, + None, + &self.mds_matrix, + &self.round_constants, + ); + } + } +} + +/// A domain in which a Poseidon hash function is being used. +pub trait Domain { + /// Iterator that outputs padding field elements. + type Padding: IntoIterator; + + /// The name of this domain, for debug formatting purposes. + fn name() -> String; + + /// The initial capacity element, encoding this domain. + fn initial_capacity_element() -> F; + + /// Returns the padding to be appended to the input. + fn padding(input_len: usize) -> Self::Padding; +} + +/// A Poseidon hash function used with constant input length. +/// +/// Domain specified in [ePrint 2019/458 section 4.2](https://eprint.iacr.org/2019/458.pdf). +#[derive(Clone, Copy, Debug)] +pub struct ConstantLength; + +impl Domain for ConstantLength { + type Padding = iter::Take>; + + fn name() -> String { + format!("ConstantLength<{}>", L) + } + + fn initial_capacity_element() -> F { + // Capacity value is $length \cdot 2^64 + (o-1)$ where o is the output length. + // We hard-code an output length of 1. + F::from_u128((L as u128) << 64) + } + + fn padding(input_len: usize) -> Self::Padding { + assert_eq!(input_len, L); + // For constant-input-length hashing, we pad the input with zeroes to a multiple + // of RATE. On its own this would not be sponge-compliant padding, but the + // Poseidon authors encode the constant length into the capacity element, ensuring + // that inputs of different lengths do not share the same permutation. + let k = (L + RATE - 1) / RATE; + iter::repeat(F::ZERO).take(k * RATE - L) + } +} + +/// A Poseidon hash function, built around a sponge. +pub struct Hash< + F: Field, + S: Spec, + D: Domain, + const T: usize, + const RATE: usize, +> { + sponge: Sponge, T, RATE>, + _domain: PhantomData, +} + +impl, D: Domain, const T: usize, const RATE: usize> + fmt::Debug for Hash +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Hash") + .field("width", &T) + .field("rate", &RATE) + .field("R_F", &S::full_rounds()) + .field("R_P", &S::partial_rounds()) + .field("domain", &D::name()) + .finish() + } +} + +impl, D: Domain, const T: usize, const RATE: usize> + Hash +{ + /// Initializes a new hasher. + pub fn init() -> Self { + Hash { + sponge: Sponge::new(D::initial_capacity_element()), + _domain: PhantomData::default(), + } + } +} + +impl, const T: usize, const RATE: usize, const L: usize> + Hash, T, RATE> +{ + /// Hashes the given input. + pub fn hash(mut self, message: [F; L]) -> F { + for value in message + .into_iter() + .chain( as Domain>::padding(L)) + { + self.sponge.absorb(value); + } + self.sponge.finish_absorbing().squeeze() + } +} + +#[cfg(test)] +mod tests { + use group::ff::PrimeField; + use pasta_curves::pallas; + + use super::{permute, ConstantLength, Hash, P128Pow5T3 as OrchardNullifier, Spec}; + + #[test] + fn orchard_spec_equivalence() { + let message = [pallas::Base::from(6), pallas::Base::from(42)]; + + let (round_constants, mds, _) = OrchardNullifier::constants(); + + let hasher = Hash::<_, OrchardNullifier, ConstantLength<2>, 3, 2>::init(); + let result = hasher.hash(message); + + // The result should be equivalent to just directly applying the permutation and + // taking the first state element as the output. + let mut state = [message[0], message[1], pallas::Base::from_u128(2 << 64)]; + permute::<_, OrchardNullifier, 3, 2>(&mut state, &mds, &round_constants); + assert_eq!(state[0], result); + } +} diff --git a/halo2_gadgets_optimized/src/poseidon/primitives/fp.rs b/halo2_gadgets_optimized/src/poseidon/primitives/fp.rs new file mode 100644 index 0000000000..7041d20790 --- /dev/null +++ b/halo2_gadgets_optimized/src/poseidon/primitives/fp.rs @@ -0,0 +1,1431 @@ +//! Constants for using Poseidon with the Pallas field. +//! +//! The constants can be reproduced by running the following Sage script from +//! [this repository](https://github.com/daira/pasta-hadeshash): +//! +//! ```text +//! $ sage generate_parameters_grain.sage 1 0 255 3 8 56 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001 +//! ``` +use pasta_curves::pallas; + +// Number of round constants: 192 +// Round constants for GF(p): +pub(crate) const ROUND_CONSTANTS: [[pallas::Base; 3]; 64] = [ + [ + pallas::Base::from_raw([ + 0x5753_8c25_9642_6303, + 0x4e71_162f_3100_3b70, + 0x353f_628f_76d1_10f3, + 0x360d_7470_611e_473d, + ]), + pallas::Base::from_raw([ + 0xbdb7_4213_bf63_188b, + 0x4908_ac2f_12eb_e06f, + 0x5dc3_c6c5_febf_aa31, + 0x2bab_94d7_ae22_2d13, + ]), + pallas::Base::from_raw([ + 0x0939_d927_53cc_5dc8, + 0xef77_e7d7_3676_6c5d, + 0x2bf0_3e1a_29aa_871f, + 0x150c_93fe_f652_fb1c, + ]), + ], + [ + pallas::Base::from_raw([ + 0x1425_9dce_5377_82b2, + 0x03cc_0a60_141e_894e, + 0x955d_55db_56dc_57c1, + 0x3270_661e_6892_8b3a, + ]), + pallas::Base::from_raw([ + 0xce9f_b9ff_c345_afb3, + 0xb407_c370_f2b5_a1cc, + 0xa0b7_afe4_e205_7299, + 0x073f_116f_0412_2e25, + ]), + pallas::Base::from_raw([ + 0x8eba_d76f_c715_54d8, + 0x55c9_cd20_61ae_93ca, + 0x7aff_d09c_1f53_f5fd, + 0x2a32_ec5c_4ee5_b183, + ]), + ], + [ + pallas::Base::from_raw([ + 0x2d8c_cbe2_92ef_eead, + 0x634d_24fc_6e25_59f2, + 0x651e_2cfc_7406_28ca, + 0x2703_26ee_039d_f19e, + ]), + pallas::Base::from_raw([ + 0xa068_fc37_c182_e274, + 0x8af8_95bc_e012_f182, + 0xdc10_0fe7_fcfa_5491, + 0x27c6_642a_c633_bc66, + ]), + pallas::Base::from_raw([ + 0x9ca1_8682_e26d_7ff9, + 0x710e_1fb6_ab97_6a45, + 0xd27f_5739_6989_129d, + 0x1bdf_d8b0_1401_c70a, + ]), + ], + [ + pallas::Base::from_raw([ + 0xc832_d824_261a_35ea, + 0xf4f6_fb3f_9054_d373, + 0x14b9_d6a9_c84d_d678, + 0x162a_14c6_2f9a_89b8, + ]), + pallas::Base::from_raw([ + 0xf798_2466_7b5b_6bec, + 0xac0a_1fc7_1e2c_f0c0, + 0x2af6_f79e_3127_feea, + 0x2d19_3e0f_76de_586b, + ]), + pallas::Base::from_raw([ + 0x5d0b_f58d_c8a4_aa94, + 0x4fef_f829_8499_0ff8, + 0x8169_6ef1_104e_674f, + 0x044c_a3cc_4a85_d73b, + ]), + ], + [ + pallas::Base::from_raw([ + 0x6198_785f_0cd6_b9af, + 0xb8d9_e2d4_f314_f46f, + 0x1d04_5341_6d3e_235c, + 0x1cba_f2b3_71da_c6a8, + ]), + pallas::Base::from_raw([ + 0x343e_0761_0f3f_ede5, + 0x293c_4ab0_38fd_bbdc, + 0x0e6c_49d0_61b6_b5f4, + 0x1d5b_2777_692c_205b, + ]), + pallas::Base::from_raw([ + 0xf60e_971b_8d73_b04f, + 0x06a9_adb0_c1e6_f962, + 0xaa30_535b_dd74_9a7e, + 0x2e9b_dbba_3dd3_4bff, + ]), + ], + [ + pallas::Base::from_raw([ + 0x035a_1366_1f22_418b, + 0xde40_fbe2_6d04_7b05, + 0x8bd5_bae3_6969_299f, + 0x2de1_1886_b180_11ca, + ]), + pallas::Base::from_raw([ + 0xbc99_8884_ba96_a721, + 0x2ab9_395c_449b_e947, + 0x0d5b_4a3f_1841_dcd8, + 0x2e07_de17_80b8_a70d, + ]), + pallas::Base::from_raw([ + 0x825e_4c2b_b749_25ca, + 0x2504_40a9_9d6b_8af3, + 0xbbdb_63db_d52d_ad16, + 0x0f69_f185_4d20_ca0c, + ]), + ], + [ + pallas::Base::from_raw([ + 0x816c_0594_22dc_705e, + 0x6ce5_1135_07f9_6de9, + 0x0d13_5dc6_39fb_09a4, + 0x2eb1_b254_17fe_1767, + ]), + pallas::Base::from_raw([ + 0xb8b1_bdf4_953b_d82c, + 0xff36_c661_d26c_c42d, + 0x8c24_cb44_c3fa_b48a, + 0x115c_d0a0_643c_fb98, + ]), + pallas::Base::from_raw([ + 0xde80_1612_311d_04cd, + 0xbb57_ddf1_4e0f_958a, + 0x066d_7378_b999_868b, + 0x26ca_293f_7b2c_462d, + ]), + ], + [ + pallas::Base::from_raw([ + 0xf520_9d14_b248_20ca, + 0x0f16_0bf9_f71e_967f, + 0x2a83_0aa1_6241_2cd9, + 0x17bf_1b93_c4c7_e01a, + ]), + pallas::Base::from_raw([ + 0x05c8_6f2e_7dc2_93c5, + 0xe03c_0354_bd8c_fd38, + 0xa24f_8456_369c_85df, + 0x35b4_1a7a_c4f3_c571, + ]), + pallas::Base::from_raw([ + 0x72ac_156a_f435_d09e, + 0x64e1_4d3b_eb2d_ddde, + 0x4359_2799_4849_bea9, + 0x3b14_8008_0523_c439, + ]), + ], + [ + pallas::Base::from_raw([ + 0x2716_18d8_74b1_4c6d, + 0x08e2_8644_2a2d_3eb2, + 0x4950_856d_c907_d575, + 0x2cc6_8100_31dc_1b0d, + ]), + pallas::Base::from_raw([ + 0x91f3_18c0_9f0c_b566, + 0x9e51_7aa9_3b78_341d, + 0x0596_18e2_afd2_ef99, + 0x25bd_bbed_a1bd_e8c1, + ]), + pallas::Base::from_raw([ + 0xc631_3487_073f_7f7b, + 0x2a5e_d0a2_7b61_926c, + 0xb95f_33c2_5dde_8ac0, + 0x392a_4a87_58e0_6ee8, + ]), + ], + [ + pallas::Base::from_raw([ + 0xe7bb_cef0_2eb5_866c, + 0x5e6a_6fd1_5db8_9365, + 0x9aa6_111f_4de0_0948, + 0x272a_5587_8a08_442b, + ]), + pallas::Base::from_raw([ + 0x9b92_5b3c_5b21_e0e2, + 0xa6eb_ba01_1694_dd12, + 0xefa1_3c4e_60e2_6239, + 0x2d5b_308b_0cf0_2cdf, + ]), + pallas::Base::from_raw([ + 0xef38_c57c_3116_73ac, + 0x44df_f42f_18b4_6c56, + 0xdd5d_293d_72e2_e5f2, + 0x1654_9fc6_af2f_3b72, + ]), + ], + [ + pallas::Base::from_raw([ + 0x9b71_26d9_b468_60df, + 0x7639_8265_3442_0311, + 0xfa69_c3a2_ad52_f76d, + 0x1b10_bb7a_82af_ce39, + ]), + pallas::Base::from_raw([ + 0x90d2_7f6a_00b7_dfc8, + 0xd1b3_6968_ba04_05c0, + 0xc79c_2df7_dc98_a3be, + 0x0f1e_7505_ebd9_1d2f, + ]), + pallas::Base::from_raw([ + 0xff45_7756_b819_bb20, + 0x797f_d6e3_f18e_b1ca, + 0x537a_7497_a3b4_3f46, + 0x2f31_3faf_0d3f_6187, + ]), + ], + [ + pallas::Base::from_raw([ + 0xf0bc_3e73_2ecb_26f6, + 0x5cad_11eb_f0f7_ceb8, + 0xfa3c_a61c_0ed1_5bc5, + 0x3a5c_bb6d_e450_b481, + ]), + pallas::Base::from_raw([ + 0x8655_27cb_ca91_5982, + 0x51ba_a6e2_0f89_2b62, + 0xd920_86e2_53b4_39d6, + 0x3dab_54bc_9bef_688d, + ]), + pallas::Base::from_raw([ + 0x3680_45ac_f2b7_1ae3, + 0x4c24_b33b_410f_efd4, + 0xe280_d316_7012_3f74, + 0x06db_fb42_b979_884d, + ]), + ], + [ + pallas::Base::from_raw([ + 0xa7fc_32d2_2f18_b9d3, + 0xb8d2_de72_e3d2_c9ec, + 0xc6f0_39ea_1973_a63e, + 0x068d_6b46_08aa_e810, + ]), + pallas::Base::from_raw([ + 0x2b5d_fcc5_5725_55df, + 0xb868_a7d7_e1f1_f69a, + 0x0ee2_58c9_b8fd_fccd, + 0x366e_bfaf_a3ad_381c, + ]), + pallas::Base::from_raw([ + 0xe6bc_229e_95bc_76b1, + 0x7ef6_6d89_d044_d022, + 0x04db_3024_f41d_3f56, + 0x3967_8f65_512f_1ee4, + ]), + ], + [ + pallas::Base::from_raw([ + 0xe534_c88f_e53d_85fe, + 0xcf82_c25f_99dc_01a4, + 0xd58b_7750_a3bc_2fe1, + 0x2166_8f01_6a80_63c0, + ]), + pallas::Base::from_raw([ + 0x4bef_429b_c533_1608, + 0xe34d_ea56_439f_e195, + 0x1bc7_4936_3e98_a768, + 0x39d0_0994_a8a5_046a, + ]), + pallas::Base::from_raw([ + 0x770c_956f_60d8_81b3, + 0xb163_d416_05d3_9f99, + 0x6b20_3bbe_12fb_3425, + 0x1f9d_bdc3_f843_1263, + ]), + ], + [ + pallas::Base::from_raw([ + 0x9794_a9f7_c336_eab2, + 0xbe0b_c829_fe5e_66c6, + 0xe5f1_7b9e_0ee0_cab6, + 0x0277_45a9_cddf_ad95, + ]), + pallas::Base::from_raw([ + 0x5202_5657_abd8_aee0, + 0x2fa4_3fe2_0a45_c78d, + 0x788d_695c_61e9_3212, + 0x1cec_0803_c504_b635, + ]), + pallas::Base::from_raw([ + 0xd387_2a95_59a0_3a73, + 0xed50_82c8_dbf3_1365, + 0x7207_7448_ef87_cc6e, + 0x1235_23d7_5e9f_abc1, + ]), + ], + [ + pallas::Base::from_raw([ + 0x0017_79e3_a1d3_57f4, + 0x27fe_ba35_975e_e7e5, + 0xf419_b848_e5d6_94bf, + 0x1723_d145_2c9c_f02d, + ]), + pallas::Base::from_raw([ + 0x9dab_1ee4_dcf9_6622, + 0x21c3_f776_f572_836d, + 0xfcc0_573d_7e61_3694, + 0x1739_d180_a160_10bd, + ]), + pallas::Base::from_raw([ + 0x7029_0452_042d_048d, + 0xfafa_96fb_eb0a_b893, + 0xacce_3239_1794_b627, + 0x2d4e_6354_da9c_c554, + ]), + ], + [ + pallas::Base::from_raw([ + 0x670b_cf6f_8b48_5dcd, + 0x8f3b_d43f_9926_0621, + 0x4a86_9553_c9d0_07f8, + 0x153e_e614_2e53_5e33, + ]), + pallas::Base::from_raw([ + 0xd258_d2e2_b778_2172, + 0x968a_d442_4af8_3700, + 0x635e_f7e7_a430_b486, + 0x0c45_bfd3_a69a_aa65, + ]), + pallas::Base::from_raw([ + 0x0e56_33d2_51f7_3307, + 0x6897_ac0a_8ffa_5ff1, + 0xf2d5_6aec_8314_4600, + 0x0adf_d53b_256a_6957, + ]), + ], + [ + pallas::Base::from_raw([ + 0xac9d_36a8_b751_6d63, + 0x3f87_b28f_1c1b_e4bd, + 0x8cd1_726b_7cba_b8ee, + 0x315d_2ac8_ebdb_ac3c, + ]), + pallas::Base::from_raw([ + 0x299c_e44e_a423_d8e1, + 0xc9bb_60d1_f695_9879, + 0xcfae_c23d_2b16_883f, + 0x1b84_7271_2d02_eef4, + ]), + pallas::Base::from_raw([ + 0xc4a5_4041_98ad_f70c, + 0x367d_2c54_e369_28c9, + 0xbd0b_70fa_2255_eb6f, + 0x3c1c_d07e_fda6_ff24, + ]), + ], + [ + pallas::Base::from_raw([ + 0xbbe5_23ae_f9ab_107a, + 0x4a16_073f_738f_7e0c, + 0x687f_4e51_b2e1_dcd3, + 0x1360_52d2_6bb3_d373, + ]), + pallas::Base::from_raw([ + 0x676c_36c2_4ef9_67dd, + 0x7b3c_fbb8_7303_2681, + 0xc1bd_d859_a123_2a1d, + 0x16c9_6bee_f6a0_a848, + ]), + pallas::Base::from_raw([ + 0x067e_ec7f_2d63_40c4, + 0x0123_87ba_b4f1_662d, + 0x2ab7_fed8_f499_a9fb, + 0x284b_38c5_7ff6_5c26, + ]), + ], + [ + pallas::Base::from_raw([ + 0xaf1d_ff20_4c92_2f86, + 0xfc06_772c_1c04_11a6, + 0x39e2_4219_8897_d17c, + 0x0c59_93d1_75e8_1f66, + ]), + pallas::Base::from_raw([ + 0xbbf5_3f67_b1f8_7b15, + 0xf248_87ad_48e1_7759, + 0xfcda_655d_1ba9_c8f9, + 0x03bf_7a3f_7bd0_43da, + ]), + pallas::Base::from_raw([ + 0x9b5c_d09e_36d8_be62, + 0x4c8f_9cbe_69f0_e827, + 0xb0cf_9995_67f0_0e73, + 0x3188_fe4e_e9f9_fafb, + ]), + ], + [ + pallas::Base::from_raw([ + 0xafea_99a2_ec6c_595a, + 0x3af5_bf77_c1c4_2652, + 0x5a39_768c_480d_61e1, + 0x171f_528c_cf65_8437, + ]), + pallas::Base::from_raw([ + 0x5a05_63b9_b8e9_f1d5, + 0x812c_3286_ee70_0067, + 0x196e_4185_9b35_ef88, + 0x12f4_175c_4ab4_5afc, + ]), + pallas::Base::from_raw([ + 0x0e74_d4d3_6911_8b79, + 0x7e23_e1aa_be96_cfab, + 0x8f8f_dcf8_00a9_ac69, + 0x3a50_9e15_5cb7_ebfd, + ]), + ], + [ + pallas::Base::from_raw([ + 0x9871_2c65_678c_fd30, + 0x984b_c8f2_e4c1_b69e, + 0x1a89_920e_2504_c3b3, + 0x10f2_a685_df4a_27c8, + ]), + pallas::Base::from_raw([ + 0xe8a1_6728_cc9d_4918, + 0x5457_3c93_33c5_6321, + 0x1d8d_93d5_4ab9_1a0e, + 0x09e5_f497_90c8_a0e2, + ]), + pallas::Base::from_raw([ + 0x609a_7403_47cf_5fea, + 0x42d1_7ed6_ee0f_ab7e, + 0x2bf3_5705_d9f8_4a34, + 0x352d_69be_d80e_e3e5, + ]), + ], + [ + pallas::Base::from_raw([ + 0x3a75_8af6_fa84_e0e8, + 0xc634_debd_281b_76a6, + 0x4915_62fa_f2b1_90d3, + 0x058e_e73b_a9f3_f293, + ]), + pallas::Base::from_raw([ + 0x621a_1325_10a4_3904, + 0x092c_b921_19bc_76be, + 0xcd0f_1fc5_5b1a_3250, + 0x232f_99cc_911e_ddd9, + ]), + pallas::Base::from_raw([ + 0xc3b9_7c1e_301b_c213, + 0xf9ef_d52c_a6bc_2961, + 0x86c2_2c6c_5d48_69f0, + 0x201b_eed7_b8f3_ab81, + ]), + ], + [ + pallas::Base::from_raw([ + 0xbf6b_3431_ba94_e9bc, + 0x2938_8842_744a_1210, + 0xa1c9_291d_5860_2f51, + 0x1376_dce6_5800_30c6, + ]), + pallas::Base::from_raw([ + 0x6454_843c_5486_d7b3, + 0x072b_a8b0_2d92_e722, + 0x2b33_56c3_8238_f761, + 0x1793_199e_6fd6_ba34, + ]), + pallas::Base::from_raw([ + 0x06a3_f1d3_b433_311b, + 0x3c66_160d_c62a_acac, + 0x9fee_9c20_c87a_67df, + 0x22de_7a74_88dc_c735, + ]), + ], + [ + pallas::Base::from_raw([ + 0x30d6_e3fd_516b_47a8, + 0xdbe0_b77f_ae77_e1d0, + 0xdf8f_f37f_e2d8_edf8, + 0x3514_d5e9_066b_b160, + ]), + pallas::Base::from_raw([ + 0x1937_7427_137a_81c7, + 0xff45_3d6f_900f_144a, + 0xf919_a00d_abbf_5fa5, + 0x30cd_3006_931a_d636, + ]), + pallas::Base::from_raw([ + 0x5b6a_7422_0692_b506, + 0x8f9e_4b2c_ae2e_bb51, + 0x41f8_1a5c_f613_c8df, + 0x253d_1a5c_5293_4127, + ]), + ], + [ + pallas::Base::from_raw([ + 0x73f6_66cb_86a4_8e8e, + 0x851b_3a59_c990_fafc, + 0xa35e_9613_e7f5_fe92, + 0x035b_461c_02d7_9d19, + ]), + pallas::Base::from_raw([ + 0x7cfb_f86a_3aa0_4780, + 0x92b1_283c_2d5f_ccde, + 0x5bc0_0eed_d56b_93e0, + 0x23a9_9280_79d1_75bd, + ]), + pallas::Base::from_raw([ + 0xf1e4_ccd7_3fa0_0a82, + 0xb5e2_ea34_36ee_f957, + 0xf159_4a07_63c6_11ab, + 0x13a7_785a_e134_ea92, + ]), + ], + [ + pallas::Base::from_raw([ + 0xbbf0_4f52_52de_4279, + 0x3889_c578_6344_6d88, + 0x4962_ae3c_0da1_7e31, + 0x39fc_e308_b7d4_3c57, + ]), + pallas::Base::from_raw([ + 0x3b57_e344_89b5_3fad, + 0xbef0_0a08_c6ed_38d2, + 0xc0fd_f016_62f6_0d22, + 0x1aae_1883_3f8e_1d3a, + ]), + pallas::Base::from_raw([ + 0x5551_3e03_3398_513f, + 0x27c1_b3fd_8f85_d8a8, + 0x8b2e_80c0_64fd_83ed, + 0x1a76_1ce8_2400_af01, + ]), + ], + [ + pallas::Base::from_raw([ + 0x5244_ca74_9b73_e481, + 0xdcf6_af28_30a5_0287, + 0x16dd_1a87_ca22_e1cc, + 0x275a_03e4_5add_a7c3, + ]), + pallas::Base::from_raw([ + 0x58a2_53cf_b6a9_5786, + 0x07e5_6145_3fc5_648b, + 0xeb08_e47e_5fea_bcf8, + 0x2e5a_10f0_8b5a_b8bb, + ]), + pallas::Base::from_raw([ + 0xe033_d82c_efe7_8ce3, + 0xc141_a5b6_d594_bec4, + 0xb84e_9c33_3b29_32f1, + 0x1459_cb85_8720_8473, + ]), + ], + [ + pallas::Base::from_raw([ + 0x5cec_7e7b_338f_be1b, + 0x52f9_332f_bffc_fbbd, + 0x7b92_ce81_0e14_a400, + 0x193a_e592_1d78_b5de, + ]), + pallas::Base::from_raw([ + 0x6022_4be6_7248_e82c, + 0x3743_84f4_a072_8205, + 0x8911_1fb2_c466_0281, + 0x3097_898a_5d00_11a4, + ]), + pallas::Base::from_raw([ + 0x5499_80de_8629_30f5, + 0x1979_b2d1_c465_b4d9, + 0x5717_82fd_96ce_54b4, + 0x378d_97bf_8c86_4ae7, + ]), + ], + [ + pallas::Base::from_raw([ + 0x37ea_32a9_71d1_7884, + 0xdbc7_f5cb_4609_3421, + 0x8813_6287_ce37_6b08, + 0x2eb0_4ea7_c01d_97ec, + ]), + pallas::Base::from_raw([ + 0xead3_726f_1af2_e7b0, + 0x861c_bda4_7680_4e6c, + 0x2302_a1c2_2e49_baec, + 0x3642_5347_ea03_f641, + ]), + pallas::Base::from_raw([ + 0xecd6_27e5_9590_d09e, + 0x3f5b_5ca5_a19a_9701, + 0xcc99_6cd8_5c98_a1d8, + 0x26b7_2df4_7408_ad42, + ]), + ], + [ + pallas::Base::from_raw([ + 0x59be_ce31_f0a3_1e95, + 0xde01_212e_e458_8f89, + 0x1f05_636c_610b_89aa, + 0x1301_80e4_4e29_24db, + ]), + pallas::Base::from_raw([ + 0x9ea8_e7bc_7926_3550, + 0xdf77_93cc_89e5_b52f, + 0x7327_5aca_ed5f_579c, + 0x219e_9773_7d39_79ba, + ]), + pallas::Base::from_raw([ + 0x9c12_635d_f251_d153, + 0x3b06_72dd_7d42_cbb4, + 0x3461_363f_81c4_89a2, + 0x3cdb_9359_8a5c_a528, + ]), + ], + [ + pallas::Base::from_raw([ + 0x2861_ce16_f219_d5a9, + 0x4ad0_4470_45a7_c5aa, + 0x2072_4b92_7a0c_a81c, + 0x0e59_e6f3_32d7_ed37, + ]), + pallas::Base::from_raw([ + 0x43b0_a3fc_ff20_36bd, + 0x172c_c07b_9d33_fbf9, + 0x3d73_6946_7222_697a, + 0x1b06_4342_d51a_4275, + ]), + pallas::Base::from_raw([ + 0x3eb3_1022_8a0e_5f6c, + 0x78fa_9fb9_1712_21b7, + 0x2f36_3c55_b288_2e0b, + 0x30b8_2a99_8cbd_8e8a, + ]), + ], + [ + pallas::Base::from_raw([ + 0xe46f_6d42_9874_0107, + 0x8ad7_1ea7_15be_0573, + 0x63df_7a76_e858_a4aa, + 0x23e4_ab37_183a_cba4, + ]), + pallas::Base::from_raw([ + 0xfca9_95e2_b599_14a1, + 0xacfe_1464_0de0_44f2, + 0x5d33_094e_0bed_a75b, + 0x2795_d5c5_fa42_8022, + ]), + pallas::Base::from_raw([ + 0xc26d_909d_ee8b_53c0, + 0xa668_7c3d_f16c_8fe4, + 0xd765_f26d_d03f_4c45, + 0x3001_ca40_1e89_601c, + ]), + ], + [ + pallas::Base::from_raw([ + 0xe7fe_a6bd_f347_1380, + 0xe84b_5beb_ae4e_501d, + 0xf7bf_86e8_9280_827f, + 0x0072_e45c_c676_b08e, + ]), + pallas::Base::from_raw([ + 0xd0c5_4dde_b26b_86c0, + 0xb648_29e2_d40e_41bd, + 0xe2ab_e4c5_18ce_599e, + 0x13de_7054_8487_4bb5, + ]), + pallas::Base::from_raw([ + 0x3891_5b43_2a99_59a5, + 0x82bb_18e5_af1b_05bb, + 0x3159_50f1_211d_efe8, + 0x0408_a9fc_f9d6_1abf, + ]), + ], + [ + pallas::Base::from_raw([ + 0x3407_0cbe_e268_86a0, + 0xae4d_23b0_b41b_e9a8, + 0xbb4e_4a14_00cc_d2c4, + 0x2780_b9e7_5b55_676e, + ]), + pallas::Base::from_raw([ + 0x9405_5920_98b4_056f, + 0xdc4d_8fbe_fe24_405a, + 0xf803_33ec_8563_4ac9, + 0x3a57_0d4d_7c4e_7ac3, + ]), + pallas::Base::from_raw([ + 0x78d2_b247_8995_20b4, + 0xe2cc_1507_bebd_cc62, + 0xf347_c247_fcf0_9294, + 0x0c13_cca7_cb1f_9d2c, + ]), + ], + [ + pallas::Base::from_raw([ + 0x2e8c_88f7_7074_70e0, + 0x0b50_bb2e_b82d_f74d, + 0xd261_4a19_7c6b_794b, + 0x14f5_9baa_03cd_0ca4, + ]), + pallas::Base::from_raw([ + 0xbe52_476e_0a16_f3be, + 0xa51d_54ed_e661_67f5, + 0x6f54_6e17_04c3_9c60, + 0x307d_efee_925d_fb43, + ]), + pallas::Base::from_raw([ + 0x380b_67d8_0473_dce3, + 0x6611_0683_6adf_e5e7, + 0x7a07_e767_4b5a_2621, + 0x1960_cd51_1a91_e060, + ]), + ], + [ + pallas::Base::from_raw([ + 0x15aa_f1f7_7125_89dd, + 0xb8ee_335d_8828_4cbe, + 0xca2a_d0fb_5667_2500, + 0x2301_ef9c_63ea_84c5, + ]), + pallas::Base::from_raw([ + 0x5e68_478c_4d60_27a9, + 0xc861_82d1_b424_6b58, + 0xd10f_4cd5_2be9_7f6b, + 0x029a_5a47_da79_a488, + ]), + pallas::Base::from_raw([ + 0x2cc4_f962_eaae_2260, + 0xf97f_e46b_6a92_5428, + 0x2360_d17d_890e_55cb, + 0x32d7_b16a_7f11_cc96, + ]), + ], + [ + pallas::Base::from_raw([ + 0xc0ca_b915_d536_3d9f, + 0xa5f2_404c_d7b3_5eb0, + 0x18e8_57a9_8d49_8cf7, + 0x2670_3e48_c03b_81ca, + ]), + pallas::Base::from_raw([ + 0xf691_123a_e112_b928, + 0xf443_88bd_6b89_221e, + 0x88ac_8d25_a246_03f1, + 0x0486_82a3_5b32_65bc, + ]), + pallas::Base::from_raw([ + 0x3ab7_defc_b8d8_03e2, + 0x91d6_e171_5164_775e, + 0xd72c_ddc6_cf06_b507, + 0x06b1_3904_41fa_7030, + ]), + ], + [ + pallas::Base::from_raw([ + 0xbcd7_9541_4a6e_2e86, + 0x43b3_60f6_386a_86d7, + 0x1689_426d_ce05_fcd8, + 0x31aa_0eeb_868c_626d, + ]), + pallas::Base::from_raw([ + 0xed77_f5d5_76b9_9cc3, + 0x90ef_d8f4_1b20_78b2, + 0x057a_bad3_764c_104b, + 0x2394_64f7_5bf7_b6af, + ]), + pallas::Base::from_raw([ + 0xb2cb_4873_07c1_cecf, + 0xa5cc_47c5_9654_b2a7, + 0xa45e_19ed_813a_54ab, + 0x0a64_d4c0_4fd4_26bd, + ]), + ], + [ + pallas::Base::from_raw([ + 0x1f73_1532_2f65_8735, + 0x777c_7a92_1a06_2e9d, + 0x576a_4ad2_5986_0fb1, + 0x21fb_bdbb_7367_0734, + ]), + pallas::Base::from_raw([ + 0x6743_2400_3fc5_2146, + 0x5b86_d294_63d3_1564, + 0xd937_1ca2_eb95_acf3, + 0x31b8_6f3c_f017_05d4, + ]), + pallas::Base::from_raw([ + 0x7045_f48a_a4eb_4f6f, + 0x1354_1d65_157e_e1ce, + 0x05ef_1736_d090_56f6, + 0x2bfd_e533_5437_7c91, + ]), + ], + [ + pallas::Base::from_raw([ + 0x5a13_a58d_2001_1e2f, + 0xf4d5_239c_11d0_eafa, + 0xd558_f36e_65f8_eca7, + 0x1233_ca93_6ec2_4671, + ]), + pallas::Base::from_raw([ + 0x6e70_af0a_7a92_4b3a, + 0x8780_58d0_234a_576f, + 0xc437_846d_8e0b_2b30, + 0x27d4_52a4_3ac7_dea2, + ]), + pallas::Base::from_raw([ + 0xa025_76b9_4392_f980, + 0x6a30_641a_1c3d_87b2, + 0xe816_ea8d_a493_e0fa, + 0x2699_dba8_2184_e413, + ]), + ], + [ + pallas::Base::from_raw([ + 0x608c_6f7a_61b5_6e55, + 0xf185_8466_4f8c_ab49, + 0xc398_8bae_e42e_4b10, + 0x36c7_22f0_efcc_8803, + ]), + pallas::Base::from_raw([ + 0x6e49_ac17_0dbb_7fcd, + 0x85c3_8899_a7b5_a833, + 0x08b0_f2ec_89cc_aa37, + 0x02b3_ff48_861e_339b, + ]), + pallas::Base::from_raw([ + 0xa8c5_ae03_ad98_e405, + 0x6fc3_ff4c_49eb_59ad, + 0x6016_2f44_27bc_657b, + 0x0b70_d061_d58d_8a7f, + ]), + ], + [ + pallas::Base::from_raw([ + 0x2e06_cc4a_f33b_0a06, + 0xad3d_e8be_46ed_9693, + 0xf875_3ade_b9d7_cee2, + 0x3fc2_a13f_127f_96a4, + ]), + pallas::Base::from_raw([ + 0xc120_80ac_117e_e15f, + 0x00cb_3d62_1e17_1d80, + 0x1bd6_3434_ac8c_419f, + 0x0c41_a6e4_8dd2_3a51, + ]), + pallas::Base::from_raw([ + 0x9685_213e_9692_f5e1, + 0x72aa_ad7e_4e75_339d, + 0xed44_7653_7169_084e, + 0x2de8_072a_6bd8_6884, + ]), + ], + [ + pallas::Base::from_raw([ + 0x0ad0_1184_567b_027c, + 0xb81c_f735_cc9c_39c0, + 0x9d34_96a3_d9fe_05ec, + 0x0355_7a8f_7b38_a17f, + ]), + pallas::Base::from_raw([ + 0x45bc_b5ac_0082_6abc, + 0x060f_4336_3d81_8e54, + 0xee97_6d34_282f_1a37, + 0x0b5f_5955_2f49_8735, + ]), + pallas::Base::from_raw([ + 0x2f29_09e1_7e22_b0df, + 0xf5d6_46e5_7507_e548, + 0xfedb_b185_70dc_7300, + 0x0e29_23a5_fee7_b878, + ]), + ], + [ + pallas::Base::from_raw([ + 0xf71e_ed73_f15b_3326, + 0xcf1c_b37c_3b03_2af6, + 0xc787_be97_020a_7fdd, + 0x1d78_5005_a7a0_0592, + ]), + pallas::Base::from_raw([ + 0x0acf_bfb2_23f8_f00d, + 0xa590_b88a_3b06_0294, + 0x0ba5_fedc_b8f2_5bd2, + 0x1ad7_72c2_73d9_c6df, + ]), + pallas::Base::from_raw([ + 0xc1ce_13d6_0f2f_5031, + 0x8105_10eb_61f0_672d, + 0xa78f_3275_c278_234b, + 0x027b_d647_85fc_bd2a, + ]), + ], + [ + pallas::Base::from_raw([ + 0x8337_f5e0_7923_a853, + 0xe224_3134_6945_7b8e, + 0xce6f_8ffe_a103_1b6d, + 0x2080_0f44_1b4a_0526, + ]), + pallas::Base::from_raw([ + 0xa33d_7bed_89a4_408a, + 0x36cd_c8ee_d662_ad37, + 0x6eea_2cd4_9f43_12b4, + 0x3d5a_d61d_7b65_f938, + ]), + pallas::Base::from_raw([ + 0x3bbb_ae94_cc19_5284, + 0x1df9_6cc0_3ea4_b26d, + 0x02c5_f91b_e4dd_8e3d, + 0x1333_8bc3_51fc_46dd, + ]), + ], + [ + pallas::Base::from_raw([ + 0xc527_1c29_7852_819e, + 0x646c_49f9_b46c_bf19, + 0xb87d_b1e2_af3e_a923, + 0x25e5_2be5_07c9_2760, + ]), + pallas::Base::from_raw([ + 0x5c38_0ab7_01b5_2ea9, + 0xa34c_83a3_485c_6b2d, + 0x7109_6d8b_1b98_3c98, + 0x1c49_2d64_c157_aaa4, + ]), + pallas::Base::from_raw([ + 0xa20c_0b3d_a0da_4ca3, + 0xd434_87bc_288d_f682, + 0xf4e6_c5e7_a573_f592, + 0x0c5b_8015_7999_2718, + ]), + ], + [ + pallas::Base::from_raw([ + 0x7ea3_3c93_e408_33cf, + 0x584e_9e62_a7f9_554e, + 0x6869_5c0c_d7cb_f43d, + 0x1090_b1b4_d2be_be7a, + ]), + pallas::Base::from_raw([ + 0xe383_e1ec_3baa_8d69, + 0x1b21_8e35_ecf2_328e, + 0x68f5_ce5c_bed1_9cad, + 0x33e3_8018_a801_387a, + ]), + pallas::Base::from_raw([ + 0xb76b_0b3d_787e_e953, + 0x5f4a_02d2_8729_e3ae, + 0xeef8_d83d_0e87_6bac, + 0x1654_af18_772b_2da5, + ]), + ], + [ + pallas::Base::from_raw([ + 0xef7c_e6a0_1326_5477, + 0xbb08_9387_0367_ec6c, + 0x4474_2de8_8c5a_b0d5, + 0x1678_be3c_c9c6_7993, + ]), + pallas::Base::from_raw([ + 0xaf5d_4789_3348_f766, + 0xdaf1_8183_55b1_3b4f, + 0x7ff9_c6be_546e_928a, + 0x3780_bd1e_01f3_4c22, + ]), + pallas::Base::from_raw([ + 0xa123_8032_0d7c_c1de, + 0x5d11_e69a_a6c0_b98c, + 0x0786_018e_7cb7_7267, + 0x1e83_d631_5c9f_125b, + ]), + ], + [ + pallas::Base::from_raw([ + 0x1799_603e_855c_e731, + 0xc486_894d_76e0_c33b, + 0x160b_4155_2f29_31c8, + 0x354a_fd0a_2f9d_0b26, + ]), + pallas::Base::from_raw([ + 0x8b99_7ee0_6be1_bff3, + 0x60b0_0dbe_1fac_ed07, + 0x2d8a_ffa6_2905_c5a5, + 0x00cd_6d29_f166_eadc, + ]), + pallas::Base::from_raw([ + 0x08d0_6419_1708_2f2c, + 0xc60d_0197_3f18_3057, + 0xdbe0_e3d7_cdbc_66ef, + 0x1d62_1935_2768_e3ae, + ]), + ], + [ + pallas::Base::from_raw([ + 0xfa08_dd98_0638_7577, + 0xafe3_ca1d_b8d4_f529, + 0xe48d_2370_d7d1_a142, + 0x1463_36e2_5db5_181d, + ]), + pallas::Base::from_raw([ + 0xa901_d3ce_84de_0ad4, + 0x022e_54b4_9c13_d907, + 0x997a_2116_3e2e_43df, + 0x0005_d8e0_85fd_72ee, + ]), + pallas::Base::from_raw([ + 0x1c36_f313_4196_4484, + 0x6f8e_bc1d_2296_021a, + 0x0dd5_e61c_8a4e_8642, + 0x364e_97c7_a389_3227, + ]), + ], + [ + pallas::Base::from_raw([ + 0xd7a0_0c03_d2e0_baaa, + 0xfa97_ec80_ad30_7a52, + 0x561c_6fff_1534_6878, + 0x0118_9910_671b_c16b, + ]), + pallas::Base::from_raw([ + 0x63fd_8ac5_7a95_ca8c, + 0x4c0f_7e00_1df4_90aa, + 0x5229_dfaa_0123_1a45, + 0x162a_7c80_f4d2_d12e, + ]), + pallas::Base::from_raw([ + 0x32e6_9efb_22f4_0b96, + 0xcaff_31b4_fda3_2124, + 0x2604_e4af_b09f_8603, + 0x2a0d_6c09_5766_66bb, + ]), + ], + [ + pallas::Base::from_raw([ + 0xc0a0_180f_8cbf_c0d2, + 0xf444_d10d_63a7_4e2c, + 0xe16a_4d60_3d5a_808e, + 0x0978_e5c5_1e1e_5649, + ]), + pallas::Base::from_raw([ + 0x03f4_460e_bc35_1b6e, + 0x0508_7d90_3bda_cfd1, + 0xebe1_9bbd_ce25_1011, + 0x1bdc_ee3a_aca9_cd25, + ]), + pallas::Base::from_raw([ + 0xf619_64bf_3ade_7670, + 0x0c94_7321_e007_5e3f, + 0xe494_7914_0b19_44fd, + 0x1862_cccb_70b5_b885, + ]), + ], + [ + pallas::Base::from_raw([ + 0xc326_7da6_e94a_dc50, + 0x39ee_99c1_cc6e_5dda, + 0xbc26_cc88_3a19_87e1, + 0x1f3e_91d8_63c1_6922, + ]), + pallas::Base::from_raw([ + 0x0f85_b4ac_2c36_7406, + 0xfa66_1465_c656_ad99, + 0xef5c_08f8_478f_663a, + 0x1af4_7a48_a601_6a49, + ]), + pallas::Base::from_raw([ + 0x0eab_cd87_e7d0_1b15, + 0x1c36_98b0_a2e3_da10, + 0x009d_5733_8c69_3505, + 0x3c8e_e901_956e_3d3f, + ]), + ], + [ + pallas::Base::from_raw([ + 0x8b94_7721_8967_3476, + 0xe10c_e2b7_069f_4dbd, + 0x68d0_b024_f591_b520, + 0x1660_a8cd_e7fe_c553, + ]), + pallas::Base::from_raw([ + 0x9d8d_0f67_fdaa_79d5, + 0x3963_c2c1_f558_6e2f, + 0x1303_9363_34dd_1132, + 0x0f6d_9919_29d5_e4e7, + ]), + pallas::Base::from_raw([ + 0x7a43_3091_e1ce_2d3a, + 0x4e7f_da77_0712_f343, + 0xcc62_5eaa_ab52_b4dc, + 0x02b9_cea1_921c_d9f6, + ]), + ], + [ + pallas::Base::from_raw([ + 0x3797_b2d8_3760_43b3, + 0xd8ca_f468_976f_0472, + 0x214f_7c67_84ac_b565, + 0x14a3_23b9_9b90_0331, + ]), + pallas::Base::from_raw([ + 0x347f_ef2c_00f0_953a, + 0x718b_7fbc_7788_af78, + 0xec01_ea79_642d_5760, + 0x1904_76b5_80cb_9277, + ]), + pallas::Base::from_raw([ + 0xff4e_7e6f_b268_dfd7, + 0x9660_902b_6008_7651, + 0xa424_63d3_0b44_2b6f, + 0x090a_3a9d_869d_2eef, + ]), + ], + [ + pallas::Base::from_raw([ + 0xf983_387e_a045_6203, + 0xe365_0013_04f9_a11e, + 0x0dbe_8fd2_270a_6795, + 0x3877_a955_8636_7567, + ]), + pallas::Base::from_raw([ + 0x39c0_af0f_e01f_4a06, + 0x6011_8c53_a218_1352, + 0x5df3_9a2c_c63d_dc0a, + 0x2d89_4691_240f_e953, + ]), + pallas::Base::from_raw([ + 0x1aca_9eaf_9bba_9850, + 0x5914_e855_eeb4_4aa1, + 0x7ef7_1780_2016_6189, + 0x21b9_c182_92bd_bc59, + ]), + ], + [ + pallas::Base::from_raw([ + 0x33f5_09a7_4ad9_d39b, + 0x272e_1cc6_c36a_2968, + 0x505a_05f2_a6ae_834c, + 0x2fe7_6be7_cff7_23e2, + ]), + pallas::Base::from_raw([ + 0x0df9_fa97_277f_a8b4, + 0xd15b_ff84_0dda_e8a5, + 0x9299_81d7_cfce_253b, + 0x187a_a448_f391_e3ca, + ]), + pallas::Base::from_raw([ + 0xf0c6_6af5_ffc7_3736, + 0x663c_cf7b_2ffe_4b5e, + 0x007a_b3aa_3617_f422, + 0x0b70_83ad_7517_07bf, + ]), + ], + [ + pallas::Base::from_raw([ + 0x2f9b_20f1_fbd4_9791, + 0x1975_b962_f6cb_8e0b, + 0x3bc4_ca99_02c5_2acb, + 0x030d_dbb4_7049_3f16, + ]), + pallas::Base::from_raw([ + 0x3a1c_62ca_8fbf_2525, + 0x8fb8_ab9d_60ea_17b2, + 0x950b_0ab1_8d35_46df, + 0x3130_fbaf_fb5a_a82a, + ]), + pallas::Base::from_raw([ + 0x43a8_7618_0dc3_82e0, + 0x15ce_2ead_2fcd_051e, + 0x4f74_d74b_ac2e_e457, + 0x337f_5447_07c4_30f0, + ]), + ], + [ + pallas::Base::from_raw([ + 0x26de_98a8_736d_1d11, + 0x7d8e_471a_9fb9_5fef, + 0xac9d_91b0_930d_ac75, + 0x3499_7991_9015_394f, + ]), + pallas::Base::from_raw([ + 0xccfc_b618_31d5_c775, + 0x3bf9_3da6_fff3_1d95, + 0x2305_cd7a_921e_c5f1, + 0x027c_c4ef_e3fb_35dd, + ]), + pallas::Base::from_raw([ + 0xc3fa_2629_635d_27de, + 0x67f1_c6b7_3147_64af, + 0x61b7_1a36_9868_2ad2, + 0x037f_9f23_6595_4c5b, + ]), + ], + [ + pallas::Base::from_raw([ + 0x77c5_b024_8483_71ae, + 0x6041_4abe_362d_01c9, + 0x10f1_cc6d_f8b4_bcd7, + 0x1f69_7cac_4d07_feb7, + ]), + pallas::Base::from_raw([ + 0x786a_dd24_4aa0_ef29, + 0x3145_c478_0631_09d6, + 0x26e6_c851_fbd5_72a6, + 0x267a_750f_e5d7_cfbc, + ]), + pallas::Base::from_raw([ + 0x180e_2b4d_3e75_6f65, + 0xaf28_5fa8_2ce4_fae5, + 0x678c_9996_d9a4_72c8, + 0x0c91_feab_4a43_193a, + ]), + ], + [ + pallas::Base::from_raw([ + 0x79c4_7c57_3ac4_10f7, + 0x7e3b_83af_4a4b_a3ba, + 0x2186_c303_8ea0_5e69, + 0x1745_569a_0a3e_3014, + ]), + pallas::Base::from_raw([ + 0x1e03_8852_2696_191f, + 0xfdff_66c6_f3b5_ffe1, + 0xeca5_1207_78a5_6711, + 0x2986_3d54_6e7e_7c0d, + ]), + pallas::Base::from_raw([ + 0x2f22_5e63_66bf_e390, + 0xa79a_03df_8339_94c6, + 0xbf06_bae4_9ef8_53f6, + 0x1148_d6ab_2bd0_0192, + ]), + ], + [ + pallas::Base::from_raw([ + 0xf4f6_331a_8b26_5d15, + 0xf745_f45d_350d_41d4, + 0xe18b_1499_060d_a366, + 0x02e0_e121_b0f3_dfef, + ]), + pallas::Base::from_raw([ + 0x078a_e6aa_1510_54b7, + 0x6904_0173_6d44_a653, + 0xb89e_f73a_40a2_b274, + 0x0d0a_a46e_76a6_a278, + ]), + pallas::Base::from_raw([ + 0x9a4d_532c_7b6e_0958, + 0x392d_de71_0f1f_06db, + 0xeee5_45f3_fa6d_3d08, + 0x1394_3675_b04a_a986, + ]), + ], + [ + pallas::Base::from_raw([ + 0x961f_c818_dcbb_66b5, + 0xc9f2_b325_7530_dafe, + 0xd97a_11d6_3088_f5d9, + 0x2901_ec61_942d_34aa, + ]), + pallas::Base::from_raw([ + 0xfdf5_44b9_63d1_fdc7, + 0x22ff_a2a2_af9f_a3e3, + 0xf431_d544_34a3_e0cf, + 0x2020_4a21_05d2_2e7e, + ]), + pallas::Base::from_raw([ + 0x1211_b9e2_190d_6852, + 0xa004_abe8_e015_28c4, + 0x5c1e_3e9e_27a5_71c3, + 0x3a8a_6282_9512_1d5c, + ]), + ], +]; +// Secure MDS: 0 +// n: 255 +// t: 3 +// N: 765 +// Result Algorithm 1: +// [True, 0] +// Result Algorithm 2: +// [True, None] +// Result Algorithm 3: +// [True, None] +// Prime number: 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001 +// MDS matrix: +pub(crate) const MDS: [[pallas::Base; 3]; 3] = [ + [ + pallas::Base::from_raw([ + 0x323f_2486_d7e1_1b63, + 0x97d7_a0ab_2385_0b56, + 0xb3d5_9fbd_c8c9_ead4, + 0x0ab5_e5b8_74a6_8de7, + ]), + pallas::Base::from_raw([ + 0x8eca_5596_e996_ab5e, + 0x240d_4a7c_bf73_5736, + 0x293f_0f0d_886c_7954, + 0x3191_6628_e58a_5abb, + ]), + pallas::Base::from_raw([ + 0x19d1_cf25_d8e8_345d, + 0xa0a3_b71a_5fb1_5735, + 0xd803_952b_bb36_4fdf, + 0x07c0_45d5_f5e9_e5a6, + ]), + ], + [ + pallas::Base::from_raw([ + 0xd049_cdc8_d085_167c, + 0x3a0a_4640_48bd_770a, + 0xf8e2_4f66_822c_2d9f, + 0x2331_6263_0ebf_9ed7, + ]), + pallas::Base::from_raw([ + 0x4022_7011_3e04_7a2e, + 0x78f8_365c_85bb_ab07, + 0xb366_6454_8d60_957d, + 0x25ca_e259_9892_a8b0, + ]), + pallas::Base::from_raw([ + 0xf84d_806f_685f_747a, + 0x9aad_3d82_62ef_d83f, + 0x7493_8717_989a_1957, + 0x22f5_b5e1_e608_1c97, + ]), + ], + [ + pallas::Base::from_raw([ + 0xfee7_a994_4f84_dbe4, + 0x2168_0eab_c56b_c15d, + 0xf333_aa91_c383_3464, + 0x2e29_dd59_c64b_1037, + ]), + pallas::Base::from_raw([ + 0xc771_effa_4326_3664, + 0xcbea_f48b_3a06_24c3, + 0x92d1_5e7d_ceef_1665, + 0x1d1a_ab4e_c1cd_6788, + ]), + pallas::Base::from_raw([ + 0x1563_9415_f6e8_5ef1, + 0x7587_2c39_b59a_31f6, + 0x51e0_cbea_d655_16b9, + 0x3bf7_6308_6a18_9364, + ]), + ], +]; + +pub(crate) const MDS_INV: [[pallas::Base; 3]; 3] = [ + [ + pallas::Base::from_raw([ + 0xc6de_463c_d140_4e6b, + 0x4543_705f_35e9_8ab5, + 0xcc59_ffd0_0de8_6443, + 0x2cc0_57f3_fa14_687a, + ]), + pallas::Base::from_raw([ + 0x1718_4041_7cab_7576, + 0xfadb_f8ae_7ae2_4796, + 0x5fd7_2b55_df20_8385, + 0x32e7_c439_f2f9_67e5, + ]), + pallas::Base::from_raw([ + 0x9426_45bd_7d44_64e0, + 0x1403_db6f_5030_2040, + 0xf461_778a_bf6c_91fa, + 0x2eae_5df8_c311_5969, + ]), + ], + [ + pallas::Base::from_raw([ + 0xa1ca_1516_a4a1_a6a0, + 0x13f0_74fd_e9a1_8b29, + 0xdb18_b4ae_fe68_d26d, + 0x07bf_3684_8106_7199, + ]), + pallas::Base::from_raw([ + 0xe824_25bc_1b23_a059, + 0xbb1d_6504_0c85_c1bf, + 0x018a_918b_9dac_5dad, + 0x2aec_6906_c63f_3cf1, + ]), + pallas::Base::from_raw([ + 0xe054_1adf_238e_0781, + 0x76b2_a713_9db7_1b36, + 0x1215_944a_64a2_46b2, + 0x0952_e024_3aec_2af0, + ]), + ], + [ + pallas::Base::from_raw([ + 0x2a41_8d8d_73a7_c908, + 0xaef9_112e_952f_dbb5, + 0x723a_63a0_c09d_ab26, + 0x2fcb_ba6f_9159_a219, + ]), + pallas::Base::from_raw([ + 0x76ef_ab42_d4fb_a90b, + 0xc5e4_960d_7424_cd37, + 0xb4dd_d4b4_d645_2256, + 0x1ec7_3725_74f3_851b, + ]), + pallas::Base::from_raw([ + 0xadc8_933c_6f3c_72ee, + 0x87a7_435d_30f8_be81, + 0x3c26_fa4b_7d25_b1e4, + 0x0d0c_2efd_6472_f12a, + ]), + ], +]; diff --git a/halo2_gadgets_optimized/src/poseidon/primitives/fq.rs b/halo2_gadgets_optimized/src/poseidon/primitives/fq.rs new file mode 100644 index 0000000000..a22f25aea8 --- /dev/null +++ b/halo2_gadgets_optimized/src/poseidon/primitives/fq.rs @@ -0,0 +1,1431 @@ +//! Constants for using Poseidon with the Vesta field. +//! +//! The constants can be reproduced by running the following Sage script from +//! [this repository](https://github.com/daira/pasta-hadeshash): +//! +//! ```text +//! sage generate_parameters_grain.sage 1 0 255 3 8 56 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 +//! ``` +use pasta_curves::vesta; + +// Number of round constants: 192 +// Round constants for GF(p): +pub(crate) const ROUND_CONSTANTS: [[vesta::Base; 3]; 64] = [ + [ + vesta::Base::from_raw([ + 0x5753_8c25_9642_6303, + 0x4e71_162f_3100_3b70, + 0x353f_628f_76d1_10f3, + 0x360d_7470_611e_473d, + ]), + vesta::Base::from_raw([ + 0xbdb7_4213_bf63_188b, + 0x4908_ac2f_12eb_e06f, + 0x5dc3_c6c5_febf_aa31, + 0x2bab_94d7_ae22_2d13, + ]), + vesta::Base::from_raw([ + 0x0939_d927_53cc_5dc8, + 0xef77_e7d7_3676_6c5d, + 0x2bf0_3e1a_29aa_871f, + 0x150c_93fe_f652_fb1c, + ]), + ], + [ + vesta::Base::from_raw([ + 0x1425_9dce_5377_82b2, + 0x03cc_0a60_141e_894e, + 0x955d_55db_56dc_57c1, + 0x3270_661e_6892_8b3a, + ]), + vesta::Base::from_raw([ + 0xce9f_b9ff_c345_afb3, + 0xb407_c370_f2b5_a1cc, + 0xa0b7_afe4_e205_7299, + 0x073f_116f_0412_2e25, + ]), + vesta::Base::from_raw([ + 0x8eba_d76f_c715_54d8, + 0x55c9_cd20_61ae_93ca, + 0x7aff_d09c_1f53_f5fd, + 0x2a32_ec5c_4ee5_b183, + ]), + ], + [ + vesta::Base::from_raw([ + 0x2d8c_cbe2_92ef_eead, + 0x634d_24fc_6e25_59f2, + 0x651e_2cfc_7406_28ca, + 0x2703_26ee_039d_f19e, + ]), + vesta::Base::from_raw([ + 0xa068_fc37_c182_e274, + 0x8af8_95bc_e012_f182, + 0xdc10_0fe7_fcfa_5491, + 0x27c6_642a_c633_bc66, + ]), + vesta::Base::from_raw([ + 0x9ca1_8682_e26d_7ff9, + 0x710e_1fb6_ab97_6a45, + 0xd27f_5739_6989_129d, + 0x1bdf_d8b0_1401_c70a, + ]), + ], + [ + vesta::Base::from_raw([ + 0xc832_d824_261a_35ea, + 0xf4f6_fb3f_9054_d373, + 0x14b9_d6a9_c84d_d678, + 0x162a_14c6_2f9a_89b8, + ]), + vesta::Base::from_raw([ + 0xf798_2466_7b5b_6bec, + 0xac0a_1fc7_1e2c_f0c0, + 0x2af6_f79e_3127_feea, + 0x2d19_3e0f_76de_586b, + ]), + vesta::Base::from_raw([ + 0x5d0b_f58d_c8a4_aa94, + 0x4fef_f829_8499_0ff8, + 0x8169_6ef1_104e_674f, + 0x044c_a3cc_4a85_d73b, + ]), + ], + [ + vesta::Base::from_raw([ + 0x6198_785f_0cd6_b9af, + 0xb8d9_e2d4_f314_f46f, + 0x1d04_5341_6d3e_235c, + 0x1cba_f2b3_71da_c6a8, + ]), + vesta::Base::from_raw([ + 0x343e_0761_0f3f_ede5, + 0x293c_4ab0_38fd_bbdc, + 0x0e6c_49d0_61b6_b5f4, + 0x1d5b_2777_692c_205b, + ]), + vesta::Base::from_raw([ + 0xf60e_971b_8d73_b04f, + 0x06a9_adb0_c1e6_f962, + 0xaa30_535b_dd74_9a7e, + 0x2e9b_dbba_3dd3_4bff, + ]), + ], + [ + vesta::Base::from_raw([ + 0x035a_1366_1f22_418b, + 0xde40_fbe2_6d04_7b05, + 0x8bd5_bae3_6969_299f, + 0x2de1_1886_b180_11ca, + ]), + vesta::Base::from_raw([ + 0xbc99_8884_ba96_a721, + 0x2ab9_395c_449b_e947, + 0x0d5b_4a3f_1841_dcd8, + 0x2e07_de17_80b8_a70d, + ]), + vesta::Base::from_raw([ + 0x825e_4c2b_b749_25ca, + 0x2504_40a9_9d6b_8af3, + 0xbbdb_63db_d52d_ad16, + 0x0f69_f185_4d20_ca0c, + ]), + ], + [ + vesta::Base::from_raw([ + 0x816c_0594_22dc_705e, + 0x6ce5_1135_07f9_6de9, + 0x0d13_5dc6_39fb_09a4, + 0x2eb1_b254_17fe_1767, + ]), + vesta::Base::from_raw([ + 0xb8b1_bdf4_953b_d82c, + 0xff36_c661_d26c_c42d, + 0x8c24_cb44_c3fa_b48a, + 0x115c_d0a0_643c_fb98, + ]), + vesta::Base::from_raw([ + 0xde80_1612_311d_04cd, + 0xbb57_ddf1_4e0f_958a, + 0x066d_7378_b999_868b, + 0x26ca_293f_7b2c_462d, + ]), + ], + [ + vesta::Base::from_raw([ + 0xf520_9d14_b248_20ca, + 0x0f16_0bf9_f71e_967f, + 0x2a83_0aa1_6241_2cd9, + 0x17bf_1b93_c4c7_e01a, + ]), + vesta::Base::from_raw([ + 0x05c8_6f2e_7dc2_93c5, + 0xe03c_0354_bd8c_fd38, + 0xa24f_8456_369c_85df, + 0x35b4_1a7a_c4f3_c571, + ]), + vesta::Base::from_raw([ + 0x72ac_156a_f435_d09e, + 0x64e1_4d3b_eb2d_ddde, + 0x4359_2799_4849_bea9, + 0x3b14_8008_0523_c439, + ]), + ], + [ + vesta::Base::from_raw([ + 0x2716_18d8_74b1_4c6d, + 0x08e2_8644_2a2d_3eb2, + 0x4950_856d_c907_d575, + 0x2cc6_8100_31dc_1b0d, + ]), + vesta::Base::from_raw([ + 0x91f3_18c0_9f0c_b566, + 0x9e51_7aa9_3b78_341d, + 0x0596_18e2_afd2_ef99, + 0x25bd_bbed_a1bd_e8c1, + ]), + vesta::Base::from_raw([ + 0xc631_3487_073f_7f7b, + 0x2a5e_d0a2_7b61_926c, + 0xb95f_33c2_5dde_8ac0, + 0x392a_4a87_58e0_6ee8, + ]), + ], + [ + vesta::Base::from_raw([ + 0xe7bb_cef0_2eb5_866c, + 0x5e6a_6fd1_5db8_9365, + 0x9aa6_111f_4de0_0948, + 0x272a_5587_8a08_442b, + ]), + vesta::Base::from_raw([ + 0x9b92_5b3c_5b21_e0e2, + 0xa6eb_ba01_1694_dd12, + 0xefa1_3c4e_60e2_6239, + 0x2d5b_308b_0cf0_2cdf, + ]), + vesta::Base::from_raw([ + 0xef38_c57c_3116_73ac, + 0x44df_f42f_18b4_6c56, + 0xdd5d_293d_72e2_e5f2, + 0x1654_9fc6_af2f_3b72, + ]), + ], + [ + vesta::Base::from_raw([ + 0x9b71_26d9_b468_60df, + 0x7639_8265_3442_0311, + 0xfa69_c3a2_ad52_f76d, + 0x1b10_bb7a_82af_ce39, + ]), + vesta::Base::from_raw([ + 0x90d2_7f6a_00b7_dfc8, + 0xd1b3_6968_ba04_05c0, + 0xc79c_2df7_dc98_a3be, + 0x0f1e_7505_ebd9_1d2f, + ]), + vesta::Base::from_raw([ + 0xff45_7756_b819_bb20, + 0x797f_d6e3_f18e_b1ca, + 0x537a_7497_a3b4_3f46, + 0x2f31_3faf_0d3f_6187, + ]), + ], + [ + vesta::Base::from_raw([ + 0xf0bc_3e73_2ecb_26f6, + 0x5cad_11eb_f0f7_ceb8, + 0xfa3c_a61c_0ed1_5bc5, + 0x3a5c_bb6d_e450_b481, + ]), + vesta::Base::from_raw([ + 0x8655_27cb_ca91_5982, + 0x51ba_a6e2_0f89_2b62, + 0xd920_86e2_53b4_39d6, + 0x3dab_54bc_9bef_688d, + ]), + vesta::Base::from_raw([ + 0x3680_45ac_f2b7_1ae3, + 0x4c24_b33b_410f_efd4, + 0xe280_d316_7012_3f74, + 0x06db_fb42_b979_884d, + ]), + ], + [ + vesta::Base::from_raw([ + 0xa7fc_32d2_2f18_b9d3, + 0xb8d2_de72_e3d2_c9ec, + 0xc6f0_39ea_1973_a63e, + 0x068d_6b46_08aa_e810, + ]), + vesta::Base::from_raw([ + 0x2b5d_fcc5_5725_55df, + 0xb868_a7d7_e1f1_f69a, + 0x0ee2_58c9_b8fd_fccd, + 0x366e_bfaf_a3ad_381c, + ]), + vesta::Base::from_raw([ + 0xe6bc_229e_95bc_76b1, + 0x7ef6_6d89_d044_d022, + 0x04db_3024_f41d_3f56, + 0x3967_8f65_512f_1ee4, + ]), + ], + [ + vesta::Base::from_raw([ + 0xe534_c88f_e53d_85fe, + 0xcf82_c25f_99dc_01a4, + 0xd58b_7750_a3bc_2fe1, + 0x2166_8f01_6a80_63c0, + ]), + vesta::Base::from_raw([ + 0x4bef_429b_c533_1608, + 0xe34d_ea56_439f_e195, + 0x1bc7_4936_3e98_a768, + 0x39d0_0994_a8a5_046a, + ]), + vesta::Base::from_raw([ + 0x770c_956f_60d8_81b3, + 0xb163_d416_05d3_9f99, + 0x6b20_3bbe_12fb_3425, + 0x1f9d_bdc3_f843_1263, + ]), + ], + [ + vesta::Base::from_raw([ + 0x9794_a9f7_c336_eab2, + 0xbe0b_c829_fe5e_66c6, + 0xe5f1_7b9e_0ee0_cab6, + 0x0277_45a9_cddf_ad95, + ]), + vesta::Base::from_raw([ + 0x5202_5657_abd8_aee0, + 0x2fa4_3fe2_0a45_c78d, + 0x788d_695c_61e9_3212, + 0x1cec_0803_c504_b635, + ]), + vesta::Base::from_raw([ + 0xd387_2a95_59a0_3a73, + 0xed50_82c8_dbf3_1365, + 0x7207_7448_ef87_cc6e, + 0x1235_23d7_5e9f_abc1, + ]), + ], + [ + vesta::Base::from_raw([ + 0x0017_79e3_a1d3_57f4, + 0x27fe_ba35_975e_e7e5, + 0xf419_b848_e5d6_94bf, + 0x1723_d145_2c9c_f02d, + ]), + vesta::Base::from_raw([ + 0x9dab_1ee4_dcf9_6622, + 0x21c3_f776_f572_836d, + 0xfcc0_573d_7e61_3694, + 0x1739_d180_a160_10bd, + ]), + vesta::Base::from_raw([ + 0x7029_0452_042d_048d, + 0xfafa_96fb_eb0a_b893, + 0xacce_3239_1794_b627, + 0x2d4e_6354_da9c_c554, + ]), + ], + [ + vesta::Base::from_raw([ + 0x670b_cf6f_8b48_5dcd, + 0x8f3b_d43f_9926_0621, + 0x4a86_9553_c9d0_07f8, + 0x153e_e614_2e53_5e33, + ]), + vesta::Base::from_raw([ + 0xd258_d2e2_b778_2172, + 0x968a_d442_4af8_3700, + 0x635e_f7e7_a430_b486, + 0x0c45_bfd3_a69a_aa65, + ]), + vesta::Base::from_raw([ + 0x0e56_33d2_51f7_3307, + 0x6897_ac0a_8ffa_5ff1, + 0xf2d5_6aec_8314_4600, + 0x0adf_d53b_256a_6957, + ]), + ], + [ + vesta::Base::from_raw([ + 0xac9d_36a8_b751_6d63, + 0x3f87_b28f_1c1b_e4bd, + 0x8cd1_726b_7cba_b8ee, + 0x315d_2ac8_ebdb_ac3c, + ]), + vesta::Base::from_raw([ + 0x299c_e44e_a423_d8e1, + 0xc9bb_60d1_f695_9879, + 0xcfae_c23d_2b16_883f, + 0x1b84_7271_2d02_eef4, + ]), + vesta::Base::from_raw([ + 0xc4a5_4041_98ad_f70c, + 0x367d_2c54_e369_28c9, + 0xbd0b_70fa_2255_eb6f, + 0x3c1c_d07e_fda6_ff24, + ]), + ], + [ + vesta::Base::from_raw([ + 0xbbe5_23ae_f9ab_107a, + 0x4a16_073f_738f_7e0c, + 0x687f_4e51_b2e1_dcd3, + 0x1360_52d2_6bb3_d373, + ]), + vesta::Base::from_raw([ + 0x676c_36c2_4ef9_67dd, + 0x7b3c_fbb8_7303_2681, + 0xc1bd_d859_a123_2a1d, + 0x16c9_6bee_f6a0_a848, + ]), + vesta::Base::from_raw([ + 0x067e_ec7f_2d63_40c4, + 0x0123_87ba_b4f1_662d, + 0x2ab7_fed8_f499_a9fb, + 0x284b_38c5_7ff6_5c26, + ]), + ], + [ + vesta::Base::from_raw([ + 0xaf1d_ff20_4c92_2f86, + 0xfc06_772c_1c04_11a6, + 0x39e2_4219_8897_d17c, + 0x0c59_93d1_75e8_1f66, + ]), + vesta::Base::from_raw([ + 0xbbf5_3f67_b1f8_7b15, + 0xf248_87ad_48e1_7759, + 0xfcda_655d_1ba9_c8f9, + 0x03bf_7a3f_7bd0_43da, + ]), + vesta::Base::from_raw([ + 0x9b5c_d09e_36d8_be62, + 0x4c8f_9cbe_69f0_e827, + 0xb0cf_9995_67f0_0e73, + 0x3188_fe4e_e9f9_fafb, + ]), + ], + [ + vesta::Base::from_raw([ + 0xafea_99a2_ec6c_595a, + 0x3af5_bf77_c1c4_2652, + 0x5a39_768c_480d_61e1, + 0x171f_528c_cf65_8437, + ]), + vesta::Base::from_raw([ + 0x5a05_63b9_b8e9_f1d5, + 0x812c_3286_ee70_0067, + 0x196e_4185_9b35_ef88, + 0x12f4_175c_4ab4_5afc, + ]), + vesta::Base::from_raw([ + 0x0e74_d4d3_6911_8b79, + 0x7e23_e1aa_be96_cfab, + 0x8f8f_dcf8_00a9_ac69, + 0x3a50_9e15_5cb7_ebfd, + ]), + ], + [ + vesta::Base::from_raw([ + 0x9871_2c65_678c_fd30, + 0x984b_c8f2_e4c1_b69e, + 0x1a89_920e_2504_c3b3, + 0x10f2_a685_df4a_27c8, + ]), + vesta::Base::from_raw([ + 0xe8a1_6728_cc9d_4918, + 0x5457_3c93_33c5_6321, + 0x1d8d_93d5_4ab9_1a0e, + 0x09e5_f497_90c8_a0e2, + ]), + vesta::Base::from_raw([ + 0x609a_7403_47cf_5fea, + 0x42d1_7ed6_ee0f_ab7e, + 0x2bf3_5705_d9f8_4a34, + 0x352d_69be_d80e_e3e5, + ]), + ], + [ + vesta::Base::from_raw([ + 0x3a75_8af6_fa84_e0e8, + 0xc634_debd_281b_76a6, + 0x4915_62fa_f2b1_90d3, + 0x058e_e73b_a9f3_f293, + ]), + vesta::Base::from_raw([ + 0x621a_1325_10a4_3904, + 0x092c_b921_19bc_76be, + 0xcd0f_1fc5_5b1a_3250, + 0x232f_99cc_911e_ddd9, + ]), + vesta::Base::from_raw([ + 0xc3b9_7c1e_301b_c213, + 0xf9ef_d52c_a6bc_2961, + 0x86c2_2c6c_5d48_69f0, + 0x201b_eed7_b8f3_ab81, + ]), + ], + [ + vesta::Base::from_raw([ + 0xbf6b_3431_ba94_e9bc, + 0x2938_8842_744a_1210, + 0xa1c9_291d_5860_2f51, + 0x1376_dce6_5800_30c6, + ]), + vesta::Base::from_raw([ + 0x6454_843c_5486_d7b3, + 0x072b_a8b0_2d92_e722, + 0x2b33_56c3_8238_f761, + 0x1793_199e_6fd6_ba34, + ]), + vesta::Base::from_raw([ + 0x06a3_f1d3_b433_311b, + 0x3c66_160d_c62a_acac, + 0x9fee_9c20_c87a_67df, + 0x22de_7a74_88dc_c735, + ]), + ], + [ + vesta::Base::from_raw([ + 0x30d6_e3fd_516b_47a8, + 0xdbe0_b77f_ae77_e1d0, + 0xdf8f_f37f_e2d8_edf8, + 0x3514_d5e9_066b_b160, + ]), + vesta::Base::from_raw([ + 0x1937_7427_137a_81c7, + 0xff45_3d6f_900f_144a, + 0xf919_a00d_abbf_5fa5, + 0x30cd_3006_931a_d636, + ]), + vesta::Base::from_raw([ + 0x5b6a_7422_0692_b506, + 0x8f9e_4b2c_ae2e_bb51, + 0x41f8_1a5c_f613_c8df, + 0x253d_1a5c_5293_4127, + ]), + ], + [ + vesta::Base::from_raw([ + 0x73f6_66cb_86a4_8e8e, + 0x851b_3a59_c990_fafc, + 0xa35e_9613_e7f5_fe92, + 0x035b_461c_02d7_9d19, + ]), + vesta::Base::from_raw([ + 0x7cfb_f86a_3aa0_4780, + 0x92b1_283c_2d5f_ccde, + 0x5bc0_0eed_d56b_93e0, + 0x23a9_9280_79d1_75bd, + ]), + vesta::Base::from_raw([ + 0xf1e4_ccd7_3fa0_0a82, + 0xb5e2_ea34_36ee_f957, + 0xf159_4a07_63c6_11ab, + 0x13a7_785a_e134_ea92, + ]), + ], + [ + vesta::Base::from_raw([ + 0xbbf0_4f52_52de_4279, + 0x3889_c578_6344_6d88, + 0x4962_ae3c_0da1_7e31, + 0x39fc_e308_b7d4_3c57, + ]), + vesta::Base::from_raw([ + 0x3b57_e344_89b5_3fad, + 0xbef0_0a08_c6ed_38d2, + 0xc0fd_f016_62f6_0d22, + 0x1aae_1883_3f8e_1d3a, + ]), + vesta::Base::from_raw([ + 0x5551_3e03_3398_513f, + 0x27c1_b3fd_8f85_d8a8, + 0x8b2e_80c0_64fd_83ed, + 0x1a76_1ce8_2400_af01, + ]), + ], + [ + vesta::Base::from_raw([ + 0x5244_ca74_9b73_e481, + 0xdcf6_af28_30a5_0287, + 0x16dd_1a87_ca22_e1cc, + 0x275a_03e4_5add_a7c3, + ]), + vesta::Base::from_raw([ + 0x58a2_53cf_b6a9_5786, + 0x07e5_6145_3fc5_648b, + 0xeb08_e47e_5fea_bcf8, + 0x2e5a_10f0_8b5a_b8bb, + ]), + vesta::Base::from_raw([ + 0xe033_d82c_efe7_8ce3, + 0xc141_a5b6_d594_bec4, + 0xb84e_9c33_3b29_32f1, + 0x1459_cb85_8720_8473, + ]), + ], + [ + vesta::Base::from_raw([ + 0x5cec_7e7b_338f_be1b, + 0x52f9_332f_bffc_fbbd, + 0x7b92_ce81_0e14_a400, + 0x193a_e592_1d78_b5de, + ]), + vesta::Base::from_raw([ + 0x6022_4be6_7248_e82c, + 0x3743_84f4_a072_8205, + 0x8911_1fb2_c466_0281, + 0x3097_898a_5d00_11a4, + ]), + vesta::Base::from_raw([ + 0x5499_80de_8629_30f5, + 0x1979_b2d1_c465_b4d9, + 0x5717_82fd_96ce_54b4, + 0x378d_97bf_8c86_4ae7, + ]), + ], + [ + vesta::Base::from_raw([ + 0x37ea_32a9_71d1_7884, + 0xdbc7_f5cb_4609_3421, + 0x8813_6287_ce37_6b08, + 0x2eb0_4ea7_c01d_97ec, + ]), + vesta::Base::from_raw([ + 0xead3_726f_1af2_e7b0, + 0x861c_bda4_7680_4e6c, + 0x2302_a1c2_2e49_baec, + 0x3642_5347_ea03_f641, + ]), + vesta::Base::from_raw([ + 0xecd6_27e5_9590_d09e, + 0x3f5b_5ca5_a19a_9701, + 0xcc99_6cd8_5c98_a1d8, + 0x26b7_2df4_7408_ad42, + ]), + ], + [ + vesta::Base::from_raw([ + 0x59be_ce31_f0a3_1e95, + 0xde01_212e_e458_8f89, + 0x1f05_636c_610b_89aa, + 0x1301_80e4_4e29_24db, + ]), + vesta::Base::from_raw([ + 0x9ea8_e7bc_7926_3550, + 0xdf77_93cc_89e5_b52f, + 0x7327_5aca_ed5f_579c, + 0x219e_9773_7d39_79ba, + ]), + vesta::Base::from_raw([ + 0x9c12_635d_f251_d153, + 0x3b06_72dd_7d42_cbb4, + 0x3461_363f_81c4_89a2, + 0x3cdb_9359_8a5c_a528, + ]), + ], + [ + vesta::Base::from_raw([ + 0x2861_ce16_f219_d5a9, + 0x4ad0_4470_45a7_c5aa, + 0x2072_4b92_7a0c_a81c, + 0x0e59_e6f3_32d7_ed37, + ]), + vesta::Base::from_raw([ + 0x43b0_a3fc_ff20_36bd, + 0x172c_c07b_9d33_fbf9, + 0x3d73_6946_7222_697a, + 0x1b06_4342_d51a_4275, + ]), + vesta::Base::from_raw([ + 0x3eb3_1022_8a0e_5f6c, + 0x78fa_9fb9_1712_21b7, + 0x2f36_3c55_b288_2e0b, + 0x30b8_2a99_8cbd_8e8a, + ]), + ], + [ + vesta::Base::from_raw([ + 0xe46f_6d42_9874_0107, + 0x8ad7_1ea7_15be_0573, + 0x63df_7a76_e858_a4aa, + 0x23e4_ab37_183a_cba4, + ]), + vesta::Base::from_raw([ + 0xfca9_95e2_b599_14a1, + 0xacfe_1464_0de0_44f2, + 0x5d33_094e_0bed_a75b, + 0x2795_d5c5_fa42_8022, + ]), + vesta::Base::from_raw([ + 0xc26d_909d_ee8b_53c0, + 0xa668_7c3d_f16c_8fe4, + 0xd765_f26d_d03f_4c45, + 0x3001_ca40_1e89_601c, + ]), + ], + [ + vesta::Base::from_raw([ + 0xe7fe_a6bd_f347_1380, + 0xe84b_5beb_ae4e_501d, + 0xf7bf_86e8_9280_827f, + 0x0072_e45c_c676_b08e, + ]), + vesta::Base::from_raw([ + 0xd0c5_4dde_b26b_86c0, + 0xb648_29e2_d40e_41bd, + 0xe2ab_e4c5_18ce_599e, + 0x13de_7054_8487_4bb5, + ]), + vesta::Base::from_raw([ + 0x3891_5b43_2a99_59a5, + 0x82bb_18e5_af1b_05bb, + 0x3159_50f1_211d_efe8, + 0x0408_a9fc_f9d6_1abf, + ]), + ], + [ + vesta::Base::from_raw([ + 0x3407_0cbe_e268_86a0, + 0xae4d_23b0_b41b_e9a8, + 0xbb4e_4a14_00cc_d2c4, + 0x2780_b9e7_5b55_676e, + ]), + vesta::Base::from_raw([ + 0x9405_5920_98b4_056f, + 0xdc4d_8fbe_fe24_405a, + 0xf803_33ec_8563_4ac9, + 0x3a57_0d4d_7c4e_7ac3, + ]), + vesta::Base::from_raw([ + 0x78d2_b247_8995_20b4, + 0xe2cc_1507_bebd_cc62, + 0xf347_c247_fcf0_9294, + 0x0c13_cca7_cb1f_9d2c, + ]), + ], + [ + vesta::Base::from_raw([ + 0x2e8c_88f7_7074_70e0, + 0x0b50_bb2e_b82d_f74d, + 0xd261_4a19_7c6b_794b, + 0x14f5_9baa_03cd_0ca4, + ]), + vesta::Base::from_raw([ + 0xbe52_476e_0a16_f3be, + 0xa51d_54ed_e661_67f5, + 0x6f54_6e17_04c3_9c60, + 0x307d_efee_925d_fb43, + ]), + vesta::Base::from_raw([ + 0x380b_67d8_0473_dce3, + 0x6611_0683_6adf_e5e7, + 0x7a07_e767_4b5a_2621, + 0x1960_cd51_1a91_e060, + ]), + ], + [ + vesta::Base::from_raw([ + 0x15aa_f1f7_7125_89dd, + 0xb8ee_335d_8828_4cbe, + 0xca2a_d0fb_5667_2500, + 0x2301_ef9c_63ea_84c5, + ]), + vesta::Base::from_raw([ + 0x5e68_478c_4d60_27a9, + 0xc861_82d1_b424_6b58, + 0xd10f_4cd5_2be9_7f6b, + 0x029a_5a47_da79_a488, + ]), + vesta::Base::from_raw([ + 0x2cc4_f962_eaae_2260, + 0xf97f_e46b_6a92_5428, + 0x2360_d17d_890e_55cb, + 0x32d7_b16a_7f11_cc96, + ]), + ], + [ + vesta::Base::from_raw([ + 0xc0ca_b915_d536_3d9f, + 0xa5f2_404c_d7b3_5eb0, + 0x18e8_57a9_8d49_8cf7, + 0x2670_3e48_c03b_81ca, + ]), + vesta::Base::from_raw([ + 0xf691_123a_e112_b928, + 0xf443_88bd_6b89_221e, + 0x88ac_8d25_a246_03f1, + 0x0486_82a3_5b32_65bc, + ]), + vesta::Base::from_raw([ + 0x3ab7_defc_b8d8_03e2, + 0x91d6_e171_5164_775e, + 0xd72c_ddc6_cf06_b507, + 0x06b1_3904_41fa_7030, + ]), + ], + [ + vesta::Base::from_raw([ + 0xbcd7_9541_4a6e_2e86, + 0x43b3_60f6_386a_86d7, + 0x1689_426d_ce05_fcd8, + 0x31aa_0eeb_868c_626d, + ]), + vesta::Base::from_raw([ + 0xed77_f5d5_76b9_9cc3, + 0x90ef_d8f4_1b20_78b2, + 0x057a_bad3_764c_104b, + 0x2394_64f7_5bf7_b6af, + ]), + vesta::Base::from_raw([ + 0xb2cb_4873_07c1_cecf, + 0xa5cc_47c5_9654_b2a7, + 0xa45e_19ed_813a_54ab, + 0x0a64_d4c0_4fd4_26bd, + ]), + ], + [ + vesta::Base::from_raw([ + 0x1f73_1532_2f65_8735, + 0x777c_7a92_1a06_2e9d, + 0x576a_4ad2_5986_0fb1, + 0x21fb_bdbb_7367_0734, + ]), + vesta::Base::from_raw([ + 0x6743_2400_3fc5_2146, + 0x5b86_d294_63d3_1564, + 0xd937_1ca2_eb95_acf3, + 0x31b8_6f3c_f017_05d4, + ]), + vesta::Base::from_raw([ + 0x7045_f48a_a4eb_4f6f, + 0x1354_1d65_157e_e1ce, + 0x05ef_1736_d090_56f6, + 0x2bfd_e533_5437_7c91, + ]), + ], + [ + vesta::Base::from_raw([ + 0x5a13_a58d_2001_1e2f, + 0xf4d5_239c_11d0_eafa, + 0xd558_f36e_65f8_eca7, + 0x1233_ca93_6ec2_4671, + ]), + vesta::Base::from_raw([ + 0x6e70_af0a_7a92_4b3a, + 0x8780_58d0_234a_576f, + 0xc437_846d_8e0b_2b30, + 0x27d4_52a4_3ac7_dea2, + ]), + vesta::Base::from_raw([ + 0xa025_76b9_4392_f980, + 0x6a30_641a_1c3d_87b2, + 0xe816_ea8d_a493_e0fa, + 0x2699_dba8_2184_e413, + ]), + ], + [ + vesta::Base::from_raw([ + 0x608c_6f7a_61b5_6e55, + 0xf185_8466_4f8c_ab49, + 0xc398_8bae_e42e_4b10, + 0x36c7_22f0_efcc_8803, + ]), + vesta::Base::from_raw([ + 0x6e49_ac17_0dbb_7fcd, + 0x85c3_8899_a7b5_a833, + 0x08b0_f2ec_89cc_aa37, + 0x02b3_ff48_861e_339b, + ]), + vesta::Base::from_raw([ + 0xa8c5_ae03_ad98_e405, + 0x6fc3_ff4c_49eb_59ad, + 0x6016_2f44_27bc_657b, + 0x0b70_d061_d58d_8a7f, + ]), + ], + [ + vesta::Base::from_raw([ + 0x2e06_cc4a_f33b_0a06, + 0xad3d_e8be_46ed_9693, + 0xf875_3ade_b9d7_cee2, + 0x3fc2_a13f_127f_96a4, + ]), + vesta::Base::from_raw([ + 0xc120_80ac_117e_e15f, + 0x00cb_3d62_1e17_1d80, + 0x1bd6_3434_ac8c_419f, + 0x0c41_a6e4_8dd2_3a51, + ]), + vesta::Base::from_raw([ + 0x9685_213e_9692_f5e1, + 0x72aa_ad7e_4e75_339d, + 0xed44_7653_7169_084e, + 0x2de8_072a_6bd8_6884, + ]), + ], + [ + vesta::Base::from_raw([ + 0x0ad0_1184_567b_027c, + 0xb81c_f735_cc9c_39c0, + 0x9d34_96a3_d9fe_05ec, + 0x0355_7a8f_7b38_a17f, + ]), + vesta::Base::from_raw([ + 0x45bc_b5ac_0082_6abc, + 0x060f_4336_3d81_8e54, + 0xee97_6d34_282f_1a37, + 0x0b5f_5955_2f49_8735, + ]), + vesta::Base::from_raw([ + 0x2f29_09e1_7e22_b0df, + 0xf5d6_46e5_7507_e548, + 0xfedb_b185_70dc_7300, + 0x0e29_23a5_fee7_b878, + ]), + ], + [ + vesta::Base::from_raw([ + 0xf71e_ed73_f15b_3326, + 0xcf1c_b37c_3b03_2af6, + 0xc787_be97_020a_7fdd, + 0x1d78_5005_a7a0_0592, + ]), + vesta::Base::from_raw([ + 0x0acf_bfb2_23f8_f00d, + 0xa590_b88a_3b06_0294, + 0x0ba5_fedc_b8f2_5bd2, + 0x1ad7_72c2_73d9_c6df, + ]), + vesta::Base::from_raw([ + 0xc1ce_13d6_0f2f_5031, + 0x8105_10eb_61f0_672d, + 0xa78f_3275_c278_234b, + 0x027b_d647_85fc_bd2a, + ]), + ], + [ + vesta::Base::from_raw([ + 0x8337_f5e0_7923_a853, + 0xe224_3134_6945_7b8e, + 0xce6f_8ffe_a103_1b6d, + 0x2080_0f44_1b4a_0526, + ]), + vesta::Base::from_raw([ + 0xa33d_7bed_89a4_408a, + 0x36cd_c8ee_d662_ad37, + 0x6eea_2cd4_9f43_12b4, + 0x3d5a_d61d_7b65_f938, + ]), + vesta::Base::from_raw([ + 0x3bbb_ae94_cc19_5284, + 0x1df9_6cc0_3ea4_b26d, + 0x02c5_f91b_e4dd_8e3d, + 0x1333_8bc3_51fc_46dd, + ]), + ], + [ + vesta::Base::from_raw([ + 0xc527_1c29_7852_819e, + 0x646c_49f9_b46c_bf19, + 0xb87d_b1e2_af3e_a923, + 0x25e5_2be5_07c9_2760, + ]), + vesta::Base::from_raw([ + 0x5c38_0ab7_01b5_2ea9, + 0xa34c_83a3_485c_6b2d, + 0x7109_6d8b_1b98_3c98, + 0x1c49_2d64_c157_aaa4, + ]), + vesta::Base::from_raw([ + 0xa20c_0b3d_a0da_4ca3, + 0xd434_87bc_288d_f682, + 0xf4e6_c5e7_a573_f592, + 0x0c5b_8015_7999_2718, + ]), + ], + [ + vesta::Base::from_raw([ + 0x7ea3_3c93_e408_33cf, + 0x584e_9e62_a7f9_554e, + 0x6869_5c0c_d7cb_f43d, + 0x1090_b1b4_d2be_be7a, + ]), + vesta::Base::from_raw([ + 0xe383_e1ec_3baa_8d69, + 0x1b21_8e35_ecf2_328e, + 0x68f5_ce5c_bed1_9cad, + 0x33e3_8018_a801_387a, + ]), + vesta::Base::from_raw([ + 0xb76b_0b3d_787e_e953, + 0x5f4a_02d2_8729_e3ae, + 0xeef8_d83d_0e87_6bac, + 0x1654_af18_772b_2da5, + ]), + ], + [ + vesta::Base::from_raw([ + 0xef7c_e6a0_1326_5477, + 0xbb08_9387_0367_ec6c, + 0x4474_2de8_8c5a_b0d5, + 0x1678_be3c_c9c6_7993, + ]), + vesta::Base::from_raw([ + 0xaf5d_4789_3348_f766, + 0xdaf1_8183_55b1_3b4f, + 0x7ff9_c6be_546e_928a, + 0x3780_bd1e_01f3_4c22, + ]), + vesta::Base::from_raw([ + 0xa123_8032_0d7c_c1de, + 0x5d11_e69a_a6c0_b98c, + 0x0786_018e_7cb7_7267, + 0x1e83_d631_5c9f_125b, + ]), + ], + [ + vesta::Base::from_raw([ + 0x1799_603e_855c_e731, + 0xc486_894d_76e0_c33b, + 0x160b_4155_2f29_31c8, + 0x354a_fd0a_2f9d_0b26, + ]), + vesta::Base::from_raw([ + 0x8b99_7ee0_6be1_bff3, + 0x60b0_0dbe_1fac_ed07, + 0x2d8a_ffa6_2905_c5a5, + 0x00cd_6d29_f166_eadc, + ]), + vesta::Base::from_raw([ + 0x08d0_6419_1708_2f2c, + 0xc60d_0197_3f18_3057, + 0xdbe0_e3d7_cdbc_66ef, + 0x1d62_1935_2768_e3ae, + ]), + ], + [ + vesta::Base::from_raw([ + 0xfa08_dd98_0638_7577, + 0xafe3_ca1d_b8d4_f529, + 0xe48d_2370_d7d1_a142, + 0x1463_36e2_5db5_181d, + ]), + vesta::Base::from_raw([ + 0xa901_d3ce_84de_0ad4, + 0x022e_54b4_9c13_d907, + 0x997a_2116_3e2e_43df, + 0x0005_d8e0_85fd_72ee, + ]), + vesta::Base::from_raw([ + 0x1c36_f313_4196_4484, + 0x6f8e_bc1d_2296_021a, + 0x0dd5_e61c_8a4e_8642, + 0x364e_97c7_a389_3227, + ]), + ], + [ + vesta::Base::from_raw([ + 0xd7a0_0c03_d2e0_baaa, + 0xfa97_ec80_ad30_7a52, + 0x561c_6fff_1534_6878, + 0x0118_9910_671b_c16b, + ]), + vesta::Base::from_raw([ + 0x63fd_8ac5_7a95_ca8c, + 0x4c0f_7e00_1df4_90aa, + 0x5229_dfaa_0123_1a45, + 0x162a_7c80_f4d2_d12e, + ]), + vesta::Base::from_raw([ + 0x32e6_9efb_22f4_0b96, + 0xcaff_31b4_fda3_2124, + 0x2604_e4af_b09f_8603, + 0x2a0d_6c09_5766_66bb, + ]), + ], + [ + vesta::Base::from_raw([ + 0xc0a0_180f_8cbf_c0d2, + 0xf444_d10d_63a7_4e2c, + 0xe16a_4d60_3d5a_808e, + 0x0978_e5c5_1e1e_5649, + ]), + vesta::Base::from_raw([ + 0x03f4_460e_bc35_1b6e, + 0x0508_7d90_3bda_cfd1, + 0xebe1_9bbd_ce25_1011, + 0x1bdc_ee3a_aca9_cd25, + ]), + vesta::Base::from_raw([ + 0xf619_64bf_3ade_7670, + 0x0c94_7321_e007_5e3f, + 0xe494_7914_0b19_44fd, + 0x1862_cccb_70b5_b885, + ]), + ], + [ + vesta::Base::from_raw([ + 0xc326_7da6_e94a_dc50, + 0x39ee_99c1_cc6e_5dda, + 0xbc26_cc88_3a19_87e1, + 0x1f3e_91d8_63c1_6922, + ]), + vesta::Base::from_raw([ + 0x0f85_b4ac_2c36_7406, + 0xfa66_1465_c656_ad99, + 0xef5c_08f8_478f_663a, + 0x1af4_7a48_a601_6a49, + ]), + vesta::Base::from_raw([ + 0x0eab_cd87_e7d0_1b15, + 0x1c36_98b0_a2e3_da10, + 0x009d_5733_8c69_3505, + 0x3c8e_e901_956e_3d3f, + ]), + ], + [ + vesta::Base::from_raw([ + 0x8b94_7721_8967_3476, + 0xe10c_e2b7_069f_4dbd, + 0x68d0_b024_f591_b520, + 0x1660_a8cd_e7fe_c553, + ]), + vesta::Base::from_raw([ + 0x9d8d_0f67_fdaa_79d5, + 0x3963_c2c1_f558_6e2f, + 0x1303_9363_34dd_1132, + 0x0f6d_9919_29d5_e4e7, + ]), + vesta::Base::from_raw([ + 0x7a43_3091_e1ce_2d3a, + 0x4e7f_da77_0712_f343, + 0xcc62_5eaa_ab52_b4dc, + 0x02b9_cea1_921c_d9f6, + ]), + ], + [ + vesta::Base::from_raw([ + 0x3797_b2d8_3760_43b3, + 0xd8ca_f468_976f_0472, + 0x214f_7c67_84ac_b565, + 0x14a3_23b9_9b90_0331, + ]), + vesta::Base::from_raw([ + 0x347f_ef2c_00f0_953a, + 0x718b_7fbc_7788_af78, + 0xec01_ea79_642d_5760, + 0x1904_76b5_80cb_9277, + ]), + vesta::Base::from_raw([ + 0xff4e_7e6f_b268_dfd7, + 0x9660_902b_6008_7651, + 0xa424_63d3_0b44_2b6f, + 0x090a_3a9d_869d_2eef, + ]), + ], + [ + vesta::Base::from_raw([ + 0xf983_387e_a045_6203, + 0xe365_0013_04f9_a11e, + 0x0dbe_8fd2_270a_6795, + 0x3877_a955_8636_7567, + ]), + vesta::Base::from_raw([ + 0x39c0_af0f_e01f_4a06, + 0x6011_8c53_a218_1352, + 0x5df3_9a2c_c63d_dc0a, + 0x2d89_4691_240f_e953, + ]), + vesta::Base::from_raw([ + 0x1aca_9eaf_9bba_9850, + 0x5914_e855_eeb4_4aa1, + 0x7ef7_1780_2016_6189, + 0x21b9_c182_92bd_bc59, + ]), + ], + [ + vesta::Base::from_raw([ + 0x33f5_09a7_4ad9_d39b, + 0x272e_1cc6_c36a_2968, + 0x505a_05f2_a6ae_834c, + 0x2fe7_6be7_cff7_23e2, + ]), + vesta::Base::from_raw([ + 0x0df9_fa97_277f_a8b4, + 0xd15b_ff84_0dda_e8a5, + 0x9299_81d7_cfce_253b, + 0x187a_a448_f391_e3ca, + ]), + vesta::Base::from_raw([ + 0xf0c6_6af5_ffc7_3736, + 0x663c_cf7b_2ffe_4b5e, + 0x007a_b3aa_3617_f422, + 0x0b70_83ad_7517_07bf, + ]), + ], + [ + vesta::Base::from_raw([ + 0x2f9b_20f1_fbd4_9791, + 0x1975_b962_f6cb_8e0b, + 0x3bc4_ca99_02c5_2acb, + 0x030d_dbb4_7049_3f16, + ]), + vesta::Base::from_raw([ + 0x3a1c_62ca_8fbf_2525, + 0x8fb8_ab9d_60ea_17b2, + 0x950b_0ab1_8d35_46df, + 0x3130_fbaf_fb5a_a82a, + ]), + vesta::Base::from_raw([ + 0x43a8_7618_0dc3_82e0, + 0x15ce_2ead_2fcd_051e, + 0x4f74_d74b_ac2e_e457, + 0x337f_5447_07c4_30f0, + ]), + ], + [ + vesta::Base::from_raw([ + 0x26de_98a8_736d_1d11, + 0x7d8e_471a_9fb9_5fef, + 0xac9d_91b0_930d_ac75, + 0x3499_7991_9015_394f, + ]), + vesta::Base::from_raw([ + 0xccfc_b618_31d5_c775, + 0x3bf9_3da6_fff3_1d95, + 0x2305_cd7a_921e_c5f1, + 0x027c_c4ef_e3fb_35dd, + ]), + vesta::Base::from_raw([ + 0xc3fa_2629_635d_27de, + 0x67f1_c6b7_3147_64af, + 0x61b7_1a36_9868_2ad2, + 0x037f_9f23_6595_4c5b, + ]), + ], + [ + vesta::Base::from_raw([ + 0x77c5_b024_8483_71ae, + 0x6041_4abe_362d_01c9, + 0x10f1_cc6d_f8b4_bcd7, + 0x1f69_7cac_4d07_feb7, + ]), + vesta::Base::from_raw([ + 0x786a_dd24_4aa0_ef29, + 0x3145_c478_0631_09d6, + 0x26e6_c851_fbd5_72a6, + 0x267a_750f_e5d7_cfbc, + ]), + vesta::Base::from_raw([ + 0x180e_2b4d_3e75_6f65, + 0xaf28_5fa8_2ce4_fae5, + 0x678c_9996_d9a4_72c8, + 0x0c91_feab_4a43_193a, + ]), + ], + [ + vesta::Base::from_raw([ + 0x79c4_7c57_3ac4_10f7, + 0x7e3b_83af_4a4b_a3ba, + 0x2186_c303_8ea0_5e69, + 0x1745_569a_0a3e_3014, + ]), + vesta::Base::from_raw([ + 0x1e03_8852_2696_191f, + 0xfdff_66c6_f3b5_ffe1, + 0xeca5_1207_78a5_6711, + 0x2986_3d54_6e7e_7c0d, + ]), + vesta::Base::from_raw([ + 0x2f22_5e63_66bf_e390, + 0xa79a_03df_8339_94c6, + 0xbf06_bae4_9ef8_53f6, + 0x1148_d6ab_2bd0_0192, + ]), + ], + [ + vesta::Base::from_raw([ + 0xf4f6_331a_8b26_5d15, + 0xf745_f45d_350d_41d4, + 0xe18b_1499_060d_a366, + 0x02e0_e121_b0f3_dfef, + ]), + vesta::Base::from_raw([ + 0x078a_e6aa_1510_54b7, + 0x6904_0173_6d44_a653, + 0xb89e_f73a_40a2_b274, + 0x0d0a_a46e_76a6_a278, + ]), + vesta::Base::from_raw([ + 0x9a4d_532c_7b6e_0958, + 0x392d_de71_0f1f_06db, + 0xeee5_45f3_fa6d_3d08, + 0x1394_3675_b04a_a986, + ]), + ], + [ + vesta::Base::from_raw([ + 0x961f_c818_dcbb_66b5, + 0xc9f2_b325_7530_dafe, + 0xd97a_11d6_3088_f5d9, + 0x2901_ec61_942d_34aa, + ]), + vesta::Base::from_raw([ + 0xfdf5_44b9_63d1_fdc7, + 0x22ff_a2a2_af9f_a3e3, + 0xf431_d544_34a3_e0cf, + 0x2020_4a21_05d2_2e7e, + ]), + vesta::Base::from_raw([ + 0x1211_b9e2_190d_6852, + 0xa004_abe8_e015_28c4, + 0x5c1e_3e9e_27a5_71c3, + 0x3a8a_6282_9512_1d5c, + ]), + ], +]; + +// n: 255 +// t: 3 +// N: 765 +// Result Algorithm 1: +// [True, 0] +// Result Algorithm 2: +// [True, None] +// Result Algorithm 3: +// [True, None] +// Prime number: 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 +// MDS matrix: +pub(crate) const MDS: [[vesta::Base; 3]; 3] = [ + [ + vesta::Base::from_raw([ + 0xeb4f_1f74_2963_421f, + 0x5f71_0afc_43dd_c5f6, + 0x9191_3f56_cf21_af2b, + 0x1853_b497_7c6f_a227, + ]), + vesta::Base::from_raw([ + 0x45e5_1db6_ac6f_e4a7, + 0x5a0f_a4df_a500_bcad, + 0x63f4_84c1_0fcf_0586, + 0x3d83_1189_cfbb_c452, + ]), + vesta::Base::from_raw([ + 0xd188_37f9_8347_f137, + 0x3f89_65c7_8083_8a94, + 0x4ba8_8b9e_4017_19c0, + 0x3a0e_3f84_d3c1_77d8, + ]), + ], + [ + vesta::Base::from_raw([ + 0x84fd_7923_337c_f77e, + 0x2896_f8d0_fd5c_9a75, + 0x8e9d_c529_f471_8f83, + 0x35e2_6e39_8450_6279, + ]), + vesta::Base::from_raw([ + 0x3eb9_24f5_6fff_7908, + 0x3641_cecf_3a2a_5a8a, + 0x00cd_7dbe_a799_70ab, + 0x10a8_1663_02cb_753c, + ]), + vesta::Base::from_raw([ + 0xb672_27c1_a141_ae94, + 0x198e_1aee_777e_2521, + 0xf434_92ce_5121_4b00, + 0x314f_762a_506d_321b, + ]), + ], + [ + vesta::Base::from_raw([ + 0xabcb_d614_eaf5_eba1, + 0xa90f_28b0_cb31_76fb, + 0xcb2e_ab86_ef31_d915, + 0x07b8_5627_c832_782a, + ]), + vesta::Base::from_raw([ + 0xc255_efd0_06b5_db1c, + 0xb5d9_85dc_1630_a4b2, + 0x9756_4e1b_5d1a_c72f, + 0x2a2d_e13e_70f2_7e16, + ]), + vesta::Base::from_raw([ + 0xcffd_f529_3334_29fc, + 0x21e3_af7e_f123_32cd, + 0xfff5_40a8_7327_c7ce, + 0x2c60_94d1_c6e1_caba, + ]), + ], +]; + +pub(crate) const MDS_INV: [[vesta::Base; 3]; 3] = [ + [ + vesta::Base::from_raw([ + 0xb204_ddc6_5e58_2044, + 0x47a6_0484_b0a9_9c91, + 0xcaf5_4d78_24c1_200e, + 0x36df_4950_21cf_7828, + ]), + vesta::Base::from_raw([ + 0x6a6b_94ad_aa0d_9c9e, + 0xe2cd_38b9_59d4_61ff, + 0xe43e_c4bf_3e0d_f00c, + 0x034f_beae_4650_c2c7, + ]), + vesta::Base::from_raw([ + 0xa862_7a02_8c1a_f7d6, + 0x841b_ebf1_a15b_746e, + 0x1fd5_6832_d0ab_5570, + 0x20a8_64d6_790f_7c1c, + ]), + ], + [ + vesta::Base::from_raw([ + 0x3470_d5c5_53bc_9d20, + 0x1f95_660f_eb5d_b121, + 0xdd31_97ac_c894_9076, + 0x2d08_703d_48ec_d7dc, + ]), + vesta::Base::from_raw([ + 0x6b5b_42b0_67d8_30f3, + 0x6169_b6fa_721a_470e, + 0xeff3_18a2_8983_158a, + 0x2db1_0ecd_507a_2f27, + ]), + vesta::Base::from_raw([ + 0xfbae_b537_d278_4760, + 0x0068_e709_07e7_089d, + 0x926a_5fc0_cc1e_f726, + 0x0c8a_58c0_6473_cdfa, + ]), + ], + [ + vesta::Base::from_raw([ + 0x3a5a_ca10_7129_6e61, + 0x4ad4_442e_96c9_d5e8, + 0x5432_f0c0_b908_a411, + 0x2a64_2dca_695d_744d, + ]), + vesta::Base::from_raw([ + 0x1bd9_bfcb_be02_5ff1, + 0x24f6_ad43_b703_ad90, + 0xebb7_238d_f00d_17e7, + 0x114e_c796_fb40_3f5f, + ]), + vesta::Base::from_raw([ + 0x67f0_642e_14a9_c3bf, + 0xf6a6_9176_7069_7a97, + 0x0408_110d_c66e_b147, + 0x2825_e067_5968_dbeb, + ]), + ], +]; diff --git a/halo2_gadgets_optimized/src/poseidon/primitives/grain.rs b/halo2_gadgets_optimized/src/poseidon/primitives/grain.rs new file mode 100644 index 0000000000..8c8c9734cf --- /dev/null +++ b/halo2_gadgets_optimized/src/poseidon/primitives/grain.rs @@ -0,0 +1,195 @@ +//! The Grain LFSR in self-shrinking mode, as used by Poseidon. + +use std::marker::PhantomData; + +use bitvec::prelude::*; +use group::ff::{Field, FromUniformBytes, PrimeField}; + +const STATE: usize = 80; + +#[derive(Debug, Clone, Copy)] +pub(super) enum FieldType { + /// GF(2^n) + #[allow(dead_code)] + Binary, + /// GF(p) + PrimeOrder, +} + +impl FieldType { + fn tag(&self) -> u8 { + match self { + FieldType::Binary => 0, + FieldType::PrimeOrder => 1, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub(super) enum SboxType { + /// x^alpha + Pow, + /// x^(-1) + #[allow(dead_code)] + Inv, +} + +impl SboxType { + fn tag(&self) -> u8 { + match self { + SboxType::Pow => 0, + SboxType::Inv => 1, + } + } +} + +pub(super) struct Grain { + state: BitArr!(for 80, in u8, Msb0), + next_bit: usize, + _field: PhantomData, +} + +impl Grain { + pub(super) fn new(sbox: SboxType, t: u16, r_f: u16, r_p: u16) -> Self { + // Initialize the LFSR state. + let mut state = bitarr![u8, Msb0; 1; STATE]; + let mut set_bits = |offset: usize, len, value| { + // Poseidon reference impl sets initial state bits in MSB order. + for i in 0..len { + *state.get_mut(offset + len - 1 - i).unwrap() = (value >> i) & 1 != 0; + } + }; + set_bits(0, 2, FieldType::PrimeOrder.tag() as u16); + set_bits(2, 4, sbox.tag() as u16); + set_bits(6, 12, F::NUM_BITS as u16); + set_bits(18, 12, t); + set_bits(30, 10, r_f); + set_bits(40, 10, r_p); + + let mut grain = Grain { + state, + next_bit: STATE, + _field: PhantomData::default(), + }; + + // Discard the first 160 bits. + for _ in 0..20 { + grain.load_next_8_bits(); + grain.next_bit = STATE; + } + + grain + } + + fn load_next_8_bits(&mut self) { + let mut new_bits = 0u8; + for i in 0..8 { + new_bits |= ((self.state[i + 62] + ^ self.state[i + 51] + ^ self.state[i + 38] + ^ self.state[i + 23] + ^ self.state[i + 13] + ^ self.state[i]) as u8) + << i; + } + self.state.rotate_left(8); + self.next_bit -= 8; + for i in 0..8 { + *self.state.get_mut(self.next_bit + i).unwrap() = (new_bits >> i) & 1 != 0; + } + } + + fn get_next_bit(&mut self) -> bool { + if self.next_bit == STATE { + self.load_next_8_bits(); + } + let ret = self.state[self.next_bit]; + self.next_bit += 1; + ret + } + + /// Returns the next field element from this Grain instantiation. + pub(super) fn next_field_element(&mut self) -> F { + // Loop until we get an element in the field. + loop { + let mut bytes = F::Repr::default(); + + // Poseidon reference impl interprets the bits as a repr in MSB order, because + // it's easy to do that in Python. Meanwhile, our field elements all use LSB + // order. There's little motivation to diverge from the reference impl; these + // are all constants, so we aren't introducing big-endianness into the rest of + // the circuit (assuming unkeyed Poseidon, but we probably wouldn't want to + // implement Grain inside a circuit, so we'd use a different round constant + // derivation function there). + let view = bytes.as_mut(); + for (i, bit) in self.take(F::NUM_BITS as usize).enumerate() { + // If we diverged from the reference impl and interpreted the bits in LSB + // order, we would remove this line. + let i = F::NUM_BITS as usize - 1 - i; + + view[i / 8] |= if bit { 1 << (i % 8) } else { 0 }; + } + + if let Some(f) = F::from_repr_vartime(bytes) { + break f; + } + } + } +} + +impl> Grain { + /// Returns the next field element from this Grain instantiation, without using + /// rejection sampling. + pub(super) fn next_field_element_without_rejection(&mut self) -> F { + let mut bytes = [0u8; 64]; + + // Poseidon reference impl interprets the bits as a repr in MSB order, because + // it's easy to do that in Python. Additionally, it does not use rejection + // sampling in cases where the constants don't specifically need to be uniformly + // random for security. We do not provide APIs that take a field-element-sized + // array and reduce it modulo the field order, because those are unsafe APIs to + // offer generally (accidentally using them can lead to divergence in consensus + // systems due to not rejecting canonical forms). + // + // Given that we don't want to diverge from the reference implementation, we hack + // around this restriction by serializing the bits into a 64-byte array and then + // calling F::from_bytes_wide. PLEASE DO NOT COPY THIS INTO YOUR OWN CODE! + let view = bytes.as_mut(); + for (i, bit) in self.take(F::NUM_BITS as usize).enumerate() { + // If we diverged from the reference impl and interpreted the bits in LSB + // order, we would remove this line. + let i = F::NUM_BITS as usize - 1 - i; + + view[i / 8] |= if bit { 1 << (i % 8) } else { 0 }; + } + + F::from_uniform_bytes(&bytes) + } +} + +impl Iterator for Grain { + type Item = bool; + + fn next(&mut self) -> Option { + // Evaluate bits in pairs: + // - If the first bit is a 1, output the second bit. + // - If the first bit is a 0, discard the second bit. + while !self.get_next_bit() { + self.get_next_bit(); + } + Some(self.get_next_bit()) + } +} + +#[cfg(test)] +mod tests { + use pasta_curves::Fp; + + use super::{Grain, SboxType}; + + #[test] + fn grain() { + let mut grain = Grain::::new(SboxType::Pow, 3, 8, 56); + let _f = grain.next_field_element(); + } +} diff --git a/halo2_gadgets_optimized/src/poseidon/primitives/mds.rs b/halo2_gadgets_optimized/src/poseidon/primitives/mds.rs new file mode 100644 index 0000000000..bc633478cc --- /dev/null +++ b/halo2_gadgets_optimized/src/poseidon/primitives/mds.rs @@ -0,0 +1,129 @@ +use ff::FromUniformBytes; + +use super::{grain::Grain, Mds}; + +pub(super) fn generate_mds + Ord, const T: usize>( + grain: &mut Grain, + mut select: usize, +) -> (Mds, Mds) { + let (xs, ys, mds) = loop { + // Generate two [F; T] arrays of unique field elements. + let (xs, ys) = loop { + let mut vals: Vec<_> = (0..2 * T) + .map(|_| grain.next_field_element_without_rejection()) + .collect(); + + // Check that we have unique field elements. + let mut unique = vals.clone(); + unique.sort_unstable(); + unique.dedup(); + if vals.len() == unique.len() { + let rhs = vals.split_off(T); + break (vals, rhs); + } + }; + + // We need to ensure that the MDS is secure. Instead of checking the MDS against + // the relevant algorithms directly, we witness a fixed number of MDS matrices + // that we need to sample from the given Grain state before obtaining a secure + // matrix. This can be determined out-of-band via the reference implementation in + // Sage. + if select != 0 { + select -= 1; + continue; + } + + // Generate a Cauchy matrix, with elements a_ij in the form: + // a_ij = 1/(x_i + y_j); x_i + y_j != 0 + // + // It would be much easier to use the alternate definition: + // a_ij = 1/(x_i - y_j); x_i - y_j != 0 + // + // These are clearly equivalent on `y <- -y`, but it is easier to work with the + // negative formulation, because ensuring that xs ∪ ys is unique implies that + // x_i - y_j != 0 by construction (whereas the positive case does not hold). It + // also makes computation of the matrix inverse simpler below (the theorem used + // was formulated for the negative definition). + // + // However, the Poseidon paper and reference impl use the positive formulation, + // and we want to rely on the reference impl for MDS security, so we use the same + // formulation. + let mut mds = [[F::ZERO; T]; T]; + #[allow(clippy::needless_range_loop)] + for i in 0..T { + for j in 0..T { + let sum = xs[i] + ys[j]; + // We leverage the secure MDS selection counter to also check this. + assert!(!sum.is_zero_vartime()); + mds[i][j] = sum.invert().unwrap(); + } + } + + break (xs, ys, mds); + }; + + // Compute the inverse. All square Cauchy matrices have a non-zero determinant and + // thus are invertible. The inverse for a Cauchy matrix of the form: + // + // a_ij = 1/(x_i - y_j); x_i - y_j != 0 + // + // has elements b_ij given by: + // + // b_ij = (x_j - y_i) A_j(y_i) B_i(x_j) (Schechter 1959, Theorem 1) + // + // where A_i(x) and B_i(x) are the Lagrange polynomials for xs and ys respectively. + // + // We adapt this to the positive Cauchy formulation by negating ys. + let mut mds_inv = [[F::ZERO; T]; T]; + let l = |xs: &[F], j, x: F| { + let x_j = xs[j]; + xs.iter().enumerate().fold(F::ONE, |acc, (m, x_m)| { + if m == j { + acc + } else { + // We hard-code the type, to avoid spurious "cannot infer type" rustc errors. + let denominator: F = x_j - x_m; + + // We can invert freely; by construction, the elements of xs are distinct. + let denominator_inverted: F = denominator.invert().unwrap(); + + acc * (x - x_m) * denominator_inverted + } + }) + }; + let neg_ys: Vec<_> = ys.iter().map(|y| -*y).collect(); + for i in 0..T { + for j in 0..T { + mds_inv[i][j] = (xs[j] - neg_ys[i]) * l(&xs, j, neg_ys[i]) * l(&neg_ys, i, xs[j]); + } + } + + (mds, mds_inv) +} + +#[cfg(test)] +mod tests { + use group::ff::Field; + use pasta_curves::Fp; + + use super::{generate_mds, Grain}; + + #[test] + fn poseidon_mds() { + const T: usize = 3; + let mut grain = Grain::new(super::super::grain::SboxType::Pow, T as u16, 8, 56); + let (mds, mds_inv) = generate_mds::(&mut grain, 0); + + // Verify that MDS * MDS^-1 = I. + #[allow(clippy::needless_range_loop)] + for i in 0..T { + for j in 0..T { + let expected = if i == j { Fp::ONE } else { Fp::ZERO }; + assert_eq!( + (0..T).fold(Fp::ZERO, |acc, k| acc + (mds[i][k] * mds_inv[k][j])), + expected + ); + } + } + } +} diff --git a/halo2_gadgets_optimized/src/poseidon/primitives/p128pow5t3.rs b/halo2_gadgets_optimized/src/poseidon/primitives/p128pow5t3.rs new file mode 100644 index 0000000000..ff5317406f --- /dev/null +++ b/halo2_gadgets_optimized/src/poseidon/primitives/p128pow5t3.rs @@ -0,0 +1,320 @@ +use halo2_proofs::arithmetic::Field; +use pasta_curves::{pallas::Base as Fp, vesta::Base as Fq}; + +use super::{Mds, Spec}; + +/// Poseidon-128 using the $x^5$ S-box, with a width of 3 field elements, and the +/// standard number of rounds for 128-bit security "with margin". +/// +/// The standard specification for this set of parameters (on either of the Pasta +/// fields) uses $R_F = 8, R_P = 56$. This is conveniently an even number of +/// partial rounds, making it easier to construct a Halo 2 circuit. +#[derive(Debug)] +pub struct P128Pow5T3; + +impl Spec for P128Pow5T3 { + fn full_rounds() -> usize { + 8 + } + + fn partial_rounds() -> usize { + 56 + } + + fn sbox(val: Fp) -> Fp { + val.pow_vartime([5]) + } + + fn secure_mds() -> usize { + unimplemented!() + } + + fn constants() -> (Vec<[Fp; 3]>, Mds, Mds) { + ( + super::fp::ROUND_CONSTANTS[..].to_vec(), + super::fp::MDS, + super::fp::MDS_INV, + ) + } +} + +impl Spec for P128Pow5T3 { + fn full_rounds() -> usize { + 8 + } + + fn partial_rounds() -> usize { + 56 + } + + fn sbox(val: Fq) -> Fq { + val.pow_vartime([5]) + } + + fn secure_mds() -> usize { + unimplemented!() + } + + fn constants() -> (Vec<[Fq; 3]>, Mds, Mds) { + ( + super::fq::ROUND_CONSTANTS[..].to_vec(), + super::fq::MDS, + super::fq::MDS_INV, + ) + } +} + +#[cfg(test)] +mod tests { + use ff::{Field, FromUniformBytes, PrimeField}; + use std::marker::PhantomData; + + use super::{ + super::{fp, fq}, + Fp, Fq, + }; + use crate::poseidon::primitives::{ + generate_constants, permute, ConstantLength, Hash, Mds, Spec, + }; + + /// The same Poseidon specification as poseidon::P128Pow5T3, but constructed + /// such that its constants will be generated at runtime. + #[derive(Debug)] + pub struct P128Pow5T3Gen(PhantomData); + + impl P128Pow5T3Gen { + #![allow(dead_code)] + pub fn new() -> Self { + P128Pow5T3Gen(PhantomData::default()) + } + } + + impl + Ord, const SECURE_MDS: usize> Spec + for P128Pow5T3Gen + { + fn full_rounds() -> usize { + 8 + } + + fn partial_rounds() -> usize { + 56 + } + + fn sbox(val: F) -> F { + val.pow_vartime([5]) + } + + fn secure_mds() -> usize { + SECURE_MDS + } + + fn constants() -> (Vec<[F; 3]>, Mds, Mds) { + generate_constants::<_, Self, 3, 2>() + } + } + + #[test] + fn verify_constants() { + fn verify_constants_helper + Ord>( + expected_round_constants: [[F; 3]; 64], + expected_mds: [[F; 3]; 3], + expected_mds_inv: [[F; 3]; 3], + ) { + let (round_constants, mds, mds_inv) = P128Pow5T3Gen::::constants(); + + for (actual, expected) in round_constants + .iter() + .flatten() + .zip(expected_round_constants.iter().flatten()) + { + assert_eq!(actual, expected); + } + + for (actual, expected) in mds.iter().flatten().zip(expected_mds.iter().flatten()) { + assert_eq!(actual, expected); + } + + for (actual, expected) in mds_inv + .iter() + .flatten() + .zip(expected_mds_inv.iter().flatten()) + { + assert_eq!(actual, expected); + } + } + + verify_constants_helper(fp::ROUND_CONSTANTS, fp::MDS, fp::MDS_INV); + verify_constants_helper(fq::ROUND_CONSTANTS, fq::MDS, fq::MDS_INV); + } + + #[test] + fn test_against_reference() { + { + // , using parameters from + // `generate_parameters_grain.sage 1 0 255 3 8 56 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001`. + // The test vector is generated by `sage poseidonperm_x5_pallas_3.sage --rust` + + let mut input = [ + Fp::from_raw([ + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + ]), + Fp::from_raw([ + 0x0000_0000_0000_0001, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + ]), + Fp::from_raw([ + 0x0000_0000_0000_0002, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + ]), + ]; + + let expected_output = [ + Fp::from_raw([ + 0xaeb1_bc02_4aec_a456, + 0xf7e6_9a71_d0b6_42a0, + 0x94ef_b364_f966_240f, + 0x2a52_6acd_0b64_b453, + ]), + Fp::from_raw([ + 0x012a_3e96_28e5_b82a, + 0xdcd4_2e7f_bed9_dafe, + 0x76ff_7dae_343d_5512, + 0x13c5_d156_8b4a_a430, + ]), + Fp::from_raw([ + 0x3590_29a1_d34e_9ddd, + 0xf7cf_dfe1_bda4_2c7b, + 0x256f_cd59_7984_561a, + 0x0a49_c868_c697_6544, + ]), + ]; + + permute::, 3, 2>(&mut input, &fp::MDS, &fp::ROUND_CONSTANTS); + assert_eq!(input, expected_output); + } + + { + // , using parameters from + // `generate_parameters_grain.sage 1 0 255 3 8 56 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001`. + // The test vector is generated by `sage poseidonperm_x5_vesta_3.sage --rust` + + let mut input = [ + Fq::from_raw([ + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + ]), + Fq::from_raw([ + 0x0000_0000_0000_0001, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + ]), + Fq::from_raw([ + 0x0000_0000_0000_0002, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + ]), + ]; + + let expected_output = [ + Fq::from_raw([ + 0x0eb0_8ea8_13be_be59, + 0x4d43_d197_3dd3_36c6, + 0xeddd_74f2_2f8f_2ff7, + 0x315a_1f4c_db94_2f7c, + ]), + Fq::from_raw([ + 0xf9f1_26e6_1ea1_65f1, + 0x413e_e0eb_7bbd_2198, + 0x642a_dee0_dd13_aa48, + 0x3be4_75f2_d764_2bde, + ]), + Fq::from_raw([ + 0x14d5_4237_2a7b_a0d9, + 0x5019_bfd4_e042_3fa0, + 0x117f_db24_20d8_ea60, + 0x25ab_8aec_e953_7168, + ]), + ]; + + permute::, 3, 2>(&mut input, &fq::MDS, &fq::ROUND_CONSTANTS); + assert_eq!(input, expected_output); + } + } + + #[test] + fn permute_test_vectors() { + { + let (round_constants, mds, _) = super::P128Pow5T3::constants(); + + for tv in crate::poseidon::primitives::test_vectors::fp::permute() { + let mut state = [ + Fp::from_repr(tv.initial_state[0]).unwrap(), + Fp::from_repr(tv.initial_state[1]).unwrap(), + Fp::from_repr(tv.initial_state[2]).unwrap(), + ]; + + permute::(&mut state, &mds, &round_constants); + + for (expected, actual) in tv.final_state.iter().zip(state.iter()) { + assert_eq!(&actual.to_repr(), expected); + } + } + } + + { + let (round_constants, mds, _) = super::P128Pow5T3::constants(); + + for tv in crate::poseidon::primitives::test_vectors::fq::permute() { + let mut state = [ + Fq::from_repr(tv.initial_state[0]).unwrap(), + Fq::from_repr(tv.initial_state[1]).unwrap(), + Fq::from_repr(tv.initial_state[2]).unwrap(), + ]; + + permute::(&mut state, &mds, &round_constants); + + for (expected, actual) in tv.final_state.iter().zip(state.iter()) { + assert_eq!(&actual.to_repr(), expected); + } + } + } + } + + #[test] + fn hash_test_vectors() { + for tv in crate::poseidon::primitives::test_vectors::fp::hash() { + let message = [ + Fp::from_repr(tv.input[0]).unwrap(), + Fp::from_repr(tv.input[1]).unwrap(), + ]; + + let result = + Hash::<_, super::P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash(message); + + assert_eq!(result.to_repr(), tv.output); + } + + for tv in crate::poseidon::primitives::test_vectors::fq::hash() { + let message = [ + Fq::from_repr(tv.input[0]).unwrap(), + Fq::from_repr(tv.input[1]).unwrap(), + ]; + + let result = + Hash::<_, super::P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash(message); + + assert_eq!(result.to_repr(), tv.output); + } + } +} diff --git a/halo2_gadgets_optimized/src/poseidon/primitives/test_vectors.rs b/halo2_gadgets_optimized/src/poseidon/primitives/test_vectors.rs new file mode 100644 index 0000000000..1c345a4853 --- /dev/null +++ b/halo2_gadgets_optimized/src/poseidon/primitives/test_vectors.rs @@ -0,0 +1,1261 @@ +//! Test vectors for [`OrchardNullifier`]. + +pub(crate) struct PermuteTestVector { + pub(crate) initial_state: [[u8; 32]; 3], + pub(crate) final_state: [[u8; 32]; 3], +} + +pub(crate) struct HashTestVector { + pub(crate) input: [[u8; 32]; 2], + pub(crate) output: [u8; 32], +} + +pub(crate) mod fp { + use super::*; + + pub(crate) fn permute() -> Vec { + use PermuteTestVector as TestVector; + + // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_poseidon/permute/fp.py + vec![ + TestVector { + initial_state: [ + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + [ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + [ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + ], + final_state: [ + [ + 0x56, 0xa4, 0xec, 0x4a, 0x02, 0xbc, 0xb1, 0xae, 0xa0, 0x42, 0xb6, 0xd0, + 0x71, 0x9a, 0xe6, 0xf7, 0x0f, 0x24, 0x66, 0xf9, 0x64, 0xb3, 0xef, 0x94, + 0x53, 0xb4, 0x64, 0x0b, 0xcd, 0x6a, 0x52, 0x2a, + ], + [ + 0x2a, 0xb8, 0xe5, 0x28, 0x96, 0x3e, 0x2a, 0x01, 0xfe, 0xda, 0xd9, 0xbe, + 0x7f, 0x2e, 0xd4, 0xdc, 0x12, 0x55, 0x3d, 0x34, 0xae, 0x7d, 0xff, 0x76, + 0x30, 0xa4, 0x4a, 0x8b, 0x56, 0xd1, 0xc5, 0x13, + ], + [ + 0xdd, 0x9d, 0x4e, 0xd3, 0xa1, 0x29, 0x90, 0x35, 0x7b, 0x2c, 0xa4, 0xbd, + 0xe1, 0xdf, 0xcf, 0xf7, 0x1a, 0x56, 0x84, 0x79, 0x59, 0xcd, 0x6f, 0x25, + 0x44, 0x65, 0x97, 0xc6, 0x68, 0xc8, 0x49, 0x0a, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0x5c, 0x7a, 0x8f, 0x73, 0xad, 0xfc, 0x70, 0xfb, 0x3f, 0x13, 0x94, 0x49, + 0xac, 0x6b, 0x57, 0x07, 0x4c, 0x4d, 0x6e, 0x66, 0xb1, 0x64, 0x93, 0x9d, + 0xaf, 0xfa, 0x2e, 0xf6, 0xee, 0x69, 0x21, 0x08, + ], + [ + 0x1a, 0xdd, 0x86, 0xb3, 0xf2, 0xe1, 0xbd, 0xa6, 0x2a, 0x5d, 0x2e, 0x0e, + 0x98, 0x2b, 0x77, 0xe6, 0xb0, 0xef, 0x9c, 0xa3, 0xf2, 0x49, 0x88, 0xc7, + 0xb3, 0x53, 0x42, 0x01, 0xcf, 0xb1, 0xcd, 0x0d, + ], + [ + 0xbd, 0x69, 0xb8, 0x25, 0x32, 0xb6, 0x94, 0x0f, 0xf2, 0x59, 0x0f, 0x67, + 0x9b, 0xa9, 0xc7, 0x27, 0x1f, 0xe0, 0x1f, 0x7e, 0x9c, 0x8e, 0x36, 0xd6, + 0xa5, 0xe2, 0x9d, 0x4e, 0x30, 0xa7, 0x35, 0x14, + ], + ], + final_state: [ + [ + 0xd0, 0x6e, 0x2f, 0x83, 0x38, 0x92, 0x8a, 0x7e, 0xe7, 0x38, 0x0c, 0x77, + 0x92, 0x80, 0x87, 0xcd, 0xa2, 0xfd, 0x29, 0x61, 0xa1, 0x52, 0x69, 0x03, + 0x7a, 0x22, 0xd6, 0xd1, 0x20, 0xae, 0xdd, 0x21, + ], + [ + 0x29, 0x55, 0xa4, 0x5f, 0x41, 0x6f, 0x10, 0xd6, 0xbc, 0x79, 0xac, 0x94, + 0xd0, 0xc0, 0x69, 0xc9, 0x49, 0xe5, 0xf4, 0xbd, 0x09, 0x48, 0x1e, 0x1f, + 0x36, 0x8c, 0xb9, 0xb8, 0xee, 0x51, 0x14, 0x0d, + ], + [ + 0x0d, 0x83, 0x76, 0xbb, 0xe9, 0xd6, 0x5d, 0x2b, 0x1e, 0x13, 0x6f, 0xb7, + 0xd9, 0x82, 0xab, 0x87, 0xc5, 0x1c, 0x40, 0x30, 0x44, 0xbe, 0x5c, 0x79, + 0x9d, 0x56, 0xbb, 0x68, 0xac, 0xf9, 0x5b, 0x10, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0xbc, 0x50, 0x98, 0x42, 0x55, 0xd6, 0xaf, 0xbe, 0x9e, 0xf9, 0x28, 0x48, + 0xed, 0x5a, 0xc0, 0x08, 0x62, 0xc2, 0xfa, 0x7b, 0x2f, 0xec, 0xbc, 0xb6, + 0x4b, 0x69, 0x68, 0x91, 0x2a, 0x63, 0x81, 0x0e, + ], + [ + 0x3d, 0xc1, 0x66, 0xd5, 0x6a, 0x1d, 0x62, 0xf5, 0xa8, 0xd7, 0x55, 0x1d, + 0xb5, 0xfd, 0x93, 0x13, 0xe8, 0xc7, 0x20, 0x3d, 0x99, 0x6a, 0xf7, 0xd4, + 0x77, 0x08, 0x37, 0x56, 0xd5, 0x9a, 0xf8, 0x0d, + ], + [ + 0x05, 0xa7, 0x45, 0xf4, 0x5d, 0x7f, 0xf6, 0xdb, 0x10, 0xbc, 0x67, 0xfd, + 0xf0, 0xf0, 0x3e, 0xbf, 0x81, 0x30, 0xab, 0x33, 0x36, 0x26, 0x97, 0xb0, + 0xe4, 0xe4, 0xc7, 0x63, 0xcc, 0xb8, 0xf6, 0x36, + ], + ], + final_state: [ + [ + 0x0b, 0x77, 0xec, 0x53, 0x07, 0x14, 0x5a, 0x0c, 0x05, 0x2d, 0xc7, 0xa9, + 0xd6, 0xf9, 0x6a, 0xc3, 0x41, 0xae, 0x72, 0x64, 0x08, 0x32, 0xd5, 0x8e, + 0x51, 0xeb, 0x92, 0xa4, 0x17, 0x80, 0x17, 0x12, + ], + [ + 0x3b, 0x52, 0x3f, 0x44, 0xf0, 0x0e, 0x46, 0x3f, 0x8b, 0x0f, 0xd7, 0xd4, + 0xfc, 0x0e, 0x28, 0x0c, 0xdb, 0xde, 0xb9, 0x27, 0xf1, 0x81, 0x68, 0x07, + 0x7b, 0xb3, 0x62, 0xf2, 0x67, 0x5a, 0x2e, 0x18, + ], + [ + 0x95, 0x7a, 0x97, 0x06, 0xff, 0xcc, 0x35, 0x15, 0x64, 0xae, 0x80, 0x2a, + 0x99, 0x11, 0x31, 0x4c, 0x05, 0xe2, 0x3e, 0x22, 0xaf, 0xcf, 0x83, 0x40, + 0x59, 0xdf, 0x80, 0xfa, 0xc1, 0x05, 0x76, 0x26, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0x49, 0x5c, 0x22, 0x2f, 0x7f, 0xba, 0x1e, 0x31, 0xde, 0xfa, 0x3d, 0x5a, + 0x57, 0xef, 0xc2, 0xe1, 0xe9, 0xb0, 0x1a, 0x03, 0x55, 0x87, 0xd5, 0xfb, + 0x1a, 0x38, 0xe0, 0x1d, 0x94, 0x90, 0x3d, 0x3c, + ], + [ + 0x3d, 0x0a, 0xd3, 0x36, 0x1f, 0xec, 0x09, 0x77, 0x90, 0xd9, 0xbe, 0x0e, + 0x42, 0x98, 0x8d, 0x7d, 0x25, 0xc9, 0xa1, 0x38, 0xf4, 0x9b, 0x1a, 0x53, + 0x7e, 0xdc, 0xf0, 0x4b, 0xe3, 0x4a, 0x98, 0x11, + ], + [ + 0xa4, 0xaf, 0x9d, 0xb6, 0xd2, 0x7b, 0x50, 0x72, 0x83, 0x5f, 0x0c, 0x3e, + 0x88, 0x39, 0x5e, 0xd7, 0xa4, 0x1b, 0x00, 0x52, 0xad, 0x80, 0x84, 0xa8, + 0xb9, 0xda, 0x94, 0x8d, 0x32, 0x0d, 0xad, 0x16, + ], + ], + final_state: [ + [ + 0x67, 0x80, 0x08, 0x3f, 0x7f, 0x82, 0xcb, 0x42, 0x54, 0xe7, 0xb6, 0x6f, + 0x4b, 0x83, 0x84, 0x6a, 0xc9, 0x77, 0x3f, 0xb9, 0xc3, 0x9c, 0x6e, 0xc9, + 0x81, 0x8b, 0x06, 0x22, 0x23, 0x09, 0x55, 0x2a, + ], + [ + 0xa5, 0xf9, 0xa5, 0x7e, 0x2c, 0x40, 0xb1, 0x58, 0xd8, 0x16, 0x53, 0x43, + 0xe6, 0x02, 0x65, 0x2c, 0x3e, 0xfc, 0x0b, 0x64, 0xdd, 0xca, 0xee, 0xe5, + 0xce, 0x3d, 0x95, 0x1f, 0xd5, 0x9f, 0x50, 0x08, + ], + [ + 0xdc, 0xa4, 0x64, 0x36, 0x12, 0x7c, 0x47, 0x7e, 0x83, 0x95, 0x0f, 0xa0, + 0x7c, 0xc6, 0x8a, 0x56, 0x6e, 0x54, 0x18, 0x55, 0xad, 0xc2, 0x68, 0x52, + 0x97, 0x87, 0x35, 0x24, 0x88, 0x92, 0x1e, 0x3b, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0x4d, 0x54, 0x31, 0xe6, 0x43, 0x7d, 0x0b, 0x5b, 0xed, 0xbb, 0xcd, 0xaf, + 0x34, 0x5b, 0x86, 0xc4, 0x12, 0x1f, 0xc0, 0x0f, 0xe7, 0xf2, 0x35, 0x73, + 0x42, 0x76, 0xd3, 0x8d, 0x47, 0xf1, 0xe1, 0x11, + ], + [ + 0xdd, 0x0c, 0x7a, 0x1d, 0x81, 0x1c, 0x7d, 0x9c, 0xd4, 0x6d, 0x37, 0x7b, + 0x3f, 0xde, 0xab, 0x3f, 0xb6, 0x79, 0xf3, 0xdc, 0x60, 0x1d, 0x00, 0x82, + 0x85, 0xed, 0xcb, 0xda, 0xe6, 0x9c, 0xe8, 0x3c, + ], + [ + 0x19, 0xe4, 0xaa, 0xc0, 0x35, 0x90, 0x17, 0xec, 0x85, 0xa1, 0x83, 0xd2, + 0x20, 0x53, 0xdb, 0x33, 0xf7, 0x34, 0x76, 0xf2, 0x1a, 0x48, 0x2e, 0xc9, + 0x37, 0x83, 0x65, 0xc8, 0xf7, 0x39, 0x3c, 0x14, + ], + ], + final_state: [ + [ + 0x89, 0x99, 0x8e, 0x5e, 0x0f, 0xa1, 0x95, 0x2a, 0x40, 0xb8, 0xb5, 0x2b, + 0x62, 0xd9, 0x45, 0x70, 0xa4, 0x9a, 0x7d, 0x91, 0xdd, 0x22, 0x6d, 0x69, + 0x2b, 0xc9, 0xb1, 0xa6, 0x13, 0xc9, 0x08, 0x30, + ], + [ + 0xd0, 0xee, 0x44, 0xd9, 0xa9, 0x0d, 0x90, 0x79, 0xef, 0xfb, 0x24, 0x86, + 0xd3, 0xd8, 0x4d, 0x1a, 0x18, 0x4e, 0xdf, 0x14, 0x97, 0x0b, 0xac, 0x36, + 0xc7, 0x48, 0x04, 0xc7, 0xff, 0xbe, 0xe5, 0x0b, + ], + [ + 0x04, 0x81, 0x45, 0xa6, 0x61, 0xce, 0x78, 0x7c, 0x7e, 0x12, 0x2a, 0xc6, + 0x44, 0x7e, 0x9b, 0xa3, 0x93, 0xd3, 0x67, 0xac, 0x05, 0x4f, 0xaa, 0xc5, + 0xb7, 0xb5, 0xf7, 0x19, 0x2b, 0x2f, 0xde, 0x21, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0xe2, 0x88, 0x53, 0x15, 0xeb, 0x46, 0x71, 0x09, 0x8b, 0x79, 0x53, 0x5e, + 0x79, 0x0f, 0xe5, 0x3e, 0x29, 0xfe, 0xf2, 0xb3, 0x76, 0x66, 0x97, 0xac, + 0x32, 0xb4, 0xf4, 0x73, 0xf4, 0x68, 0xa0, 0x08, + ], + [ + 0xe6, 0x23, 0x89, 0xfc, 0x16, 0x57, 0xe0, 0xde, 0xf0, 0xb6, 0x32, 0xc6, + 0xae, 0x25, 0xf9, 0xf7, 0x83, 0xb2, 0x7d, 0xb5, 0x9a, 0x4a, 0x15, 0x3d, + 0x88, 0x2d, 0x2b, 0x21, 0x03, 0x59, 0x65, 0x15, + ], + [ + 0xeb, 0x94, 0x94, 0xc6, 0xd2, 0x27, 0xe2, 0x16, 0x3b, 0x46, 0x99, 0xd9, + 0x91, 0xf4, 0x33, 0xbf, 0x94, 0x86, 0xa7, 0xaf, 0xcf, 0x4a, 0x0d, 0x9c, + 0x73, 0x1e, 0x98, 0x5d, 0x99, 0x58, 0x9c, 0x0b, + ], + ], + final_state: [ + [ + 0xce, 0x2d, 0x1f, 0x8d, 0x67, 0x7f, 0xfb, 0xfd, 0x73, 0xb2, 0x35, 0xe8, + 0xc6, 0x87, 0xfb, 0x42, 0x18, 0x7f, 0x78, 0x81, 0xc3, 0xce, 0x9c, 0x79, + 0x4f, 0x2b, 0xd4, 0x61, 0x40, 0xf7, 0xcc, 0x2a, + ], + [ + 0xaf, 0x82, 0x92, 0x39, 0xb6, 0xd5, 0x5d, 0x5f, 0x43, 0xec, 0x6f, 0x32, + 0xb8, 0x4a, 0x2a, 0x01, 0x1e, 0x64, 0xc5, 0x74, 0x73, 0x9f, 0x87, 0xcb, + 0x47, 0xdc, 0x70, 0x23, 0x83, 0xfa, 0x5a, 0x34, + ], + [ + 0x03, 0xd1, 0x08, 0x5b, 0x21, 0x4c, 0x69, 0xb8, 0xbf, 0xe8, 0x91, 0x02, + 0xbd, 0x61, 0x7e, 0xce, 0x0c, 0x54, 0x00, 0x17, 0x96, 0x40, 0x41, 0x05, + 0xc5, 0x33, 0x30, 0xd2, 0x49, 0x58, 0x1d, 0x0f, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0xb7, 0x38, 0xe8, 0xaa, 0x0a, 0x15, 0x26, 0xa5, 0xbd, 0xef, 0x61, 0x31, + 0x20, 0x37, 0x2e, 0x83, 0x1a, 0x20, 0xda, 0x8a, 0xba, 0x18, 0xd1, 0xdb, + 0xeb, 0xbc, 0x86, 0x2d, 0xed, 0x42, 0x43, 0x1e, + ], + [ + 0x91, 0x47, 0x69, 0x30, 0xe3, 0x38, 0x5c, 0xd3, 0xe3, 0x37, 0x9e, 0x38, + 0x53, 0xd9, 0x34, 0x67, 0xe0, 0x01, 0xaf, 0xa2, 0xfb, 0x8d, 0xc3, 0x43, + 0x6d, 0x75, 0xa4, 0xa6, 0xf2, 0x65, 0x72, 0x10, + ], + [ + 0x4b, 0x19, 0x22, 0x32, 0xec, 0xb9, 0xf0, 0xc0, 0x24, 0x11, 0xe5, 0x25, + 0x96, 0xbc, 0x5e, 0x90, 0x45, 0x7e, 0x74, 0x59, 0x39, 0xff, 0xed, 0xbd, + 0x12, 0x86, 0x3c, 0xe7, 0x1a, 0x02, 0xaf, 0x11, + ], + ], + final_state: [ + [ + 0x5f, 0xcc, 0xd8, 0x7d, 0x2f, 0x66, 0x7b, 0x9e, 0xe3, 0x88, 0xf3, 0x4c, + 0x1c, 0x71, 0x06, 0x87, 0x12, 0x7b, 0xff, 0x5b, 0x02, 0x21, 0xfd, 0x8a, + 0x52, 0x94, 0x88, 0x66, 0x91, 0x57, 0x94, 0x2b, + ], + [ + 0x89, 0x62, 0xb5, 0x80, 0x30, 0xaa, 0x63, 0x52, 0xd9, 0x90, 0xf3, 0xb9, + 0x00, 0x1c, 0xcb, 0xe8, 0x8a, 0x56, 0x27, 0x58, 0x1b, 0xbf, 0xb9, 0x01, + 0xac, 0x4a, 0x6a, 0xed, 0xfa, 0xe5, 0xc6, 0x34, + ], + [ + 0x7c, 0x0b, 0x76, 0x59, 0xf2, 0x4c, 0x98, 0xaf, 0x31, 0x0e, 0x3e, 0x8d, + 0x82, 0xb5, 0xf3, 0x99, 0x43, 0x3c, 0xdd, 0xa5, 0x8f, 0x48, 0xd9, 0xef, + 0x8d, 0xd0, 0xca, 0x86, 0x42, 0x72, 0xda, 0x3f, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0x7b, 0x41, 0x7a, 0xdb, 0x63, 0xb3, 0x71, 0x22, 0xa5, 0xbf, 0x62, 0xd2, + 0x6f, 0x1e, 0x7f, 0x26, 0x8f, 0xb8, 0x6b, 0x12, 0xb5, 0x6d, 0xa9, 0xc3, + 0x82, 0x85, 0x7d, 0xee, 0xcc, 0x40, 0xa9, 0x0d, + ], + [ + 0x5e, 0x29, 0x35, 0x39, 0x71, 0xb3, 0x49, 0x94, 0xb6, 0x21, 0xb0, 0xb2, + 0x61, 0xae, 0xb3, 0x78, 0x6d, 0xd9, 0x84, 0xd5, 0x67, 0xdb, 0x28, 0x57, + 0xb9, 0x27, 0xb7, 0xfa, 0xe2, 0xdb, 0x58, 0x31, + ], + [ + 0x05, 0x41, 0x5d, 0x46, 0x42, 0x78, 0x9d, 0x38, 0xf5, 0x0b, 0x8d, 0xbc, + 0xc1, 0x29, 0xca, 0xb3, 0xd1, 0x7d, 0x19, 0xf3, 0x35, 0x5b, 0xcf, 0x73, + 0xce, 0xcb, 0x8c, 0xb8, 0xa5, 0xda, 0x01, 0x30, + ], + ], + final_state: [ + [ + 0x9e, 0xe1, 0xad, 0xdc, 0x6f, 0x64, 0xda, 0xb6, 0xac, 0xdc, 0xea, 0xec, + 0xc1, 0xfb, 0xbc, 0x8a, 0x32, 0x45, 0x8e, 0x49, 0xc1, 0x9e, 0x79, 0x85, + 0x56, 0xc6, 0x4b, 0x59, 0x8b, 0xa6, 0xff, 0x14, + ], + [ + 0x42, 0xcc, 0x10, 0x36, 0x4f, 0xd6, 0x59, 0xc3, 0xcc, 0x77, 0x25, 0x84, + 0xdb, 0x91, 0xc4, 0x9a, 0x38, 0x67, 0x2b, 0x69, 0x24, 0x93, 0xb9, 0x07, + 0x5f, 0x16, 0x53, 0xca, 0x1f, 0xae, 0x1c, 0x33, + ], + [ + 0xff, 0x41, 0xf3, 0x51, 0x80, 0x14, 0x56, 0xc4, 0x96, 0x0b, 0x39, 0x3a, + 0xff, 0xa8, 0x62, 0x13, 0xa7, 0xea, 0xc0, 0x6c, 0x66, 0x21, 0x3b, 0x45, + 0xc3, 0xb5, 0x0e, 0xc6, 0x48, 0xd6, 0x7d, 0x0d, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0x71, 0x52, 0xf1, 0x39, 0x36, 0xa2, 0x70, 0x57, 0x26, 0x70, 0xdc, 0x82, + 0xd3, 0x90, 0x26, 0xc6, 0xcb, 0x4c, 0xd4, 0xb0, 0xf7, 0xf5, 0xaa, 0x2a, + 0x4f, 0x5a, 0x53, 0x41, 0xec, 0x5d, 0xd7, 0x15, + ], + [ + 0x40, 0x6f, 0x2f, 0xdd, 0x2a, 0xfa, 0x73, 0x3f, 0x5f, 0x64, 0x1c, 0x8c, + 0x21, 0x86, 0x2a, 0x1b, 0xaf, 0xce, 0x26, 0x09, 0xd9, 0xee, 0xcf, 0xa1, + 0x58, 0xcf, 0xb5, 0xcd, 0x79, 0xf8, 0x80, 0x08, + ], + [ + 0xe2, 0x15, 0xdc, 0x7d, 0x96, 0x57, 0xba, 0xd3, 0xfb, 0x88, 0xb0, 0x1e, + 0x99, 0x38, 0x44, 0x54, 0x36, 0x24, 0xc2, 0x5f, 0xa9, 0x59, 0xcc, 0x97, + 0x48, 0x9c, 0xe7, 0x57, 0x45, 0x82, 0x4b, 0x37, + ], + ], + final_state: [ + [ + 0x63, 0x09, 0x15, 0xd7, 0xd8, 0x25, 0xeb, 0x74, 0x37, 0xb0, 0xe4, 0x6e, + 0x37, 0x28, 0x6a, 0x88, 0xb3, 0x89, 0xdc, 0x69, 0x85, 0x93, 0x07, 0x11, + 0x6d, 0x34, 0x7b, 0x98, 0xca, 0x14, 0x5c, 0x31, + ], + [ + 0xaa, 0x58, 0x1b, 0xae, 0xe9, 0x4f, 0xb5, 0x46, 0xa7, 0x61, 0xf1, 0x7a, + 0x5d, 0x6e, 0xaa, 0x70, 0x29, 0x52, 0x78, 0x42, 0xf3, 0x1c, 0x39, 0x87, + 0xb8, 0x68, 0xed, 0x7d, 0xaf, 0xfd, 0xb5, 0x34, + ], + [ + 0x7d, 0xc1, 0x17, 0xb3, 0x39, 0x1a, 0xab, 0x85, 0xde, 0x9f, 0x42, 0x4d, + 0xb6, 0x65, 0x1e, 0x00, 0x45, 0xab, 0x79, 0x98, 0xf2, 0x8e, 0x54, 0x10, + 0x15, 0x35, 0x90, 0x61, 0x99, 0xce, 0x1f, 0x1a, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0x86, 0x8c, 0x53, 0x23, 0x9c, 0xfb, 0xdf, 0x73, 0xca, 0xec, 0x65, 0x60, + 0x40, 0x37, 0x31, 0x4f, 0xaa, 0xce, 0xb5, 0x62, 0x18, 0xc6, 0xbd, 0x30, + 0xf8, 0x37, 0x4a, 0xc1, 0x33, 0x86, 0x79, 0x3f, + ], + [ + 0x21, 0xa9, 0xfb, 0x80, 0xad, 0x03, 0xbc, 0x0c, 0xda, 0x4a, 0x44, 0x94, + 0x6c, 0x00, 0xe1, 0xb1, 0xa1, 0xdf, 0x0e, 0x5b, 0x87, 0xb5, 0xbe, 0xce, + 0x47, 0x7a, 0x70, 0x96, 0x49, 0xe9, 0x50, 0x06, + ], + [ + 0x04, 0x91, 0x39, 0x48, 0x25, 0x64, 0xf1, 0x85, 0xc7, 0x90, 0x0e, 0x83, + 0xc7, 0x38, 0x07, 0x0a, 0xf6, 0x55, 0x6d, 0xf6, 0xed, 0x4b, 0x4d, 0xdd, + 0x3d, 0x9a, 0x69, 0xf5, 0x33, 0x57, 0xd7, 0x36, + ], + ], + final_state: [ + [ + 0x6a, 0x5a, 0x19, 0x19, 0xa4, 0x49, 0xa5, 0xe0, 0x29, 0x71, 0x1f, 0x48, + 0x8a, 0xdb, 0xd6, 0xb0, 0x3e, 0x5c, 0x92, 0x7b, 0x6f, 0x9d, 0x9d, 0x35, + 0xc5, 0xb3, 0xcc, 0xeb, 0x76, 0x60, 0x52, 0x03, + ], + [ + 0x80, 0x47, 0x5b, 0x46, 0x89, 0x59, 0x61, 0x47, 0xab, 0x2a, 0xdf, 0x01, + 0x73, 0xdb, 0x28, 0x9b, 0x3a, 0x26, 0xa1, 0x04, 0x84, 0x21, 0x73, 0xe8, + 0x8b, 0xdb, 0xfe, 0xc0, 0x4a, 0x28, 0x67, 0x1b, + ], + [ + 0x1e, 0xf3, 0xc8, 0xd0, 0xf5, 0x44, 0x44, 0xf5, 0x55, 0xb1, 0x5f, 0x7b, + 0xc9, 0xfa, 0x4f, 0xfa, 0x0f, 0x56, 0x7c, 0x0f, 0x19, 0xac, 0x7d, 0x0f, + 0xf9, 0x44, 0xfd, 0x36, 0x42, 0x6e, 0x32, 0x3a, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0x7d, 0x4f, 0x5c, 0xcb, 0x01, 0x64, 0x3c, 0x31, 0xdb, 0x84, 0x5e, 0xec, + 0xd5, 0xd6, 0x3d, 0xc1, 0x6a, 0x95, 0xe3, 0x02, 0x5b, 0x97, 0x92, 0xff, + 0xf7, 0xf2, 0x44, 0xfc, 0x71, 0x62, 0x69, 0x39, + ], + [ + 0x26, 0xd6, 0x2e, 0x95, 0x96, 0xfa, 0x82, 0x5c, 0x6b, 0xf2, 0x1a, 0xff, + 0x9e, 0x68, 0x62, 0x5a, 0x19, 0x24, 0x40, 0xea, 0x06, 0x82, 0x81, 0x23, + 0xd9, 0x78, 0x84, 0x80, 0x6f, 0x15, 0xfa, 0x08, + ], + [ + 0xd9, 0x52, 0x75, 0x4a, 0x23, 0x64, 0xb6, 0x66, 0xff, 0xc3, 0x0f, 0xdb, + 0x01, 0x47, 0x86, 0xda, 0x3a, 0x61, 0x28, 0xae, 0xf7, 0x84, 0xa6, 0x46, + 0x10, 0xa8, 0x9d, 0x1a, 0x70, 0x99, 0x21, 0x2d, + ], + ], + final_state: [ + [ + 0x1b, 0x4a, 0xc9, 0xbe, 0xf5, 0x6b, 0xdb, 0x6f, 0xb4, 0x2d, 0x3e, 0x3c, + 0xd3, 0xa2, 0xac, 0x70, 0xa4, 0xc4, 0x0c, 0x42, 0x5b, 0x0b, 0xd6, 0x67, + 0x9c, 0xa5, 0x7b, 0x30, 0x7e, 0xf1, 0xd4, 0x2f, + ], + [ + 0x1a, 0x2e, 0xf4, 0x11, 0x94, 0xaa, 0xa2, 0x34, 0x32, 0xe0, 0x86, 0xed, + 0x8a, 0xdb, 0xd1, 0xde, 0xec, 0x3c, 0x7c, 0xb3, 0x96, 0xde, 0x35, 0xba, + 0xe9, 0x5a, 0xaf, 0x5a, 0x08, 0xa0, 0xec, 0x36, + ], + [ + 0x68, 0xeb, 0x80, 0xc7, 0x3e, 0x2c, 0xcb, 0xde, 0xe1, 0xba, 0x71, 0x24, + 0x77, 0x61, 0xd5, 0xb5, 0xec, 0xc6, 0x20, 0xe6, 0xe4, 0x8e, 0x00, 0x3b, + 0x02, 0x3d, 0x9f, 0x55, 0x61, 0x66, 0x2f, 0x20, + ], + ], + }, + ] + } + + pub(crate) fn hash() -> Vec { + use HashTestVector as TestVector; + + // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_poseidon/hash/fp.py + vec![ + TestVector { + input: [ + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + [ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + ], + output: [ + 0x83, 0x58, 0xd7, 0x11, 0xa0, 0x32, 0x9d, 0x38, 0xbe, 0xcd, 0x54, 0xfb, 0xa7, + 0xc2, 0x83, 0xed, 0x3e, 0x08, 0x9a, 0x39, 0xc9, 0x1b, 0x6a, 0x9d, 0x10, 0xef, + 0xb0, 0x2b, 0xc3, 0xf1, 0x2f, 0x06, + ], + }, + TestVector { + input: [ + [ + 0x5c, 0x7a, 0x8f, 0x73, 0xad, 0xfc, 0x70, 0xfb, 0x3f, 0x13, 0x94, 0x49, + 0xac, 0x6b, 0x57, 0x07, 0x4c, 0x4d, 0x6e, 0x66, 0xb1, 0x64, 0x93, 0x9d, + 0xaf, 0xfa, 0x2e, 0xf6, 0xee, 0x69, 0x21, 0x08, + ], + [ + 0x1a, 0xdd, 0x86, 0xb3, 0xf2, 0xe1, 0xbd, 0xa6, 0x2a, 0x5d, 0x2e, 0x0e, + 0x98, 0x2b, 0x77, 0xe6, 0xb0, 0xef, 0x9c, 0xa3, 0xf2, 0x49, 0x88, 0xc7, + 0xb3, 0x53, 0x42, 0x01, 0xcf, 0xb1, 0xcd, 0x0d, + ], + ], + output: [ + 0xdb, 0x26, 0x75, 0xff, 0x3e, 0xf8, 0xfe, 0x30, 0xc4, 0xd5, 0xde, 0x61, 0xca, + 0xc0, 0x2a, 0x8e, 0xf1, 0xa0, 0x85, 0x23, 0xbe, 0x92, 0x39, 0x4b, 0x79, 0xd2, + 0x67, 0x26, 0x30, 0x3b, 0xe6, 0x03, + ], + }, + TestVector { + input: [ + [ + 0xbd, 0x69, 0xb8, 0x25, 0x32, 0xb6, 0x94, 0x0f, 0xf2, 0x59, 0x0f, 0x67, + 0x9b, 0xa9, 0xc7, 0x27, 0x1f, 0xe0, 0x1f, 0x7e, 0x9c, 0x8e, 0x36, 0xd6, + 0xa5, 0xe2, 0x9d, 0x4e, 0x30, 0xa7, 0x35, 0x14, + ], + [ + 0xbc, 0x50, 0x98, 0x42, 0x55, 0xd6, 0xaf, 0xbe, 0x9e, 0xf9, 0x28, 0x48, + 0xed, 0x5a, 0xc0, 0x08, 0x62, 0xc2, 0xfa, 0x7b, 0x2f, 0xec, 0xbc, 0xb6, + 0x4b, 0x69, 0x68, 0x91, 0x2a, 0x63, 0x81, 0x0e, + ], + ], + output: [ + 0xf5, 0x12, 0x1d, 0x1e, 0x1d, 0x5c, 0xfe, 0x8d, 0xa8, 0x96, 0xac, 0x0f, 0x9c, + 0x18, 0x3d, 0x76, 0x00, 0x31, 0xf6, 0xef, 0x8c, 0x7a, 0x41, 0xe6, 0x5e, 0xb0, + 0x07, 0xcd, 0xdc, 0x1d, 0x14, 0x3d, + ], + }, + TestVector { + input: [ + [ + 0x3d, 0xc1, 0x66, 0xd5, 0x6a, 0x1d, 0x62, 0xf5, 0xa8, 0xd7, 0x55, 0x1d, + 0xb5, 0xfd, 0x93, 0x13, 0xe8, 0xc7, 0x20, 0x3d, 0x99, 0x6a, 0xf7, 0xd4, + 0x77, 0x08, 0x37, 0x56, 0xd5, 0x9a, 0xf8, 0x0d, + ], + [ + 0x05, 0xa7, 0x45, 0xf4, 0x5d, 0x7f, 0xf6, 0xdb, 0x10, 0xbc, 0x67, 0xfd, + 0xf0, 0xf0, 0x3e, 0xbf, 0x81, 0x30, 0xab, 0x33, 0x36, 0x26, 0x97, 0xb0, + 0xe4, 0xe4, 0xc7, 0x63, 0xcc, 0xb8, 0xf6, 0x36, + ], + ], + output: [ + 0xa4, 0x16, 0xa5, 0xe7, 0x13, 0x51, 0x36, 0xa0, 0x50, 0x56, 0x90, 0x00, 0x58, + 0xfa, 0x50, 0xbf, 0x18, 0x6a, 0xd7, 0x33, 0x90, 0xac, 0xe6, 0x32, 0x3d, 0x8d, + 0x81, 0xaa, 0x8a, 0xdb, 0xd4, 0x11, + ], + }, + TestVector { + input: [ + [ + 0x49, 0x5c, 0x22, 0x2f, 0x7f, 0xba, 0x1e, 0x31, 0xde, 0xfa, 0x3d, 0x5a, + 0x57, 0xef, 0xc2, 0xe1, 0xe9, 0xb0, 0x1a, 0x03, 0x55, 0x87, 0xd5, 0xfb, + 0x1a, 0x38, 0xe0, 0x1d, 0x94, 0x90, 0x3d, 0x3c, + ], + [ + 0x3d, 0x0a, 0xd3, 0x36, 0x1f, 0xec, 0x09, 0x77, 0x90, 0xd9, 0xbe, 0x0e, + 0x42, 0x98, 0x8d, 0x7d, 0x25, 0xc9, 0xa1, 0x38, 0xf4, 0x9b, 0x1a, 0x53, + 0x7e, 0xdc, 0xf0, 0x4b, 0xe3, 0x4a, 0x98, 0x11, + ], + ], + output: [ + 0x1a, 0xba, 0xf3, 0x06, 0xfe, 0xd0, 0x5f, 0xa8, 0x92, 0x84, 0x8c, 0x49, 0xf6, + 0xba, 0x10, 0x41, 0x63, 0x43, 0x3f, 0x3f, 0x63, 0x31, 0x08, 0xa1, 0x3b, 0xc1, + 0x5b, 0x2a, 0x1d, 0x55, 0xd4, 0x0c, + ], + }, + TestVector { + input: [ + [ + 0xa4, 0xaf, 0x9d, 0xb6, 0xd2, 0x7b, 0x50, 0x72, 0x83, 0x5f, 0x0c, 0x3e, + 0x88, 0x39, 0x5e, 0xd7, 0xa4, 0x1b, 0x00, 0x52, 0xad, 0x80, 0x84, 0xa8, + 0xb9, 0xda, 0x94, 0x8d, 0x32, 0x0d, 0xad, 0x16, + ], + [ + 0x4d, 0x54, 0x31, 0xe6, 0x43, 0x7d, 0x0b, 0x5b, 0xed, 0xbb, 0xcd, 0xaf, + 0x34, 0x5b, 0x86, 0xc4, 0x12, 0x1f, 0xc0, 0x0f, 0xe7, 0xf2, 0x35, 0x73, + 0x42, 0x76, 0xd3, 0x8d, 0x47, 0xf1, 0xe1, 0x11, + ], + ], + output: [ + 0x04, 0xa1, 0x8a, 0xeb, 0x59, 0x3f, 0x79, 0x0b, 0x76, 0xa3, 0x99, 0xb7, 0xc1, + 0x52, 0x8a, 0xcd, 0xed, 0xe9, 0x3b, 0x3b, 0x2c, 0x49, 0x6b, 0xd7, 0x1b, 0xd5, + 0x87, 0xcb, 0xd7, 0xcf, 0xdf, 0x35, + ], + }, + TestVector { + input: [ + [ + 0xdd, 0x0c, 0x7a, 0x1d, 0x81, 0x1c, 0x7d, 0x9c, 0xd4, 0x6d, 0x37, 0x7b, + 0x3f, 0xde, 0xab, 0x3f, 0xb6, 0x79, 0xf3, 0xdc, 0x60, 0x1d, 0x00, 0x82, + 0x85, 0xed, 0xcb, 0xda, 0xe6, 0x9c, 0xe8, 0x3c, + ], + [ + 0x19, 0xe4, 0xaa, 0xc0, 0x35, 0x90, 0x17, 0xec, 0x85, 0xa1, 0x83, 0xd2, + 0x20, 0x53, 0xdb, 0x33, 0xf7, 0x34, 0x76, 0xf2, 0x1a, 0x48, 0x2e, 0xc9, + 0x37, 0x83, 0x65, 0xc8, 0xf7, 0x39, 0x3c, 0x14, + ], + ], + output: [ + 0x11, 0x03, 0xcc, 0xdc, 0x00, 0xd0, 0xf3, 0x5f, 0x65, 0x83, 0x14, 0x11, 0x6b, + 0xc2, 0xbc, 0xd9, 0x43, 0x74, 0xa9, 0x1f, 0xf9, 0x87, 0x7e, 0x70, 0x66, 0x33, + 0x29, 0x04, 0x2b, 0xd2, 0xf6, 0x1f, + ], + }, + TestVector { + input: [ + [ + 0xe2, 0x88, 0x53, 0x15, 0xeb, 0x46, 0x71, 0x09, 0x8b, 0x79, 0x53, 0x5e, + 0x79, 0x0f, 0xe5, 0x3e, 0x29, 0xfe, 0xf2, 0xb3, 0x76, 0x66, 0x97, 0xac, + 0x32, 0xb4, 0xf4, 0x73, 0xf4, 0x68, 0xa0, 0x08, + ], + [ + 0xe6, 0x23, 0x89, 0xfc, 0x16, 0x57, 0xe0, 0xde, 0xf0, 0xb6, 0x32, 0xc6, + 0xae, 0x25, 0xf9, 0xf7, 0x83, 0xb2, 0x7d, 0xb5, 0x9a, 0x4a, 0x15, 0x3d, + 0x88, 0x2d, 0x2b, 0x21, 0x03, 0x59, 0x65, 0x15, + ], + ], + output: [ + 0xf8, 0xf8, 0xc6, 0x5f, 0x43, 0x7c, 0x45, 0xbe, 0xac, 0x11, 0xeb, 0x7d, 0x9e, + 0x47, 0x58, 0x6d, 0x87, 0x9a, 0xfd, 0x6f, 0x93, 0x04, 0x35, 0xbe, 0x0c, 0x01, + 0xd1, 0x9c, 0x89, 0x5b, 0x8d, 0x10, + ], + }, + TestVector { + input: [ + [ + 0xeb, 0x94, 0x94, 0xc6, 0xd2, 0x27, 0xe2, 0x16, 0x3b, 0x46, 0x99, 0xd9, + 0x91, 0xf4, 0x33, 0xbf, 0x94, 0x86, 0xa7, 0xaf, 0xcf, 0x4a, 0x0d, 0x9c, + 0x73, 0x1e, 0x98, 0x5d, 0x99, 0x58, 0x9c, 0x0b, + ], + [ + 0xb7, 0x38, 0xe8, 0xaa, 0x0a, 0x15, 0x26, 0xa5, 0xbd, 0xef, 0x61, 0x31, + 0x20, 0x37, 0x2e, 0x83, 0x1a, 0x20, 0xda, 0x8a, 0xba, 0x18, 0xd1, 0xdb, + 0xeb, 0xbc, 0x86, 0x2d, 0xed, 0x42, 0x43, 0x1e, + ], + ], + output: [ + 0x5a, 0xeb, 0x48, 0x96, 0x21, 0xb0, 0x2e, 0x8e, 0x69, 0x27, 0xb9, 0x4f, 0xd2, + 0x9a, 0x61, 0x01, 0x83, 0xdf, 0x7f, 0x42, 0x87, 0xe9, 0xcb, 0xf1, 0xcc, 0xc8, + 0x81, 0xd7, 0xd0, 0xb7, 0x38, 0x27, + ], + }, + TestVector { + input: [ + [ + 0x91, 0x47, 0x69, 0x30, 0xe3, 0x38, 0x5c, 0xd3, 0xe3, 0x37, 0x9e, 0x38, + 0x53, 0xd9, 0x34, 0x67, 0xe0, 0x01, 0xaf, 0xa2, 0xfb, 0x8d, 0xc3, 0x43, + 0x6d, 0x75, 0xa4, 0xa6, 0xf2, 0x65, 0x72, 0x10, + ], + [ + 0x4b, 0x19, 0x22, 0x32, 0xec, 0xb9, 0xf0, 0xc0, 0x24, 0x11, 0xe5, 0x25, + 0x96, 0xbc, 0x5e, 0x90, 0x45, 0x7e, 0x74, 0x59, 0x39, 0xff, 0xed, 0xbd, + 0x12, 0x86, 0x3c, 0xe7, 0x1a, 0x02, 0xaf, 0x11, + ], + ], + output: [ + 0xb0, 0x14, 0x47, 0x20, 0xf5, 0xf2, 0xa2, 0x5d, 0x49, 0x2a, 0x50, 0x4e, 0xc0, + 0x73, 0x7f, 0x09, 0x7e, 0xd8, 0x52, 0x17, 0x4f, 0x55, 0xf5, 0x86, 0x30, 0x91, + 0x30, 0x6c, 0x1a, 0xf2, 0x00, 0x35, + ], + }, + TestVector { + input: [ + [ + 0x7b, 0x41, 0x7a, 0xdb, 0x63, 0xb3, 0x71, 0x22, 0xa5, 0xbf, 0x62, 0xd2, + 0x6f, 0x1e, 0x7f, 0x26, 0x8f, 0xb8, 0x6b, 0x12, 0xb5, 0x6d, 0xa9, 0xc3, + 0x82, 0x85, 0x7d, 0xee, 0xcc, 0x40, 0xa9, 0x0d, + ], + [ + 0x5e, 0x29, 0x35, 0x39, 0x71, 0xb3, 0x49, 0x94, 0xb6, 0x21, 0xb0, 0xb2, + 0x61, 0xae, 0xb3, 0x78, 0x6d, 0xd9, 0x84, 0xd5, 0x67, 0xdb, 0x28, 0x57, + 0xb9, 0x27, 0xb7, 0xfa, 0xe2, 0xdb, 0x58, 0x31, + ], + ], + output: [ + 0xbb, 0xbe, 0xb7, 0x42, 0xd6, 0xe7, 0xc0, 0x1a, 0xdb, 0xf4, 0xd3, 0x85, 0x5e, + 0x35, 0xfe, 0xc4, 0x62, 0x04, 0x30, 0x89, 0xc1, 0x8b, 0xa8, 0x02, 0x90, 0x64, + 0x7b, 0xb0, 0xe5, 0x81, 0xad, 0x11, + ], + }, + ] + } +} + +pub(crate) mod fq { + use super::*; + + pub(crate) fn permute() -> Vec { + use PermuteTestVector as TestVector; + + // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_poseidon/permute/fq.py + vec![ + TestVector { + initial_state: [ + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + [ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + [ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + ], + final_state: [ + [ + 0x59, 0xbe, 0xbe, 0x13, 0xa8, 0x8e, 0xb0, 0x0e, 0xc6, 0x36, 0xd3, 0x3d, + 0x97, 0xd1, 0x43, 0x4d, 0xf7, 0x2f, 0x8f, 0x2f, 0xf2, 0x74, 0xdd, 0xed, + 0x7c, 0x2f, 0x94, 0xdb, 0x4c, 0x1f, 0x5a, 0x31, + ], + [ + 0xf1, 0x65, 0xa1, 0x1e, 0xe6, 0x26, 0xf1, 0xf9, 0x98, 0x21, 0xbd, 0x7b, + 0xeb, 0xe0, 0x3e, 0x41, 0x48, 0xaa, 0x13, 0xdd, 0xe0, 0xde, 0x2a, 0x64, + 0xde, 0x2b, 0x64, 0xd7, 0xf2, 0x75, 0xe4, 0x3b, + ], + [ + 0xd9, 0xa0, 0x7b, 0x2a, 0x37, 0x42, 0xd5, 0x14, 0xa0, 0x3f, 0x42, 0xe0, + 0xd4, 0xbf, 0x19, 0x50, 0x60, 0xea, 0xd8, 0x20, 0x24, 0xdb, 0x7f, 0x11, + 0x68, 0x71, 0x53, 0xe9, 0xec, 0x8a, 0xab, 0x25, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0x5c, 0x7a, 0x8f, 0x73, 0x79, 0x42, 0x57, 0x08, 0x7e, 0x63, 0x4c, 0x49, + 0xac, 0x6b, 0x57, 0x07, 0x4c, 0x4d, 0x6e, 0x66, 0xb1, 0x64, 0x93, 0x9d, + 0xaf, 0xfa, 0x2e, 0xf6, 0xee, 0x69, 0x21, 0x08, + ], + [ + 0x1a, 0xdd, 0x86, 0xb3, 0x8a, 0x6d, 0x8a, 0xc0, 0xa6, 0xfd, 0x9e, 0x0d, + 0x98, 0x2b, 0x77, 0xe6, 0xb0, 0xef, 0x9c, 0xa3, 0xf2, 0x49, 0x88, 0xc7, + 0xb3, 0x53, 0x42, 0x01, 0xcf, 0xb1, 0xcd, 0x0d, + ], + [ + 0xbd, 0x69, 0xb8, 0x25, 0xca, 0x41, 0x61, 0x29, 0x6e, 0xfa, 0x7f, 0x66, + 0x9b, 0xa9, 0xc7, 0x27, 0x1f, 0xe0, 0x1f, 0x7e, 0x9c, 0x8e, 0x36, 0xd6, + 0xa5, 0xe2, 0x9d, 0x4e, 0x30, 0xa7, 0x35, 0x14, + ], + ], + final_state: [ + [ + 0xcd, 0x8f, 0x83, 0x92, 0xdf, 0xc7, 0x72, 0x8f, 0x5f, 0x6d, 0x85, 0x4c, + 0xc4, 0x60, 0x70, 0xa4, 0x0c, 0xba, 0x7a, 0x80, 0x33, 0x2d, 0xdc, 0x65, + 0xcb, 0xe2, 0x4a, 0xc3, 0xde, 0x23, 0x5e, 0x0e, + ], + [ + 0xc2, 0x53, 0xe5, 0x95, 0x3c, 0x83, 0xaa, 0x8a, 0x23, 0xd4, 0xd5, 0x58, + 0x7f, 0xbf, 0xc0, 0x7e, 0x78, 0x33, 0x1f, 0x7d, 0x46, 0xd1, 0xf5, 0xfa, + 0x54, 0x4d, 0x6a, 0xbd, 0xd4, 0x24, 0x1b, 0x27, + ], + [ + 0xb8, 0xc8, 0x33, 0x9b, 0xf9, 0x47, 0x2a, 0xd1, 0xc5, 0x27, 0xb7, 0x5e, + 0x99, 0x81, 0x2c, 0xa9, 0x1c, 0x5c, 0xbd, 0x7f, 0x4d, 0x46, 0x6f, 0x1a, + 0x13, 0x5a, 0x67, 0x50, 0x66, 0x76, 0x64, 0x34, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0xbc, 0x50, 0x98, 0x42, 0xb9, 0xa7, 0x62, 0xe5, 0x58, 0xea, 0x51, 0x47, + 0xed, 0x5a, 0xc0, 0x08, 0x62, 0xc2, 0xfa, 0x7b, 0x2f, 0xec, 0xbc, 0xb6, + 0x4b, 0x69, 0x68, 0x91, 0x2a, 0x63, 0x81, 0x0e, + ], + [ + 0x3d, 0xc1, 0x66, 0xd5, 0x6a, 0x1d, 0x62, 0xf5, 0xa8, 0xd7, 0x55, 0x1d, + 0xb5, 0xfd, 0x93, 0x13, 0xe8, 0xc7, 0x20, 0x3d, 0x99, 0x6a, 0xf7, 0xd4, + 0x77, 0x08, 0x37, 0x56, 0xd5, 0x9a, 0xf8, 0x0d, + ], + [ + 0x05, 0xa7, 0x45, 0xf4, 0x29, 0xc5, 0xdc, 0xe8, 0x4e, 0x0c, 0x20, 0xfd, + 0xf0, 0xf0, 0x3e, 0xbf, 0x81, 0x30, 0xab, 0x33, 0x36, 0x26, 0x97, 0xb0, + 0xe4, 0xe4, 0xc7, 0x63, 0xcc, 0xb8, 0xf6, 0x36, + ], + ], + final_state: [ + [ + 0xa3, 0x5b, 0x56, 0x64, 0x62, 0x4a, 0x78, 0x49, 0x9e, 0xce, 0xe0, 0xfa, + 0x05, 0x18, 0x79, 0xa2, 0xad, 0x1c, 0xa4, 0x53, 0x9b, 0x5b, 0xd2, 0xa4, + 0x67, 0xe2, 0xea, 0x8d, 0x4e, 0x2d, 0x40, 0x08, + ], + [ + 0x36, 0xc2, 0x21, 0x7a, 0xe5, 0x75, 0xaa, 0xf8, 0xf2, 0x54, 0xd6, 0xe0, + 0x60, 0x10, 0x1c, 0xdc, 0x85, 0xaa, 0x39, 0x3c, 0x09, 0x54, 0x3b, 0xf0, + 0x48, 0x99, 0x7a, 0x7c, 0x5c, 0xb9, 0x27, 0x02, + ], + [ + 0x38, 0x12, 0x7e, 0xbe, 0xaf, 0x11, 0xae, 0x56, 0x64, 0x47, 0x14, 0x05, + 0x29, 0x3b, 0x60, 0x1c, 0x43, 0xf0, 0x3e, 0x8e, 0x40, 0x78, 0x11, 0x3a, + 0x63, 0x37, 0x10, 0x11, 0x9f, 0x9a, 0x1b, 0x1f, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0x49, 0x5c, 0x22, 0x2f, 0x7f, 0xba, 0x1e, 0x31, 0xde, 0xfa, 0x3d, 0x5a, + 0x57, 0xef, 0xc2, 0xe1, 0xe9, 0xb0, 0x1a, 0x03, 0x55, 0x87, 0xd5, 0xfb, + 0x1a, 0x38, 0xe0, 0x1d, 0x94, 0x90, 0x3d, 0x3c, + ], + [ + 0x3d, 0x0a, 0xd3, 0x36, 0xeb, 0x31, 0xf0, 0x83, 0xce, 0x29, 0x77, 0x0e, + 0x42, 0x98, 0x8d, 0x7d, 0x25, 0xc9, 0xa1, 0x38, 0xf4, 0x9b, 0x1a, 0x53, + 0x7e, 0xdc, 0xf0, 0x4b, 0xe3, 0x4a, 0x98, 0x11, + ], + [ + 0xa4, 0xaf, 0x9d, 0xb6, 0x36, 0x4d, 0x03, 0x99, 0x3d, 0x50, 0x35, 0x3d, + 0x88, 0x39, 0x5e, 0xd7, 0xa4, 0x1b, 0x00, 0x52, 0xad, 0x80, 0x84, 0xa8, + 0xb9, 0xda, 0x94, 0x8d, 0x32, 0x0d, 0xad, 0x16, + ], + ], + final_state: [ + [ + 0x5c, 0x76, 0x63, 0x4f, 0xc7, 0x1a, 0x43, 0x7a, 0x3c, 0xc7, 0x89, 0x9d, + 0xb3, 0xb5, 0x1c, 0xea, 0xe6, 0x9a, 0xd0, 0x0b, 0x14, 0x96, 0xa6, 0x80, + 0x32, 0xd3, 0x83, 0x17, 0x37, 0x08, 0x79, 0x18, + ], + [ + 0xd3, 0xcc, 0x4e, 0xab, 0x45, 0x0a, 0xac, 0xc4, 0x5f, 0x9b, 0x32, 0x7e, + 0xbb, 0x9a, 0x50, 0xb8, 0x59, 0xca, 0x08, 0x0e, 0x10, 0x6b, 0x54, 0xc3, + 0x1c, 0x09, 0xc2, 0x1e, 0x1d, 0x79, 0xdf, 0x2a, + ], + [ + 0x09, 0x1e, 0x8f, 0x1e, 0xe9, 0xba, 0x00, 0xa3, 0xe3, 0xcf, 0x85, 0xd5, + 0xd6, 0x95, 0x3d, 0x25, 0xe0, 0x1e, 0x8b, 0xdb, 0x43, 0xde, 0x0f, 0xb7, + 0x30, 0x82, 0x4e, 0x6a, 0x8f, 0x69, 0x7e, 0x1c, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0x4d, 0x54, 0x31, 0xe6, 0xdb, 0x08, 0xd8, 0x74, 0x69, 0x5c, 0x3e, 0xaf, + 0x34, 0x5b, 0x86, 0xc4, 0x12, 0x1f, 0xc0, 0x0f, 0xe7, 0xf2, 0x35, 0x73, + 0x42, 0x76, 0xd3, 0x8d, 0x47, 0xf1, 0xe1, 0x11, + ], + [ + 0xdd, 0x0c, 0x7a, 0x1d, 0xe5, 0xed, 0x2f, 0xc3, 0x8e, 0x5e, 0x60, 0x7a, + 0x3f, 0xde, 0xab, 0x3f, 0xb6, 0x79, 0xf3, 0xdc, 0x60, 0x1d, 0x00, 0x82, + 0x85, 0xed, 0xcb, 0xda, 0xe6, 0x9c, 0xe8, 0x3c, + ], + [ + 0x19, 0xe4, 0xaa, 0xc0, 0xcd, 0x1b, 0xe4, 0x05, 0x02, 0x42, 0xf4, 0xd1, + 0x20, 0x53, 0xdb, 0x33, 0xf7, 0x34, 0x76, 0xf2, 0x1a, 0x48, 0x2e, 0xc9, + 0x37, 0x83, 0x65, 0xc8, 0xf7, 0x39, 0x3c, 0x14, + ], + ], + final_state: [ + [ + 0x00, 0xff, 0x3c, 0x20, 0xd5, 0xac, 0x28, 0x33, 0xe6, 0xd3, 0x84, 0x27, + 0xd0, 0x44, 0x06, 0x17, 0x9e, 0x31, 0xf3, 0xde, 0xd0, 0xe0, 0x33, 0xab, + 0x4f, 0x51, 0xfc, 0xb4, 0x28, 0xf8, 0x39, 0x1b, + ], + [ + 0x2a, 0x63, 0x7a, 0xa0, 0x4f, 0xb8, 0x0d, 0x9c, 0x50, 0xf3, 0x16, 0xb6, + 0x36, 0x7f, 0xa4, 0xf6, 0xed, 0x52, 0xd0, 0x7c, 0x99, 0xa1, 0x30, 0x29, + 0xd9, 0x3f, 0xae, 0xd3, 0xdd, 0x1e, 0xbc, 0x2f, + ], + [ + 0x12, 0x31, 0x54, 0xbb, 0x87, 0x60, 0x13, 0x94, 0x5f, 0x54, 0x69, 0x34, + 0x9d, 0x5f, 0xc3, 0xfc, 0xfc, 0xc9, 0xd2, 0xda, 0xb8, 0x06, 0x43, 0x0d, + 0x49, 0x69, 0x46, 0xf3, 0xbf, 0x2b, 0x61, 0x11, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0xe2, 0x88, 0x53, 0x15, 0xeb, 0x46, 0x71, 0x09, 0x8b, 0x79, 0x53, 0x5e, + 0x79, 0x0f, 0xe5, 0x3e, 0x29, 0xfe, 0xf2, 0xb3, 0x76, 0x66, 0x97, 0xac, + 0x32, 0xb4, 0xf4, 0x73, 0xf4, 0x68, 0xa0, 0x08, + ], + [ + 0xe6, 0x23, 0x89, 0xfc, 0xe2, 0x9c, 0xc6, 0xeb, 0x2e, 0x07, 0xeb, 0xc5, + 0xae, 0x25, 0xf9, 0xf7, 0x83, 0xb2, 0x7d, 0xb5, 0x9a, 0x4a, 0x15, 0x3d, + 0x88, 0x2d, 0x2b, 0x21, 0x03, 0x59, 0x65, 0x15, + ], + [ + 0xeb, 0x94, 0x94, 0xc6, 0x6a, 0xb3, 0xae, 0x30, 0xb7, 0xe6, 0x09, 0xd9, + 0x91, 0xf4, 0x33, 0xbf, 0x94, 0x86, 0xa7, 0xaf, 0xcf, 0x4a, 0x0d, 0x9c, + 0x73, 0x1e, 0x98, 0x5d, 0x99, 0x58, 0x9c, 0x0b, + ], + ], + final_state: [ + [ + 0xe7, 0x1e, 0xb4, 0x88, 0x51, 0xd7, 0x73, 0xb5, 0xa3, 0xb5, 0xd2, 0xb6, + 0xf6, 0xeb, 0x01, 0xc3, 0x79, 0x3f, 0x2f, 0xeb, 0xdf, 0xd1, 0xb9, 0x53, + 0xf0, 0x6f, 0xa9, 0x59, 0xc7, 0x26, 0xbc, 0x18, + ], + [ + 0x37, 0x71, 0xf8, 0x29, 0xfb, 0xf2, 0x74, 0x87, 0xf1, 0xdf, 0x2b, 0x5e, + 0xe9, 0x94, 0x97, 0x0b, 0x14, 0xd7, 0x13, 0xce, 0xae, 0x73, 0xa6, 0x33, + 0x95, 0x78, 0x4d, 0xcd, 0xf9, 0xaa, 0x30, 0x30, + ], + [ + 0x48, 0x06, 0xaf, 0xf7, 0x5e, 0xd3, 0xc6, 0xb9, 0x72, 0x1b, 0xc5, 0x23, + 0x0d, 0xd7, 0x76, 0xf9, 0x27, 0x44, 0x62, 0x90, 0x97, 0xcf, 0x5c, 0x2b, + 0x7f, 0x14, 0x2c, 0xf2, 0x74, 0xa5, 0x07, 0x37, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0xb7, 0x38, 0xe8, 0xaa, 0xd6, 0x5a, 0x0c, 0xb2, 0xfb, 0x3f, 0x1a, 0x31, + 0x20, 0x37, 0x2e, 0x83, 0x1a, 0x20, 0xda, 0x8a, 0xba, 0x18, 0xd1, 0xdb, + 0xeb, 0xbc, 0x86, 0x2d, 0xed, 0x42, 0x43, 0x1e, + ], + [ + 0x91, 0x47, 0x69, 0x30, 0xaf, 0x7e, 0x42, 0xe0, 0x21, 0x88, 0x56, 0x38, + 0x53, 0xd9, 0x34, 0x67, 0xe0, 0x01, 0xaf, 0xa2, 0xfb, 0x8d, 0xc3, 0x43, + 0x6d, 0x75, 0xa4, 0xa6, 0xf2, 0x65, 0x72, 0x10, + ], + [ + 0x4b, 0x19, 0x22, 0x32, 0xec, 0xb9, 0xf0, 0xc0, 0x24, 0x11, 0xe5, 0x25, + 0x96, 0xbc, 0x5e, 0x90, 0x45, 0x7e, 0x74, 0x59, 0x39, 0xff, 0xed, 0xbd, + 0x12, 0x86, 0x3c, 0xe7, 0x1a, 0x02, 0xaf, 0x11, + ], + ], + final_state: [ + [ + 0x9d, 0x58, 0x16, 0x08, 0x94, 0x8a, 0xd0, 0x15, 0xf5, 0x38, 0x82, 0xc0, + 0x2d, 0x22, 0x40, 0x2f, 0x71, 0xbd, 0x52, 0x7a, 0xb6, 0xb0, 0xbd, 0xab, + 0x5e, 0xaf, 0xef, 0x0c, 0xd9, 0x41, 0xe8, 0x33, + ], + [ + 0xf4, 0x69, 0xd9, 0x80, 0x6d, 0x0b, 0x9d, 0x92, 0x46, 0x6b, 0xbd, 0xe4, + 0x90, 0x4b, 0x88, 0x2d, 0x29, 0xcc, 0x45, 0x6d, 0xef, 0xa4, 0x77, 0x3f, + 0x5d, 0x9a, 0x92, 0x79, 0x6c, 0x60, 0xed, 0x1d, + ], + [ + 0x3c, 0xf1, 0xa7, 0x38, 0x35, 0xf9, 0x42, 0x5d, 0x46, 0x87, 0xa0, 0x9b, + 0xea, 0xcf, 0x48, 0x9a, 0xa6, 0x0e, 0xcb, 0xfc, 0xae, 0xa0, 0x61, 0xc9, + 0x7e, 0xd3, 0x72, 0x86, 0x1c, 0x08, 0x0a, 0x3d, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0x7b, 0x41, 0x7a, 0xdb, 0xfb, 0x3e, 0x3e, 0x3c, 0x21, 0x60, 0xd3, 0xd1, + 0x6f, 0x1e, 0x7f, 0x26, 0x8f, 0xb8, 0x6b, 0x12, 0xb5, 0x6d, 0xa9, 0xc3, + 0x82, 0x85, 0x7d, 0xee, 0xcc, 0x40, 0xa9, 0x0d, + ], + [ + 0x5e, 0x29, 0x35, 0x39, 0x3d, 0xf9, 0x2f, 0xa1, 0xf4, 0x71, 0x68, 0xb2, + 0x61, 0xae, 0xb3, 0x78, 0x6d, 0xd9, 0x84, 0xd5, 0x67, 0xdb, 0x28, 0x57, + 0xb9, 0x27, 0xb7, 0xfa, 0xe2, 0xdb, 0x58, 0x31, + ], + [ + 0x05, 0x41, 0x5d, 0x46, 0x42, 0x78, 0x9d, 0x38, 0xf5, 0x0b, 0x8d, 0xbc, + 0xc1, 0x29, 0xca, 0xb3, 0xd1, 0x7d, 0x19, 0xf3, 0x35, 0x5b, 0xcf, 0x73, + 0xce, 0xcb, 0x8c, 0xb8, 0xa5, 0xda, 0x01, 0x30, + ], + ], + final_state: [ + [ + 0x73, 0x2c, 0x01, 0x83, 0x41, 0x7f, 0xdf, 0x33, 0x43, 0xc1, 0xef, 0x69, + 0xfd, 0xf6, 0xb3, 0xe7, 0xfd, 0x52, 0x9e, 0xe8, 0x44, 0x63, 0x48, 0xf2, + 0x78, 0x50, 0x74, 0xaf, 0xe2, 0x97, 0xe5, 0x39, + ], + [ + 0xc0, 0x33, 0xd0, 0x1c, 0xb2, 0x29, 0x7f, 0x14, 0xdc, 0xcf, 0x8a, 0x37, + 0xc8, 0x90, 0x02, 0x09, 0x46, 0x5c, 0xc7, 0x41, 0x24, 0x50, 0xe0, 0xb0, + 0x82, 0x84, 0xf9, 0xaa, 0xa1, 0x18, 0xde, 0x34, + ], + [ + 0x17, 0xf9, 0xa6, 0x65, 0x38, 0x93, 0xea, 0x76, 0xbe, 0x60, 0x00, 0x20, + 0xb8, 0xff, 0xbf, 0xd9, 0x53, 0xae, 0x4a, 0x94, 0xad, 0x00, 0x10, 0x43, + 0x37, 0xb9, 0xf7, 0xde, 0x69, 0x88, 0x60, 0x31, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0x71, 0x52, 0xf1, 0x39, 0x36, 0xa2, 0x70, 0x57, 0x26, 0x70, 0xdc, 0x82, + 0xd3, 0x90, 0x26, 0xc6, 0xcb, 0x4c, 0xd4, 0xb0, 0xf7, 0xf5, 0xaa, 0x2a, + 0x4f, 0x5a, 0x53, 0x41, 0xec, 0x5d, 0xd7, 0x15, + ], + [ + 0x40, 0x6f, 0x2f, 0xdd, 0x2a, 0xfa, 0x73, 0x3f, 0x5f, 0x64, 0x1c, 0x8c, + 0x21, 0x86, 0x2a, 0x1b, 0xaf, 0xce, 0x26, 0x09, 0xd9, 0xee, 0xcf, 0xa1, + 0x58, 0xcf, 0xb5, 0xcd, 0x79, 0xf8, 0x80, 0x08, + ], + [ + 0xe2, 0x15, 0xdc, 0x7d, 0x62, 0x9d, 0xa0, 0xe0, 0x39, 0xd9, 0x68, 0x1e, + 0x99, 0x38, 0x44, 0x54, 0x36, 0x24, 0xc2, 0x5f, 0xa9, 0x59, 0xcc, 0x97, + 0x48, 0x9c, 0xe7, 0x57, 0x45, 0x82, 0x4b, 0x37, + ], + ], + final_state: [ + [ + 0x16, 0x0a, 0x24, 0x98, 0x48, 0x62, 0xea, 0xe0, 0xa3, 0x33, 0x50, 0x7b, + 0x36, 0x11, 0x93, 0x13, 0x71, 0xc6, 0x1d, 0x8e, 0x65, 0x71, 0x38, 0xcf, + 0xb2, 0xfa, 0x3b, 0x0f, 0x4d, 0xe5, 0xed, 0x3b, + ], + [ + 0xc5, 0xbd, 0x5a, 0x00, 0x44, 0xb6, 0xdb, 0xfe, 0x88, 0xb6, 0x97, 0xf7, + 0x1e, 0xa0, 0x55, 0xb4, 0xe2, 0x32, 0x42, 0x66, 0x6b, 0xf4, 0xe1, 0xb0, + 0x27, 0x52, 0xee, 0xce, 0x08, 0xfb, 0xe8, 0x05, + ], + [ + 0x30, 0x34, 0xdc, 0x8e, 0x8d, 0x4f, 0x6e, 0x33, 0x53, 0x83, 0xb9, 0x01, + 0x35, 0x8a, 0xe4, 0xb7, 0x5f, 0xcc, 0xc7, 0x22, 0x69, 0xdb, 0x83, 0x37, + 0x89, 0xce, 0xd4, 0xc0, 0xad, 0x83, 0x25, 0x1e, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0x86, 0x8c, 0x53, 0x23, 0x9c, 0xfb, 0xdf, 0x73, 0xca, 0xec, 0x65, 0x60, + 0x40, 0x37, 0x31, 0x4f, 0xaa, 0xce, 0xb5, 0x62, 0x18, 0xc6, 0xbd, 0x30, + 0xf8, 0x37, 0x4a, 0xc1, 0x33, 0x86, 0x79, 0x3f, + ], + [ + 0x21, 0xa9, 0xfb, 0x80, 0xad, 0x03, 0xbc, 0x0c, 0xda, 0x4a, 0x44, 0x94, + 0x6c, 0x00, 0xe1, 0xb1, 0xa1, 0xdf, 0x0e, 0x5b, 0x87, 0xb5, 0xbe, 0xce, + 0x47, 0x7a, 0x70, 0x96, 0x49, 0xe9, 0x50, 0x06, + ], + [ + 0x04, 0x91, 0x39, 0x48, 0xf1, 0xa9, 0xd7, 0x92, 0x05, 0xe1, 0xc6, 0x82, + 0xc7, 0x38, 0x07, 0x0a, 0xf6, 0x55, 0x6d, 0xf6, 0xed, 0x4b, 0x4d, 0xdd, + 0x3d, 0x9a, 0x69, 0xf5, 0x33, 0x57, 0xd7, 0x36, + ], + ], + final_state: [ + [ + 0x63, 0xe6, 0x3f, 0x14, 0xcc, 0x49, 0xec, 0x8f, 0x59, 0x93, 0x33, 0xae, + 0x04, 0x2c, 0xb4, 0x0c, 0x6f, 0xa8, 0x5f, 0x2d, 0x67, 0x64, 0xde, 0xad, + 0x13, 0x16, 0x44, 0x04, 0x97, 0x8b, 0x12, 0x03, + ], + [ + 0xc5, 0x61, 0xf3, 0x87, 0xb4, 0xaa, 0x32, 0x60, 0x09, 0x0f, 0x01, 0x73, + 0x88, 0x01, 0xb5, 0x34, 0xbe, 0x39, 0x6a, 0x13, 0xee, 0x11, 0x6b, 0x21, + 0xdb, 0x76, 0x10, 0x69, 0x59, 0x3d, 0xb6, 0x2f, + ], + [ + 0x10, 0x1a, 0x4b, 0xfd, 0x56, 0x89, 0xd5, 0x5a, 0xa7, 0x0e, 0xcf, 0x44, + 0x6d, 0xc3, 0x1a, 0x89, 0xbc, 0x62, 0xde, 0xb7, 0xed, 0x36, 0xf5, 0x49, + 0x19, 0x9c, 0xe1, 0x7b, 0xac, 0xe7, 0x32, 0x1b, + ], + ], + }, + TestVector { + initial_state: [ + [ + 0x7d, 0x4f, 0x5c, 0xcb, 0x99, 0xef, 0x08, 0x4b, 0x57, 0x25, 0xcf, 0xeb, + 0xd5, 0xd6, 0x3d, 0xc1, 0x6a, 0x95, 0xe3, 0x02, 0x5b, 0x97, 0x92, 0xff, + 0xf7, 0xf2, 0x44, 0xfc, 0x71, 0x62, 0x69, 0x39, + ], + [ + 0x26, 0xd6, 0x2e, 0x95, 0x96, 0xfa, 0x82, 0x5c, 0x6b, 0xf2, 0x1a, 0xff, + 0x9e, 0x68, 0x62, 0x5a, 0x19, 0x24, 0x40, 0xea, 0x06, 0x82, 0x81, 0x23, + 0xd9, 0x78, 0x84, 0x80, 0x6f, 0x15, 0xfa, 0x08, + ], + [ + 0xd9, 0x52, 0x75, 0x4a, 0xef, 0xa9, 0x9c, 0x73, 0x3d, 0x14, 0xc8, 0xda, + 0x01, 0x47, 0x86, 0xda, 0x3a, 0x61, 0x28, 0xae, 0xf7, 0x84, 0xa6, 0x46, + 0x10, 0xa8, 0x9d, 0x1a, 0x70, 0x99, 0x21, 0x2d, + ], + ], + final_state: [ + [ + 0x45, 0xed, 0x54, 0x17, 0x40, 0x7b, 0xfd, 0xb7, 0x97, 0xbc, 0xfe, 0x70, + 0x74, 0xdf, 0xf8, 0x0e, 0x32, 0xa5, 0x62, 0xed, 0x88, 0x73, 0x78, 0x1d, + 0xbc, 0xf4, 0xf6, 0x7e, 0x06, 0xbe, 0x0c, 0x23, + ], + [ + 0x13, 0x2f, 0x3f, 0x55, 0xd8, 0xfb, 0xfd, 0x46, 0x7b, 0x2a, 0xe2, 0x2b, + 0x8c, 0x64, 0x93, 0x43, 0x64, 0xcf, 0x9c, 0x4a, 0x0b, 0x07, 0xed, 0xb4, + 0x02, 0x87, 0xc3, 0x92, 0xc9, 0xc1, 0x45, 0x12, + ], + [ + 0xd0, 0x51, 0xc3, 0x7f, 0xf6, 0x4c, 0xad, 0xa2, 0xb4, 0x82, 0xf1, 0x1f, + 0x85, 0x64, 0x39, 0x6b, 0x75, 0xe3, 0xf8, 0x1b, 0x35, 0x52, 0xd8, 0x9a, + 0xf4, 0x92, 0xcf, 0x00, 0x52, 0x3c, 0x04, 0x15, + ], + ], + }, + ] + } + + pub(crate) fn hash() -> Vec { + use HashTestVector as TestVector; + + // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_poseidon/hash/fq.py + vec![ + TestVector { + input: [ + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + [ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + ], + output: [ + 0x4e, 0x68, 0xf6, 0x85, 0x70, 0x29, 0x57, 0xf3, 0xbf, 0x54, 0x6b, 0x7a, 0x09, + 0x01, 0x31, 0x4e, 0x51, 0x4f, 0x19, 0x5e, 0xe3, 0xb1, 0x64, 0x46, 0x22, 0x77, + 0x9d, 0x93, 0xdf, 0x96, 0xba, 0x15, + ], + }, + TestVector { + input: [ + [ + 0x5c, 0x7a, 0x8f, 0x73, 0x79, 0x42, 0x57, 0x08, 0x7e, 0x63, 0x4c, 0x49, + 0xac, 0x6b, 0x57, 0x07, 0x4c, 0x4d, 0x6e, 0x66, 0xb1, 0x64, 0x93, 0x9d, + 0xaf, 0xfa, 0x2e, 0xf6, 0xee, 0x69, 0x21, 0x08, + ], + [ + 0x1a, 0xdd, 0x86, 0xb3, 0x8a, 0x6d, 0x8a, 0xc0, 0xa6, 0xfd, 0x9e, 0x0d, + 0x98, 0x2b, 0x77, 0xe6, 0xb0, 0xef, 0x9c, 0xa3, 0xf2, 0x49, 0x88, 0xc7, + 0xb3, 0x53, 0x42, 0x01, 0xcf, 0xb1, 0xcd, 0x0d, + ], + ], + output: [ + 0x0c, 0x0a, 0xd9, 0x0a, 0x2e, 0x0c, 0xba, 0x0e, 0xe6, 0xa5, 0x5a, 0xc5, 0x38, + 0xa4, 0x35, 0xc9, 0x39, 0x04, 0x98, 0xae, 0xb5, 0x30, 0x3e, 0x3d, 0xad, 0x70, + 0x3a, 0xd1, 0xdc, 0xdb, 0x43, 0x2b, + ], + }, + TestVector { + input: [ + [ + 0xbd, 0x69, 0xb8, 0x25, 0xca, 0x41, 0x61, 0x29, 0x6e, 0xfa, 0x7f, 0x66, + 0x9b, 0xa9, 0xc7, 0x27, 0x1f, 0xe0, 0x1f, 0x7e, 0x9c, 0x8e, 0x36, 0xd6, + 0xa5, 0xe2, 0x9d, 0x4e, 0x30, 0xa7, 0x35, 0x14, + ], + [ + 0xbc, 0x50, 0x98, 0x42, 0xb9, 0xa7, 0x62, 0xe5, 0x58, 0xea, 0x51, 0x47, + 0xed, 0x5a, 0xc0, 0x08, 0x62, 0xc2, 0xfa, 0x7b, 0x2f, 0xec, 0xbc, 0xb6, + 0x4b, 0x69, 0x68, 0x91, 0x2a, 0x63, 0x81, 0x0e, + ], + ], + output: [ + 0xa6, 0x0c, 0xc8, 0xb8, 0x53, 0xaf, 0xce, 0xdb, 0xa1, 0x44, 0x65, 0xd5, 0x31, + 0xc7, 0x3c, 0xbc, 0xe1, 0x9e, 0x46, 0x0b, 0xa8, 0x04, 0x62, 0x2d, 0xf5, 0x21, + 0x23, 0x1d, 0xa1, 0x21, 0xc6, 0x08, + ], + }, + TestVector { + input: [ + [ + 0x3d, 0xc1, 0x66, 0xd5, 0x6a, 0x1d, 0x62, 0xf5, 0xa8, 0xd7, 0x55, 0x1d, + 0xb5, 0xfd, 0x93, 0x13, 0xe8, 0xc7, 0x20, 0x3d, 0x99, 0x6a, 0xf7, 0xd4, + 0x77, 0x08, 0x37, 0x56, 0xd5, 0x9a, 0xf8, 0x0d, + ], + [ + 0x05, 0xa7, 0x45, 0xf4, 0x29, 0xc5, 0xdc, 0xe8, 0x4e, 0x0c, 0x20, 0xfd, + 0xf0, 0xf0, 0x3e, 0xbf, 0x81, 0x30, 0xab, 0x33, 0x36, 0x26, 0x97, 0xb0, + 0xe4, 0xe4, 0xc7, 0x63, 0xcc, 0xb8, 0xf6, 0x36, + ], + ], + output: [ + 0xad, 0xa8, 0xca, 0xe4, 0x6a, 0x04, 0x2d, 0x00, 0xb0, 0x2e, 0x33, 0xc3, 0x6e, + 0x65, 0x8c, 0x22, 0x13, 0xfe, 0x81, 0x34, 0x53, 0x8b, 0x56, 0x03, 0x19, 0xe9, + 0x99, 0xf3, 0xf5, 0x82, 0x90, 0x00, + ], + }, + TestVector { + input: [ + [ + 0x49, 0x5c, 0x22, 0x2f, 0x7f, 0xba, 0x1e, 0x31, 0xde, 0xfa, 0x3d, 0x5a, + 0x57, 0xef, 0xc2, 0xe1, 0xe9, 0xb0, 0x1a, 0x03, 0x55, 0x87, 0xd5, 0xfb, + 0x1a, 0x38, 0xe0, 0x1d, 0x94, 0x90, 0x3d, 0x3c, + ], + [ + 0x3d, 0x0a, 0xd3, 0x36, 0xeb, 0x31, 0xf0, 0x83, 0xce, 0x29, 0x77, 0x0e, + 0x42, 0x98, 0x8d, 0x7d, 0x25, 0xc9, 0xa1, 0x38, 0xf4, 0x9b, 0x1a, 0x53, + 0x7e, 0xdc, 0xf0, 0x4b, 0xe3, 0x4a, 0x98, 0x11, + ], + ], + output: [ + 0x19, 0x94, 0x9f, 0xc7, 0x74, 0x04, 0x99, 0x37, 0x00, 0x58, 0x12, 0x7d, 0x04, + 0x0f, 0x11, 0x24, 0x5e, 0xba, 0x6c, 0x37, 0x80, 0xe9, 0x3e, 0x26, 0x16, 0xf4, + 0xc1, 0x77, 0x56, 0x30, 0x78, 0x2d, + ], + }, + TestVector { + input: [ + [ + 0xa4, 0xaf, 0x9d, 0xb6, 0x36, 0x4d, 0x03, 0x99, 0x3d, 0x50, 0x35, 0x3d, + 0x88, 0x39, 0x5e, 0xd7, 0xa4, 0x1b, 0x00, 0x52, 0xad, 0x80, 0x84, 0xa8, + 0xb9, 0xda, 0x94, 0x8d, 0x32, 0x0d, 0xad, 0x16, + ], + [ + 0x4d, 0x54, 0x31, 0xe6, 0xdb, 0x08, 0xd8, 0x74, 0x69, 0x5c, 0x3e, 0xaf, + 0x34, 0x5b, 0x86, 0xc4, 0x12, 0x1f, 0xc0, 0x0f, 0xe7, 0xf2, 0x35, 0x73, + 0x42, 0x76, 0xd3, 0x8d, 0x47, 0xf1, 0xe1, 0x11, + ], + ], + output: [ + 0x29, 0x6e, 0xba, 0xb4, 0xb4, 0x5a, 0xb9, 0x20, 0x97, 0xa7, 0xe6, 0xe7, 0xcc, + 0x6d, 0xd7, 0xd4, 0x7a, 0x12, 0x3e, 0x85, 0x50, 0xa3, 0x3d, 0xf1, 0x20, 0xcc, + 0xa5, 0x38, 0x90, 0x67, 0x1b, 0x21, + ], + }, + TestVector { + input: [ + [ + 0xdd, 0x0c, 0x7a, 0x1d, 0xe5, 0xed, 0x2f, 0xc3, 0x8e, 0x5e, 0x60, 0x7a, + 0x3f, 0xde, 0xab, 0x3f, 0xb6, 0x79, 0xf3, 0xdc, 0x60, 0x1d, 0x00, 0x82, + 0x85, 0xed, 0xcb, 0xda, 0xe6, 0x9c, 0xe8, 0x3c, + ], + [ + 0x19, 0xe4, 0xaa, 0xc0, 0xcd, 0x1b, 0xe4, 0x05, 0x02, 0x42, 0xf4, 0xd1, + 0x20, 0x53, 0xdb, 0x33, 0xf7, 0x34, 0x76, 0xf2, 0x1a, 0x48, 0x2e, 0xc9, + 0x37, 0x83, 0x65, 0xc8, 0xf7, 0x39, 0x3c, 0x14, + ], + ], + output: [ + 0xa8, 0x87, 0x6e, 0x8d, 0x2f, 0x30, 0x0a, 0x62, 0x05, 0x4b, 0x49, 0x4c, 0x8f, + 0x21, 0xc1, 0xd0, 0xad, 0xbd, 0xac, 0x89, 0xbf, 0x2a, 0xad, 0x9f, 0x3c, 0x1b, + 0x10, 0xc4, 0x78, 0x8c, 0x2d, 0x3d, + ], + }, + TestVector { + input: [ + [ + 0xe2, 0x88, 0x53, 0x15, 0xeb, 0x46, 0x71, 0x09, 0x8b, 0x79, 0x53, 0x5e, + 0x79, 0x0f, 0xe5, 0x3e, 0x29, 0xfe, 0xf2, 0xb3, 0x76, 0x66, 0x97, 0xac, + 0x32, 0xb4, 0xf4, 0x73, 0xf4, 0x68, 0xa0, 0x08, + ], + [ + 0xe6, 0x23, 0x89, 0xfc, 0xe2, 0x9c, 0xc6, 0xeb, 0x2e, 0x07, 0xeb, 0xc5, + 0xae, 0x25, 0xf9, 0xf7, 0x83, 0xb2, 0x7d, 0xb5, 0x9a, 0x4a, 0x15, 0x3d, + 0x88, 0x2d, 0x2b, 0x21, 0x03, 0x59, 0x65, 0x15, + ], + ], + output: [ + 0xc2, 0xda, 0xcb, 0x1e, 0xea, 0xed, 0x88, 0x0b, 0x87, 0xd0, 0x4d, 0xd9, 0x61, + 0x95, 0x73, 0x0e, 0x98, 0xbd, 0x0f, 0x14, 0x77, 0x7b, 0x3e, 0xf0, 0xda, 0x40, + 0xe4, 0xc0, 0x87, 0xb1, 0x9d, 0x28, + ], + }, + TestVector { + input: [ + [ + 0xeb, 0x94, 0x94, 0xc6, 0x6a, 0xb3, 0xae, 0x30, 0xb7, 0xe6, 0x09, 0xd9, + 0x91, 0xf4, 0x33, 0xbf, 0x94, 0x86, 0xa7, 0xaf, 0xcf, 0x4a, 0x0d, 0x9c, + 0x73, 0x1e, 0x98, 0x5d, 0x99, 0x58, 0x9c, 0x0b, + ], + [ + 0xb7, 0x38, 0xe8, 0xaa, 0xd6, 0x5a, 0x0c, 0xb2, 0xfb, 0x3f, 0x1a, 0x31, + 0x20, 0x37, 0x2e, 0x83, 0x1a, 0x20, 0xda, 0x8a, 0xba, 0x18, 0xd1, 0xdb, + 0xeb, 0xbc, 0x86, 0x2d, 0xed, 0x42, 0x43, 0x1e, + ], + ], + output: [ + 0x5a, 0x55, 0xe3, 0x08, 0x2e, 0x55, 0xa5, 0x66, 0xb9, 0xca, 0xb1, 0xca, 0xf4, + 0x48, 0xf7, 0x0f, 0x8c, 0x9a, 0x53, 0xa1, 0xc9, 0xf6, 0x9e, 0x2a, 0x80, 0xdd, + 0xb8, 0x58, 0x3f, 0x99, 0x01, 0x26, + ], + }, + TestVector { + input: [ + [ + 0x91, 0x47, 0x69, 0x30, 0xaf, 0x7e, 0x42, 0xe0, 0x21, 0x88, 0x56, 0x38, + 0x53, 0xd9, 0x34, 0x67, 0xe0, 0x01, 0xaf, 0xa2, 0xfb, 0x8d, 0xc3, 0x43, + 0x6d, 0x75, 0xa4, 0xa6, 0xf2, 0x65, 0x72, 0x10, + ], + [ + 0x4b, 0x19, 0x22, 0x32, 0xec, 0xb9, 0xf0, 0xc0, 0x24, 0x11, 0xe5, 0x25, + 0x96, 0xbc, 0x5e, 0x90, 0x45, 0x7e, 0x74, 0x59, 0x39, 0xff, 0xed, 0xbd, + 0x12, 0x86, 0x3c, 0xe7, 0x1a, 0x02, 0xaf, 0x11, + ], + ], + output: [ + 0xca, 0xc6, 0x68, 0x8a, 0x3d, 0x2a, 0x7d, 0xca, 0xe1, 0xd4, 0x60, 0x1f, 0x9b, + 0xf0, 0x6d, 0x58, 0x00, 0x8f, 0x24, 0x85, 0x6a, 0xe6, 0x00, 0xf0, 0xe0, 0x90, + 0x07, 0x23, 0xaf, 0xa1, 0x20, 0x03, + ], + }, + TestVector { + input: [ + [ + 0x7b, 0x41, 0x7a, 0xdb, 0xfb, 0x3e, 0x3e, 0x3c, 0x21, 0x60, 0xd3, 0xd1, + 0x6f, 0x1e, 0x7f, 0x26, 0x8f, 0xb8, 0x6b, 0x12, 0xb5, 0x6d, 0xa9, 0xc3, + 0x82, 0x85, 0x7d, 0xee, 0xcc, 0x40, 0xa9, 0x0d, + ], + [ + 0x5e, 0x29, 0x35, 0x39, 0x3d, 0xf9, 0x2f, 0xa1, 0xf4, 0x71, 0x68, 0xb2, + 0x61, 0xae, 0xb3, 0x78, 0x6d, 0xd9, 0x84, 0xd5, 0x67, 0xdb, 0x28, 0x57, + 0xb9, 0x27, 0xb7, 0xfa, 0xe2, 0xdb, 0x58, 0x31, + ], + ], + output: [ + 0xd7, 0xe7, 0x83, 0x91, 0x97, 0x83, 0xb0, 0x8b, 0x5f, 0xad, 0x08, 0x9d, 0x57, + 0x1e, 0xc1, 0x8f, 0xb4, 0x63, 0x28, 0x53, 0x99, 0x3f, 0x35, 0xe3, 0xee, 0x54, + 0x3d, 0x4e, 0xed, 0xf6, 0x5f, 0x38, + ], + }, + ] + } +} diff --git a/halo2_gadgets_optimized/src/sha256.rs b/halo2_gadgets_optimized/src/sha256.rs new file mode 100644 index 0000000000..3e4cdafa1a --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256.rs @@ -0,0 +1,166 @@ +//! The [SHA-256] hash function. +//! +//! [SHA-256]: https://tools.ietf.org/html/rfc6234 + +use std::cmp::min; +use std::convert::TryInto; +use std::fmt; + +use group::ff::Field; +use halo2_proofs::{ + circuit::{Chip, Layouter}, + plonk::Error, +}; + +mod table16; + +pub use table16::{BlockWord, Table16Chip, Table16Config}; + +/// The size of a SHA-256 block, in 32-bit words. +pub const BLOCK_SIZE: usize = 16; +/// The size of a SHA-256 digest, in 32-bit words. +const DIGEST_SIZE: usize = 8; + +/// The set of circuit instructions required to use the [`Sha256`] gadget. +pub trait Sha256Instructions: Chip { + /// Variable representing the SHA-256 internal state. + type State: Clone + fmt::Debug; + /// Variable representing a 32-bit word of the input block to the SHA-256 compression + /// function. + type BlockWord: Copy + fmt::Debug + Default; + + /// Places the SHA-256 IV in the circuit, returning the initial state variable. + fn initialization_vector(&self, layouter: &mut impl Layouter) -> Result; + + /// Creates an initial state from the output state of a previous block + fn initialization( + &self, + layouter: &mut impl Layouter, + init_state: &Self::State, + ) -> Result; + + /// Starting from the given initialized state, processes a block of input and returns the + /// final state. + fn compress( + &self, + layouter: &mut impl Layouter, + initialized_state: &Self::State, + input: [Self::BlockWord; BLOCK_SIZE], + ) -> Result; + + /// Converts the given state into a message digest. + fn digest( + &self, + layouter: &mut impl Layouter, + state: &Self::State, + ) -> Result<[Self::BlockWord; DIGEST_SIZE], Error>; +} + +/// The output of a SHA-256 circuit invocation. +#[derive(Debug)] +pub struct Sha256Digest([BlockWord; DIGEST_SIZE]); + +/// A gadget that constrains a SHA-256 invocation. It supports input at a granularity of +/// 32 bits. +#[derive(Debug)] +pub struct Sha256> { + chip: CS, + state: CS::State, + cur_block: Vec, + length: usize, +} + +impl> Sha256 { + /// Create a new hasher instance. + pub fn new(chip: Sha256Chip, mut layouter: impl Layouter) -> Result { + let state = chip.initialization_vector(&mut layouter)?; + Ok(Sha256 { + chip, + state, + cur_block: Vec::with_capacity(BLOCK_SIZE), + length: 0, + }) + } + + /// Digest data, updating the internal state. + pub fn update( + &mut self, + mut layouter: impl Layouter, + mut data: &[Sha256Chip::BlockWord], + ) -> Result<(), Error> { + self.length += data.len() * 32; + + // Fill the current block, if possible. + let remaining = BLOCK_SIZE - self.cur_block.len(); + let (l, r) = data.split_at(min(remaining, data.len())); + self.cur_block.extend_from_slice(l); + data = r; + + // If we still don't have a full block, we are done. + if self.cur_block.len() < BLOCK_SIZE { + return Ok(()); + } + + // Process the now-full current block. + self.state = self.chip.compress( + &mut layouter, + &self.state, + self.cur_block[..] + .try_into() + .expect("cur_block.len() == BLOCK_SIZE"), + )?; + self.cur_block.clear(); + + // Process any additional full blocks. + let mut chunks_iter = data.chunks_exact(BLOCK_SIZE); + for chunk in &mut chunks_iter { + self.state = self.chip.initialization(&mut layouter, &self.state)?; + self.state = self.chip.compress( + &mut layouter, + &self.state, + chunk.try_into().expect("chunk.len() == BLOCK_SIZE"), + )?; + } + + // Cache the remaining partial block, if any. + let rem = chunks_iter.remainder(); + self.cur_block.extend_from_slice(rem); + + Ok(()) + } + + /// Retrieve result and consume hasher instance. + pub fn finalize( + mut self, + mut layouter: impl Layouter, + ) -> Result, Error> { + // Pad the remaining block + if !self.cur_block.is_empty() { + let padding = vec![Sha256Chip::BlockWord::default(); BLOCK_SIZE - self.cur_block.len()]; + self.cur_block.extend_from_slice(&padding); + self.state = self.chip.initialization(&mut layouter, &self.state)?; + self.state = self.chip.compress( + &mut layouter, + &self.state, + self.cur_block[..] + .try_into() + .expect("cur_block.len() == BLOCK_SIZE"), + )?; + } + self.chip + .digest(&mut layouter, &self.state) + .map(Sha256Digest) + } + + /// Convenience function to compute hash of the data. It will handle hasher creation, + /// data feeding and finalization. + pub fn digest( + chip: Sha256Chip, + mut layouter: impl Layouter, + data: &[Sha256Chip::BlockWord], + ) -> Result, Error> { + let mut hasher = Self::new(chip, layouter.namespace(|| "init"))?; + hasher.update(layouter.namespace(|| "update"), data)?; + hasher.finalize(layouter.namespace(|| "finalize")) + } +} diff --git a/halo2_gadgets_optimized/src/sha256/table16.rs b/halo2_gadgets_optimized/src/sha256/table16.rs new file mode 100644 index 0000000000..9e56c78a4d --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16.rs @@ -0,0 +1,515 @@ +use std::convert::TryInto; +use std::marker::PhantomData; + +use super::Sha256Instructions; +use halo2_proofs::{ + circuit::{AssignedCell, Chip, Layouter, Region, Value}, + pasta::pallas, + plonk::{Advice, Any, Assigned, Column, ConstraintSystem, Error}, +}; + +mod compression; +mod gates; +mod message_schedule; +mod spread_table; +mod util; + +use compression::*; +use gates::*; +use message_schedule::*; +use spread_table::*; +use util::*; + +const ROUNDS: usize = 64; +const STATE: usize = 8; + +#[allow(clippy::unreadable_literal)] +pub(crate) const ROUND_CONSTANTS: [u32; ROUNDS] = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, +]; + +const IV: [u32; STATE] = [ + 0x6a09_e667, + 0xbb67_ae85, + 0x3c6e_f372, + 0xa54f_f53a, + 0x510e_527f, + 0x9b05_688c, + 0x1f83_d9ab, + 0x5be0_cd19, +]; + +#[derive(Clone, Copy, Debug, Default)] +/// A word in a `Table16` message block. +// TODO: Make the internals of this struct private. +pub struct BlockWord(pub Value); + +#[derive(Clone, Debug)] +/// Little-endian bits (up to 64 bits) +pub struct Bits([bool; LEN]); + +impl Bits { + fn spread(&self) -> [bool; SPREAD] { + spread_bits(self.0) + } +} + +impl std::ops::Deref for Bits { + type Target = [bool; LEN]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From<[bool; LEN]> for Bits { + fn from(bits: [bool; LEN]) -> Self { + Self(bits) + } +} + +impl From<&Bits> for [bool; LEN] { + fn from(bits: &Bits) -> Self { + bits.0 + } +} + +impl From<&Bits> for Assigned { + fn from(bits: &Bits) -> Assigned { + assert!(LEN <= 64); + pallas::Base::from(lebs2ip(&bits.0)).into() + } +} + +impl From<&Bits<16>> for u16 { + fn from(bits: &Bits<16>) -> u16 { + lebs2ip(&bits.0) as u16 + } +} + +impl From for Bits<16> { + fn from(int: u16) -> Bits<16> { + Bits(i2lebsp::<16>(int.into())) + } +} + +impl From<&Bits<32>> for u32 { + fn from(bits: &Bits<32>) -> u32 { + lebs2ip(&bits.0) as u32 + } +} + +impl From for Bits<32> { + fn from(int: u32) -> Bits<32> { + Bits(i2lebsp::<32>(int.into())) + } +} + +#[derive(Clone, Debug)] +pub struct AssignedBits(AssignedCell, pallas::Base>); + +impl std::ops::Deref for AssignedBits { + type Target = AssignedCell, pallas::Base>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AssignedBits { + fn assign_bits + std::fmt::Debug + Clone>( + region: &mut Region<'_, pallas::Base>, + annotation: A, + column: impl Into>, + offset: usize, + value: Value, + ) -> Result + where + A: Fn() -> AR, + AR: Into, + >::Error: std::fmt::Debug, + { + let value: Value<[bool; LEN]> = value.map(|v| v.try_into().unwrap()); + let value: Value> = value.map(|v| v.into()); + + let column: Column = column.into(); + match column.column_type() { + Any::Advice => { + region.assign_advice(annotation, column.try_into().unwrap(), offset, || { + value.clone() + }) + } + Any::Fixed => { + region.assign_fixed(annotation, column.try_into().unwrap(), offset, || { + value.clone() + }) + } + _ => panic!("Cannot assign to instance column"), + } + .map(AssignedBits) + } +} + +impl AssignedBits<16> { + fn value_u16(&self) -> Value { + self.value().map(|v| v.into()) + } + + fn assign( + region: &mut Region<'_, pallas::Base>, + annotation: A, + column: impl Into>, + offset: usize, + value: Value, + ) -> Result + where + A: Fn() -> AR, + AR: Into, + { + let column: Column = column.into(); + let value: Value> = value.map(|v| v.into()); + match column.column_type() { + Any::Advice => { + region.assign_advice(annotation, column.try_into().unwrap(), offset, || { + value.clone() + }) + } + Any::Fixed => { + region.assign_fixed(annotation, column.try_into().unwrap(), offset, || { + value.clone() + }) + } + _ => panic!("Cannot assign to instance column"), + } + .map(AssignedBits) + } +} + +impl AssignedBits<32> { + fn value_u32(&self) -> Value { + self.value().map(|v| v.into()) + } + + fn assign( + region: &mut Region<'_, pallas::Base>, + annotation: A, + column: impl Into>, + offset: usize, + value: Value, + ) -> Result + where + A: Fn() -> AR, + AR: Into, + { + let column: Column = column.into(); + let value: Value> = value.map(|v| v.into()); + match column.column_type() { + Any::Advice => { + region.assign_advice(annotation, column.try_into().unwrap(), offset, || { + value.clone() + }) + } + Any::Fixed => { + region.assign_fixed(annotation, column.try_into().unwrap(), offset, || { + value.clone() + }) + } + _ => panic!("Cannot assign to instance column"), + } + .map(AssignedBits) + } +} + +/// Configuration for a [`Table16Chip`]. +#[derive(Clone, Debug)] +pub struct Table16Config { + lookup: SpreadTableConfig, + message_schedule: MessageScheduleConfig, + compression: CompressionConfig, +} + +/// A chip that implements SHA-256 with a maximum lookup table size of $2^16$. +#[derive(Clone, Debug)] +pub struct Table16Chip { + config: Table16Config, + _marker: PhantomData, +} + +impl Chip for Table16Chip { + type Config = Table16Config; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} + +impl Table16Chip { + /// Reconstructs this chip from the given config. + pub fn construct(config: >::Config) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + /// Configures a circuit to include this chip. + pub fn configure( + meta: &mut ConstraintSystem, + ) -> >::Config { + // Columns required by this chip: + let message_schedule = meta.advice_column(); + let extras = [ + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + ]; + + // - Three advice columns to interact with the lookup table. + let input_tag = meta.advice_column(); + let input_dense = meta.advice_column(); + let input_spread = meta.advice_column(); + + let lookup = SpreadTableChip::configure(meta, input_tag, input_dense, input_spread); + let lookup_inputs = lookup.input.clone(); + + // Rename these here for ease of matching the gates to the specification. + let _a_0 = lookup_inputs.tag; + let a_1 = lookup_inputs.dense; + let a_2 = lookup_inputs.spread; + let a_3 = extras[0]; + let a_4 = extras[1]; + let a_5 = message_schedule; + let a_6 = extras[2]; + let a_7 = extras[3]; + let a_8 = extras[4]; + let _a_9 = extras[5]; + + // Add all advice columns to permutation + for column in [a_1, a_2, a_3, a_4, a_5, a_6, a_7, a_8].iter() { + meta.enable_equality(*column); + } + + let compression = + CompressionConfig::configure(meta, lookup_inputs.clone(), message_schedule, extras); + + let message_schedule = + MessageScheduleConfig::configure(meta, lookup_inputs, message_schedule, extras); + + Table16Config { + lookup, + message_schedule, + compression, + } + } + + /// Loads the lookup table required by this chip into the circuit. + pub fn load( + config: Table16Config, + layouter: &mut impl Layouter, + ) -> Result<(), Error> { + SpreadTableChip::load(config.lookup, layouter) + } +} + +impl Sha256Instructions for Table16Chip { + type State = State; + type BlockWord = BlockWord; + + fn initialization_vector( + &self, + layouter: &mut impl Layouter, + ) -> Result { + self.config().compression.initialize_with_iv(layouter, IV) + } + + fn initialization( + &self, + layouter: &mut impl Layouter, + init_state: &Self::State, + ) -> Result { + self.config() + .compression + .initialize_with_state(layouter, init_state.clone()) + } + + // Given an initialized state and an input message block, compress the + // message block and return the final state. + fn compress( + &self, + layouter: &mut impl Layouter, + initialized_state: &Self::State, + input: [Self::BlockWord; super::BLOCK_SIZE], + ) -> Result { + let config = self.config(); + let (_, w_halves) = config.message_schedule.process(layouter, input)?; + config + .compression + .compress(layouter, initialized_state.clone(), w_halves) + } + + fn digest( + &self, + layouter: &mut impl Layouter, + state: &Self::State, + ) -> Result<[Self::BlockWord; super::DIGEST_SIZE], Error> { + // Copy the dense forms of the state variable chunks down to this gate. + // Reconstruct the 32-bit dense words. + self.config().compression.digest(layouter, state.clone()) + } +} + +/// Common assignment patterns used by Table16 regions. +trait Table16Assignment { + /// Assign cells for general spread computation used in sigma, ch, ch_neg, maj gates + #[allow(clippy::too_many_arguments)] + #[allow(clippy::type_complexity)] + fn assign_spread_outputs( + &self, + region: &mut Region<'_, pallas::Base>, + lookup: &SpreadInputs, + a_3: Column, + row: usize, + r_0_even: Value<[bool; 16]>, + r_0_odd: Value<[bool; 16]>, + r_1_even: Value<[bool; 16]>, + r_1_odd: Value<[bool; 16]>, + ) -> Result< + ( + (AssignedBits<16>, AssignedBits<16>), + (AssignedBits<16>, AssignedBits<16>), + ), + Error, + > { + // Lookup R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} + let r_0_even = SpreadVar::with_lookup( + region, + lookup, + row - 1, + r_0_even.map(SpreadWord::<16, 32>::new), + )?; + let r_0_odd = + SpreadVar::with_lookup(region, lookup, row, r_0_odd.map(SpreadWord::<16, 32>::new))?; + let r_1_even = SpreadVar::with_lookup( + region, + lookup, + row + 1, + r_1_even.map(SpreadWord::<16, 32>::new), + )?; + let r_1_odd = SpreadVar::with_lookup( + region, + lookup, + row + 2, + r_1_odd.map(SpreadWord::<16, 32>::new), + )?; + + // Assign and copy R_1^{odd} + r_1_odd + .spread + .copy_advice(|| "Assign and copy R_1^{odd}", region, a_3, row)?; + + Ok(( + (r_0_even.dense, r_1_even.dense), + (r_0_odd.dense, r_1_odd.dense), + )) + } + + /// Assign outputs of sigma gates + #[allow(clippy::too_many_arguments)] + fn assign_sigma_outputs( + &self, + region: &mut Region<'_, pallas::Base>, + lookup: &SpreadInputs, + a_3: Column, + row: usize, + r_0_even: Value<[bool; 16]>, + r_0_odd: Value<[bool; 16]>, + r_1_even: Value<[bool; 16]>, + r_1_odd: Value<[bool; 16]>, + ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { + let (even, _odd) = self.assign_spread_outputs( + region, lookup, a_3, row, r_0_even, r_0_odd, r_1_even, r_1_odd, + )?; + + Ok(even) + } +} + +#[cfg(test)] +#[cfg(feature = "test-dev-graph")] +mod tests { + use super::super::{Sha256, BLOCK_SIZE}; + use super::{message_schedule::msg_schedule_test_input, Table16Chip, Table16Config}; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + pasta::pallas, + plonk::{Circuit, ConstraintSystem, Error}, + }; + + #[test] + fn print_sha256_circuit() { + use plotters::prelude::*; + struct MyCircuit {} + + impl Circuit for MyCircuit { + type Config = Table16Config; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + MyCircuit {} + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + Table16Chip::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let table16_chip = Table16Chip::construct(config.clone()); + Table16Chip::load(config, &mut layouter)?; + + // Test vector: "abc" + let test_input = msg_schedule_test_input(); + + // Create a message of length 31 blocks + let mut input = Vec::with_capacity(31 * BLOCK_SIZE); + for _ in 0..31 { + input.extend_from_slice(&test_input); + } + + Sha256::digest(table16_chip, layouter.namespace(|| "'abc' * 31"), &input)?; + + Ok(()) + } + } + + let root = + BitMapBackend::new("sha-256-table16-chip-layout.png", (1024, 3480)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root + .titled("16-bit Table SHA-256 Chip", ("sans-serif", 60)) + .unwrap(); + + let circuit = MyCircuit {}; + halo2_proofs::dev::CircuitLayout::default() + .render::(17, &circuit, &root) + .unwrap(); + } +} diff --git a/halo2_gadgets_optimized/src/sha256/table16/compression.rs b/halo2_gadgets_optimized/src/sha256/table16/compression.rs new file mode 100644 index 0000000000..528b1d9a98 --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16/compression.rs @@ -0,0 +1,1005 @@ +use super::{ + super::DIGEST_SIZE, + util::{i2lebsp, lebs2ip}, + AssignedBits, BlockWord, SpreadInputs, SpreadVar, Table16Assignment, ROUNDS, STATE, +}; +use halo2_proofs::{ + circuit::{Layouter, Value}, + pasta::pallas, + plonk::{Advice, Column, ConstraintSystem, Error, Selector}, + poly::Rotation, +}; +use std::convert::TryInto; +use std::ops::Range; + +mod compression_gates; +mod compression_util; +mod subregion_digest; +mod subregion_initial; +mod subregion_main; + +use compression_gates::CompressionGate; + +pub trait UpperSigmaVar< + const A_LEN: usize, + const B_LEN: usize, + const C_LEN: usize, + const D_LEN: usize, +> +{ + fn spread_a(&self) -> Value<[bool; A_LEN]>; + fn spread_b(&self) -> Value<[bool; B_LEN]>; + fn spread_c(&self) -> Value<[bool; C_LEN]>; + fn spread_d(&self) -> Value<[bool; D_LEN]>; + + fn xor_upper_sigma(&self) -> Value<[bool; 64]> { + self.spread_a() + .zip(self.spread_b()) + .zip(self.spread_c()) + .zip(self.spread_d()) + .map(|(((a, b), c), d)| { + let xor_0 = b + .iter() + .chain(c.iter()) + .chain(d.iter()) + .chain(a.iter()) + .copied() + .collect::>(); + let xor_1 = c + .iter() + .chain(d.iter()) + .chain(a.iter()) + .chain(b.iter()) + .copied() + .collect::>(); + let xor_2 = d + .iter() + .chain(a.iter()) + .chain(b.iter()) + .chain(c.iter()) + .copied() + .collect::>(); + + let xor_0 = lebs2ip::<64>(&xor_0.try_into().unwrap()); + let xor_1 = lebs2ip::<64>(&xor_1.try_into().unwrap()); + let xor_2 = lebs2ip::<64>(&xor_2.try_into().unwrap()); + + i2lebsp(xor_0 + xor_1 + xor_2) + }) + } +} + +/// A variable that represents the `[A,B,C,D]` words of the SHA-256 internal state. +/// +/// The structure of this variable is influenced by the following factors: +/// - In `Σ_0(A)` we need `A` to be split into pieces `(a,b,c,d)` of lengths `(2,11,9,10)` +/// bits respectively (counting from the little end), as well as their spread forms. +/// - `Maj(A,B,C)` requires having the bits of each input in spread form. For `A` we can +/// reuse the pieces from `Σ_0(A)`. Since `B` and `C` are assigned from `A` and `B` +/// respectively in each round, we therefore also have the same pieces in earlier rows. +/// We align the columns to make it efficient to copy-constrain these forms where they +/// are needed. +#[derive(Clone, Debug)] +pub struct AbcdVar { + a: SpreadVar<2, 4>, + b: SpreadVar<11, 22>, + c_lo: SpreadVar<3, 6>, + c_mid: SpreadVar<3, 6>, + c_hi: SpreadVar<3, 6>, + d: SpreadVar<10, 20>, +} + +impl AbcdVar { + fn a_range() -> Range { + 0..2 + } + + fn b_range() -> Range { + 2..13 + } + + fn c_lo_range() -> Range { + 13..16 + } + + fn c_mid_range() -> Range { + 16..19 + } + + fn c_hi_range() -> Range { + 19..22 + } + + fn d_range() -> Range { + 22..32 + } + + fn pieces(val: u32) -> Vec> { + let val: [bool; 32] = i2lebsp(val.into()); + vec![ + val[Self::a_range()].to_vec(), + val[Self::b_range()].to_vec(), + val[Self::c_lo_range()].to_vec(), + val[Self::c_mid_range()].to_vec(), + val[Self::c_hi_range()].to_vec(), + val[Self::d_range()].to_vec(), + ] + } +} + +impl UpperSigmaVar<4, 22, 18, 20> for AbcdVar { + fn spread_a(&self) -> Value<[bool; 4]> { + self.a.spread.value().map(|v| v.0) + } + + fn spread_b(&self) -> Value<[bool; 22]> { + self.b.spread.value().map(|v| v.0) + } + + fn spread_c(&self) -> Value<[bool; 18]> { + self.c_lo + .spread + .value() + .zip(self.c_mid.spread.value()) + .zip(self.c_hi.spread.value()) + .map(|((c_lo, c_mid), c_hi)| { + c_lo.iter() + .chain(c_mid.iter()) + .chain(c_hi.iter()) + .copied() + .collect::>() + .try_into() + .unwrap() + }) + } + + fn spread_d(&self) -> Value<[bool; 20]> { + self.d.spread.value().map(|v| v.0) + } +} + +/// A variable that represents the `[E,F,G,H]` words of the SHA-256 internal state. +/// +/// The structure of this variable is influenced by the following factors: +/// - In `Σ_1(E)` we need `E` to be split into pieces `(a,b,c,d)` of lengths `(6,5,14,7)` +/// bits respectively (counting from the little end), as well as their spread forms. +/// - `Ch(E,F,G)` requires having the bits of each input in spread form. For `E` we can +/// reuse the pieces from `Σ_1(E)`. Since `F` and `G` are assigned from `E` and `F` +/// respectively in each round, we therefore also have the same pieces in earlier rows. +/// We align the columns to make it efficient to copy-constrain these forms where they +/// are needed. +#[derive(Clone, Debug)] +pub struct EfghVar { + a_lo: SpreadVar<3, 6>, + a_hi: SpreadVar<3, 6>, + b_lo: SpreadVar<2, 4>, + b_hi: SpreadVar<3, 6>, + c: SpreadVar<14, 28>, + d: SpreadVar<7, 14>, +} + +impl EfghVar { + fn a_lo_range() -> Range { + 0..3 + } + + fn a_hi_range() -> Range { + 3..6 + } + + fn b_lo_range() -> Range { + 6..8 + } + + fn b_hi_range() -> Range { + 8..11 + } + + fn c_range() -> Range { + 11..25 + } + + fn d_range() -> Range { + 25..32 + } + + fn pieces(val: u32) -> Vec> { + let val: [bool; 32] = i2lebsp(val.into()); + vec![ + val[Self::a_lo_range()].to_vec(), + val[Self::a_hi_range()].to_vec(), + val[Self::b_lo_range()].to_vec(), + val[Self::b_hi_range()].to_vec(), + val[Self::c_range()].to_vec(), + val[Self::d_range()].to_vec(), + ] + } +} + +impl UpperSigmaVar<12, 10, 28, 14> for EfghVar { + fn spread_a(&self) -> Value<[bool; 12]> { + self.a_lo + .spread + .value() + .zip(self.a_hi.spread.value()) + .map(|(a_lo, a_hi)| { + a_lo.iter() + .chain(a_hi.iter()) + .copied() + .collect::>() + .try_into() + .unwrap() + }) + } + + fn spread_b(&self) -> Value<[bool; 10]> { + self.b_lo + .spread + .value() + .zip(self.b_hi.spread.value()) + .map(|(b_lo, b_hi)| { + b_lo.iter() + .chain(b_hi.iter()) + .copied() + .collect::>() + .try_into() + .unwrap() + }) + } + + fn spread_c(&self) -> Value<[bool; 28]> { + self.c.spread.value().map(|v| v.0) + } + + fn spread_d(&self) -> Value<[bool; 14]> { + self.d.spread.value().map(|v| v.0) + } +} + +#[derive(Clone, Debug)] +pub struct RoundWordDense(AssignedBits<16>, AssignedBits<16>); + +impl From<(AssignedBits<16>, AssignedBits<16>)> for RoundWordDense { + fn from(halves: (AssignedBits<16>, AssignedBits<16>)) -> Self { + Self(halves.0, halves.1) + } +} + +impl RoundWordDense { + pub fn value(&self) -> Value { + self.0 + .value_u16() + .zip(self.1.value_u16()) + .map(|(lo, hi)| lo as u32 + (1 << 16) * hi as u32) + } +} + +#[derive(Clone, Debug)] +pub struct RoundWordSpread(AssignedBits<32>, AssignedBits<32>); + +impl From<(AssignedBits<32>, AssignedBits<32>)> for RoundWordSpread { + fn from(halves: (AssignedBits<32>, AssignedBits<32>)) -> Self { + Self(halves.0, halves.1) + } +} + +impl RoundWordSpread { + pub fn value(&self) -> Value { + self.0 + .value_u32() + .zip(self.1.value_u32()) + .map(|(lo, hi)| lo as u64 + (1 << 32) * hi as u64) + } +} + +#[derive(Clone, Debug)] +pub struct RoundWordA { + pieces: Option, + dense_halves: RoundWordDense, + spread_halves: Option, +} + +impl RoundWordA { + pub fn new( + pieces: AbcdVar, + dense_halves: RoundWordDense, + spread_halves: RoundWordSpread, + ) -> Self { + RoundWordA { + pieces: Some(pieces), + dense_halves, + spread_halves: Some(spread_halves), + } + } + + pub fn new_dense(dense_halves: RoundWordDense) -> Self { + RoundWordA { + pieces: None, + dense_halves, + spread_halves: None, + } + } +} + +#[derive(Clone, Debug)] +pub struct RoundWordE { + pieces: Option, + dense_halves: RoundWordDense, + spread_halves: Option, +} + +impl RoundWordE { + pub fn new( + pieces: EfghVar, + dense_halves: RoundWordDense, + spread_halves: RoundWordSpread, + ) -> Self { + RoundWordE { + pieces: Some(pieces), + dense_halves, + spread_halves: Some(spread_halves), + } + } + + pub fn new_dense(dense_halves: RoundWordDense) -> Self { + RoundWordE { + pieces: None, + dense_halves, + spread_halves: None, + } + } +} + +#[derive(Clone, Debug)] +pub struct RoundWord { + dense_halves: RoundWordDense, + spread_halves: RoundWordSpread, +} + +impl RoundWord { + pub fn new(dense_halves: RoundWordDense, spread_halves: RoundWordSpread) -> Self { + RoundWord { + dense_halves, + spread_halves, + } + } +} + +/// The internal state for SHA-256. +#[derive(Clone, Debug)] +pub struct State { + a: Option, + b: Option, + c: Option, + d: Option, + e: Option, + f: Option, + g: Option, + h: Option, +} + +impl State { + #[allow(clippy::many_single_char_names)] + #[allow(clippy::too_many_arguments)] + pub fn new( + a: StateWord, + b: StateWord, + c: StateWord, + d: StateWord, + e: StateWord, + f: StateWord, + g: StateWord, + h: StateWord, + ) -> Self { + State { + a: Some(a), + b: Some(b), + c: Some(c), + d: Some(d), + e: Some(e), + f: Some(f), + g: Some(g), + h: Some(h), + } + } + + pub fn empty_state() -> Self { + State { + a: None, + b: None, + c: None, + d: None, + e: None, + f: None, + g: None, + h: None, + } + } +} + +#[derive(Clone, Debug)] +pub enum StateWord { + A(RoundWordA), + B(RoundWord), + C(RoundWord), + D(RoundWordDense), + E(RoundWordE), + F(RoundWord), + G(RoundWord), + H(RoundWordDense), +} + +#[derive(Clone, Debug)] +pub(super) struct CompressionConfig { + lookup: SpreadInputs, + message_schedule: Column, + extras: [Column; 6], + + s_ch: Selector, + s_ch_neg: Selector, + s_maj: Selector, + s_h_prime: Selector, + s_a_new: Selector, + s_e_new: Selector, + + s_upper_sigma_0: Selector, + s_upper_sigma_1: Selector, + + // Decomposition gate for AbcdVar + s_decompose_abcd: Selector, + // Decomposition gate for EfghVar + s_decompose_efgh: Selector, + + s_digest: Selector, +} + +impl Table16Assignment for CompressionConfig {} + +impl CompressionConfig { + pub(super) fn configure( + meta: &mut ConstraintSystem, + lookup: SpreadInputs, + message_schedule: Column, + extras: [Column; 6], + ) -> Self { + let s_ch = meta.selector(); + let s_ch_neg = meta.selector(); + let s_maj = meta.selector(); + let s_h_prime = meta.selector(); + let s_a_new = meta.selector(); + let s_e_new = meta.selector(); + + let s_upper_sigma_0 = meta.selector(); + let s_upper_sigma_1 = meta.selector(); + + // Decomposition gate for AbcdVar + let s_decompose_abcd = meta.selector(); + // Decomposition gate for EfghVar + let s_decompose_efgh = meta.selector(); + + let s_digest = meta.selector(); + + // Rename these here for ease of matching the gates to the specification. + let a_0 = lookup.tag; + let a_1 = lookup.dense; + let a_2 = lookup.spread; + let a_3 = extras[0]; + let a_4 = extras[1]; + let a_5 = message_schedule; + let a_6 = extras[2]; + let a_7 = extras[3]; + let a_8 = extras[4]; + let a_9 = extras[5]; + + // Decompose `A,B,C,D` words into (2, 11, 9, 10)-bit chunks. + // `c` is split into (3, 3, 3)-bit c_lo, c_mid, c_hi. + meta.create_gate("decompose ABCD", |meta| { + let s_decompose_abcd = meta.query_selector(s_decompose_abcd); + let a = meta.query_advice(a_3, Rotation::next()); // 2-bit chunk + let spread_a = meta.query_advice(a_4, Rotation::next()); + let b = meta.query_advice(a_1, Rotation::cur()); // 11-bit chunk + let spread_b = meta.query_advice(a_2, Rotation::cur()); + let tag_b = meta.query_advice(a_0, Rotation::cur()); + let c_lo = meta.query_advice(a_3, Rotation::cur()); // 3-bit chunk + let spread_c_lo = meta.query_advice(a_4, Rotation::cur()); + let c_mid = meta.query_advice(a_5, Rotation::cur()); // 3-bit chunk + let spread_c_mid = meta.query_advice(a_6, Rotation::cur()); + let c_hi = meta.query_advice(a_5, Rotation::next()); // 3-bit chunk + let spread_c_hi = meta.query_advice(a_6, Rotation::next()); + let d = meta.query_advice(a_1, Rotation::next()); // 7-bit chunk + let spread_d = meta.query_advice(a_2, Rotation::next()); + let tag_d = meta.query_advice(a_0, Rotation::next()); + let word_lo = meta.query_advice(a_7, Rotation::cur()); + let spread_word_lo = meta.query_advice(a_8, Rotation::cur()); + let word_hi = meta.query_advice(a_7, Rotation::next()); + let spread_word_hi = meta.query_advice(a_8, Rotation::next()); + + CompressionGate::s_decompose_abcd( + s_decompose_abcd, + a, + spread_a, + b, + spread_b, + tag_b, + c_lo, + spread_c_lo, + c_mid, + spread_c_mid, + c_hi, + spread_c_hi, + d, + spread_d, + tag_d, + word_lo, + spread_word_lo, + word_hi, + spread_word_hi, + ) + }); + + // Decompose `E,F,G,H` words into (6, 5, 14, 7)-bit chunks. + // `a` is split into (3, 3)-bit a_lo, a_hi + // `b` is split into (2, 3)-bit b_lo, b_hi + meta.create_gate("Decompose EFGH", |meta| { + let s_decompose_efgh = meta.query_selector(s_decompose_efgh); + let a_lo = meta.query_advice(a_3, Rotation::next()); // 3-bit chunk + let spread_a_lo = meta.query_advice(a_4, Rotation::next()); + let a_hi = meta.query_advice(a_5, Rotation::next()); // 3-bit chunk + let spread_a_hi = meta.query_advice(a_6, Rotation::next()); + let b_lo = meta.query_advice(a_3, Rotation::cur()); // 2-bit chunk + let spread_b_lo = meta.query_advice(a_4, Rotation::cur()); + let b_hi = meta.query_advice(a_5, Rotation::cur()); // 3-bit chunk + let spread_b_hi = meta.query_advice(a_6, Rotation::cur()); + let c = meta.query_advice(a_1, Rotation::next()); // 14-bit chunk + let spread_c = meta.query_advice(a_2, Rotation::next()); + let tag_c = meta.query_advice(a_0, Rotation::next()); + let d = meta.query_advice(a_1, Rotation::cur()); // 7-bit chunk + let spread_d = meta.query_advice(a_2, Rotation::cur()); + let tag_d = meta.query_advice(a_0, Rotation::cur()); + let word_lo = meta.query_advice(a_7, Rotation::cur()); + let spread_word_lo = meta.query_advice(a_8, Rotation::cur()); + let word_hi = meta.query_advice(a_7, Rotation::next()); + let spread_word_hi = meta.query_advice(a_8, Rotation::next()); + + CompressionGate::s_decompose_efgh( + s_decompose_efgh, + a_lo, + spread_a_lo, + a_hi, + spread_a_hi, + b_lo, + spread_b_lo, + b_hi, + spread_b_hi, + c, + spread_c, + tag_c, + d, + spread_d, + tag_d, + word_lo, + spread_word_lo, + word_hi, + spread_word_hi, + ) + }); + + // s_upper_sigma_0 on abcd words + // (2, 11, 9, 10)-bit chunks + meta.create_gate("s_upper_sigma_0", |meta| { + let s_upper_sigma_0 = meta.query_selector(s_upper_sigma_0); + let spread_r0_even = meta.query_advice(a_2, Rotation::prev()); + let spread_r0_odd = meta.query_advice(a_2, Rotation::cur()); + let spread_r1_even = meta.query_advice(a_2, Rotation::next()); + let spread_r1_odd = meta.query_advice(a_3, Rotation::cur()); + + let spread_a = meta.query_advice(a_3, Rotation::next()); + let spread_b = meta.query_advice(a_5, Rotation::cur()); + let spread_c_lo = meta.query_advice(a_3, Rotation::prev()); + let spread_c_mid = meta.query_advice(a_4, Rotation::prev()); + let spread_c_hi = meta.query_advice(a_4, Rotation::next()); + let spread_d = meta.query_advice(a_4, Rotation::cur()); + + CompressionGate::s_upper_sigma_0( + s_upper_sigma_0, + spread_r0_even, + spread_r0_odd, + spread_r1_even, + spread_r1_odd, + spread_a, + spread_b, + spread_c_lo, + spread_c_mid, + spread_c_hi, + spread_d, + ) + }); + + // s_upper_sigma_1 on efgh words + // (6, 5, 14, 7)-bit chunks + meta.create_gate("s_upper_sigma_1", |meta| { + let s_upper_sigma_1 = meta.query_selector(s_upper_sigma_1); + let spread_r0_even = meta.query_advice(a_2, Rotation::prev()); + let spread_r0_odd = meta.query_advice(a_2, Rotation::cur()); + let spread_r1_even = meta.query_advice(a_2, Rotation::next()); + let spread_r1_odd = meta.query_advice(a_3, Rotation::cur()); + let spread_a_lo = meta.query_advice(a_3, Rotation::next()); + let spread_a_hi = meta.query_advice(a_4, Rotation::next()); + let spread_b_lo = meta.query_advice(a_3, Rotation::prev()); + let spread_b_hi = meta.query_advice(a_4, Rotation::prev()); + let spread_c = meta.query_advice(a_5, Rotation::cur()); + let spread_d = meta.query_advice(a_4, Rotation::cur()); + + CompressionGate::s_upper_sigma_1( + s_upper_sigma_1, + spread_r0_even, + spread_r0_odd, + spread_r1_even, + spread_r1_odd, + spread_a_lo, + spread_a_hi, + spread_b_lo, + spread_b_hi, + spread_c, + spread_d, + ) + }); + + // s_ch on efgh words + // First part of choice gate on (E, F, G), E ∧ F + meta.create_gate("s_ch", |meta| { + let s_ch = meta.query_selector(s_ch); + let spread_p0_even = meta.query_advice(a_2, Rotation::prev()); + let spread_p0_odd = meta.query_advice(a_2, Rotation::cur()); + let spread_p1_even = meta.query_advice(a_2, Rotation::next()); + let spread_p1_odd = meta.query_advice(a_3, Rotation::cur()); + let spread_e_lo = meta.query_advice(a_3, Rotation::prev()); + let spread_e_hi = meta.query_advice(a_4, Rotation::prev()); + let spread_f_lo = meta.query_advice(a_3, Rotation::next()); + let spread_f_hi = meta.query_advice(a_4, Rotation::next()); + + CompressionGate::s_ch( + s_ch, + spread_p0_even, + spread_p0_odd, + spread_p1_even, + spread_p1_odd, + spread_e_lo, + spread_e_hi, + spread_f_lo, + spread_f_hi, + ) + }); + + // s_ch_neg on efgh words + // Second part of Choice gate on (E, F, G), ¬E ∧ G + meta.create_gate("s_ch_neg", |meta| { + let s_ch_neg = meta.query_selector(s_ch_neg); + let spread_q0_even = meta.query_advice(a_2, Rotation::prev()); + let spread_q0_odd = meta.query_advice(a_2, Rotation::cur()); + let spread_q1_even = meta.query_advice(a_2, Rotation::next()); + let spread_q1_odd = meta.query_advice(a_3, Rotation::cur()); + let spread_e_lo = meta.query_advice(a_5, Rotation::prev()); + let spread_e_hi = meta.query_advice(a_5, Rotation::cur()); + let spread_e_neg_lo = meta.query_advice(a_3, Rotation::prev()); + let spread_e_neg_hi = meta.query_advice(a_4, Rotation::prev()); + let spread_g_lo = meta.query_advice(a_3, Rotation::next()); + let spread_g_hi = meta.query_advice(a_4, Rotation::next()); + + CompressionGate::s_ch_neg( + s_ch_neg, + spread_q0_even, + spread_q0_odd, + spread_q1_even, + spread_q1_odd, + spread_e_lo, + spread_e_hi, + spread_e_neg_lo, + spread_e_neg_hi, + spread_g_lo, + spread_g_hi, + ) + }); + + // s_maj on abcd words + meta.create_gate("s_maj", |meta| { + let s_maj = meta.query_selector(s_maj); + let spread_m0_even = meta.query_advice(a_2, Rotation::prev()); + let spread_m0_odd = meta.query_advice(a_2, Rotation::cur()); + let spread_m1_even = meta.query_advice(a_2, Rotation::next()); + let spread_m1_odd = meta.query_advice(a_3, Rotation::cur()); + let spread_a_lo = meta.query_advice(a_4, Rotation::prev()); + let spread_a_hi = meta.query_advice(a_5, Rotation::prev()); + let spread_b_lo = meta.query_advice(a_4, Rotation::cur()); + let spread_b_hi = meta.query_advice(a_5, Rotation::cur()); + let spread_c_lo = meta.query_advice(a_4, Rotation::next()); + let spread_c_hi = meta.query_advice(a_5, Rotation::next()); + + CompressionGate::s_maj( + s_maj, + spread_m0_even, + spread_m0_odd, + spread_m1_even, + spread_m1_odd, + spread_a_lo, + spread_a_hi, + spread_b_lo, + spread_b_hi, + spread_c_lo, + spread_c_hi, + ) + }); + + // s_h_prime to compute H' = H + Ch(E, F, G) + s_upper_sigma_1(E) + K + W + meta.create_gate("s_h_prime", |meta| { + let s_h_prime = meta.query_selector(s_h_prime); + let h_prime_lo = meta.query_advice(a_7, Rotation::next()); + let h_prime_hi = meta.query_advice(a_8, Rotation::next()); + let h_prime_carry = meta.query_advice(a_9, Rotation::next()); + let sigma_e_lo = meta.query_advice(a_4, Rotation::cur()); + let sigma_e_hi = meta.query_advice(a_5, Rotation::cur()); + let ch_lo = meta.query_advice(a_1, Rotation::cur()); + let ch_hi = meta.query_advice(a_6, Rotation::next()); + let ch_neg_lo = meta.query_advice(a_5, Rotation::prev()); + let ch_neg_hi = meta.query_advice(a_5, Rotation::next()); + let h_lo = meta.query_advice(a_7, Rotation::prev()); + let h_hi = meta.query_advice(a_7, Rotation::cur()); + let k_lo = meta.query_advice(a_6, Rotation::prev()); + let k_hi = meta.query_advice(a_6, Rotation::cur()); + let w_lo = meta.query_advice(a_8, Rotation::prev()); + let w_hi = meta.query_advice(a_8, Rotation::cur()); + + CompressionGate::s_h_prime( + s_h_prime, + h_prime_lo, + h_prime_hi, + h_prime_carry, + sigma_e_lo, + sigma_e_hi, + ch_lo, + ch_hi, + ch_neg_lo, + ch_neg_hi, + h_lo, + h_hi, + k_lo, + k_hi, + w_lo, + w_hi, + ) + }); + + // s_a_new + meta.create_gate("s_a_new", |meta| { + let s_a_new = meta.query_selector(s_a_new); + let a_new_lo = meta.query_advice(a_8, Rotation::cur()); + let a_new_hi = meta.query_advice(a_8, Rotation::next()); + let a_new_carry = meta.query_advice(a_9, Rotation::cur()); + let sigma_a_lo = meta.query_advice(a_6, Rotation::cur()); + let sigma_a_hi = meta.query_advice(a_6, Rotation::next()); + let maj_abc_lo = meta.query_advice(a_1, Rotation::cur()); + let maj_abc_hi = meta.query_advice(a_3, Rotation::prev()); + let h_prime_lo = meta.query_advice(a_7, Rotation::prev()); + let h_prime_hi = meta.query_advice(a_8, Rotation::prev()); + + CompressionGate::s_a_new( + s_a_new, + a_new_lo, + a_new_hi, + a_new_carry, + sigma_a_lo, + sigma_a_hi, + maj_abc_lo, + maj_abc_hi, + h_prime_lo, + h_prime_hi, + ) + }); + + // s_e_new + meta.create_gate("s_e_new", |meta| { + let s_e_new = meta.query_selector(s_e_new); + let e_new_lo = meta.query_advice(a_8, Rotation::cur()); + let e_new_hi = meta.query_advice(a_8, Rotation::next()); + let e_new_carry = meta.query_advice(a_9, Rotation::next()); + let d_lo = meta.query_advice(a_7, Rotation::cur()); + let d_hi = meta.query_advice(a_7, Rotation::next()); + let h_prime_lo = meta.query_advice(a_7, Rotation::prev()); + let h_prime_hi = meta.query_advice(a_8, Rotation::prev()); + + CompressionGate::s_e_new( + s_e_new, + e_new_lo, + e_new_hi, + e_new_carry, + d_lo, + d_hi, + h_prime_lo, + h_prime_hi, + ) + }); + + // s_digest for final round + meta.create_gate("s_digest", |meta| { + let s_digest = meta.query_selector(s_digest); + let lo_0 = meta.query_advice(a_3, Rotation::cur()); + let hi_0 = meta.query_advice(a_4, Rotation::cur()); + let word_0 = meta.query_advice(a_5, Rotation::cur()); + let lo_1 = meta.query_advice(a_6, Rotation::cur()); + let hi_1 = meta.query_advice(a_7, Rotation::cur()); + let word_1 = meta.query_advice(a_8, Rotation::cur()); + let lo_2 = meta.query_advice(a_3, Rotation::next()); + let hi_2 = meta.query_advice(a_4, Rotation::next()); + let word_2 = meta.query_advice(a_5, Rotation::next()); + let lo_3 = meta.query_advice(a_6, Rotation::next()); + let hi_3 = meta.query_advice(a_7, Rotation::next()); + let word_3 = meta.query_advice(a_8, Rotation::next()); + + CompressionGate::s_digest( + s_digest, lo_0, hi_0, word_0, lo_1, hi_1, word_1, lo_2, hi_2, word_2, lo_3, hi_3, + word_3, + ) + }); + + CompressionConfig { + lookup, + message_schedule, + extras, + s_ch, + s_ch_neg, + s_maj, + s_h_prime, + s_a_new, + s_e_new, + s_upper_sigma_0, + s_upper_sigma_1, + s_decompose_abcd, + s_decompose_efgh, + s_digest, + } + } + + /// Initialize compression with a constant Initialization Vector of 32-byte words. + /// Returns an initialized state. + pub(super) fn initialize_with_iv( + &self, + layouter: &mut impl Layouter, + init_state: [u32; STATE], + ) -> Result { + let mut new_state = State::empty_state(); + layouter.assign_region( + || "initialize_with_iv", + |mut region| { + new_state = self.initialize_iv(&mut region, init_state)?; + Ok(()) + }, + )?; + Ok(new_state) + } + + /// Initialize compression with some initialized state. This could be a state + /// output from a previous compression round. + pub(super) fn initialize_with_state( + &self, + layouter: &mut impl Layouter, + init_state: State, + ) -> Result { + let mut new_state = State::empty_state(); + layouter.assign_region( + || "initialize_with_state", + |mut region| { + new_state = self.initialize_state(&mut region, init_state.clone())?; + Ok(()) + }, + )?; + Ok(new_state) + } + + /// Given an initialized state and a message schedule, perform 64 compression rounds. + pub(super) fn compress( + &self, + layouter: &mut impl Layouter, + initialized_state: State, + w_halves: [(AssignedBits<16>, AssignedBits<16>); ROUNDS], + ) -> Result { + let mut state = State::empty_state(); + layouter.assign_region( + || "compress", + |mut region| { + state = initialized_state.clone(); + for (idx, w_halves) in w_halves.iter().enumerate() { + state = self.assign_round(&mut region, idx.into(), state.clone(), w_halves)?; + } + Ok(()) + }, + )?; + Ok(state) + } + + /// After the final round, convert the state into the final digest. + pub(super) fn digest( + &self, + layouter: &mut impl Layouter, + state: State, + ) -> Result<[BlockWord; DIGEST_SIZE], Error> { + let mut digest = [BlockWord(Value::known(0)); DIGEST_SIZE]; + layouter.assign_region( + || "digest", + |mut region| { + digest = self.assign_digest(&mut region, state.clone())?; + + Ok(()) + }, + )?; + Ok(digest) + } +} + +#[cfg(test)] +mod tests { + use super::super::{ + super::BLOCK_SIZE, msg_schedule_test_input, BlockWord, Table16Chip, Table16Config, IV, + }; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + dev::MockProver, + pasta::pallas, + plonk::{Circuit, ConstraintSystem, Error}, + }; + + #[test] + fn compress() { + struct MyCircuit {} + + impl Circuit for MyCircuit { + type Config = Table16Config; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + MyCircuit {} + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + Table16Chip::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + Table16Chip::load(config.clone(), &mut layouter)?; + + // Test vector: "abc" + let input: [BlockWord; BLOCK_SIZE] = msg_schedule_test_input(); + + let (_, w_halves) = config.message_schedule.process(&mut layouter, input)?; + + let compression = config.compression.clone(); + let initial_state = compression.initialize_with_iv(&mut layouter, IV)?; + + let state = config + .compression + .compress(&mut layouter, initial_state, w_halves)?; + + let digest = config.compression.digest(&mut layouter, state)?; + for (idx, digest_word) in digest.iter().enumerate() { + digest_word.0.assert_if_known(|digest_word| { + (*digest_word as u64 + IV[idx] as u64) as u32 + == super::compression_util::COMPRESSION_OUTPUT[idx] + }); + } + + Ok(()) + } + } + + let circuit: MyCircuit = MyCircuit {}; + + let prover = match MockProver::::run(17, &circuit, vec![]) { + Ok(prover) => prover, + Err(e) => panic!("{:?}", e), + }; + assert_eq!(prover.verify(), Ok(())); + } +} diff --git a/halo2_gadgets_optimized/src/sha256/table16/compression/compression_gates.rs b/halo2_gadgets_optimized/src/sha256/table16/compression/compression_gates.rs new file mode 100644 index 0000000000..dada6bbf5a --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16/compression/compression_gates.rs @@ -0,0 +1,443 @@ +use super::super::{util::*, Gate}; + +use group::ff::{Field, PrimeField}; +use halo2_proofs::plonk::{Constraint, Constraints, Expression}; +use std::marker::PhantomData; + +pub struct CompressionGate(PhantomData); + +impl CompressionGate { + fn ones() -> Expression { + Expression::Constant(F::ONE) + } + + // Decompose `A,B,C,D` words + // (2, 11, 9, 10)-bit chunks + #[allow(clippy::too_many_arguments)] + pub fn s_decompose_abcd( + s_decompose_abcd: Expression, + a: Expression, + spread_a: Expression, + b: Expression, + spread_b: Expression, + tag_b: Expression, + c_lo: Expression, + spread_c_lo: Expression, + c_mid: Expression, + spread_c_mid: Expression, + c_hi: Expression, + spread_c_hi: Expression, + d: Expression, + spread_d: Expression, + tag_d: Expression, + word_lo: Expression, + spread_word_lo: Expression, + word_hi: Expression, + spread_word_hi: Expression, + ) -> Constraints< + F, + (&'static str, Expression), + impl Iterator)>, + > { + let check_spread_and_range = + Gate::three_bit_spread_and_range(c_lo.clone(), spread_c_lo.clone()) + .chain(Gate::three_bit_spread_and_range( + c_mid.clone(), + spread_c_mid.clone(), + )) + .chain(Gate::three_bit_spread_and_range( + c_hi.clone(), + spread_c_hi.clone(), + )) + .chain(Gate::two_bit_spread_and_range(a.clone(), spread_a.clone())); + let range_check_tag_b = Gate::range_check(tag_b, 0, 2); + let range_check_tag_d = Gate::range_check(tag_d, 0, 1); + let dense_check = a + + b * F::from(1 << 2) + + c_lo * F::from(1 << 13) + + c_mid * F::from(1 << 16) + + c_hi * F::from(1 << 19) + + d * F::from(1 << 22) + + word_lo * (-F::ONE) + + word_hi * F::from(1 << 16) * (-F::ONE); + let spread_check = spread_a + + spread_b * F::from(1 << 4) + + spread_c_lo * F::from(1 << 26) + + spread_c_mid * F::from(1 << 32) + + spread_c_hi * F::from(1 << 38) + + spread_d * F::from(1 << 44) + + spread_word_lo * (-F::ONE) + + spread_word_hi * F::from(1 << 32) * (-F::ONE); + + Constraints::with_selector( + s_decompose_abcd, + check_spread_and_range + .chain(Some(("range_check_tag_b", range_check_tag_b))) + .chain(Some(("range_check_tag_d", range_check_tag_d))) + .chain(Some(("dense_check", dense_check))) + .chain(Some(("spread_check", spread_check))), + ) + } + + // Decompose `E,F,G,H` words + // (6, 5, 14, 7)-bit chunks + #[allow(clippy::too_many_arguments)] + pub fn s_decompose_efgh( + s_decompose_efgh: Expression, + a_lo: Expression, + spread_a_lo: Expression, + a_hi: Expression, + spread_a_hi: Expression, + b_lo: Expression, + spread_b_lo: Expression, + b_hi: Expression, + spread_b_hi: Expression, + c: Expression, + spread_c: Expression, + tag_c: Expression, + d: Expression, + spread_d: Expression, + tag_d: Expression, + word_lo: Expression, + spread_word_lo: Expression, + word_hi: Expression, + spread_word_hi: Expression, + ) -> Constraints< + F, + (&'static str, Expression), + impl Iterator)>, + > { + let check_spread_and_range = + Gate::three_bit_spread_and_range(a_lo.clone(), spread_a_lo.clone()) + .chain(Gate::three_bit_spread_and_range( + a_hi.clone(), + spread_a_hi.clone(), + )) + .chain(Gate::three_bit_spread_and_range( + b_hi.clone(), + spread_b_hi.clone(), + )) + .chain(Gate::two_bit_spread_and_range( + b_lo.clone(), + spread_b_lo.clone(), + )); + let range_check_tag_c = Gate::range_check(tag_c, 0, 4); + let range_check_tag_d = Gate::range_check(tag_d, 0, 0); + let dense_check = a_lo + + a_hi * F::from(1 << 3) + + b_lo * F::from(1 << 6) + + b_hi * F::from(1 << 8) + + c * F::from(1 << 11) + + d * F::from(1 << 25) + + word_lo * (-F::ONE) + + word_hi * F::from(1 << 16) * (-F::ONE); + let spread_check = spread_a_lo + + spread_a_hi * F::from(1 << 6) + + spread_b_lo * F::from(1 << 12) + + spread_b_hi * F::from(1 << 16) + + spread_c * F::from(1 << 22) + + spread_d * F::from(1 << 50) + + spread_word_lo * (-F::ONE) + + spread_word_hi * F::from(1 << 32) * (-F::ONE); + + Constraints::with_selector( + s_decompose_efgh, + check_spread_and_range + .chain(Some(("range_check_tag_c", range_check_tag_c))) + .chain(Some(("range_check_tag_d", range_check_tag_d))) + .chain(Some(("dense_check", dense_check))) + .chain(Some(("spread_check", spread_check))), + ) + } + + // s_upper_sigma_0 on abcd words + // (2, 11, 9, 10)-bit chunks + #[allow(clippy::too_many_arguments)] + pub fn s_upper_sigma_0( + s_upper_sigma_0: Expression, + spread_r0_even: Expression, + spread_r0_odd: Expression, + spread_r1_even: Expression, + spread_r1_odd: Expression, + spread_a: Expression, + spread_b: Expression, + spread_c_lo: Expression, + spread_c_mid: Expression, + spread_c_hi: Expression, + spread_d: Expression, + ) -> Option<(&'static str, Expression)> { + let spread_witness = spread_r0_even + + spread_r0_odd * F::from(2) + + (spread_r1_even + spread_r1_odd * F::from(2)) * F::from(1 << 32); + let xor_0 = spread_b.clone() + + spread_c_lo.clone() * F::from(1 << 22) + + spread_c_mid.clone() * F::from(1 << 28) + + spread_c_hi.clone() * F::from(1 << 34) + + spread_d.clone() * F::from(1 << 40) + + spread_a.clone() * F::from(1 << 60); + let xor_1 = spread_c_lo.clone() + + spread_c_mid.clone() * F::from(1 << 6) + + spread_c_hi.clone() * F::from(1 << 12) + + spread_d.clone() * F::from(1 << 18) + + spread_a.clone() * F::from(1 << 38) + + spread_b.clone() * F::from(1 << 42); + let xor_2 = spread_d + + spread_a * F::from(1 << 20) + + spread_b * F::from(1 << 24) + + spread_c_lo * F::from(1 << 46) + + spread_c_mid * F::from(1 << 52) + + spread_c_hi * F::from(1 << 58); + let xor = xor_0 + xor_1 + xor_2; + let check = spread_witness + (xor * -F::ONE); + + Some(("s_upper_sigma_0", s_upper_sigma_0 * check)) + } + + // s_upper_sigma_1 on efgh words + // (6, 5, 14, 7)-bit chunks + #[allow(clippy::too_many_arguments)] + pub fn s_upper_sigma_1( + s_upper_sigma_1: Expression, + spread_r0_even: Expression, + spread_r0_odd: Expression, + spread_r1_even: Expression, + spread_r1_odd: Expression, + spread_a_lo: Expression, + spread_a_hi: Expression, + spread_b_lo: Expression, + spread_b_hi: Expression, + spread_c: Expression, + spread_d: Expression, + ) -> Option<(&'static str, Expression)> { + let spread_witness = spread_r0_even + + spread_r0_odd * F::from(2) + + (spread_r1_even + spread_r1_odd * F::from(2)) * F::from(1 << 32); + + let xor_0 = spread_b_lo.clone() + + spread_b_hi.clone() * F::from(1 << 4) + + spread_c.clone() * F::from(1 << 10) + + spread_d.clone() * F::from(1 << 38) + + spread_a_lo.clone() * F::from(1 << 52) + + spread_a_hi.clone() * F::from(1 << 58); + let xor_1 = spread_c.clone() + + spread_d.clone() * F::from(1 << 28) + + spread_a_lo.clone() * F::from(1 << 42) + + spread_a_hi.clone() * F::from(1 << 48) + + spread_b_lo.clone() * F::from(1 << 54) + + spread_b_hi.clone() * F::from(1 << 58); + let xor_2 = spread_d + + spread_a_lo * F::from(1 << 14) + + spread_a_hi * F::from(1 << 20) + + spread_b_lo * F::from(1 << 26) + + spread_b_hi * F::from(1 << 30) + + spread_c * F::from(1 << 36); + let xor = xor_0 + xor_1 + xor_2; + let check = spread_witness + (xor * -F::ONE); + + Some(("s_upper_sigma_1", s_upper_sigma_1 * check)) + } + + // First part of choice gate on (E, F, G), E ∧ F + #[allow(clippy::too_many_arguments)] + pub fn s_ch( + s_ch: Expression, + spread_p0_even: Expression, + spread_p0_odd: Expression, + spread_p1_even: Expression, + spread_p1_odd: Expression, + spread_e_lo: Expression, + spread_e_hi: Expression, + spread_f_lo: Expression, + spread_f_hi: Expression, + ) -> Option<(&'static str, Expression)> { + let lhs_lo = spread_e_lo + spread_f_lo; + let lhs_hi = spread_e_hi + spread_f_hi; + let lhs = lhs_lo + lhs_hi * F::from(1 << 32); + + let rhs_even = spread_p0_even + spread_p1_even * F::from(1 << 32); + let rhs_odd = spread_p0_odd + spread_p1_odd * F::from(1 << 32); + let rhs = rhs_even + rhs_odd * F::from(2); + + let check = lhs + rhs * -F::ONE; + + Some(("s_ch", s_ch * check)) + } + + // Second part of Choice gate on (E, F, G), ¬E ∧ G + #[allow(clippy::too_many_arguments)] + pub fn s_ch_neg( + s_ch_neg: Expression, + spread_q0_even: Expression, + spread_q0_odd: Expression, + spread_q1_even: Expression, + spread_q1_odd: Expression, + spread_e_lo: Expression, + spread_e_hi: Expression, + spread_e_neg_lo: Expression, + spread_e_neg_hi: Expression, + spread_g_lo: Expression, + spread_g_hi: Expression, + ) -> Constraints< + F, + (&'static str, Expression), + impl Iterator)>, + > { + let neg_check = { + let evens = Self::ones() * F::from(MASK_EVEN_32 as u64); + // evens - spread_e_lo = spread_e_neg_lo + let lo_check = spread_e_neg_lo.clone() + spread_e_lo + (evens.clone() * (-F::ONE)); + // evens - spread_e_hi = spread_e_neg_hi + let hi_check = spread_e_neg_hi.clone() + spread_e_hi + (evens * (-F::ONE)); + + std::iter::empty() + .chain(Some(("lo_check", lo_check))) + .chain(Some(("hi_check", hi_check))) + }; + + let lhs_lo = spread_e_neg_lo + spread_g_lo; + let lhs_hi = spread_e_neg_hi + spread_g_hi; + let lhs = lhs_lo + lhs_hi * F::from(1 << 32); + + let rhs_even = spread_q0_even + spread_q1_even * F::from(1 << 32); + let rhs_odd = spread_q0_odd + spread_q1_odd * F::from(1 << 32); + let rhs = rhs_even + rhs_odd * F::from(2); + + Constraints::with_selector(s_ch_neg, neg_check.chain(Some(("s_ch_neg", lhs - rhs)))) + } + + // Majority gate on (A, B, C) + #[allow(clippy::too_many_arguments)] + pub fn s_maj( + s_maj: Expression, + spread_m_0_even: Expression, + spread_m_0_odd: Expression, + spread_m_1_even: Expression, + spread_m_1_odd: Expression, + spread_a_lo: Expression, + spread_a_hi: Expression, + spread_b_lo: Expression, + spread_b_hi: Expression, + spread_c_lo: Expression, + spread_c_hi: Expression, + ) -> Option<(&'static str, Expression)> { + let maj_even = spread_m_0_even + spread_m_1_even * F::from(1 << 32); + let maj_odd = spread_m_0_odd + spread_m_1_odd * F::from(1 << 32); + let maj = maj_even + maj_odd * F::from(2); + + let a = spread_a_lo + spread_a_hi * F::from(1 << 32); + let b = spread_b_lo + spread_b_hi * F::from(1 << 32); + let c = spread_c_lo + spread_c_hi * F::from(1 << 32); + let sum = a + b + c; + + Some(("maj", s_maj * (sum - maj))) + } + + // s_h_prime to get H' = H + Ch(E, F, G) + s_upper_sigma_1(E) + K + W + #[allow(clippy::too_many_arguments)] + pub fn s_h_prime( + s_h_prime: Expression, + h_prime_lo: Expression, + h_prime_hi: Expression, + h_prime_carry: Expression, + sigma_e_lo: Expression, + sigma_e_hi: Expression, + ch_lo: Expression, + ch_hi: Expression, + ch_neg_lo: Expression, + ch_neg_hi: Expression, + h_lo: Expression, + h_hi: Expression, + k_lo: Expression, + k_hi: Expression, + w_lo: Expression, + w_hi: Expression, + ) -> Option<(&'static str, Expression)> { + let lo = h_lo + ch_lo + ch_neg_lo + sigma_e_lo + k_lo + w_lo; + let hi = h_hi + ch_hi + ch_neg_hi + sigma_e_hi + k_hi + w_hi; + + let sum = lo + hi * F::from(1 << 16); + let h_prime = h_prime_lo + h_prime_hi * F::from(1 << 16); + + let check = sum - (h_prime_carry * F::from(1 << 32)) - h_prime; + + Some(("s_h_prime", s_h_prime * check)) + } + + // s_a_new to get A_new = H' + Maj(A, B, C) + s_upper_sigma_0(A) + #[allow(clippy::too_many_arguments)] + pub fn s_a_new( + s_a_new: Expression, + a_new_lo: Expression, + a_new_hi: Expression, + a_new_carry: Expression, + sigma_a_lo: Expression, + sigma_a_hi: Expression, + maj_abc_lo: Expression, + maj_abc_hi: Expression, + h_prime_lo: Expression, + h_prime_hi: Expression, + ) -> Option<(&'static str, Expression)> { + let lo = sigma_a_lo + maj_abc_lo + h_prime_lo; + let hi = sigma_a_hi + maj_abc_hi + h_prime_hi; + let sum = lo + hi * F::from(1 << 16); + let a_new = a_new_lo + a_new_hi * F::from(1 << 16); + + let check = sum - (a_new_carry * F::from(1 << 32)) - a_new; + + Some(("s_a_new", s_a_new * check)) + } + + // s_e_new to get E_new = H' + D + #[allow(clippy::too_many_arguments)] + pub fn s_e_new( + s_e_new: Expression, + e_new_lo: Expression, + e_new_hi: Expression, + e_new_carry: Expression, + d_lo: Expression, + d_hi: Expression, + h_prime_lo: Expression, + h_prime_hi: Expression, + ) -> Option<(&'static str, Expression)> { + let lo = h_prime_lo + d_lo; + let hi = h_prime_hi + d_hi; + let sum = lo + hi * F::from(1 << 16); + let e_new = e_new_lo + e_new_hi * F::from(1 << 16); + + let check = sum - (e_new_carry * F::from(1 << 32)) - e_new; + + Some(("s_e_new", s_e_new * check)) + } + + // s_digest on final round + #[allow(clippy::too_many_arguments)] + pub fn s_digest( + s_digest: Expression, + lo_0: Expression, + hi_0: Expression, + word_0: Expression, + lo_1: Expression, + hi_1: Expression, + word_1: Expression, + lo_2: Expression, + hi_2: Expression, + word_2: Expression, + lo_3: Expression, + hi_3: Expression, + word_3: Expression, + ) -> impl IntoIterator> { + let check_lo_hi = |lo: Expression, hi: Expression, word: Expression| { + lo + hi * F::from(1 << 16) - word + }; + + Constraints::with_selector( + s_digest, + [ + ("check_lo_hi_0", check_lo_hi(lo_0, hi_0, word_0)), + ("check_lo_hi_1", check_lo_hi(lo_1, hi_1, word_1)), + ("check_lo_hi_2", check_lo_hi(lo_2, hi_2, word_2)), + ("check_lo_hi_3", check_lo_hi(lo_3, hi_3, word_3)), + ], + ) + } +} diff --git a/halo2_gadgets_optimized/src/sha256/table16/compression/compression_util.rs b/halo2_gadgets_optimized/src/sha256/table16/compression/compression_util.rs new file mode 100644 index 0000000000..75f6e0b56d --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16/compression/compression_util.rs @@ -0,0 +1,991 @@ +use super::{ + AbcdVar, CompressionConfig, EfghVar, RoundWord, RoundWordA, RoundWordDense, RoundWordE, + RoundWordSpread, State, UpperSigmaVar, +}; +use crate::sha256::table16::{ + util::*, AssignedBits, SpreadVar, SpreadWord, StateWord, Table16Assignment, +}; +use halo2_proofs::{ + circuit::{Region, Value}, + pasta::pallas, + plonk::{Advice, Column, Error}, +}; +use std::convert::TryInto; + +// Test vector 'abc' +#[cfg(test)] +pub const COMPRESSION_OUTPUT: [u32; 8] = [ + 0b10111010011110000001011010111111, + 0b10001111000000011100111111101010, + 0b01000001010000010100000011011110, + 0b01011101101011100010001000100011, + 0b10110000000000110110000110100011, + 0b10010110000101110111101010011100, + 0b10110100000100001111111101100001, + 0b11110010000000000001010110101101, +]; + +// Rows needed for each gate +pub const SIGMA_0_ROWS: usize = 4; +pub const SIGMA_1_ROWS: usize = 4; +pub const CH_ROWS: usize = 8; +pub const MAJ_ROWS: usize = 4; +pub const DECOMPOSE_ABCD: usize = 2; +pub const DECOMPOSE_EFGH: usize = 2; + +// Rows needed for main subregion +pub const SUBREGION_MAIN_LEN: usize = 64; +pub const SUBREGION_MAIN_WORD: usize = + DECOMPOSE_ABCD + SIGMA_0_ROWS + DECOMPOSE_EFGH + SIGMA_1_ROWS + CH_ROWS + MAJ_ROWS; +pub const SUBREGION_MAIN_ROWS: usize = SUBREGION_MAIN_LEN * SUBREGION_MAIN_WORD; + +/// The initial round. +pub struct InitialRound; + +/// A main round index. +#[derive(Debug, Copy, Clone)] +pub struct MainRoundIdx(usize); + +/// Round index. +#[derive(Debug, Copy, Clone)] +pub enum RoundIdx { + Init, + Main(MainRoundIdx), +} + +impl From for RoundIdx { + fn from(_: InitialRound) -> Self { + RoundIdx::Init + } +} + +impl From for RoundIdx { + fn from(idx: MainRoundIdx) -> Self { + RoundIdx::Main(idx) + } +} + +impl MainRoundIdx { + pub(crate) fn as_usize(&self) -> usize { + self.0 + } +} + +impl From for MainRoundIdx { + fn from(idx: usize) -> Self { + MainRoundIdx(idx) + } +} + +impl std::ops::Add for MainRoundIdx { + type Output = Self; + + fn add(self, rhs: usize) -> Self::Output { + MainRoundIdx(self.0 + rhs) + } +} + +impl Ord for MainRoundIdx { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl PartialOrd for MainRoundIdx { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for MainRoundIdx { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for MainRoundIdx {} + +/// Returns starting row number of a compression round +pub fn get_round_row(round_idx: RoundIdx) -> usize { + match round_idx { + RoundIdx::Init => 0, + RoundIdx::Main(MainRoundIdx(idx)) => { + assert!(idx < 64); + idx * SUBREGION_MAIN_WORD + } + } +} + +pub fn get_decompose_e_row(round_idx: RoundIdx) -> usize { + get_round_row(round_idx) +} + +pub fn get_decompose_f_row(round_idx: InitialRound) -> usize { + get_decompose_e_row(round_idx.into()) + DECOMPOSE_EFGH +} + +pub fn get_decompose_g_row(round_idx: InitialRound) -> usize { + get_decompose_f_row(round_idx) + DECOMPOSE_EFGH +} + +pub fn get_upper_sigma_1_row(round_idx: MainRoundIdx) -> usize { + get_decompose_e_row(round_idx.into()) + DECOMPOSE_EFGH + 1 +} + +pub fn get_ch_row(round_idx: MainRoundIdx) -> usize { + get_decompose_e_row(round_idx.into()) + DECOMPOSE_EFGH + SIGMA_1_ROWS + 1 +} + +pub fn get_ch_neg_row(round_idx: MainRoundIdx) -> usize { + get_ch_row(round_idx) + CH_ROWS / 2 +} + +pub fn get_decompose_a_row(round_idx: RoundIdx) -> usize { + match round_idx { + RoundIdx::Init => get_h_row(round_idx) + DECOMPOSE_EFGH, + RoundIdx::Main(mri) => get_ch_neg_row(mri) - 1 + CH_ROWS / 2, + } +} + +pub fn get_upper_sigma_0_row(round_idx: MainRoundIdx) -> usize { + get_decompose_a_row(round_idx.into()) + DECOMPOSE_ABCD + 1 +} + +pub fn get_decompose_b_row(round_idx: InitialRound) -> usize { + get_decompose_a_row(round_idx.into()) + DECOMPOSE_ABCD +} + +pub fn get_decompose_c_row(round_idx: InitialRound) -> usize { + get_decompose_b_row(round_idx) + DECOMPOSE_ABCD +} + +pub fn get_maj_row(round_idx: MainRoundIdx) -> usize { + get_upper_sigma_0_row(round_idx) + SIGMA_0_ROWS +} + +// Get state word rows +pub fn get_h_row(round_idx: RoundIdx) -> usize { + match round_idx { + RoundIdx::Init => get_decompose_g_row(InitialRound) + DECOMPOSE_EFGH, + RoundIdx::Main(mri) => get_ch_row(mri) - 1, + } +} + +pub fn get_h_prime_row(round_idx: MainRoundIdx) -> usize { + get_ch_row(round_idx) +} + +pub fn get_d_row(round_idx: RoundIdx) -> usize { + match round_idx { + RoundIdx::Init => get_decompose_c_row(InitialRound) + DECOMPOSE_ABCD, + RoundIdx::Main(mri) => get_ch_row(mri) + 2, + } +} + +pub fn get_e_new_row(round_idx: MainRoundIdx) -> usize { + get_d_row(round_idx.into()) +} + +pub fn get_a_new_row(round_idx: MainRoundIdx) -> usize { + get_maj_row(round_idx) +} + +pub fn get_digest_abcd_row() -> usize { + SUBREGION_MAIN_ROWS +} + +pub fn get_digest_efgh_row() -> usize { + get_digest_abcd_row() + 2 +} + +impl CompressionConfig { + pub(super) fn decompose_abcd( + &self, + region: &mut Region<'_, pallas::Base>, + row: usize, + val: Value, + ) -> Result { + self.s_decompose_abcd.enable(region, row)?; + + let a_3 = self.extras[0]; + let a_4 = self.extras[1]; + let a_5 = self.message_schedule; + let a_6 = self.extras[2]; + + let spread_pieces = val.map(AbcdVar::pieces); + let spread_pieces = spread_pieces.transpose_vec(6); + + let a = SpreadVar::without_lookup( + region, + a_3, + row + 1, + a_4, + row + 1, + spread_pieces[0].clone().map(SpreadWord::<2, 4>::try_new), + )?; + let b = SpreadVar::with_lookup( + region, + &self.lookup, + row, + spread_pieces[1].clone().map(SpreadWord::<11, 22>::try_new), + )?; + let c_lo = SpreadVar::without_lookup( + region, + a_3, + row, + a_4, + row, + spread_pieces[2].clone().map(SpreadWord::<3, 6>::try_new), + )?; + let c_mid = SpreadVar::without_lookup( + region, + a_5, + row, + a_6, + row, + spread_pieces[3].clone().map(SpreadWord::<3, 6>::try_new), + )?; + let c_hi = SpreadVar::without_lookup( + region, + a_5, + row + 1, + a_6, + row + 1, + spread_pieces[4].clone().map(SpreadWord::<3, 6>::try_new), + )?; + let d = SpreadVar::with_lookup( + region, + &self.lookup, + row + 1, + spread_pieces[5].clone().map(SpreadWord::<10, 20>::try_new), + )?; + + Ok(AbcdVar { + a, + b, + c_lo, + c_mid, + c_hi, + d, + }) + } + + pub(super) fn decompose_efgh( + &self, + region: &mut Region<'_, pallas::Base>, + row: usize, + val: Value, + ) -> Result { + self.s_decompose_efgh.enable(region, row)?; + + let a_3 = self.extras[0]; + let a_4 = self.extras[1]; + let a_5 = self.message_schedule; + let a_6 = self.extras[2]; + + let spread_pieces = val.map(EfghVar::pieces); + let spread_pieces = spread_pieces.transpose_vec(6); + + let a_lo = SpreadVar::without_lookup( + region, + a_3, + row + 1, + a_4, + row + 1, + spread_pieces[0].clone().map(SpreadWord::try_new), + )?; + let a_hi = SpreadVar::without_lookup( + region, + a_5, + row + 1, + a_6, + row + 1, + spread_pieces[1].clone().map(SpreadWord::try_new), + )?; + let b_lo = SpreadVar::without_lookup( + region, + a_3, + row, + a_4, + row, + spread_pieces[2].clone().map(SpreadWord::try_new), + )?; + let b_hi = SpreadVar::without_lookup( + region, + a_5, + row, + a_6, + row, + spread_pieces[3].clone().map(SpreadWord::try_new), + )?; + let c = SpreadVar::with_lookup( + region, + &self.lookup, + row + 1, + spread_pieces[4].clone().map(SpreadWord::try_new), + )?; + let d = SpreadVar::with_lookup( + region, + &self.lookup, + row, + spread_pieces[5].clone().map(SpreadWord::try_new), + )?; + + Ok(EfghVar { + a_lo, + a_hi, + b_lo, + b_hi, + c, + d, + }) + } + + pub(super) fn decompose_a( + &self, + region: &mut Region<'_, pallas::Base>, + round_idx: RoundIdx, + a_val: Value, + ) -> Result { + let row = get_decompose_a_row(round_idx); + + let (dense_halves, spread_halves) = self.assign_word_halves(region, row, a_val)?; + let a_pieces = self.decompose_abcd(region, row, a_val)?; + Ok(RoundWordA::new(a_pieces, dense_halves, spread_halves)) + } + + pub(super) fn decompose_e( + &self, + region: &mut Region<'_, pallas::Base>, + round_idx: RoundIdx, + e_val: Value, + ) -> Result { + let row = get_decompose_e_row(round_idx); + + let (dense_halves, spread_halves) = self.assign_word_halves(region, row, e_val)?; + let e_pieces = self.decompose_efgh(region, row, e_val)?; + Ok(RoundWordE::new(e_pieces, dense_halves, spread_halves)) + } + + pub(super) fn assign_upper_sigma_0( + &self, + region: &mut Region<'_, pallas::Base>, + round_idx: MainRoundIdx, + word: AbcdVar, + ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { + // Rename these here for ease of matching the gates to the specification. + let a_3 = self.extras[0]; + let a_4 = self.extras[1]; + let a_5 = self.message_schedule; + + let row = get_upper_sigma_0_row(round_idx); + + self.s_upper_sigma_0.enable(region, row)?; + + // Assign `spread_a` and copy constraint + word.a + .spread + .copy_advice(|| "spread_a", region, a_3, row + 1)?; + // Assign `spread_b` and copy constraint + word.b.spread.copy_advice(|| "spread_b", region, a_5, row)?; + // Assign `spread_c_lo` and copy constraint + word.c_lo + .spread + .copy_advice(|| "spread_c_lo", region, a_3, row - 1)?; + // Assign `spread_c_mid` and copy constraint + word.c_mid + .spread + .copy_advice(|| "spread_c_mid", region, a_4, row - 1)?; + // Assign `spread_c_hi` and copy constraint + word.c_hi + .spread + .copy_advice(|| "spread_c_hi", region, a_4, row + 1)?; + // Assign `spread_d` and copy constraint + word.d.spread.copy_advice(|| "spread_d", region, a_4, row)?; + + // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} + let r = word.xor_upper_sigma(); + let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); + let r_0_even = r_0.map(even_bits); + let r_0_odd = r_0.map(odd_bits); + + let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); + let r_1_even = r_1.map(even_bits); + let r_1_odd = r_1.map(odd_bits); + + self.assign_sigma_outputs( + region, + &self.lookup, + a_3, + row, + r_0_even, + r_0_odd, + r_1_even, + r_1_odd, + ) + } + + pub(super) fn assign_upper_sigma_1( + &self, + region: &mut Region<'_, pallas::Base>, + round_idx: MainRoundIdx, + word: EfghVar, + ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { + // Rename these here for ease of matching the gates to the specification. + let a_3 = self.extras[0]; + let a_4 = self.extras[1]; + let a_5 = self.message_schedule; + + let row = get_upper_sigma_1_row(round_idx); + + self.s_upper_sigma_1.enable(region, row)?; + + // Assign `spread_a_lo` and copy constraint + word.a_lo + .spread + .copy_advice(|| "spread_a_lo", region, a_3, row + 1)?; + // Assign `spread_a_hi` and copy constraint + word.a_hi + .spread + .copy_advice(|| "spread_a_hi", region, a_4, row + 1)?; + // Assign `spread_b_lo` and copy constraint + word.b_lo + .spread + .copy_advice(|| "spread_b_lo", region, a_3, row - 1)?; + // Assign `spread_b_hi` and copy constraint + word.b_hi + .spread + .copy_advice(|| "spread_b_hi", region, a_4, row - 1)?; + // Assign `spread_c` and copy constraint + word.c.spread.copy_advice(|| "spread_c", region, a_5, row)?; + // Assign `spread_d` and copy constraint + word.d.spread.copy_advice(|| "spread_d", region, a_4, row)?; + + // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} + // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} + let r = word.xor_upper_sigma(); + let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); + let r_0_even = r_0.map(even_bits); + let r_0_odd = r_0.map(odd_bits); + + let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); + let r_1_even = r_1.map(even_bits); + let r_1_odd = r_1.map(odd_bits); + + self.assign_sigma_outputs( + region, + &self.lookup, + a_3, + row, + r_0_even, + r_0_odd, + r_1_even, + r_1_odd, + ) + } + + fn assign_ch_outputs( + &self, + region: &mut Region<'_, pallas::Base>, + row: usize, + r_0_even: Value<[bool; 16]>, + r_0_odd: Value<[bool; 16]>, + r_1_even: Value<[bool; 16]>, + r_1_odd: Value<[bool; 16]>, + ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { + let a_3 = self.extras[0]; + + let (_even, odd) = self.assign_spread_outputs( + region, + &self.lookup, + a_3, + row, + r_0_even, + r_0_odd, + r_1_even, + r_1_odd, + )?; + + Ok(odd) + } + + pub(super) fn assign_ch( + &self, + region: &mut Region<'_, pallas::Base>, + round_idx: MainRoundIdx, + spread_halves_e: RoundWordSpread, + spread_halves_f: RoundWordSpread, + ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { + let a_3 = self.extras[0]; + let a_4 = self.extras[1]; + + let row = get_ch_row(round_idx); + + self.s_ch.enable(region, row)?; + + // Assign and copy spread_e_lo, spread_e_hi + spread_halves_e + .0 + .copy_advice(|| "spread_e_lo", region, a_3, row - 1)?; + spread_halves_e + .1 + .copy_advice(|| "spread_e_hi", region, a_4, row - 1)?; + + // Assign and copy spread_f_lo, spread_f_hi + spread_halves_f + .0 + .copy_advice(|| "spread_f_lo", region, a_3, row + 1)?; + spread_halves_f + .1 + .copy_advice(|| "spread_f_hi", region, a_4, row + 1)?; + + let p: Value<[bool; 64]> = spread_halves_e + .value() + .zip(spread_halves_f.value()) + .map(|(e, f)| i2lebsp(e + f)); + + let p_0: Value<[bool; 32]> = p.map(|p| p[..32].try_into().unwrap()); + let p_0_even = p_0.map(even_bits); + let p_0_odd = p_0.map(odd_bits); + + let p_1: Value<[bool; 32]> = p.map(|p| p[32..].try_into().unwrap()); + let p_1_even = p_1.map(even_bits); + let p_1_odd = p_1.map(odd_bits); + + self.assign_ch_outputs(region, row, p_0_even, p_0_odd, p_1_even, p_1_odd) + } + + pub(super) fn assign_ch_neg( + &self, + region: &mut Region<'_, pallas::Base>, + round_idx: MainRoundIdx, + spread_halves_e: RoundWordSpread, + spread_halves_g: RoundWordSpread, + ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { + let row = get_ch_neg_row(round_idx); + + self.s_ch_neg.enable(region, row)?; + + let a_3 = self.extras[0]; + let a_4 = self.extras[1]; + let a_5 = self.message_schedule; + + // Assign and copy spread_e_lo, spread_e_hi + spread_halves_e + .0 + .copy_advice(|| "spread_e_lo", region, a_5, row - 1)?; + spread_halves_e + .1 + .copy_advice(|| "spread_e_hi", region, a_5, row)?; + + // Assign and copy spread_g_lo, spread_g_hi + spread_halves_g + .0 + .copy_advice(|| "spread_g_lo", region, a_3, row + 1)?; + spread_halves_g + .1 + .copy_advice(|| "spread_g_hi", region, a_4, row + 1)?; + + // Calculate neg_e_lo + let spread_neg_e_lo = spread_halves_e + .0 + .value() + .map(|spread_e_lo| negate_spread(spread_e_lo.0)); + // Assign spread_neg_e_lo + AssignedBits::<32>::assign_bits( + region, + || "spread_neg_e_lo", + a_3, + row - 1, + spread_neg_e_lo, + )?; + + // Calculate neg_e_hi + let spread_neg_e_hi = spread_halves_e + .1 + .value() + .map(|spread_e_hi| negate_spread(spread_e_hi.0)); + // Assign spread_neg_e_hi + AssignedBits::<32>::assign_bits( + region, + || "spread_neg_e_hi", + a_4, + row - 1, + spread_neg_e_hi, + )?; + + let p: Value<[bool; 64]> = { + let spread_neg_e = spread_neg_e_lo + .zip(spread_neg_e_hi) + .map(|(lo, hi)| lebs2ip(&lo) + (1 << 32) * lebs2ip(&hi)); + spread_neg_e + .zip(spread_halves_g.value()) + .map(|(neg_e, g)| i2lebsp(neg_e + g)) + }; + + let p_0: Value<[bool; 32]> = p.map(|p| p[..32].try_into().unwrap()); + let p_0_even = p_0.map(even_bits); + let p_0_odd = p_0.map(odd_bits); + + let p_1: Value<[bool; 32]> = p.map(|p| p[32..].try_into().unwrap()); + let p_1_even = p_1.map(even_bits); + let p_1_odd = p_1.map(odd_bits); + + self.assign_ch_outputs(region, row, p_0_even, p_0_odd, p_1_even, p_1_odd) + } + + fn assign_maj_outputs( + &self, + region: &mut Region<'_, pallas::Base>, + row: usize, + r_0_even: Value<[bool; 16]>, + r_0_odd: Value<[bool; 16]>, + r_1_even: Value<[bool; 16]>, + r_1_odd: Value<[bool; 16]>, + ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { + let a_3 = self.extras[0]; + let (_even, odd) = self.assign_spread_outputs( + region, + &self.lookup, + a_3, + row, + r_0_even, + r_0_odd, + r_1_even, + r_1_odd, + )?; + + Ok(odd) + } + + pub(super) fn assign_maj( + &self, + region: &mut Region<'_, pallas::Base>, + round_idx: MainRoundIdx, + spread_halves_a: RoundWordSpread, + spread_halves_b: RoundWordSpread, + spread_halves_c: RoundWordSpread, + ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { + let a_4 = self.extras[1]; + let a_5 = self.message_schedule; + + let row = get_maj_row(round_idx); + + self.s_maj.enable(region, row)?; + + // Assign and copy spread_a_lo, spread_a_hi + spread_halves_a + .0 + .copy_advice(|| "spread_a_lo", region, a_4, row - 1)?; + spread_halves_a + .1 + .copy_advice(|| "spread_a_hi", region, a_5, row - 1)?; + + // Assign and copy spread_b_lo, spread_b_hi + spread_halves_b + .0 + .copy_advice(|| "spread_b_lo", region, a_4, row)?; + spread_halves_b + .1 + .copy_advice(|| "spread_b_hi", region, a_5, row)?; + + // Assign and copy spread_c_lo, spread_c_hi + spread_halves_c + .0 + .copy_advice(|| "spread_c_lo", region, a_4, row + 1)?; + spread_halves_c + .1 + .copy_advice(|| "spread_c_hi", region, a_5, row + 1)?; + + let m: Value<[bool; 64]> = spread_halves_a + .value() + .zip(spread_halves_b.value()) + .zip(spread_halves_c.value()) + .map(|((a, b), c)| i2lebsp(a + b + c)); + + let m_0: Value<[bool; 32]> = m.map(|m| m[..32].try_into().unwrap()); + let m_0_even = m_0.map(even_bits); + let m_0_odd = m_0.map(odd_bits); + + let m_1: Value<[bool; 32]> = m.map(|m| m[32..].try_into().unwrap()); + let m_1_even = m_1.map(even_bits); + let m_1_odd = m_1.map(odd_bits); + + self.assign_maj_outputs(region, row, m_0_even, m_0_odd, m_1_even, m_1_odd) + } + + // s_h_prime to get H' = H + Ch(E, F, G) + s_upper_sigma_1(E) + K + W + #[allow(clippy::too_many_arguments)] + pub(super) fn assign_h_prime( + &self, + region: &mut Region<'_, pallas::Base>, + round_idx: MainRoundIdx, + h: RoundWordDense, + ch: (AssignedBits<16>, AssignedBits<16>), + ch_neg: (AssignedBits<16>, AssignedBits<16>), + sigma_1: (AssignedBits<16>, AssignedBits<16>), + k: u32, + w: &(AssignedBits<16>, AssignedBits<16>), + ) -> Result { + let row = get_h_prime_row(round_idx); + self.s_h_prime.enable(region, row)?; + + let a_4 = self.extras[1]; + let a_5 = self.message_schedule; + let a_6 = self.extras[2]; + let a_7 = self.extras[3]; + let a_8 = self.extras[4]; + let a_9 = self.extras[5]; + + // Assign and copy h + h.0.copy_advice(|| "h_lo", region, a_7, row - 1)?; + h.1.copy_advice(|| "h_hi", region, a_7, row)?; + + // Assign and copy sigma_1 + sigma_1.0.copy_advice(|| "sigma_1_lo", region, a_4, row)?; + sigma_1.1.copy_advice(|| "sigma_1_hi", region, a_5, row)?; + + // Assign k + let k: [bool; 32] = i2lebsp(k.into()); + let k_lo: [bool; 16] = k[..16].try_into().unwrap(); + let k_hi: [bool; 16] = k[16..].try_into().unwrap(); + { + AssignedBits::<16>::assign_bits(region, || "k_lo", a_6, row - 1, Value::known(k_lo))?; + AssignedBits::<16>::assign_bits(region, || "k_hi", a_6, row, Value::known(k_hi))?; + } + + // Assign and copy w + w.0.copy_advice(|| "w_lo", region, a_8, row - 1)?; + w.1.copy_advice(|| "w_hi", region, a_8, row)?; + + // Assign and copy ch + ch.1.copy_advice(|| "ch_neg_hi", region, a_6, row + 1)?; + + // Assign and copy ch_neg + ch_neg.0.copy_advice(|| "ch_neg_lo", region, a_5, row - 1)?; + ch_neg.1.copy_advice(|| "ch_neg_hi", region, a_5, row + 1)?; + + // Assign h_prime_lo, h_prime_hi, h_prime_carry + { + let (h_prime, h_prime_carry) = sum_with_carry(vec![ + (h.0.value_u16(), h.1.value_u16()), + (ch.0.value_u16(), ch.1.value_u16()), + (ch_neg.0.value_u16(), ch_neg.1.value_u16()), + (sigma_1.0.value_u16(), sigma_1.1.value_u16()), + ( + Value::known(lebs2ip(&k_lo) as u16), + Value::known(lebs2ip(&k_hi) as u16), + ), + (w.0.value_u16(), w.1.value_u16()), + ]); + + region.assign_advice( + || "h_prime_carry", + a_9, + row + 1, + || h_prime_carry.map(pallas::Base::from), + )?; + + let h_prime: Value<[bool; 32]> = h_prime.map(|w| i2lebsp(w.into())); + let h_prime_lo: Value<[bool; 16]> = h_prime.map(|w| w[..16].try_into().unwrap()); + let h_prime_hi: Value<[bool; 16]> = h_prime.map(|w| w[16..].try_into().unwrap()); + + let h_prime_lo = + AssignedBits::<16>::assign_bits(region, || "h_prime_lo", a_7, row + 1, h_prime_lo)?; + let h_prime_hi = + AssignedBits::<16>::assign_bits(region, || "h_prime_hi", a_8, row + 1, h_prime_hi)?; + + Ok((h_prime_lo, h_prime_hi).into()) + } + } + + // s_e_new to get E_new = H' + D + pub(super) fn assign_e_new( + &self, + region: &mut Region<'_, pallas::Base>, + round_idx: MainRoundIdx, + d: &RoundWordDense, + h_prime: &RoundWordDense, + ) -> Result { + let row = get_e_new_row(round_idx); + + self.s_e_new.enable(region, row)?; + + let a_7 = self.extras[3]; + let a_8 = self.extras[4]; + let a_9 = self.extras[5]; + + // Assign and copy d_lo, d_hi + d.0.copy_advice(|| "d_lo", region, a_7, row)?; + d.1.copy_advice(|| "d_hi", region, a_7, row + 1)?; + + // Assign e_new, e_new_carry + let (e_new, e_new_carry) = sum_with_carry(vec![ + (h_prime.0.value_u16(), h_prime.1.value_u16()), + (d.0.value_u16(), d.1.value_u16()), + ]); + + let e_new_dense = self.assign_word_halves_dense(region, row, a_8, row + 1, a_8, e_new)?; + region.assign_advice( + || "e_new_carry", + a_9, + row + 1, + || e_new_carry.map(pallas::Base::from), + )?; + + Ok(e_new_dense) + } + + // s_a_new to get A_new = H' + Maj(A, B, C) + s_upper_sigma_0(A) + pub(super) fn assign_a_new( + &self, + region: &mut Region<'_, pallas::Base>, + round_idx: MainRoundIdx, + maj: (AssignedBits<16>, AssignedBits<16>), + sigma_0: (AssignedBits<16>, AssignedBits<16>), + h_prime: RoundWordDense, + ) -> Result { + let row = get_a_new_row(round_idx); + + self.s_a_new.enable(region, row)?; + + let a_3 = self.extras[0]; + let a_6 = self.extras[2]; + let a_7 = self.extras[3]; + let a_8 = self.extras[4]; + let a_9 = self.extras[5]; + + // Assign and copy maj_1 + maj.1.copy_advice(|| "maj_1_hi", region, a_3, row - 1)?; + + // Assign and copy sigma_0 + sigma_0.0.copy_advice(|| "sigma_0_lo", region, a_6, row)?; + sigma_0 + .1 + .copy_advice(|| "sigma_0_hi", region, a_6, row + 1)?; + + // Assign and copy h_prime + h_prime + .0 + .copy_advice(|| "h_prime_lo", region, a_7, row - 1)?; + h_prime + .1 + .copy_advice(|| "h_prime_hi", region, a_8, row - 1)?; + + // Assign a_new, a_new_carry + let (a_new, a_new_carry) = sum_with_carry(vec![ + (h_prime.0.value_u16(), h_prime.1.value_u16()), + (sigma_0.0.value_u16(), sigma_0.1.value_u16()), + (maj.0.value_u16(), maj.1.value_u16()), + ]); + + let a_new_dense = self.assign_word_halves_dense(region, row, a_8, row + 1, a_8, a_new)?; + region.assign_advice( + || "a_new_carry", + a_9, + row, + || a_new_carry.map(pallas::Base::from), + )?; + + Ok(a_new_dense) + } + + pub fn assign_word_halves_dense( + &self, + region: &mut Region<'_, pallas::Base>, + lo_row: usize, + lo_col: Column, + hi_row: usize, + hi_col: Column, + word: Value, + ) -> Result { + let word: Value<[bool; 32]> = word.map(|w| i2lebsp(w.into())); + + let lo = { + let lo: Value<[bool; 16]> = word.map(|w| w[..16].try_into().unwrap()); + AssignedBits::<16>::assign_bits(region, || "lo", lo_col, lo_row, lo)? + }; + + let hi = { + let hi: Value<[bool; 16]> = word.map(|w| w[16..].try_into().unwrap()); + AssignedBits::<16>::assign_bits(region, || "hi", hi_col, hi_row, hi)? + }; + + Ok((lo, hi).into()) + } + + // Assign hi and lo halves for both dense and spread versions of a word + #[allow(clippy::type_complexity)] + pub fn assign_word_halves( + &self, + region: &mut Region<'_, pallas::Base>, + row: usize, + word: Value, + ) -> Result<(RoundWordDense, RoundWordSpread), Error> { + // Rename these here for ease of matching the gates to the specification. + let a_7 = self.extras[3]; + let a_8 = self.extras[4]; + + let word: Value<[bool; 32]> = word.map(|w| i2lebsp(w.into())); + let lo: Value<[bool; 16]> = word.map(|w| w[..16].try_into().unwrap()); + let hi: Value<[bool; 16]> = word.map(|w| w[16..].try_into().unwrap()); + + let w_lo = SpreadVar::without_lookup(region, a_7, row, a_8, row, lo.map(SpreadWord::new))?; + let w_hi = + SpreadVar::without_lookup(region, a_7, row + 1, a_8, row + 1, hi.map(SpreadWord::new))?; + + Ok(( + (w_lo.dense, w_hi.dense).into(), + (w_lo.spread, w_hi.spread).into(), + )) + } +} + +#[allow(clippy::many_single_char_names)] +pub fn match_state( + state: State, +) -> ( + RoundWordA, + RoundWord, + RoundWord, + RoundWordDense, + RoundWordE, + RoundWord, + RoundWord, + RoundWordDense, +) { + let a = match state.a { + Some(StateWord::A(a)) => a, + _ => unreachable!(), + }; + let b = match state.b { + Some(StateWord::B(b)) => b, + _ => unreachable!(), + }; + let c = match state.c { + Some(StateWord::C(c)) => c, + _ => unreachable!(), + }; + let d = match state.d { + Some(StateWord::D(d)) => d, + _ => unreachable!(), + }; + let e = match state.e { + Some(StateWord::E(e)) => e, + _ => unreachable!(), + }; + let f = match state.f { + Some(StateWord::F(f)) => f, + _ => unreachable!(), + }; + let g = match state.g { + Some(StateWord::G(g)) => g, + _ => unreachable!(), + }; + let h = match state.h { + Some(StateWord::H(h)) => h, + _ => unreachable!(), + }; + + (a, b, c, d, e, f, g, h) +} diff --git a/halo2_gadgets_optimized/src/sha256/table16/compression/subregion_digest.rs b/halo2_gadgets_optimized/src/sha256/table16/compression/subregion_digest.rs new file mode 100644 index 0000000000..28df0ba9e3 --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16/compression/subregion_digest.rs @@ -0,0 +1,102 @@ +use super::super::{super::DIGEST_SIZE, BlockWord, RoundWordDense}; +use super::{compression_util::*, CompressionConfig, State}; +use halo2_proofs::{ + circuit::{Region, Value}, + pasta::pallas, + plonk::{Advice, Column, Error}, +}; + +impl CompressionConfig { + #[allow(clippy::many_single_char_names)] + pub fn assign_digest( + &self, + region: &mut Region<'_, pallas::Base>, + state: State, + ) -> Result<[BlockWord; DIGEST_SIZE], Error> { + let a_3 = self.extras[0]; + let a_4 = self.extras[1]; + let a_5 = self.message_schedule; + let a_6 = self.extras[2]; + let a_7 = self.extras[3]; + let a_8 = self.extras[4]; + + let (a, b, c, d, e, f, g, h) = match_state(state); + + let abcd_row = 0; + self.s_digest.enable(region, abcd_row)?; + let efgh_row = abcd_row + 2; + self.s_digest.enable(region, efgh_row)?; + + // Assign digest for A, B, C, D + a.dense_halves + .0 + .copy_advice(|| "a_lo", region, a_3, abcd_row)?; + a.dense_halves + .1 + .copy_advice(|| "a_hi", region, a_4, abcd_row)?; + let a = a.dense_halves.value(); + region.assign_advice( + || "a", + a_5, + abcd_row, + || a.map(|a| pallas::Base::from(a as u64)), + )?; + + let b = self.assign_digest_word(region, abcd_row, a_6, a_7, a_8, b.dense_halves)?; + let c = self.assign_digest_word(region, abcd_row + 1, a_3, a_4, a_5, c.dense_halves)?; + let d = self.assign_digest_word(region, abcd_row + 1, a_6, a_7, a_8, d)?; + + // Assign digest for E, F, G, H + e.dense_halves + .0 + .copy_advice(|| "e_lo", region, a_3, efgh_row)?; + e.dense_halves + .1 + .copy_advice(|| "e_hi", region, a_4, efgh_row)?; + let e = e.dense_halves.value(); + region.assign_advice( + || "e", + a_5, + efgh_row, + || e.map(|e| pallas::Base::from(e as u64)), + )?; + + let f = self.assign_digest_word(region, efgh_row, a_6, a_7, a_8, f.dense_halves)?; + let g = self.assign_digest_word(region, efgh_row + 1, a_3, a_4, a_5, g.dense_halves)?; + let h = self.assign_digest_word(region, efgh_row + 1, a_6, a_7, a_8, h)?; + + Ok([ + BlockWord(a), + BlockWord(b), + BlockWord(c), + BlockWord(d), + BlockWord(e), + BlockWord(f), + BlockWord(g), + BlockWord(h), + ]) + } + + fn assign_digest_word( + &self, + region: &mut Region<'_, pallas::Base>, + row: usize, + lo_col: Column, + hi_col: Column, + word_col: Column, + dense_halves: RoundWordDense, + ) -> Result, Error> { + dense_halves.0.copy_advice(|| "lo", region, lo_col, row)?; + dense_halves.1.copy_advice(|| "hi", region, hi_col, row)?; + + let val = dense_halves.value(); + region.assign_advice( + || "word", + word_col, + row, + || val.map(|val| pallas::Base::from(val as u64)), + )?; + + Ok(val) + } +} diff --git a/halo2_gadgets_optimized/src/sha256/table16/compression/subregion_initial.rs b/halo2_gadgets_optimized/src/sha256/table16/compression/subregion_initial.rs new file mode 100644 index 0000000000..56e7dc6178 --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16/compression/subregion_initial.rs @@ -0,0 +1,156 @@ +use super::super::{RoundWord, StateWord, STATE}; +use super::{compression_util::*, CompressionConfig, State}; +use halo2_proofs::{ + circuit::{Region, Value}, + pasta::pallas, + plonk::Error, +}; + +impl CompressionConfig { + #[allow(clippy::many_single_char_names)] + pub fn initialize_iv( + &self, + region: &mut Region<'_, pallas::Base>, + iv: [u32; STATE], + ) -> Result { + let a_7 = self.extras[3]; + + // Decompose E into (6, 5, 14, 7)-bit chunks + let e = self.decompose_e(region, RoundIdx::Init, Value::known(iv[4]))?; + + // Decompose F, G + let f = self.decompose_f(region, InitialRound, Value::known(iv[5]))?; + let g = self.decompose_g(region, InitialRound, Value::known(iv[6]))?; + + // Assign H + let h_row = get_h_row(RoundIdx::Init); + let h = + self.assign_word_halves_dense(region, h_row, a_7, h_row + 1, a_7, Value::known(iv[7]))?; + + // Decompose A into (2, 11, 9, 10)-bit chunks + let a = self.decompose_a(region, RoundIdx::Init, Value::known(iv[0]))?; + + // Decompose B, C + let b = self.decompose_b(region, InitialRound, Value::known(iv[1]))?; + let c = self.decompose_c(region, InitialRound, Value::known(iv[2]))?; + + // Assign D + let d_row = get_d_row(RoundIdx::Init); + let d = + self.assign_word_halves_dense(region, d_row, a_7, d_row + 1, a_7, Value::known(iv[3]))?; + + Ok(State::new( + StateWord::A(a), + StateWord::B(b), + StateWord::C(c), + StateWord::D(d), + StateWord::E(e), + StateWord::F(f), + StateWord::G(g), + StateWord::H(h), + )) + } + + #[allow(clippy::many_single_char_names)] + pub fn initialize_state( + &self, + region: &mut Region<'_, pallas::Base>, + state: State, + ) -> Result { + let a_7 = self.extras[3]; + let (a, b, c, d, e, f, g, h) = match_state(state); + + // Decompose E into (6, 5, 14, 7)-bit chunks + let e = e.dense_halves.value(); + let e = self.decompose_e(region, RoundIdx::Init, e)?; + + // Decompose F, G + let f = f.dense_halves.value(); + let f = self.decompose_f(region, InitialRound, f)?; + let g = g.dense_halves.value(); + let g = self.decompose_g(region, InitialRound, g)?; + + // Assign H + let h = h.value(); + let h_row = get_h_row(RoundIdx::Init); + let h = self.assign_word_halves_dense(region, h_row, a_7, h_row + 1, a_7, h)?; + + // Decompose A into (2, 11, 9, 10)-bit chunks + let a = a.dense_halves.value(); + let a = self.decompose_a(region, RoundIdx::Init, a)?; + + // Decompose B, C + let b = b.dense_halves.value(); + let b = self.decompose_b(region, InitialRound, b)?; + let c = c.dense_halves.value(); + let c = self.decompose_c(region, InitialRound, c)?; + + // Assign D + let d = d.value(); + let d_row = get_d_row(RoundIdx::Init); + let d = self.assign_word_halves_dense(region, d_row, a_7, d_row + 1, a_7, d)?; + + Ok(State::new( + StateWord::A(a), + StateWord::B(b), + StateWord::C(c), + StateWord::D(d), + StateWord::E(e), + StateWord::F(f), + StateWord::G(g), + StateWord::H(h), + )) + } + + fn decompose_b( + &self, + region: &mut Region<'_, pallas::Base>, + round_idx: InitialRound, + b_val: Value, + ) -> Result { + let row = get_decompose_b_row(round_idx); + + let (dense_halves, spread_halves) = self.assign_word_halves(region, row, b_val)?; + self.decompose_abcd(region, row, b_val)?; + Ok(RoundWord::new(dense_halves, spread_halves)) + } + + fn decompose_c( + &self, + region: &mut Region<'_, pallas::Base>, + round_idx: InitialRound, + c_val: Value, + ) -> Result { + let row = get_decompose_c_row(round_idx); + + let (dense_halves, spread_halves) = self.assign_word_halves(region, row, c_val)?; + self.decompose_abcd(region, row, c_val)?; + Ok(RoundWord::new(dense_halves, spread_halves)) + } + + fn decompose_f( + &self, + region: &mut Region<'_, pallas::Base>, + round_idx: InitialRound, + f_val: Value, + ) -> Result { + let row = get_decompose_f_row(round_idx); + + let (dense_halves, spread_halves) = self.assign_word_halves(region, row, f_val)?; + self.decompose_efgh(region, row, f_val)?; + Ok(RoundWord::new(dense_halves, spread_halves)) + } + + fn decompose_g( + &self, + region: &mut Region<'_, pallas::Base>, + round_idx: InitialRound, + g_val: Value, + ) -> Result { + let row = get_decompose_g_row(round_idx); + + let (dense_halves, spread_halves) = self.assign_word_halves(region, row, g_val)?; + self.decompose_efgh(region, row, g_val)?; + Ok(RoundWord::new(dense_halves, spread_halves)) + } +} diff --git a/halo2_gadgets_optimized/src/sha256/table16/compression/subregion_main.rs b/halo2_gadgets_optimized/src/sha256/table16/compression/subregion_main.rs new file mode 100644 index 0000000000..2a0f433c14 --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16/compression/subregion_main.rs @@ -0,0 +1,126 @@ +use super::super::{AssignedBits, RoundWord, RoundWordA, RoundWordE, StateWord, ROUND_CONSTANTS}; +use super::{compression_util::*, CompressionConfig, State}; +use halo2_proofs::{circuit::Region, pasta::pallas, plonk::Error}; + +impl CompressionConfig { + #[allow(clippy::many_single_char_names)] + pub fn assign_round( + &self, + region: &mut Region<'_, pallas::Base>, + round_idx: MainRoundIdx, + state: State, + schedule_word: &(AssignedBits<16>, AssignedBits<16>), + ) -> Result { + let a_3 = self.extras[0]; + let a_4 = self.extras[1]; + let a_7 = self.extras[3]; + + let (a, b, c, d, e, f, g, h) = match_state(state); + + // s_upper_sigma_1(E) + let sigma_1 = self.assign_upper_sigma_1(region, round_idx, e.pieces.clone().unwrap())?; + + // Ch(E, F, G) + let ch = self.assign_ch( + region, + round_idx, + e.spread_halves.clone().unwrap(), + f.spread_halves.clone(), + )?; + let ch_neg = self.assign_ch_neg( + region, + round_idx, + e.spread_halves.clone().unwrap(), + g.spread_halves.clone(), + )?; + + // s_upper_sigma_0(A) + let sigma_0 = self.assign_upper_sigma_0(region, round_idx, a.pieces.clone().unwrap())?; + + // Maj(A, B, C) + let maj = self.assign_maj( + region, + round_idx, + a.spread_halves.clone().unwrap(), + b.spread_halves.clone(), + c.spread_halves.clone(), + )?; + + // H' = H + Ch(E, F, G) + s_upper_sigma_1(E) + K + W + let h_prime = self.assign_h_prime( + region, + round_idx, + h, + ch, + ch_neg, + sigma_1, + ROUND_CONSTANTS[round_idx.as_usize()], + schedule_word, + )?; + + // E_new = H' + D + let e_new_dense = self.assign_e_new(region, round_idx, &d, &h_prime)?; + let e_new_val = e_new_dense.value(); + + // A_new = H' + Maj(A, B, C) + sigma_0(A) + let a_new_dense = self.assign_a_new(region, round_idx, maj, sigma_0, h_prime)?; + let a_new_val = a_new_dense.value(); + + if round_idx < 63.into() { + // Assign and copy A_new + let a_new_row = get_decompose_a_row((round_idx + 1).into()); + a_new_dense + .0 + .copy_advice(|| "a_new_lo", region, a_7, a_new_row)?; + a_new_dense + .1 + .copy_advice(|| "a_new_hi", region, a_7, a_new_row + 1)?; + + // Assign and copy E_new + let e_new_row = get_decompose_e_row((round_idx + 1).into()); + e_new_dense + .0 + .copy_advice(|| "e_new_lo", region, a_7, e_new_row)?; + e_new_dense + .1 + .copy_advice(|| "e_new_hi", region, a_7, e_new_row + 1)?; + + // Decompose A into (2, 11, 9, 10)-bit chunks + let a_new = self.decompose_a(region, (round_idx + 1).into(), a_new_val)?; + + // Decompose E into (6, 5, 14, 7)-bit chunks + let e_new = self.decompose_e(region, (round_idx + 1).into(), e_new_val)?; + + Ok(State::new( + StateWord::A(a_new), + StateWord::B(RoundWord::new(a.dense_halves, a.spread_halves.unwrap())), + StateWord::C(b), + StateWord::D(c.dense_halves), + StateWord::E(e_new), + StateWord::F(RoundWord::new(e.dense_halves, e.spread_halves.unwrap())), + StateWord::G(f), + StateWord::H(g.dense_halves), + )) + } else { + let abcd_row = get_digest_abcd_row(); + let efgh_row = get_digest_efgh_row(); + + let a_final = + self.assign_word_halves_dense(region, abcd_row, a_3, abcd_row, a_4, a_new_val)?; + + let e_final = + self.assign_word_halves_dense(region, efgh_row, a_3, efgh_row, a_4, e_new_val)?; + + Ok(State::new( + StateWord::A(RoundWordA::new_dense(a_final)), + StateWord::B(RoundWord::new(a.dense_halves, a.spread_halves.unwrap())), + StateWord::C(b), + StateWord::D(c.dense_halves), + StateWord::E(RoundWordE::new_dense(e_final)), + StateWord::F(RoundWord::new(e.dense_halves, e.spread_halves.unwrap())), + StateWord::G(f), + StateWord::H(g.dense_halves), + )) + } + } +} diff --git a/halo2_gadgets_optimized/src/sha256/table16/gates.rs b/halo2_gadgets_optimized/src/sha256/table16/gates.rs new file mode 100644 index 0000000000..5c5e9da372 --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16/gates.rs @@ -0,0 +1,125 @@ +use group::ff::PrimeField; +use halo2_proofs::plonk::Expression; + +pub struct Gate(pub Expression); + +impl Gate { + fn ones() -> Expression { + Expression::Constant(F::ONE) + } + + // Helper gates + fn lagrange_interpolate( + var: Expression, + points: Vec, + evals: Vec, + ) -> (F, Expression) { + assert_eq!(points.len(), evals.len()); + let deg = points.len(); + + fn factorial(n: u64) -> u64 { + if n < 2 { + 1 + } else { + n * factorial(n - 1) + } + } + + // Scale the whole expression by factor to avoid divisions + let factor = factorial((deg - 1) as u64); + + let numerator = |var: Expression, eval: u32, idx: u64| { + let mut expr = Self::ones(); + for i in 0..deg { + let i = i as u64; + if i != idx { + expr = expr * (Self::ones() * (-F::ONE) * F::from(i) + var.clone()); + } + } + expr * F::from(u64::from(eval)) + }; + let denominator = |idx: i32| { + let mut denom: i32 = 1; + for i in 0..deg { + let i = i as i32; + if i != idx { + denom *= idx - i + } + } + if denom < 0 { + -F::ONE * F::from(factor / (-denom as u64)) + } else { + F::from(factor / (denom as u64)) + } + }; + + let mut expr = Self::ones() * F::ZERO; + for ((idx, _), eval) in points.iter().enumerate().zip(evals.iter()) { + expr = expr + numerator(var.clone(), *eval, idx as u64) * denominator(idx as i32) + } + + (F::from(factor), expr) + } + + pub fn range_check(value: Expression, lower_range: u64, upper_range: u64) -> Expression { + let mut expr = Self::ones(); + for i in lower_range..(upper_range + 1) { + expr = expr * (Self::ones() * (-F::ONE) * F::from(i) + value.clone()) + } + expr + } + + /// Spread and range check on 2-bit word + pub fn two_bit_spread_and_range( + dense: Expression, + spread: Expression, + ) -> impl Iterator)> { + let two_bit_spread = |dense: Expression, spread: Expression| { + let (factor, lagrange_poly) = Self::lagrange_interpolate( + dense, + vec![0b00, 0b01, 0b10, 0b11], + vec![0b0000, 0b0001, 0b0100, 0b0101], + ); + + lagrange_poly - spread * factor + }; + + std::iter::empty() + .chain(Some(( + "two_bit_range_check", + Self::range_check(dense.clone(), 0, (1 << 2) - 1), + ))) + .chain(Some(( + "two_bit_spread_check", + two_bit_spread(dense, spread), + ))) + } + + /// Spread and range check on 3-bit word + pub fn three_bit_spread_and_range( + dense: Expression, + spread: Expression, + ) -> impl Iterator)> { + let three_bit_spread = |dense: Expression, spread: Expression| { + let (factor, lagrange_poly) = Self::lagrange_interpolate( + dense, + vec![0b000, 0b001, 0b010, 0b011, 0b100, 0b101, 0b110, 0b111], + vec![ + 0b000000, 0b000001, 0b000100, 0b000101, 0b010000, 0b010001, 0b010100, 0b010101, + ], + ); + + lagrange_poly - spread * factor + }; + + std::iter::empty() + .chain(Some(( + "three_bit_range_check", + Self::range_check(dense.clone(), 0, (1 << 3) - 1), + ))) + .chain(Some(( + "three_bit_spread_check", + three_bit_spread(dense, spread), + ))) + } +} diff --git a/halo2_gadgets_optimized/src/sha256/table16/message_schedule.rs b/halo2_gadgets_optimized/src/sha256/table16/message_schedule.rs new file mode 100644 index 0000000000..0f92b2e132 --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16/message_schedule.rs @@ -0,0 +1,455 @@ +use std::convert::TryInto; + +use super::{super::BLOCK_SIZE, AssignedBits, BlockWord, SpreadInputs, Table16Assignment, ROUNDS}; +use halo2_proofs::{ + circuit::Layouter, + pasta::pallas, + plonk::{Advice, Column, ConstraintSystem, Error, Selector}, + poly::Rotation, +}; + +mod schedule_gates; +mod schedule_util; +mod subregion1; +mod subregion2; +mod subregion3; + +use schedule_gates::ScheduleGate; +use schedule_util::*; + +#[cfg(test)] +pub use schedule_util::msg_schedule_test_input; + +#[derive(Clone, Debug)] +pub(super) struct MessageWord(AssignedBits<32>); + +impl std::ops::Deref for MessageWord { + type Target = AssignedBits<32>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Clone, Debug)] +pub(super) struct MessageScheduleConfig { + lookup: SpreadInputs, + message_schedule: Column, + extras: [Column; 6], + + /// Construct a word using reduce_4. + s_word: Selector, + /// Decomposition gate for W_0, W_62, W_63. + s_decompose_0: Selector, + /// Decomposition gate for W_[1..14] + s_decompose_1: Selector, + /// Decomposition gate for W_[14..49] + s_decompose_2: Selector, + /// Decomposition gate for W_[49..62] + s_decompose_3: Selector, + /// sigma_0 gate for W_[1..14] + s_lower_sigma_0: Selector, + /// sigma_1 gate for W_[49..62] + s_lower_sigma_1: Selector, + /// sigma_0_v2 gate for W_[14..49] + s_lower_sigma_0_v2: Selector, + /// sigma_1_v2 gate for W_[14..49] + s_lower_sigma_1_v2: Selector, +} + +impl Table16Assignment for MessageScheduleConfig {} + +impl MessageScheduleConfig { + /// Configures the message schedule. + /// + /// `message_schedule` is the column into which the message schedule will be placed. + /// The caller must create appropriate permutations in order to load schedule words + /// into the compression rounds. + /// + /// `extras` contains columns that the message schedule will only use for internal + /// gates, and will not place any constraints on (such as lookup constraints) outside + /// itself. + #[allow(clippy::many_single_char_names)] + pub(super) fn configure( + meta: &mut ConstraintSystem, + lookup: SpreadInputs, + message_schedule: Column, + extras: [Column; 6], + ) -> Self { + // Create fixed columns for the selectors we will require. + let s_word = meta.selector(); + let s_decompose_0 = meta.selector(); + let s_decompose_1 = meta.selector(); + let s_decompose_2 = meta.selector(); + let s_decompose_3 = meta.selector(); + let s_lower_sigma_0 = meta.selector(); + let s_lower_sigma_1 = meta.selector(); + let s_lower_sigma_0_v2 = meta.selector(); + let s_lower_sigma_1_v2 = meta.selector(); + + // Rename these here for ease of matching the gates to the specification. + let a_0 = lookup.tag; + let a_1 = lookup.dense; + let a_2 = lookup.spread; + let a_3 = extras[0]; + let a_4 = extras[1]; + let a_5 = message_schedule; + let a_6 = extras[2]; + let a_7 = extras[3]; + let a_8 = extras[4]; + let a_9 = extras[5]; + + // s_word for W_[16..64] + meta.create_gate("s_word for W_[16..64]", |meta| { + let s_word = meta.query_selector(s_word); + + let sigma_0_lo = meta.query_advice(a_6, Rotation::prev()); + let sigma_0_hi = meta.query_advice(a_6, Rotation::cur()); + + let sigma_1_lo = meta.query_advice(a_7, Rotation::prev()); + let sigma_1_hi = meta.query_advice(a_7, Rotation::cur()); + + let w_minus_9_lo = meta.query_advice(a_8, Rotation::prev()); + let w_minus_9_hi = meta.query_advice(a_8, Rotation::cur()); + + let w_minus_16_lo = meta.query_advice(a_3, Rotation::prev()); + let w_minus_16_hi = meta.query_advice(a_4, Rotation::prev()); + + let word = meta.query_advice(a_5, Rotation::cur()); + let carry = meta.query_advice(a_9, Rotation::cur()); + + ScheduleGate::s_word( + s_word, + sigma_0_lo, + sigma_0_hi, + sigma_1_lo, + sigma_1_hi, + w_minus_9_lo, + w_minus_9_hi, + w_minus_16_lo, + w_minus_16_hi, + word, + carry, + ) + }); + + // s_decompose_0 for all words + meta.create_gate("s_decompose_0", |meta| { + let s_decompose_0 = meta.query_selector(s_decompose_0); + let lo = meta.query_advice(a_3, Rotation::cur()); + let hi = meta.query_advice(a_4, Rotation::cur()); + let word = meta.query_advice(a_5, Rotation::cur()); + + ScheduleGate::s_decompose_0(s_decompose_0, lo, hi, word) + }); + + // s_decompose_1 for W_[1..14] + // (3, 4, 11, 14)-bit chunks + meta.create_gate("s_decompose_1", |meta| { + let s_decompose_1 = meta.query_selector(s_decompose_1); + let a = meta.query_advice(a_3, Rotation::next()); // 3-bit chunk + let b = meta.query_advice(a_4, Rotation::next()); // 4-bit chunk + let c = meta.query_advice(a_1, Rotation::next()); // 11-bit chunk + let tag_c = meta.query_advice(a_0, Rotation::next()); + let d = meta.query_advice(a_1, Rotation::cur()); // 14-bit chunk + let tag_d = meta.query_advice(a_0, Rotation::cur()); + let word = meta.query_advice(a_5, Rotation::cur()); + + ScheduleGate::s_decompose_1(s_decompose_1, a, b, c, tag_c, d, tag_d, word) + }); + + // s_decompose_2 for W_[14..49] + // (3, 4, 3, 7, 1, 1, 13)-bit chunks + meta.create_gate("s_decompose_2", |meta| { + let s_decompose_2 = meta.query_selector(s_decompose_2); + let a = meta.query_advice(a_3, Rotation::prev()); // 3-bit chunk + let b = meta.query_advice(a_1, Rotation::next()); // 4-bit chunk + let c = meta.query_advice(a_4, Rotation::prev()); // 3-bit chunk + let d = meta.query_advice(a_1, Rotation::cur()); // 7-bit chunk + let tag_d = meta.query_advice(a_0, Rotation::cur()); + let e = meta.query_advice(a_3, Rotation::next()); // 1-bit chunk + let f = meta.query_advice(a_4, Rotation::next()); // 1-bit chunk + let g = meta.query_advice(a_1, Rotation::prev()); // 13-bit chunk + let tag_g = meta.query_advice(a_0, Rotation::prev()); + let word = meta.query_advice(a_5, Rotation::cur()); + + ScheduleGate::s_decompose_2(s_decompose_2, a, b, c, d, tag_d, e, f, g, tag_g, word) + }); + + // s_decompose_3 for W_49 to W_61 + // (10, 7, 2, 13)-bit chunks + meta.create_gate("s_decompose_3", |meta| { + let s_decompose_3 = meta.query_selector(s_decompose_3); + let a = meta.query_advice(a_1, Rotation::next()); // 10-bit chunk + let tag_a = meta.query_advice(a_0, Rotation::next()); + let b = meta.query_advice(a_4, Rotation::next()); // 7-bit chunk + let c = meta.query_advice(a_3, Rotation::next()); // 2-bit chunk + let d = meta.query_advice(a_1, Rotation::cur()); // 13-bit chunk + let tag_d = meta.query_advice(a_0, Rotation::cur()); + let word = meta.query_advice(a_5, Rotation::cur()); + + ScheduleGate::s_decompose_3(s_decompose_3, a, tag_a, b, c, d, tag_d, word) + }); + + // sigma_0 v1 on W_[1..14] + // (3, 4, 11, 14)-bit chunks + meta.create_gate("sigma_0 v1", |meta| { + ScheduleGate::s_lower_sigma_0( + meta.query_selector(s_lower_sigma_0), + meta.query_advice(a_2, Rotation::prev()), // spread_r0_even + meta.query_advice(a_2, Rotation::cur()), // spread_r0_odd + meta.query_advice(a_2, Rotation::next()), // spread_r1_even + meta.query_advice(a_3, Rotation::cur()), // spread_r1_odd + meta.query_advice(a_5, Rotation::next()), // a + meta.query_advice(a_6, Rotation::next()), // spread_a + meta.query_advice(a_6, Rotation::cur()), // b + meta.query_advice(a_3, Rotation::prev()), // b_lo + meta.query_advice(a_4, Rotation::prev()), // spread_b_lo + meta.query_advice(a_5, Rotation::prev()), // b_hi + meta.query_advice(a_6, Rotation::prev()), // spread_b_hi + meta.query_advice(a_4, Rotation::cur()), // spread_c + meta.query_advice(a_5, Rotation::cur()), // spread_d + ) + }); + + // sigma_0 v2 on W_[14..49] + // (3, 4, 3, 7, 1, 1, 13)-bit chunks + meta.create_gate("sigma_0 v2", |meta| { + ScheduleGate::s_lower_sigma_0_v2( + meta.query_selector(s_lower_sigma_0_v2), + meta.query_advice(a_2, Rotation::prev()), // spread_r0_even + meta.query_advice(a_2, Rotation::cur()), // spread_r0_odd + meta.query_advice(a_2, Rotation::next()), // spread_r1_even + meta.query_advice(a_3, Rotation::cur()), // spread_r1_odd + meta.query_advice(a_3, Rotation::next()), // a + meta.query_advice(a_4, Rotation::next()), // spread_a + meta.query_advice(a_6, Rotation::cur()), // b + meta.query_advice(a_3, Rotation::prev()), // b_lo + meta.query_advice(a_4, Rotation::prev()), // spread_b_lo + meta.query_advice(a_5, Rotation::prev()), // b_hi + meta.query_advice(a_6, Rotation::prev()), // spread_b_hi + meta.query_advice(a_5, Rotation::next()), // c + meta.query_advice(a_6, Rotation::next()), // spread_c + meta.query_advice(a_4, Rotation::cur()), // spread_d + meta.query_advice(a_7, Rotation::cur()), // spread_e + meta.query_advice(a_7, Rotation::next()), // spread_f + meta.query_advice(a_5, Rotation::cur()), // spread_g + ) + }); + + // sigma_1 v2 on W_14 to W_48 + // (3, 4, 3, 7, 1, 1, 13)-bit chunks + meta.create_gate("sigma_1 v2", |meta| { + ScheduleGate::s_lower_sigma_1_v2( + meta.query_selector(s_lower_sigma_1_v2), + meta.query_advice(a_2, Rotation::prev()), // spread_r0_even + meta.query_advice(a_2, Rotation::cur()), // spread_r0_odd + meta.query_advice(a_2, Rotation::next()), // spread_r1_even + meta.query_advice(a_3, Rotation::cur()), // spread_r1_odd + meta.query_advice(a_3, Rotation::next()), // a + meta.query_advice(a_4, Rotation::next()), // spread_a + meta.query_advice(a_6, Rotation::cur()), // b + meta.query_advice(a_3, Rotation::prev()), // b_lo + meta.query_advice(a_4, Rotation::prev()), // spread_b_lo + meta.query_advice(a_5, Rotation::prev()), // b_hi + meta.query_advice(a_6, Rotation::prev()), // spread_b_hi + meta.query_advice(a_5, Rotation::next()), // c + meta.query_advice(a_6, Rotation::next()), // spread_c + meta.query_advice(a_4, Rotation::cur()), // spread_d + meta.query_advice(a_7, Rotation::cur()), // spread_e + meta.query_advice(a_7, Rotation::next()), // spread_f + meta.query_advice(a_5, Rotation::cur()), // spread_g + ) + }); + + // sigma_1 v1 on W_49 to W_61 + // (10, 7, 2, 13)-bit chunks + meta.create_gate("sigma_1 v1", |meta| { + ScheduleGate::s_lower_sigma_1( + meta.query_selector(s_lower_sigma_1), + meta.query_advice(a_2, Rotation::prev()), // spread_r0_even + meta.query_advice(a_2, Rotation::cur()), // spread_r0_odd + meta.query_advice(a_2, Rotation::next()), // spread_r1_even + meta.query_advice(a_3, Rotation::cur()), // spread_r1_odd + meta.query_advice(a_4, Rotation::cur()), // spread_a + meta.query_advice(a_6, Rotation::cur()), // b + meta.query_advice(a_3, Rotation::prev()), // b_lo + meta.query_advice(a_4, Rotation::prev()), // spread_b_lo + meta.query_advice(a_5, Rotation::prev()), // b_mid + meta.query_advice(a_6, Rotation::prev()), // spread_b_mid + meta.query_advice(a_5, Rotation::next()), // b_hi + meta.query_advice(a_6, Rotation::next()), // spread_b_hi + meta.query_advice(a_3, Rotation::next()), // c + meta.query_advice(a_4, Rotation::next()), // spread_c + meta.query_advice(a_5, Rotation::cur()), // spread_d + ) + }); + + MessageScheduleConfig { + lookup, + message_schedule, + extras, + s_word, + s_decompose_0, + s_decompose_1, + s_decompose_2, + s_decompose_3, + s_lower_sigma_0, + s_lower_sigma_1, + s_lower_sigma_0_v2, + s_lower_sigma_1_v2, + } + } + + #[allow(clippy::type_complexity)] + pub(super) fn process( + &self, + layouter: &mut impl Layouter, + input: [BlockWord; BLOCK_SIZE], + ) -> Result< + ( + [MessageWord; ROUNDS], + [(AssignedBits<16>, AssignedBits<16>); ROUNDS], + ), + Error, + > { + let mut w = Vec::::with_capacity(ROUNDS); + let mut w_halves = Vec::<(AssignedBits<16>, AssignedBits<16>)>::with_capacity(ROUNDS); + + layouter.assign_region( + || "process message block", + |mut region| { + w = Vec::::with_capacity(ROUNDS); + w_halves = Vec::<(AssignedBits<16>, AssignedBits<16>)>::with_capacity(ROUNDS); + + // Assign all fixed columns + for index in 1..14 { + let row = get_word_row(index); + self.s_decompose_1.enable(&mut region, row)?; + self.s_lower_sigma_0.enable(&mut region, row + 3)?; + } + + for index in 14..49 { + let row = get_word_row(index); + self.s_decompose_2.enable(&mut region, row)?; + self.s_lower_sigma_0_v2.enable(&mut region, row + 3)?; + self.s_lower_sigma_1_v2 + .enable(&mut region, row + SIGMA_0_V2_ROWS + 3)?; + + let new_word_idx = index + 2; + self.s_word + .enable(&mut region, get_word_row(new_word_idx - 16) + 1)?; + } + + for index in 49..62 { + let row = get_word_row(index); + self.s_decompose_3.enable(&mut region, row)?; + self.s_lower_sigma_1.enable(&mut region, row + 3)?; + + let new_word_idx = index + 2; + self.s_word + .enable(&mut region, get_word_row(new_word_idx - 16) + 1)?; + } + + for index in 0..64 { + let row = get_word_row(index); + self.s_decompose_0.enable(&mut region, row)?; + } + + // Assign W[0..16] + for (i, word) in input.iter().enumerate() { + let (word, halves) = self.assign_word_and_halves(&mut region, word.0, i)?; + w.push(MessageWord(word)); + w_halves.push(halves); + } + + // Returns the output of sigma_0 on W_[1..14] + let lower_sigma_0_output = self.assign_subregion1(&mut region, &input[1..14])?; + + // sigma_0_v2 and sigma_1_v2 on W_[14..49] + // Returns the output of sigma_0_v2 on W_[36..49], to be used in subregion3 + let lower_sigma_0_v2_output = self.assign_subregion2( + &mut region, + lower_sigma_0_output, + &mut w, + &mut w_halves, + )?; + + // sigma_1 v1 on W[49..62] + self.assign_subregion3( + &mut region, + lower_sigma_0_v2_output, + &mut w, + &mut w_halves, + )?; + + Ok(()) + }, + )?; + + Ok((w.try_into().unwrap(), w_halves.try_into().unwrap())) + } +} + +#[cfg(test)] +mod tests { + use super::super::{ + super::BLOCK_SIZE, util::lebs2ip, BlockWord, SpreadTableChip, Table16Chip, Table16Config, + }; + use super::schedule_util::*; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + dev::MockProver, + pasta::pallas, + plonk::{Circuit, ConstraintSystem, Error}, + }; + + #[test] + fn message_schedule() { + struct MyCircuit {} + + impl Circuit for MyCircuit { + type Config = Table16Config; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + MyCircuit {} + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + Table16Chip::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // Load lookup table + SpreadTableChip::load(config.lookup.clone(), &mut layouter)?; + + // Provide input + // Test vector: "abc" + let inputs: [BlockWord; BLOCK_SIZE] = msg_schedule_test_input(); + + // Run message_scheduler to get W_[0..64] + let (w, _) = config.message_schedule.process(&mut layouter, inputs)?; + for (word, test_word) in w.iter().zip(MSG_SCHEDULE_TEST_OUTPUT.iter()) { + word.value().assert_if_known(|bits| { + let word: u32 = lebs2ip(bits) as u32; + word == *test_word + }); + } + Ok(()) + } + } + + let circuit: MyCircuit = MyCircuit {}; + + let prover = match MockProver::::run(17, &circuit, vec![]) { + Ok(prover) => prover, + Err(e) => panic!("{:?}", e), + }; + assert_eq!(prover.verify(), Ok(())); + } +} diff --git a/halo2_gadgets_optimized/src/sha256/table16/message_schedule/schedule_gates.rs b/halo2_gadgets_optimized/src/sha256/table16/message_schedule/schedule_gates.rs new file mode 100644 index 0000000000..c088205aed --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16/message_schedule/schedule_gates.rs @@ -0,0 +1,395 @@ +use super::super::Gate; + +use group::ff::{Field, PrimeField}; +use halo2_proofs::plonk::Expression; +use std::marker::PhantomData; + +pub struct ScheduleGate(PhantomData); + +impl ScheduleGate { + /// s_word for W_16 to W_63 + #[allow(clippy::too_many_arguments)] + pub fn s_word( + s_word: Expression, + sigma_0_lo: Expression, + sigma_0_hi: Expression, + sigma_1_lo: Expression, + sigma_1_hi: Expression, + w_minus_9_lo: Expression, + w_minus_9_hi: Expression, + w_minus_16_lo: Expression, + w_minus_16_hi: Expression, + word: Expression, + carry: Expression, + ) -> impl Iterator)> { + let lo = sigma_0_lo + sigma_1_lo + w_minus_9_lo + w_minus_16_lo; + let hi = sigma_0_hi + sigma_1_hi + w_minus_9_hi + w_minus_16_hi; + + let word_check = lo + + hi * F::from(1 << 16) + + (carry.clone() * F::from(1 << 32) * (-F::ONE)) + + (word * (-F::ONE)); + let carry_check = Gate::range_check(carry, 0, 3); + + [("word_check", word_check), ("carry_check", carry_check)] + .into_iter() + .map(move |(name, poly)| (name, s_word.clone() * poly)) + } + + /// s_decompose_0 for all words + pub fn s_decompose_0( + s_decompose_0: Expression, + lo: Expression, + hi: Expression, + word: Expression, + ) -> Option<(&'static str, Expression)> { + let check = lo + hi * F::from(1 << 16) - word; + Some(("s_decompose_0", s_decompose_0 * check)) + } + + /// s_decompose_1 for W_1 to W_13 + /// (3, 4, 11, 14)-bit chunks + #[allow(clippy::too_many_arguments)] + pub fn s_decompose_1( + s_decompose_1: Expression, + a: Expression, + b: Expression, + c: Expression, + tag_c: Expression, + d: Expression, + tag_d: Expression, + word: Expression, + ) -> impl Iterator)> { + let decompose_check = + a + b * F::from(1 << 3) + c * F::from(1 << 7) + d * F::from(1 << 18) + word * (-F::ONE); + let range_check_tag_c = Gate::range_check(tag_c, 0, 2); + let range_check_tag_d = Gate::range_check(tag_d, 0, 4); + + [ + ("decompose_check", decompose_check), + ("range_check_tag_c", range_check_tag_c), + ("range_check_tag_d", range_check_tag_d), + ] + .into_iter() + .map(move |(name, poly)| (name, s_decompose_1.clone() * poly)) + } + + /// s_decompose_2 for W_14 to W_48 + /// (3, 4, 3, 7, 1, 1, 13)-bit chunks + #[allow(clippy::many_single_char_names)] + #[allow(clippy::too_many_arguments)] + pub fn s_decompose_2( + s_decompose_2: Expression, + a: Expression, + b: Expression, + c: Expression, + d: Expression, + tag_d: Expression, + e: Expression, + f: Expression, + g: Expression, + tag_g: Expression, + word: Expression, + ) -> impl Iterator)> { + let decompose_check = a + + b * F::from(1 << 3) + + c * F::from(1 << 7) + + d * F::from(1 << 10) + + e * F::from(1 << 17) + + f * F::from(1 << 18) + + g * F::from(1 << 19) + + word * (-F::ONE); + let range_check_tag_d = Gate::range_check(tag_d, 0, 0); + let range_check_tag_g = Gate::range_check(tag_g, 0, 3); + + [ + ("decompose_check", decompose_check), + ("range_check_tag_g", range_check_tag_g), + ("range_check_tag_d", range_check_tag_d), + ] + .into_iter() + .map(move |(name, poly)| (name, s_decompose_2.clone() * poly)) + } + + /// s_decompose_3 for W_49 to W_61 + /// (10, 7, 2, 13)-bit chunks + #[allow(clippy::too_many_arguments)] + pub fn s_decompose_3( + s_decompose_3: Expression, + a: Expression, + tag_a: Expression, + b: Expression, + c: Expression, + d: Expression, + tag_d: Expression, + word: Expression, + ) -> impl Iterator)> { + let decompose_check = a + + b * F::from(1 << 10) + + c * F::from(1 << 17) + + d * F::from(1 << 19) + + word * (-F::ONE); + let range_check_tag_a = Gate::range_check(tag_a, 0, 1); + let range_check_tag_d = Gate::range_check(tag_d, 0, 3); + + [ + ("decompose_check", decompose_check), + ("range_check_tag_a", range_check_tag_a), + ("range_check_tag_d", range_check_tag_d), + ] + .into_iter() + .map(move |(name, poly)| (name, s_decompose_3.clone() * poly)) + } + + /// b_lo + 2^2 * b_mid = b, on W_[1..49] + fn check_b(b: Expression, b_lo: Expression, b_hi: Expression) -> Expression { + let expected_b = b_lo + b_hi * F::from(1 << 2); + expected_b - b + } + + /// sigma_0 v1 on W_1 to W_13 + /// (3, 4, 11, 14)-bit chunks + #[allow(clippy::too_many_arguments)] + pub fn s_lower_sigma_0( + s_lower_sigma_0: Expression, + spread_r0_even: Expression, + spread_r0_odd: Expression, + spread_r1_even: Expression, + spread_r1_odd: Expression, + a: Expression, + spread_a: Expression, + b: Expression, + b_lo: Expression, + spread_b_lo: Expression, + b_hi: Expression, + spread_b_hi: Expression, + spread_c: Expression, + spread_d: Expression, + ) -> impl Iterator)> { + let check_spread_and_range = + Gate::two_bit_spread_and_range(b_lo.clone(), spread_b_lo.clone()) + .chain(Gate::two_bit_spread_and_range( + b_hi.clone(), + spread_b_hi.clone(), + )) + .chain(Gate::three_bit_spread_and_range(a, spread_a.clone())); + let check_b = Self::check_b(b, b_lo, b_hi); + let spread_witness = spread_r0_even + + spread_r0_odd * F::from(2) + + (spread_r1_even + spread_r1_odd * F::from(2)) * F::from(1 << 32); + let xor_0 = spread_b_lo.clone() + + spread_b_hi.clone() * F::from(1 << 4) + + spread_c.clone() * F::from(1 << 8) + + spread_d.clone() * F::from(1 << 30); + let xor_1 = spread_c.clone() + + spread_d.clone() * F::from(1 << 22) + + spread_a.clone() * F::from(1 << 50) + + spread_b_lo.clone() * F::from(1 << 56) + + spread_b_hi.clone() * F::from(1 << 60); + let xor_2 = spread_d + + spread_a * F::from(1 << 28) + + spread_b_lo * F::from(1 << 34) + + spread_b_hi * F::from(1 << 38) + + spread_c * F::from(1 << 42); + let xor = xor_0 + xor_1 + xor_2; + + check_spread_and_range + .chain(Some(("check_b", check_b))) + .chain(Some(("lower_sigma_0", spread_witness - xor))) + .map(move |(name, poly)| (name, s_lower_sigma_0.clone() * poly)) + } + + /// sigma_1 v1 on W_49 to W_61 + /// (10, 7, 2, 13)-bit chunks + #[allow(clippy::too_many_arguments)] + pub fn s_lower_sigma_1( + s_lower_sigma_1: Expression, + spread_r0_even: Expression, + spread_r0_odd: Expression, + spread_r1_even: Expression, + spread_r1_odd: Expression, + spread_a: Expression, + b: Expression, + b_lo: Expression, + spread_b_lo: Expression, + b_mid: Expression, + spread_b_mid: Expression, + b_hi: Expression, + spread_b_hi: Expression, + c: Expression, + spread_c: Expression, + spread_d: Expression, + ) -> impl Iterator)> { + let check_spread_and_range = + Gate::two_bit_spread_and_range(b_lo.clone(), spread_b_lo.clone()) + .chain(Gate::two_bit_spread_and_range( + b_mid.clone(), + spread_b_mid.clone(), + )) + .chain(Gate::two_bit_spread_and_range(c, spread_c.clone())) + .chain(Gate::three_bit_spread_and_range( + b_hi.clone(), + spread_b_hi.clone(), + )); + // b_lo + 2^2 * b_mid + 2^4 * b_hi = b, on W_[49..62] + let check_b1 = { + let expected_b = b_lo + b_mid * F::from(1 << 2) + b_hi * F::from(1 << 4); + expected_b - b + }; + let spread_witness = spread_r0_even + + spread_r0_odd * F::from(2) + + (spread_r1_even + spread_r1_odd * F::from(2)) * F::from(1 << 32); + let xor_0 = spread_b_lo.clone() + + spread_b_mid.clone() * F::from(1 << 4) + + spread_b_hi.clone() * F::from(1 << 8) + + spread_c.clone() * F::from(1 << 14) + + spread_d.clone() * F::from(1 << 18); + let xor_1 = spread_c.clone() + + spread_d.clone() * F::from(1 << 4) + + spread_a.clone() * F::from(1 << 30) + + spread_b_lo.clone() * F::from(1 << 50) + + spread_b_mid.clone() * F::from(1 << 54) + + spread_b_hi.clone() * F::from(1 << 58); + let xor_2 = spread_d + + spread_a * F::from(1 << 26) + + spread_b_lo * F::from(1 << 46) + + spread_b_mid * F::from(1 << 50) + + spread_b_hi * F::from(1 << 54) + + spread_c * F::from(1 << 60); + let xor = xor_0 + xor_1 + xor_2; + + check_spread_and_range + .chain(Some(("check_b1", check_b1))) + .chain(Some(("lower_sigma_1", spread_witness - xor))) + .map(move |(name, poly)| (name, s_lower_sigma_1.clone() * poly)) + } + + /// sigma_0 v2 on W_14 to W_48 + /// (3, 4, 3, 7, 1, 1, 13)-bit chunks + #[allow(clippy::too_many_arguments)] + pub fn s_lower_sigma_0_v2( + s_lower_sigma_0_v2: Expression, + spread_r0_even: Expression, + spread_r0_odd: Expression, + spread_r1_even: Expression, + spread_r1_odd: Expression, + a: Expression, + spread_a: Expression, + b: Expression, + b_lo: Expression, + spread_b_lo: Expression, + b_hi: Expression, + spread_b_hi: Expression, + c: Expression, + spread_c: Expression, + spread_d: Expression, + spread_e: Expression, + spread_f: Expression, + spread_g: Expression, + ) -> impl Iterator)> { + let check_spread_and_range = + Gate::two_bit_spread_and_range(b_lo.clone(), spread_b_lo.clone()) + .chain(Gate::two_bit_spread_and_range( + b_hi.clone(), + spread_b_hi.clone(), + )) + .chain(Gate::three_bit_spread_and_range(a, spread_a.clone())) + .chain(Gate::three_bit_spread_and_range(c, spread_c.clone())); + let check_b = Self::check_b(b, b_lo, b_hi); + let spread_witness = spread_r0_even + + spread_r0_odd * F::from(2) + + (spread_r1_even + spread_r1_odd * F::from(2)) * F::from(1 << 32); + let xor_0 = spread_b_lo.clone() + + spread_b_hi.clone() * F::from(1 << 4) + + spread_c.clone() * F::from(1 << 8) + + spread_d.clone() * F::from(1 << 14) + + spread_e.clone() * F::from(1 << 28) + + spread_f.clone() * F::from(1 << 30) + + spread_g.clone() * F::from(1 << 32); + let xor_1 = spread_c.clone() + + spread_d.clone() * F::from(1 << 6) + + spread_e.clone() * F::from(1 << 20) + + spread_f.clone() * F::from(1 << 22) + + spread_g.clone() * F::from(1 << 24) + + spread_a.clone() * F::from(1 << 50) + + spread_b_lo.clone() * F::from(1 << 56) + + spread_b_hi.clone() * F::from(1 << 60); + let xor_2 = spread_f + + spread_g * F::from(1 << 2) + + spread_a * F::from(1 << 28) + + spread_b_lo * F::from(1 << 34) + + spread_b_hi * F::from(1 << 38) + + spread_c * F::from(1 << 42) + + spread_d * F::from(1 << 48) + + spread_e * F::from(1 << 62); + let xor = xor_0 + xor_1 + xor_2; + + check_spread_and_range + .chain(Some(("check_b", check_b))) + .chain(Some(("lower_sigma_0_v2", spread_witness - xor))) + .map(move |(name, poly)| (name, s_lower_sigma_0_v2.clone() * poly)) + } + + /// sigma_1 v2 on W_14 to W_48 + /// (3, 4, 3, 7, 1, 1, 13)-bit chunks + #[allow(clippy::too_many_arguments)] + pub fn s_lower_sigma_1_v2( + s_lower_sigma_1_v2: Expression, + spread_r0_even: Expression, + spread_r0_odd: Expression, + spread_r1_even: Expression, + spread_r1_odd: Expression, + a: Expression, + spread_a: Expression, + b: Expression, + b_lo: Expression, + spread_b_lo: Expression, + b_hi: Expression, + spread_b_hi: Expression, + c: Expression, + spread_c: Expression, + spread_d: Expression, + spread_e: Expression, + spread_f: Expression, + spread_g: Expression, + ) -> impl Iterator)> { + let check_spread_and_range = + Gate::two_bit_spread_and_range(b_lo.clone(), spread_b_lo.clone()) + .chain(Gate::two_bit_spread_and_range( + b_hi.clone(), + spread_b_hi.clone(), + )) + .chain(Gate::three_bit_spread_and_range(a, spread_a.clone())) + .chain(Gate::three_bit_spread_and_range(c, spread_c.clone())); + let check_b = Self::check_b(b, b_lo, b_hi); + let spread_witness = spread_r0_even + + spread_r0_odd * F::from(2) + + (spread_r1_even + spread_r1_odd * F::from(2)) * F::from(1 << 32); + let xor_0 = spread_d.clone() + + spread_e.clone() * F::from(1 << 14) + + spread_f.clone() * F::from(1 << 16) + + spread_g.clone() * F::from(1 << 18); + let xor_1 = spread_e.clone() + + spread_f.clone() * F::from(1 << 2) + + spread_g.clone() * F::from(1 << 4) + + spread_a.clone() * F::from(1 << 30) + + spread_b_lo.clone() * F::from(1 << 36) + + spread_b_hi.clone() * F::from(1 << 40) + + spread_c.clone() * F::from(1 << 44) + + spread_d.clone() * F::from(1 << 50); + let xor_2 = spread_g + + spread_a * F::from(1 << 26) + + spread_b_lo * F::from(1 << 32) + + spread_b_hi * F::from(1 << 36) + + spread_c * F::from(1 << 40) + + spread_d * F::from(1 << 46) + + spread_e * F::from(1 << 60) + + spread_f * F::from(1 << 62); + let xor = xor_0 + xor_1 + xor_2; + + check_spread_and_range + .chain(Some(("check_b", check_b))) + .chain(Some(("lower_sigma_1_v2", spread_witness - xor))) + .map(move |(name, poly)| (name, s_lower_sigma_1_v2.clone() * poly)) + } +} diff --git a/halo2_gadgets_optimized/src/sha256/table16/message_schedule/schedule_util.rs b/halo2_gadgets_optimized/src/sha256/table16/message_schedule/schedule_util.rs new file mode 100644 index 0000000000..e1e9ca4a92 --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16/message_schedule/schedule_util.rs @@ -0,0 +1,181 @@ +use super::super::AssignedBits; +use super::MessageScheduleConfig; +use halo2_proofs::{ + circuit::{Region, Value}, + pasta::pallas, + plonk::Error, +}; + +#[cfg(test)] +use super::super::{super::BLOCK_SIZE, BlockWord, ROUNDS}; + +// Rows needed for each gate +pub const DECOMPOSE_0_ROWS: usize = 2; +pub const DECOMPOSE_1_ROWS: usize = 2; +pub const DECOMPOSE_2_ROWS: usize = 3; +pub const DECOMPOSE_3_ROWS: usize = 2; +pub const SIGMA_0_V1_ROWS: usize = 4; +pub const SIGMA_0_V2_ROWS: usize = 4; +pub const SIGMA_1_V1_ROWS: usize = 4; +pub const SIGMA_1_V2_ROWS: usize = 4; + +// Rows needed for each subregion +pub const SUBREGION_0_LEN: usize = 1; // W_0 +pub const SUBREGION_0_ROWS: usize = SUBREGION_0_LEN * DECOMPOSE_0_ROWS; +pub const SUBREGION_1_WORD: usize = DECOMPOSE_1_ROWS + SIGMA_0_V1_ROWS; +pub const SUBREGION_1_LEN: usize = 13; // W_[1..14] +pub const SUBREGION_1_ROWS: usize = SUBREGION_1_LEN * SUBREGION_1_WORD; +pub const SUBREGION_2_WORD: usize = DECOMPOSE_2_ROWS + SIGMA_0_V2_ROWS + SIGMA_1_V2_ROWS; +pub const SUBREGION_2_LEN: usize = 35; // W_[14..49] +pub const SUBREGION_2_ROWS: usize = SUBREGION_2_LEN * SUBREGION_2_WORD; +pub const SUBREGION_3_WORD: usize = DECOMPOSE_3_ROWS + SIGMA_1_V1_ROWS; +pub const SUBREGION_3_LEN: usize = 13; // W[49..62] +pub const SUBREGION_3_ROWS: usize = SUBREGION_3_LEN * SUBREGION_3_WORD; +// pub const SUBREGION_4_LEN: usize = 2; // W_[62..64] +// pub const SUBREGION_4_ROWS: usize = SUBREGION_4_LEN * DECOMPOSE_0_ROWS; + +/// Returns row number of a word +pub fn get_word_row(word_idx: usize) -> usize { + assert!(word_idx <= 63); + if word_idx == 0 { + 0 + } else if (1..=13).contains(&word_idx) { + SUBREGION_0_ROWS + SUBREGION_1_WORD * (word_idx - 1) + } else if (14..=48).contains(&word_idx) { + SUBREGION_0_ROWS + SUBREGION_1_ROWS + SUBREGION_2_WORD * (word_idx - 14) + 1 + } else if (49..=61).contains(&word_idx) { + SUBREGION_0_ROWS + SUBREGION_1_ROWS + SUBREGION_2_ROWS + SUBREGION_3_WORD * (word_idx - 49) + } else { + SUBREGION_0_ROWS + + SUBREGION_1_ROWS + + SUBREGION_2_ROWS + + SUBREGION_3_ROWS + + DECOMPOSE_0_ROWS * (word_idx - 62) + } +} + +/// Test vector: "abc" +#[cfg(test)] +pub fn msg_schedule_test_input() -> [BlockWord; BLOCK_SIZE] { + [ + BlockWord(Value::known(0b01100001011000100110001110000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000011000)), + ] +} + +#[cfg(test)] +pub const MSG_SCHEDULE_TEST_OUTPUT: [u32; ROUNDS] = [ + 0b01100001011000100110001110000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000011000, + 0b01100001011000100110001110000000, + 0b00000000000011110000000000000000, + 0b01111101101010000110010000000101, + 0b01100000000000000000001111000110, + 0b00111110100111010111101101111000, + 0b00000001100000111111110000000000, + 0b00010010110111001011111111011011, + 0b11100010111000101100001110001110, + 0b11001000001000010101110000011010, + 0b10110111001101100111100110100010, + 0b11100101101111000011100100001001, + 0b00110010011001100011110001011011, + 0b10011101001000001001110101100111, + 0b11101100100001110010011011001011, + 0b01110000001000010011100010100100, + 0b11010011101101111001011100111011, + 0b10010011111101011001100101111111, + 0b00111011011010001011101001110011, + 0b10101111111101001111111111000001, + 0b11110001000010100101110001100010, + 0b00001010100010110011100110010110, + 0b01110010101011111000001100001010, + 0b10010100000010011110001100111110, + 0b00100100011001000001010100100010, + 0b10011111010001111011111110010100, + 0b11110000101001100100111101011010, + 0b00111110001001000110101001111001, + 0b00100111001100110011101110100011, + 0b00001100010001110110001111110010, + 0b10000100000010101011111100100111, + 0b01111010001010010000110101011101, + 0b00000110010111000100001111011010, + 0b11111011001111101000100111001011, + 0b11001100011101100001011111011011, + 0b10111001111001100110110000110100, + 0b10101001100110010011011001100111, + 0b10000100101110101101111011011101, + 0b11000010000101000110001010111100, + 0b00010100100001110100011100101100, + 0b10110010000011110111101010011001, + 0b11101111010101111011100111001101, + 0b11101011111001101011001000111000, + 0b10011111111000110000100101011110, + 0b01111000101111001000110101001011, + 0b10100100001111111100111100010101, + 0b01100110100010110010111111111000, + 0b11101110101010111010001011001100, + 0b00010010101100011110110111101011, +]; + +impl MessageScheduleConfig { + // Assign a word and its hi and lo halves + pub fn assign_word_and_halves( + &self, + region: &mut Region<'_, pallas::Base>, + word: Value, + word_idx: usize, + ) -> Result<(AssignedBits<32>, (AssignedBits<16>, AssignedBits<16>)), Error> { + // Rename these here for ease of matching the gates to the specification. + let a_3 = self.extras[0]; + let a_4 = self.extras[1]; + + let row = get_word_row(word_idx); + + let w_lo = { + let w_lo_val = word.map(|word| word as u16); + AssignedBits::<16>::assign(region, || format!("W_{}_lo", word_idx), a_3, row, w_lo_val)? + }; + let w_hi = { + let w_hi_val = word.map(|word| (word >> 16) as u16); + AssignedBits::<16>::assign(region, || format!("W_{}_hi", word_idx), a_4, row, w_hi_val)? + }; + + let word = AssignedBits::<32>::assign( + region, + || format!("W_{}", word_idx), + self.message_schedule, + row, + word, + )?; + + Ok((word, (w_lo, w_hi))) + } +} diff --git a/halo2_gadgets_optimized/src/sha256/table16/message_schedule/subregion1.rs b/halo2_gadgets_optimized/src/sha256/table16/message_schedule/subregion1.rs new file mode 100644 index 0000000000..8c4e003a11 --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16/message_schedule/subregion1.rs @@ -0,0 +1,223 @@ +use super::super::{util::*, AssignedBits, BlockWord, SpreadVar, SpreadWord, Table16Assignment}; +use super::{schedule_util::*, MessageScheduleConfig}; +use halo2_proofs::{ + circuit::{Region, Value}, + pasta::pallas, + plonk::Error, +}; +use std::convert::TryInto; + +// A word in subregion 1 +// (3, 4, 11, 14)-bit chunks +#[derive(Debug)] +pub struct Subregion1Word { + index: usize, + a: AssignedBits<3>, + b: AssignedBits<4>, + _c: AssignedBits<11>, + _d: AssignedBits<14>, + spread_c: AssignedBits<22>, + spread_d: AssignedBits<28>, +} + +impl Subregion1Word { + fn spread_a(&self) -> Value<[bool; 6]> { + self.a.value().map(|v| v.spread()) + } + + fn spread_b(&self) -> Value<[bool; 8]> { + self.b.value().map(|v| v.spread()) + } + + fn spread_c(&self) -> Value<[bool; 22]> { + self.spread_c.value().map(|v| v.0) + } + + fn spread_d(&self) -> Value<[bool; 28]> { + self.spread_d.value().map(|v| v.0) + } + + fn xor_lower_sigma_0(&self) -> Value<[bool; 64]> { + self.spread_a() + .zip(self.spread_b()) + .zip(self.spread_c()) + .zip(self.spread_d()) + .map(|(((a, b), c), d)| { + let xor_0 = b + .iter() + .chain(c.iter()) + .chain(d.iter()) + .chain(std::iter::repeat(&false).take(6)) + .copied() + .collect::>(); + let xor_1 = c + .iter() + .chain(d.iter()) + .chain(a.iter()) + .chain(b.iter()) + .copied() + .collect::>(); + let xor_2 = d + .iter() + .chain(a.iter()) + .chain(b.iter()) + .chain(c.iter()) + .copied() + .collect::>(); + + let xor_0 = lebs2ip::<64>(&xor_0.try_into().unwrap()); + let xor_1 = lebs2ip::<64>(&xor_1.try_into().unwrap()); + let xor_2 = lebs2ip::<64>(&xor_2.try_into().unwrap()); + + i2lebsp(xor_0 + xor_1 + xor_2) + }) + } +} + +impl MessageScheduleConfig { + pub fn assign_subregion1( + &self, + region: &mut Region<'_, pallas::Base>, + input: &[BlockWord], + ) -> Result, AssignedBits<16>)>, Error> { + assert_eq!(input.len(), SUBREGION_1_LEN); + Ok(input + .iter() + .enumerate() + .map(|(idx, word)| { + // s_decompose_1 on W_[1..14] + let subregion1_word = self + .decompose_subregion1_word( + region, + word.0.map(|word| i2lebsp(word.into())), + idx + 1, + ) + .unwrap(); + + // lower_sigma_0 on W_[1..14] + self.lower_sigma_0(region, subregion1_word).unwrap() + }) + .collect::>()) + } + + /// Pieces of length [3, 4, 11, 14] + fn decompose_subregion1_word( + &self, + region: &mut Region<'_, pallas::Base>, + word: Value<[bool; 32]>, + index: usize, + ) -> Result { + let row = get_word_row(index); + + // Rename these here for ease of matching the gates to the specification. + let a_3 = self.extras[0]; + let a_4 = self.extras[1]; + + let pieces = word.map(|word| { + vec![ + word[0..3].to_vec(), + word[3..7].to_vec(), + word[7..18].to_vec(), + word[18..32].to_vec(), + ] + }); + let pieces = pieces.transpose_vec(4); + + // Assign `a` (3-bit piece) + let a = + AssignedBits::<3>::assign_bits(region, || "word_a", a_3, row + 1, pieces[0].clone())?; + // Assign `b` (4-bit piece) + let b = + AssignedBits::<4>::assign_bits(region, || "word_b", a_4, row + 1, pieces[1].clone())?; + + // Assign `c` (11-bit piece) lookup + let spread_c = pieces[2].clone().map(SpreadWord::try_new); + let spread_c = SpreadVar::with_lookup(region, &self.lookup, row + 1, spread_c)?; + + // Assign `d` (14-bit piece) lookup + let spread_d = pieces[3].clone().map(SpreadWord::try_new); + let spread_d = SpreadVar::with_lookup(region, &self.lookup, row, spread_d)?; + + Ok(Subregion1Word { + index, + a, + b, + _c: spread_c.dense, + _d: spread_d.dense, + spread_c: spread_c.spread, + spread_d: spread_d.spread, + }) + } + + // sigma_0 v1 on a word in W_1 to W_13 + // (3, 4, 11, 14)-bit chunks + fn lower_sigma_0( + &self, + region: &mut Region<'_, pallas::Base>, + word: Subregion1Word, + ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { + let a_3 = self.extras[0]; + let a_4 = self.extras[1]; + let a_5 = self.message_schedule; + let a_6 = self.extras[2]; + + let row = get_word_row(word.index) + 3; + + // Assign `a` and copy constraint + word.a.copy_advice(|| "a", region, a_5, row + 1)?; + + // Witness `spread_a` + let spread_a = word.a.value().map(|bits| spread_bits(bits.0)); + AssignedBits::<6>::assign_bits(region, || "spread_a", a_6, row + 1, spread_a)?; + + // Split `b` (4-bit chunk) into `b_hi` and `b_lo` + // Assign `b_lo`, `spread_b_lo` + let b_lo: Value<[bool; 2]> = word.b.value().map(|b| b.0[..2].try_into().unwrap()); + let spread_b_lo = b_lo.map(spread_bits); + { + AssignedBits::<2>::assign_bits(region, || "b_lo", a_3, row - 1, b_lo)?; + + AssignedBits::<4>::assign_bits(region, || "spread_b_lo", a_4, row - 1, spread_b_lo)?; + }; + + // Split `b` (2-bit chunk) into `b_hi` and `b_lo` + // Assign `b_hi`, `spread_b_hi` + let b_hi: Value<[bool; 2]> = word.b.value().map(|b| b.0[2..].try_into().unwrap()); + let spread_b_hi = b_hi.map(spread_bits); + { + AssignedBits::<2>::assign_bits(region, || "b_hi", a_5, row - 1, b_hi)?; + + AssignedBits::<4>::assign_bits(region, || "spread_b_hi", a_6, row - 1, spread_b_hi)?; + }; + + // Assign `b` and copy constraint + word.b.copy_advice(|| "b", region, a_6, row)?; + + // Assign `spread_c` and copy constraint + word.spread_c.copy_advice(|| "spread_c", region, a_4, row)?; + + // Assign `spread_d` and copy constraint + word.spread_d.copy_advice(|| "spread_d", region, a_5, row)?; + + // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} + let r = word.xor_lower_sigma_0(); + let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); + let r_0_even = r_0.map(even_bits); + let r_0_odd = r_0.map(odd_bits); + + let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); + let r_1_even = r_1.map(even_bits); + let r_1_odd = r_1.map(odd_bits); + + self.assign_sigma_outputs( + region, + &self.lookup, + a_3, + row, + r_0_even, + r_0_odd, + r_1_even, + r_1_odd, + ) + } +} diff --git a/halo2_gadgets_optimized/src/sha256/table16/message_schedule/subregion2.rs b/halo2_gadgets_optimized/src/sha256/table16/message_schedule/subregion2.rs new file mode 100644 index 0000000000..0480d04461 --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16/message_schedule/subregion2.rs @@ -0,0 +1,487 @@ +use super::super::{util::*, AssignedBits, Bits, SpreadVar, SpreadWord, Table16Assignment}; +use super::{schedule_util::*, MessageScheduleConfig, MessageWord}; +use halo2_proofs::{ + circuit::{Region, Value}, + pasta::pallas, + plonk::Error, +}; +use std::convert::TryInto; + +/// A word in subregion 2 +/// (3, 4, 3, 7, 1, 1, 13)-bit chunks +#[derive(Clone, Debug)] +pub struct Subregion2Word { + index: usize, + a: AssignedBits<3>, + b: AssignedBits<4>, + c: AssignedBits<3>, + _d: AssignedBits<7>, + e: AssignedBits<1>, + f: AssignedBits<1>, + _g: AssignedBits<13>, + spread_d: AssignedBits<14>, + spread_g: AssignedBits<26>, +} + +impl Subregion2Word { + fn spread_a(&self) -> Value<[bool; 6]> { + self.a.value().map(|v| v.spread()) + } + + fn spread_b(&self) -> Value<[bool; 8]> { + self.b.value().map(|v| v.spread()) + } + + fn spread_c(&self) -> Value<[bool; 6]> { + self.c.value().map(|v| v.spread()) + } + + fn spread_d(&self) -> Value<[bool; 14]> { + self.spread_d.value().map(|v| v.0) + } + + fn spread_e(&self) -> Value<[bool; 2]> { + self.e.value().map(|v| v.spread()) + } + + fn spread_f(&self) -> Value<[bool; 2]> { + self.f.value().map(|v| v.spread()) + } + + fn spread_g(&self) -> Value<[bool; 26]> { + self.spread_g.value().map(|v| v.0) + } + + fn xor_sigma_0(&self) -> Value<[bool; 64]> { + self.spread_a() + .zip(self.spread_b()) + .zip(self.spread_c()) + .zip(self.spread_d()) + .zip(self.spread_e()) + .zip(self.spread_f()) + .zip(self.spread_g()) + .map(|((((((a, b), c), d), e), f), g)| { + let xor_0 = b + .iter() + .chain(c.iter()) + .chain(d.iter()) + .chain(e.iter()) + .chain(f.iter()) + .chain(g.iter()) + .chain(std::iter::repeat(&false).take(6)) + .copied() + .collect::>(); + + let xor_1 = c + .iter() + .chain(d.iter()) + .chain(e.iter()) + .chain(f.iter()) + .chain(g.iter()) + .chain(a.iter()) + .chain(b.iter()) + .copied() + .collect::>(); + + let xor_2 = f + .iter() + .chain(g.iter()) + .chain(a.iter()) + .chain(b.iter()) + .chain(c.iter()) + .chain(d.iter()) + .chain(e.iter()) + .copied() + .collect::>(); + + let xor_0 = lebs2ip::<64>(&xor_0.try_into().unwrap()); + let xor_1 = lebs2ip::<64>(&xor_1.try_into().unwrap()); + let xor_2 = lebs2ip::<64>(&xor_2.try_into().unwrap()); + + i2lebsp(xor_0 + xor_1 + xor_2) + }) + } + + fn xor_sigma_1(&self) -> Value<[bool; 64]> { + self.spread_a() + .zip(self.spread_b()) + .zip(self.spread_c()) + .zip(self.spread_d()) + .zip(self.spread_e()) + .zip(self.spread_f()) + .zip(self.spread_g()) + .map(|((((((a, b), c), d), e), f), g)| { + let xor_0 = d + .iter() + .chain(e.iter()) + .chain(f.iter()) + .chain(g.iter()) + .chain(std::iter::repeat(&false).take(20)) + .copied() + .collect::>(); + + let xor_1 = e + .iter() + .chain(f.iter()) + .chain(g.iter()) + .chain(a.iter()) + .chain(b.iter()) + .chain(c.iter()) + .chain(d.iter()) + .copied() + .collect::>(); + + let xor_2 = g + .iter() + .chain(a.iter()) + .chain(b.iter()) + .chain(c.iter()) + .chain(d.iter()) + .chain(e.iter()) + .chain(f.iter()) + .copied() + .collect::>(); + + let xor_0 = lebs2ip::<64>(&xor_0.try_into().unwrap()); + let xor_1 = lebs2ip::<64>(&xor_1.try_into().unwrap()); + let xor_2 = lebs2ip::<64>(&xor_2.try_into().unwrap()); + + i2lebsp(xor_0 + xor_1 + xor_2) + }) + } +} + +impl MessageScheduleConfig { + // W_[14..49] + pub fn assign_subregion2( + &self, + region: &mut Region<'_, pallas::Base>, + lower_sigma_0_output: Vec<(AssignedBits<16>, AssignedBits<16>)>, + w: &mut Vec, + w_halves: &mut Vec<(AssignedBits<16>, AssignedBits<16>)>, + ) -> Result, AssignedBits<16>)>, Error> { + let a_5 = self.message_schedule; + let a_6 = self.extras[2]; + let a_7 = self.extras[3]; + let a_8 = self.extras[4]; + let a_9 = self.extras[5]; + + let mut lower_sigma_0_v2_results = + Vec::<(AssignedBits<16>, AssignedBits<16>)>::with_capacity(SUBREGION_2_LEN); + let mut lower_sigma_1_v2_results = + Vec::<(AssignedBits<16>, AssignedBits<16>)>::with_capacity(SUBREGION_2_LEN); + + // Closure to compose new word + // W_i = sigma_1(W_{i - 2}) + W_{i - 7} + sigma_0(W_{i - 15}) + W_{i - 16} + // e.g. W_16 = sigma_1(W_14) + W_9 + sigma_0(W_1) + W_0 + + // sigma_0(W_[1..14]) will be used to get the new W_[16..29] + // sigma_0_v2(W_[14..36]) will be used to get the new W_[29..51] + // sigma_1_v2(W_[14..49]) will be used to get the W_[16..51] + // The lowest-index words involved will be W_[0..13] + let mut new_word = |idx: usize, + sigma_0_output: &(AssignedBits<16>, AssignedBits<16>)| + -> Result, AssignedBits<16>)>, Error> { + // Decompose word into (3, 4, 3, 7, 1, 1, 13)-bit chunks + let word = self.decompose_word(region, w[idx].value(), idx)?; + + // sigma_0 v2 and sigma_1 v2 on word + lower_sigma_0_v2_results.push(self.lower_sigma_0_v2(region, word.clone())?); + lower_sigma_1_v2_results.push(self.lower_sigma_1_v2(region, word)?); + + let new_word_idx = idx + 2; + + // Copy sigma_0(W_{i - 15}) output from Subregion 1 + sigma_0_output.0.copy_advice( + || format!("sigma_0(W_{})_lo", new_word_idx - 15), + region, + a_6, + get_word_row(new_word_idx - 16), + )?; + sigma_0_output.1.copy_advice( + || format!("sigma_0(W_{})_hi", new_word_idx - 15), + region, + a_6, + get_word_row(new_word_idx - 16) + 1, + )?; + + // Copy sigma_1(W_{i - 2}) + lower_sigma_1_v2_results[new_word_idx - 16].0.copy_advice( + || format!("sigma_1(W_{})_lo", new_word_idx - 2), + region, + a_7, + get_word_row(new_word_idx - 16), + )?; + lower_sigma_1_v2_results[new_word_idx - 16].1.copy_advice( + || format!("sigma_1(W_{})_hi", new_word_idx - 2), + region, + a_7, + get_word_row(new_word_idx - 16) + 1, + )?; + + // Copy W_{i - 7} + w_halves[new_word_idx - 7].0.copy_advice( + || format!("W_{}_lo", new_word_idx - 7), + region, + a_8, + get_word_row(new_word_idx - 16), + )?; + w_halves[new_word_idx - 7].1.copy_advice( + || format!("W_{}_hi", new_word_idx - 7), + region, + a_8, + get_word_row(new_word_idx - 16) + 1, + )?; + + // Calculate W_i, carry_i + let (word, carry) = sum_with_carry(vec![ + ( + lower_sigma_1_v2_results[new_word_idx - 16].0.value_u16(), + lower_sigma_1_v2_results[new_word_idx - 16].1.value_u16(), + ), + ( + w_halves[new_word_idx - 7].0.value_u16(), + w_halves[new_word_idx - 7].1.value_u16(), + ), + (sigma_0_output.0.value_u16(), sigma_0_output.1.value_u16()), + ( + w_halves[new_word_idx - 16].0.value_u16(), + w_halves[new_word_idx - 16].1.value_u16(), + ), + ]); + + // Assign W_i, carry_i + region.assign_advice( + || format!("W_{}", new_word_idx), + a_5, + get_word_row(new_word_idx - 16) + 1, + || word.map(|word| pallas::Base::from(word as u64)), + )?; + region.assign_advice( + || format!("carry_{}", new_word_idx), + a_9, + get_word_row(new_word_idx - 16) + 1, + || carry.map(pallas::Base::from), + )?; + let (word, halves) = self.assign_word_and_halves(region, word, new_word_idx)?; + w.push(MessageWord(word)); + w_halves.push(halves); + + Ok(lower_sigma_0_v2_results.clone()) + }; + + let mut tmp_lower_sigma_0_v2_results: Vec<(AssignedBits<16>, AssignedBits<16>)> = + Vec::with_capacity(SUBREGION_2_LEN); + + // Use up all the output from Subregion 1 lower_sigma_0 + for i in 14..27 { + tmp_lower_sigma_0_v2_results = new_word(i, &lower_sigma_0_output[i - 14])?; + } + + for i in 27..49 { + tmp_lower_sigma_0_v2_results = + new_word(i, &tmp_lower_sigma_0_v2_results[i + 2 - 15 - 14])?; + } + + // Return lower_sigma_0_v2 output for W_[36..49] + Ok(lower_sigma_0_v2_results.split_off(36 - 14)) + } + + /// Pieces of length [3, 4, 3, 7, 1, 1, 13] + fn decompose_word( + &self, + region: &mut Region<'_, pallas::Base>, + word: Value<&Bits<32>>, + index: usize, + ) -> Result { + let row = get_word_row(index); + + let pieces = word.map(|word| { + vec![ + word[0..3].to_vec(), + word[3..7].to_vec(), + word[7..10].to_vec(), + word[10..17].to_vec(), + vec![word[17]], + vec![word[18]], + word[19..32].to_vec(), + ] + }); + let pieces = pieces.transpose_vec(7); + + // Rename these here for ease of matching the gates to the specification. + let a_3 = self.extras[0]; + let a_4 = self.extras[1]; + + // Assign `a` (3-bit piece) + let a = AssignedBits::<3>::assign_bits(region, || "a", a_3, row - 1, pieces[0].clone())?; + + // Assign `b` (4-bit piece) lookup + let spread_b: Value> = pieces[1].clone().map(SpreadWord::try_new); + let spread_b = SpreadVar::with_lookup(region, &self.lookup, row + 1, spread_b)?; + + // Assign `c` (3-bit piece) + let c = AssignedBits::<3>::assign_bits(region, || "c", a_4, row - 1, pieces[2].clone())?; + + // Assign `d` (7-bit piece) lookup + let spread_d: Value> = pieces[3].clone().map(SpreadWord::try_new); + let spread_d = SpreadVar::with_lookup(region, &self.lookup, row, spread_d)?; + + // Assign `e` (1-bit piece) + let e = AssignedBits::<1>::assign_bits(region, || "e", a_3, row + 1, pieces[4].clone())?; + + // Assign `f` (1-bit piece) + let f = AssignedBits::<1>::assign_bits(region, || "f", a_4, row + 1, pieces[5].clone())?; + + // Assign `g` (13-bit piece) lookup + let spread_g = pieces[6].clone().map(SpreadWord::try_new); + let spread_g = SpreadVar::with_lookup(region, &self.lookup, row - 1, spread_g)?; + + Ok(Subregion2Word { + index, + a, + b: spread_b.dense, + c, + _d: spread_d.dense, + e, + f, + _g: spread_g.dense, + spread_d: spread_d.spread, + spread_g: spread_g.spread, + }) + } + + /// A word in subregion 2 + /// (3, 4, 3, 7, 1, 1, 13)-bit chunks + #[allow(clippy::type_complexity)] + fn assign_lower_sigma_v2_pieces( + &self, + region: &mut Region<'_, pallas::Base>, + row: usize, + word: &Subregion2Word, + ) -> Result<(), Error> { + let a_3 = self.extras[0]; + let a_4 = self.extras[1]; + let a_5 = self.message_schedule; + let a_6 = self.extras[2]; + let a_7 = self.extras[3]; + + // Assign `a` and copy constraint + word.a.copy_advice(|| "a", region, a_3, row + 1)?; + + // Witness `spread_a` + AssignedBits::<6>::assign_bits(region, || "spread_a", a_4, row + 1, word.spread_a())?; + + // Split `b` (4-bit chunk) into `b_hi` and `b_lo` + // Assign `b_lo`, `spread_b_lo` + + let b_lo: Value<[bool; 2]> = word.b.value().map(|b| b.0[..2].try_into().unwrap()); + let spread_b_lo = b_lo.map(spread_bits); + { + AssignedBits::<2>::assign_bits(region, || "b_lo", a_3, row - 1, b_lo)?; + + AssignedBits::<4>::assign_bits(region, || "spread_b_lo", a_4, row - 1, spread_b_lo)?; + }; + + // Split `b` (2-bit chunk) into `b_hi` and `b_lo` + // Assign `b_hi`, `spread_b_hi` + let b_hi: Value<[bool; 2]> = word.b.value().map(|b| b.0[2..].try_into().unwrap()); + let spread_b_hi = b_hi.map(spread_bits); + { + AssignedBits::<2>::assign_bits(region, || "b_hi", a_5, row - 1, b_hi)?; + + AssignedBits::<4>::assign_bits(region, || "spread_b_hi", a_6, row - 1, spread_b_hi)?; + }; + + // Assign `b` and copy constraint + word.b.copy_advice(|| "b", region, a_6, row)?; + + // Assign `c` and copy constraint + word.c.copy_advice(|| "c", region, a_5, row + 1)?; + + // Witness `spread_c` + AssignedBits::<6>::assign_bits(region, || "spread_c", a_6, row + 1, word.spread_c())?; + + // Assign `spread_d` and copy constraint + word.spread_d.copy_advice(|| "spread_d", region, a_4, row)?; + + // Assign `e` and copy constraint + word.e.copy_advice(|| "e", region, a_7, row)?; + + // Assign `f` and copy constraint + word.f.copy_advice(|| "f", region, a_7, row + 1)?; + + // Assign `spread_g` and copy constraint + word.spread_g.copy_advice(|| "spread_g", region, a_5, row)?; + + Ok(()) + } + + fn lower_sigma_0_v2( + &self, + region: &mut Region<'_, pallas::Base>, + word: Subregion2Word, + ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { + let a_3 = self.extras[0]; + let row = get_word_row(word.index) + 3; + + // Assign lower sigma_v2 pieces + self.assign_lower_sigma_v2_pieces(region, row, &word)?; + + // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} + let r = word.xor_sigma_0(); + let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); + let r_0_even = r_0.map(even_bits); + let r_0_odd = r_0.map(odd_bits); + + let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); + let r_1_even = r_1.map(even_bits); + let r_1_odd = r_1.map(odd_bits); + + self.assign_sigma_outputs( + region, + &self.lookup, + a_3, + row, + r_0_even, + r_0_odd, + r_1_even, + r_1_odd, + ) + } + + fn lower_sigma_1_v2( + &self, + region: &mut Region<'_, pallas::Base>, + word: Subregion2Word, + ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { + let a_3 = self.extras[0]; + let row = get_word_row(word.index) + SIGMA_0_V2_ROWS + 3; + + // Assign lower sigma_v2 pieces + self.assign_lower_sigma_v2_pieces(region, row, &word)?; + + // (3, 4, 3, 7, 1, 1, 13) + // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} + // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} + let r = word.xor_sigma_1(); + let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); + let r_0_even = r_0.map(even_bits); + let r_0_odd = r_0.map(odd_bits); + + let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); + let r_1_even = r_1.map(even_bits); + let r_1_odd = r_1.map(odd_bits); + + self.assign_sigma_outputs( + region, + &self.lookup, + a_3, + row, + r_0_even, + r_0_odd, + r_1_even, + r_1_odd, + ) + } +} diff --git a/halo2_gadgets_optimized/src/sha256/table16/message_schedule/subregion3.rs b/halo2_gadgets_optimized/src/sha256/table16/message_schedule/subregion3.rs new file mode 100644 index 0000000000..ddb36ebfed --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16/message_schedule/subregion3.rs @@ -0,0 +1,320 @@ +use super::super::{util::*, AssignedBits, Bits, SpreadVar, SpreadWord, Table16Assignment}; +use super::{schedule_util::*, MessageScheduleConfig, MessageWord}; +use halo2_proofs::{ + circuit::{Region, Value}, + pasta::pallas, + plonk::Error, +}; +use std::convert::TryInto; + +// A word in subregion 3 +// (10, 7, 2, 13)-bit chunks +pub struct Subregion3Word { + index: usize, + #[allow(dead_code)] + a: AssignedBits<10>, + b: AssignedBits<7>, + c: AssignedBits<2>, + #[allow(dead_code)] + d: AssignedBits<13>, + spread_a: AssignedBits<20>, + spread_d: AssignedBits<26>, +} + +impl Subregion3Word { + fn spread_a(&self) -> Value<[bool; 20]> { + self.spread_a.value().map(|v| v.0) + } + + fn spread_b(&self) -> Value<[bool; 14]> { + self.b.value().map(|v| v.spread()) + } + + fn spread_c(&self) -> Value<[bool; 4]> { + self.c.value().map(|v| v.spread()) + } + + fn spread_d(&self) -> Value<[bool; 26]> { + self.spread_d.value().map(|v| v.0) + } + + fn xor_lower_sigma_1(&self) -> Value<[bool; 64]> { + self.spread_a() + .zip(self.spread_b()) + .zip(self.spread_c()) + .zip(self.spread_d()) + .map(|(((a, b), c), d)| { + let xor_0 = b + .iter() + .chain(c.iter()) + .chain(d.iter()) + .chain(std::iter::repeat(&false).take(20)) + .copied() + .collect::>(); + + let xor_1 = c + .iter() + .chain(d.iter()) + .chain(a.iter()) + .chain(b.iter()) + .copied() + .collect::>(); + let xor_2 = d + .iter() + .chain(a.iter()) + .chain(b.iter()) + .chain(c.iter()) + .copied() + .collect::>(); + + let xor_0 = lebs2ip::<64>(&xor_0.try_into().unwrap()); + let xor_1 = lebs2ip::<64>(&xor_1.try_into().unwrap()); + let xor_2 = lebs2ip::<64>(&xor_2.try_into().unwrap()); + + i2lebsp(xor_0 + xor_1 + xor_2) + }) + } +} + +impl MessageScheduleConfig { + // W_[49..62] + pub fn assign_subregion3( + &self, + region: &mut Region<'_, pallas::Base>, + lower_sigma_0_v2_output: Vec<(AssignedBits<16>, AssignedBits<16>)>, + w: &mut Vec, + w_halves: &mut Vec<(AssignedBits<16>, AssignedBits<16>)>, + ) -> Result<(), Error> { + let a_5 = self.message_schedule; + let a_6 = self.extras[2]; + let a_7 = self.extras[3]; + let a_8 = self.extras[4]; + let a_9 = self.extras[5]; + + // Closure to compose new word + // W_i = sigma_1(W_{i - 2}) + W_{i - 7} + sigma_0(W_{i - 15}) + W_{i - 16} + // e.g. W_51 = sigma_1(W_49) + W_44 + sigma_0(W_36) + W_35 + + // sigma_0_v2(W_[36..49]) will be used to get the new W_[51..64] + // sigma_1(W_[49..62]) will also be used to get the W_[51..64] + // The lowest-index words involved will be W_[35..58] + let mut new_word = |idx: usize| -> Result<(), Error> { + // Decompose word into (10, 7, 2, 13)-bit chunks + let subregion3_word = self.decompose_subregion3_word(region, w[idx].value(), idx)?; + + // sigma_1 on subregion3_word + let (r_0_even, r_1_even) = self.lower_sigma_1(region, subregion3_word)?; + + let new_word_idx = idx + 2; + + // Copy sigma_0_v2(W_{i - 15}) output from Subregion 2 + lower_sigma_0_v2_output[idx - 49].0.copy_advice( + || format!("sigma_0(W_{})_lo", new_word_idx - 15), + region, + a_6, + get_word_row(new_word_idx - 16), + )?; + lower_sigma_0_v2_output[idx - 49].1.copy_advice( + || format!("sigma_0(W_{})_hi", new_word_idx - 15), + region, + a_6, + get_word_row(new_word_idx - 16) + 1, + )?; + + // Copy sigma_1(W_{i - 2}) + r_0_even.copy_advice( + || format!("sigma_1(W_{})_lo", new_word_idx - 2), + region, + a_7, + get_word_row(new_word_idx - 16), + )?; + r_1_even.copy_advice( + || format!("sigma_1(W_{})_hi", new_word_idx - 2), + region, + a_7, + get_word_row(new_word_idx - 16) + 1, + )?; + + // Copy W_{i - 7} + w_halves[new_word_idx - 7].0.copy_advice( + || format!("W_{}_lo", new_word_idx - 7), + region, + a_8, + get_word_row(new_word_idx - 16), + )?; + w_halves[new_word_idx - 7].1.copy_advice( + || format!("W_{}_hi", new_word_idx - 7), + region, + a_8, + get_word_row(new_word_idx - 16) + 1, + )?; + + // Calculate W_i, carry_i + let (word, carry) = sum_with_carry(vec![ + (r_0_even.value_u16(), r_1_even.value_u16()), + ( + w_halves[new_word_idx - 7].0.value_u16(), + w_halves[new_word_idx - 7].1.value_u16(), + ), + ( + lower_sigma_0_v2_output[idx - 49].0.value_u16(), + lower_sigma_0_v2_output[idx - 49].1.value_u16(), + ), + ( + w_halves[new_word_idx - 16].0.value_u16(), + w_halves[new_word_idx - 16].1.value_u16(), + ), + ]); + + // Assign W_i, carry_i + region.assign_advice( + || format!("W_{}", new_word_idx), + a_5, + get_word_row(new_word_idx - 16) + 1, + || word.map(|word| pallas::Base::from(word as u64)), + )?; + region.assign_advice( + || format!("carry_{}", new_word_idx), + a_9, + get_word_row(new_word_idx - 16) + 1, + || carry.map(pallas::Base::from), + )?; + let (word, halves) = self.assign_word_and_halves(region, word, new_word_idx)?; + w.push(MessageWord(word)); + w_halves.push(halves); + + Ok(()) + }; + + for i in 49..62 { + new_word(i)?; + } + + Ok(()) + } + + /// Pieces of length [10, 7, 2, 13] + fn decompose_subregion3_word( + &self, + region: &mut Region<'_, pallas::Base>, + word: Value<&Bits<32>>, + index: usize, + ) -> Result { + let row = get_word_row(index); + + // Rename these here for ease of matching the gates to the specification. + let a_3 = self.extras[0]; + let a_4 = self.extras[1]; + + let pieces = word.map(|word| { + vec![ + word[0..10].to_vec(), + word[10..17].to_vec(), + word[17..19].to_vec(), + word[19..32].to_vec(), + ] + }); + let pieces = pieces.transpose_vec(4); + + // Assign `a` (10-bit piece) + let spread_a = pieces[0].clone().map(SpreadWord::try_new); + let spread_a = SpreadVar::with_lookup(region, &self.lookup, row + 1, spread_a)?; + + // Assign `b` (7-bit piece) + let b = AssignedBits::<7>::assign_bits(region, || "b", a_4, row + 1, pieces[1].clone())?; + + // Assign `c` (2-bit piece) + let c = AssignedBits::<2>::assign_bits(region, || "c", a_3, row + 1, pieces[2].clone())?; + + // Assign `d` (13-bit piece) lookup + let spread_d = pieces[3].clone().map(SpreadWord::try_new); + let spread_d = SpreadVar::with_lookup(region, &self.lookup, row, spread_d)?; + + Ok(Subregion3Word { + index, + a: spread_a.dense, + b, + c, + d: spread_d.dense, + spread_a: spread_a.spread, + spread_d: spread_d.spread, + }) + } + + fn lower_sigma_1( + &self, + region: &mut Region<'_, pallas::Base>, + word: Subregion3Word, + ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { + let a_3 = self.extras[0]; + let a_4 = self.extras[1]; + let a_5 = self.message_schedule; + let a_6 = self.extras[2]; + + let row = get_word_row(word.index) + 3; + + // Assign `spread_a` and copy constraint + word.spread_a.copy_advice(|| "spread_a", region, a_4, row)?; + + // Split `b` (7-bit chunk) into (2, 2, 3)-bit `b_lo`, `b_mid` and `b_hi`. + // Assign `b_lo`, `spread_b_lo`, `b_mid`, `spread_b_mid`, `b_hi`, `spread_b_hi`. + + // b_lo (2-bit chunk) + { + let b_lo: Value<[bool; 2]> = word.b.value().map(|v| v[0..2].try_into().unwrap()); + let b_lo = b_lo.map(SpreadWord::<2, 4>::new); + SpreadVar::without_lookup(region, a_3, row - 1, a_4, row - 1, b_lo)?; + } + + // b_mid (2-bit chunk) + { + let b_mid: Value<[bool; 2]> = word.b.value().map(|v| v[2..4].try_into().unwrap()); + let b_mid = b_mid.map(SpreadWord::<2, 4>::new); + SpreadVar::without_lookup(region, a_5, row - 1, a_6, row - 1, b_mid)?; + } + + // b_hi (3-bit chunk) + { + let b_hi: Value<[bool; 3]> = word.b.value().map(|v| v[4..7].try_into().unwrap()); + let b_hi = b_hi.map(SpreadWord::<3, 6>::new); + SpreadVar::without_lookup(region, a_5, row + 1, a_6, row + 1, b_hi)?; + } + + // Assign `b` and copy constraint + word.b.copy_advice(|| "b", region, a_6, row)?; + + // Assign `c` and copy constraint + word.c.copy_advice(|| "c", region, a_3, row + 1)?; + + // Witness `spread_c` + { + let spread_c = word.c.value().map(spread_bits); + AssignedBits::<4>::assign_bits(region, || "spread_c", a_4, row + 1, spread_c)?; + } + + // Assign `spread_d` and copy constraint + word.spread_d.copy_advice(|| "spread_d", region, a_5, row)?; + + // (10, 7, 2, 13) + // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} + let r = word.xor_lower_sigma_1(); + let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); + let r_0_even = r_0.map(even_bits); + let r_0_odd = r_0.map(odd_bits); + + let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); + let r_1_even = r_1.map(even_bits); + let r_1_odd = r_1.map(odd_bits); + + self.assign_sigma_outputs( + region, + &self.lookup, + a_3, + row, + r_0_even, + r_0_odd, + r_1_even, + r_1_odd, + ) + } +} diff --git a/halo2_gadgets_optimized/src/sha256/table16/spread_table.rs b/halo2_gadgets_optimized/src/sha256/table16/spread_table.rs new file mode 100644 index 0000000000..9fcd7bdea7 --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16/spread_table.rs @@ -0,0 +1,454 @@ +use super::{util::*, AssignedBits}; + +use group::ff::{Field, PrimeField}; +use halo2_proofs::{ + circuit::{Chip, Layouter, Region, Value}, + pasta::pallas, + plonk::{Advice, Column, ConstraintSystem, Error, TableColumn}, + poly::Rotation, +}; +use std::convert::TryInto; +use std::marker::PhantomData; + +const BITS_7: usize = 1 << 7; +const BITS_10: usize = 1 << 10; +const BITS_11: usize = 1 << 11; +const BITS_13: usize = 1 << 13; +const BITS_14: usize = 1 << 14; + +/// An input word into a lookup, containing (tag, dense, spread) +#[derive(Copy, Clone, Debug)] +pub(super) struct SpreadWord { + pub tag: u8, + pub dense: [bool; DENSE], + pub spread: [bool; SPREAD], +} + +/// Helper function that returns tag of 16-bit input +pub fn get_tag(input: u16) -> u8 { + let input = input as usize; + if input < BITS_7 { + 0 + } else if input < BITS_10 { + 1 + } else if input < BITS_11 { + 2 + } else if input < BITS_13 { + 3 + } else if input < BITS_14 { + 4 + } else { + 5 + } +} + +impl SpreadWord { + pub(super) fn new(dense: [bool; DENSE]) -> Self { + assert!(DENSE <= 16); + SpreadWord { + tag: get_tag(lebs2ip(&dense) as u16), + dense, + spread: spread_bits(dense), + } + } + + pub(super) fn try_new + std::fmt::Debug>(dense: T) -> Self + where + >::Error: std::fmt::Debug, + { + assert!(DENSE <= 16); + let dense: [bool; DENSE] = dense.try_into().unwrap(); + SpreadWord { + tag: get_tag(lebs2ip(&dense) as u16), + dense, + spread: spread_bits(dense), + } + } +} + +/// A variable stored in advice columns corresponding to a row of [`SpreadTableConfig`]. +#[derive(Clone, Debug)] +pub(super) struct SpreadVar { + pub _tag: Value, + pub dense: AssignedBits, + pub spread: AssignedBits, +} + +impl SpreadVar { + pub(super) fn with_lookup( + region: &mut Region<'_, pallas::Base>, + cols: &SpreadInputs, + row: usize, + word: Value>, + ) -> Result { + let tag = word.map(|word| word.tag); + let dense_val = word.map(|word| word.dense); + let spread_val = word.map(|word| word.spread); + + region.assign_advice( + || "tag", + cols.tag, + row, + || tag.map(|tag| pallas::Base::from(tag as u64)), + )?; + + let dense = + AssignedBits::::assign_bits(region, || "dense", cols.dense, row, dense_val)?; + + let spread = + AssignedBits::::assign_bits(region, || "spread", cols.spread, row, spread_val)?; + + Ok(SpreadVar { + _tag: tag, + dense, + spread, + }) + } + + pub(super) fn without_lookup( + region: &mut Region<'_, pallas::Base>, + dense_col: Column, + dense_row: usize, + spread_col: Column, + spread_row: usize, + word: Value>, + ) -> Result { + let tag = word.map(|word| word.tag); + let dense_val = word.map(|word| word.dense); + let spread_val = word.map(|word| word.spread); + + let dense = AssignedBits::::assign_bits( + region, + || "dense", + dense_col, + dense_row, + dense_val, + )?; + + let spread = AssignedBits::::assign_bits( + region, + || "spread", + spread_col, + spread_row, + spread_val, + )?; + + Ok(SpreadVar { + _tag: tag, + dense, + spread, + }) + } +} + +#[derive(Clone, Debug)] +pub(super) struct SpreadInputs { + pub(super) tag: Column, + pub(super) dense: Column, + pub(super) spread: Column, +} + +#[derive(Clone, Debug)] +pub(super) struct SpreadTable { + pub(super) tag: TableColumn, + pub(super) dense: TableColumn, + pub(super) spread: TableColumn, +} + +#[derive(Clone, Debug)] +pub(super) struct SpreadTableConfig { + pub input: SpreadInputs, + pub table: SpreadTable, +} + +#[derive(Clone, Debug)] +pub(super) struct SpreadTableChip { + config: SpreadTableConfig, + _marker: PhantomData, +} + +impl Chip for SpreadTableChip { + type Config = SpreadTableConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} + +impl SpreadTableChip { + pub fn configure( + meta: &mut ConstraintSystem, + input_tag: Column, + input_dense: Column, + input_spread: Column, + ) -> >::Config { + let table_tag = meta.lookup_table_column(); + let table_dense = meta.lookup_table_column(); + let table_spread = meta.lookup_table_column(); + + meta.lookup(|meta| { + let tag_cur = meta.query_advice(input_tag, Rotation::cur()); + let dense_cur = meta.query_advice(input_dense, Rotation::cur()); + let spread_cur = meta.query_advice(input_spread, Rotation::cur()); + + vec![ + (tag_cur, table_tag), + (dense_cur, table_dense), + (spread_cur, table_spread), + ] + }); + + SpreadTableConfig { + input: SpreadInputs { + tag: input_tag, + dense: input_dense, + spread: input_spread, + }, + table: SpreadTable { + tag: table_tag, + dense: table_dense, + spread: table_spread, + }, + } + } + + pub fn load( + config: SpreadTableConfig, + layouter: &mut impl Layouter, + ) -> Result<>::Loaded, Error> { + layouter.assign_table( + || "spread table", + |mut table| { + // We generate the row values lazily (we only need them during keygen). + let mut rows = SpreadTableConfig::generate::(); + + for index in 0..(1 << 16) { + let mut row = None; + table.assign_cell( + || "tag", + config.table.tag, + index, + || { + row = rows.next(); + Value::known(row.map(|(tag, _, _)| tag).unwrap()) + }, + )?; + table.assign_cell( + || "dense", + config.table.dense, + index, + || Value::known(row.map(|(_, dense, _)| dense).unwrap()), + )?; + table.assign_cell( + || "spread", + config.table.spread, + index, + || Value::known(row.map(|(_, _, spread)| spread).unwrap()), + )?; + } + + Ok(()) + }, + ) + } +} + +impl SpreadTableConfig { + fn generate() -> impl Iterator { + (1..=(1 << 16)).scan((F::ZERO, F::ZERO, F::ZERO), |(tag, dense, spread), i| { + // We computed this table row in the previous iteration. + let res = (*tag, *dense, *spread); + + // i holds the zero-indexed row number for the next table row. + match i { + BITS_7 | BITS_10 | BITS_11 | BITS_13 | BITS_14 => *tag += F::ONE, + _ => (), + } + *dense += F::ONE; + if i & 1 == 0 { + // On even-numbered rows we recompute the spread. + *spread = F::ZERO; + for b in 0..16 { + if (i >> b) & 1 != 0 { + *spread += F::from(1 << (2 * b)); + } + } + } else { + // On odd-numbered rows we add one. + *spread += F::ONE; + } + + Some(res) + }) + } +} + +#[cfg(test)] +mod tests { + use super::{get_tag, SpreadTableChip, SpreadTableConfig}; + use rand::Rng; + + use group::ff::PrimeField; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + dev::MockProver, + pasta::Fp, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, + }; + + #[test] + fn lookup_table() { + /// This represents an advice column at a certain row in the ConstraintSystem + #[derive(Copy, Clone, Debug)] + pub struct Variable(Column, usize); + + struct MyCircuit {} + + impl Circuit for MyCircuit { + type Config = SpreadTableConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + MyCircuit {} + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let input_tag = meta.advice_column(); + let input_dense = meta.advice_column(); + let input_spread = meta.advice_column(); + + SpreadTableChip::configure(meta, input_tag, input_dense, input_spread) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + SpreadTableChip::load(config.clone(), &mut layouter)?; + + layouter.assign_region( + || "spread_test", + |mut gate| { + let mut row = 0; + let mut add_row = |tag, dense, spread| -> Result<(), Error> { + gate.assign_advice( + || "tag", + config.input.tag, + row, + || Value::known(tag), + )?; + gate.assign_advice( + || "dense", + config.input.dense, + row, + || Value::known(dense), + )?; + gate.assign_advice( + || "spread", + config.input.spread, + row, + || Value::known(spread), + )?; + row += 1; + Ok(()) + }; + + // Test the first few small values. + add_row(F::ZERO, F::from(0b000), F::from(0b000000))?; + add_row(F::ZERO, F::from(0b001), F::from(0b000001))?; + add_row(F::ZERO, F::from(0b010), F::from(0b000100))?; + add_row(F::ZERO, F::from(0b011), F::from(0b000101))?; + add_row(F::ZERO, F::from(0b100), F::from(0b010000))?; + add_row(F::ZERO, F::from(0b101), F::from(0b010001))?; + + // Test the tag boundaries: + // 7-bit + add_row(F::ZERO, F::from(0b1111111), F::from(0b01010101010101))?; + add_row(F::ONE, F::from(0b10000000), F::from(0b0100000000000000))?; + // - 10-bit + add_row( + F::ONE, + F::from(0b1111111111), + F::from(0b01010101010101010101), + )?; + add_row( + F::from(2), + F::from(0b10000000000), + F::from(0b0100000000000000000000), + )?; + // - 11-bit + add_row( + F::from(2), + F::from(0b11111111111), + F::from(0b0101010101010101010101), + )?; + add_row( + F::from(3), + F::from(0b100000000000), + F::from(0b010000000000000000000000), + )?; + // - 13-bit + add_row( + F::from(3), + F::from(0b1111111111111), + F::from(0b01010101010101010101010101), + )?; + add_row( + F::from(4), + F::from(0b10000000000000), + F::from(0b0100000000000000000000000000), + )?; + // - 14-bit + add_row( + F::from(4), + F::from(0b11111111111111), + F::from(0b0101010101010101010101010101), + )?; + add_row( + F::from(5), + F::from(0b100000000000000), + F::from(0b010000000000000000000000000000), + )?; + + // Test random lookup values + let mut rng = rand::thread_rng(); + + fn interleave_u16_with_zeros(word: u16) -> u32 { + let mut word: u32 = word.into(); + word = (word ^ (word << 8)) & 0x00ff00ff; + word = (word ^ (word << 4)) & 0x0f0f0f0f; + word = (word ^ (word << 2)) & 0x33333333; + word = (word ^ (word << 1)) & 0x55555555; + word + } + + for _ in 0..10 { + let word: u16 = rng.gen(); + add_row( + F::from(u64::from(get_tag(word))), + F::from(u64::from(word)), + F::from(u64::from(interleave_u16_with_zeros(word))), + )?; + } + + Ok(()) + }, + ) + } + } + + let circuit: MyCircuit = MyCircuit {}; + + let prover = match MockProver::::run(17, &circuit, vec![]) { + Ok(prover) => prover, + Err(e) => panic!("{:?}", e), + }; + assert_eq!(prover.verify(), Ok(())); + } +} diff --git a/halo2_gadgets_optimized/src/sha256/table16/util.rs b/halo2_gadgets_optimized/src/sha256/table16/util.rs new file mode 100644 index 0000000000..628f03742b --- /dev/null +++ b/halo2_gadgets_optimized/src/sha256/table16/util.rs @@ -0,0 +1,117 @@ +use halo2_proofs::circuit::Value; + +pub const MASK_EVEN_32: u32 = 0x55555555; + +/// The sequence of bits representing a u64 in little-endian order. +/// +/// # Panics +/// +/// Panics if the expected length of the sequence `NUM_BITS` exceeds +/// 64. +pub fn i2lebsp(int: u64) -> [bool; NUM_BITS] { + /// Takes in an FnMut closure and returns a constant-length array with elements of + /// type `Output`. + fn gen_const_array( + closure: impl FnMut(usize) -> Output, + ) -> [Output; LEN] { + gen_const_array_with_default(Default::default(), closure) + } + + fn gen_const_array_with_default( + default_value: Output, + closure: impl FnMut(usize) -> Output, + ) -> [Output; LEN] { + let mut ret: [Output; LEN] = [default_value; LEN]; + for (bit, val) in ret.iter_mut().zip((0..LEN).map(closure)) { + *bit = val; + } + ret + } + + assert!(NUM_BITS <= 64); + gen_const_array(|mask: usize| (int & (1 << mask)) != 0) +} + +/// Returns the integer representation of a little-endian bit-array. +/// Panics if the number of bits exceeds 64. +pub fn lebs2ip(bits: &[bool; K]) -> u64 { + assert!(K <= 64); + bits.iter() + .enumerate() + .fold(0u64, |acc, (i, b)| acc + if *b { 1 << i } else { 0 }) +} + +/// Helper function that interleaves a little-endian bit-array with zeros +/// in the odd indices. That is, it takes the array +/// [b_0, b_1, ..., b_n] +/// to +/// [b_0, 0, b_1, 0, ..., b_n, 0]. +/// Panics if bit-array is longer than 16 bits. +pub fn spread_bits( + bits: impl Into<[bool; DENSE]>, +) -> [bool; SPREAD] { + assert_eq!(DENSE * 2, SPREAD); + assert!(DENSE <= 16); + + let bits: [bool; DENSE] = bits.into(); + let mut spread = [false; SPREAD]; + + for (idx, bit) in bits.iter().enumerate() { + spread[idx * 2] = *bit; + } + + spread +} + +/// Negates the even bits in a spread bit-array. +pub fn negate_spread(arr: [bool; LEN]) -> [bool; LEN] { + assert_eq!(LEN % 2, 0); + + let mut neg = arr; + for even_idx in (0..LEN).step_by(2) { + let odd_idx = even_idx + 1; + assert!(!arr[odd_idx]); + + neg[even_idx] = !arr[even_idx]; + } + + neg +} + +/// Returns even bits in a bit-array +pub fn even_bits(bits: [bool; LEN]) -> [bool; HALF] { + assert_eq!(LEN % 2, 0); + let mut even_bits = [false; HALF]; + for idx in 0..HALF { + even_bits[idx] = bits[idx * 2] + } + even_bits +} + +/// Returns odd bits in a bit-array +pub fn odd_bits(bits: [bool; LEN]) -> [bool; HALF] { + assert_eq!(LEN % 2, 0); + let mut odd_bits = [false; HALF]; + for idx in 0..HALF { + odd_bits[idx] = bits[idx * 2 + 1] + } + odd_bits +} + +/// Given a vector of words as vec![(lo: u16, hi: u16)], returns their sum: u32, along +/// with a carry bit. +pub fn sum_with_carry(words: Vec<(Value, Value)>) -> (Value, Value) { + let words_lo: Value> = words.iter().map(|(lo, _)| lo.map(|lo| lo as u64)).collect(); + let words_hi: Value> = words.iter().map(|(_, hi)| hi.map(|hi| hi as u64)).collect(); + + let sum: Value = { + let sum_lo: Value = words_lo.map(|vec| vec.iter().sum()); + let sum_hi: Value = words_hi.map(|vec| vec.iter().sum()); + sum_lo.zip(sum_hi).map(|(lo, hi)| lo + (1 << 16) * hi) + }; + + let carry = sum.map(|sum| sum >> 32); + let sum = sum.map(|sum| sum as u32); + + (sum, carry) +} diff --git a/halo2_gadgets_optimized/src/sinsemilla.rs b/halo2_gadgets_optimized/src/sinsemilla.rs new file mode 100644 index 0000000000..c5b90441c6 --- /dev/null +++ b/halo2_gadgets_optimized/src/sinsemilla.rs @@ -0,0 +1,849 @@ +//! The [Sinsemilla] hash function. +//! +//! [Sinsemilla]: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash +use crate::{ + ecc::{self, EccInstructions, FixedPoints}, + utilities::{FieldValue, RangeConstrained, Var}, +}; +use group::ff::{Field, PrimeField}; +use halo2_proofs::{ + circuit::{Layouter, Value}, + plonk::Error, +}; +use pasta_curves::arithmetic::CurveAffine; +use std::fmt::Debug; + +pub mod chip; +pub mod merkle; +mod message; +pub mod primitives; + +/// The set of circuit instructions required to use the [`Sinsemilla`](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html) gadget. +/// This trait is bounded on two constant parameters: `K`, the number of bits +/// in each word accepted by the Sinsemilla hash, and `MAX_WORDS`, the maximum +/// number of words that a single hash instance can process. +pub trait SinsemillaInstructions { + /// A variable in the circuit. + type CellValue: Var; + + /// A message composed of [`Self::MessagePiece`]s. + type Message: From>; + + /// A piece in a message containing a number of `K`-bit words. + /// A [`Self::MessagePiece`] fits in a single base field element, + /// which means it can only contain up to `N` words, where + /// `N*K <= C::Base::CAPACITY`. + /// + /// For example, in the case `K = 10`, `CAPACITY = 254`, we can fit + /// up to `N = 25` words in a single base field element. + type MessagePiece: Clone + Debug; + + /// A cumulative sum `z` is used to decompose a Sinsemilla message. It + /// produces intermediate values for each word in the message, such + /// that `z_next` = (`z_cur` - `word_next`) / `2^K`. + /// + /// These intermediate values are useful for range checks on subsets + /// of the Sinsemilla message. Sinsemilla messages in the Orchard + /// protocol are composed of field elements, and we need to check + /// the canonicity of the field element encodings in certain cases. + type RunningSum; + + /// The x-coordinate of a point output of [`Self::hash_to_point`]. + type X; + /// A point output of [`Self::hash_to_point`]. + type NonIdentityPoint: Clone + Debug; + /// A type enumerating the fixed points used in `CommitDomains`. + type FixedPoints: FixedPoints; + + /// HashDomains used in this instruction. + type HashDomains: HashDomains; + /// CommitDomains used in this instruction. + type CommitDomains: CommitDomains; + + /// Witness a message piece given a field element. Returns a [`Self::MessagePiece`] + /// encoding the given message. + /// + /// # Panics + /// + /// Panics if `num_words` exceed the maximum number of `K`-bit words that + /// can fit into a single base field element. + fn witness_message_piece( + &self, + layouter: impl Layouter, + value: Value, + num_words: usize, + ) -> Result; + + /// Hashes a message to an ECC curve point. + /// This returns both the resulting point, as well as the message + /// decomposition in the form of intermediate values in a cumulative + /// sum. + /// The initial point `Q` is a public point. + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + fn hash_to_point( + &self, + layouter: impl Layouter, + Q: C, + message: Self::Message, + ) -> Result<(Self::NonIdentityPoint, Vec), Error>; + + /// Hashes a message to an ECC curve point. + /// This returns both the resulting point, as well as the message + /// decomposition in the form of intermediate values in a cumulative + /// sum. + /// The initial point `Q` is a private point. + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + fn hash_to_point_with_private_init( + &self, + layouter: impl Layouter, + Q: &Self::NonIdentityPoint, + message: Self::Message, + ) -> Result<(Self::NonIdentityPoint, Vec), Error>; + + /// Extracts the x-coordinate of the output of a Sinsemilla hash. + fn extract(point: &Self::NonIdentityPoint) -> Self::X; +} + +/// A message to be hashed. +/// +/// Composed of [`MessagePiece`]s with bitlength some multiple of `K`. +/// +/// [`MessagePiece`]: SinsemillaInstructions::MessagePiece +#[derive(Clone, Debug)] +pub struct Message + where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, +{ + chip: SinsemillaChip, + inner: SinsemillaChip::Message, +} + +impl +Message + where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, +{ + #![allow(dead_code)] + fn from_bitstring( + chip: SinsemillaChip, + mut layouter: impl Layouter, + bitstring: Vec>, + ) -> Result { + // Message must be composed of `K`-bit words. + assert_eq!(bitstring.len() % K, 0); + + // Message must have at most `MAX_WORDS` words. + assert!(bitstring.len() / K <= MAX_WORDS); + + // Each message piece must have at most `floor(C::CAPACITY / K)` words. + let piece_num_words = C::Base::CAPACITY as usize / K; + let pieces: Result, _> = bitstring + .chunks(piece_num_words * K) + .enumerate() + .map( + |(i, piece)| -> Result, Error> { + MessagePiece::from_bitstring( + chip.clone(), + layouter.namespace(|| format!("message piece {}", i)), + piece, + ) + }, + ) + .collect(); + + pieces.map(|pieces| Self::from_pieces(chip, pieces)) + } + + /// Constructs a message from a vector of [`MessagePiece`]s. + /// + /// [`MessagePiece`]: SinsemillaInstructions::MessagePiece + pub fn from_pieces( + chip: SinsemillaChip, + pieces: Vec>, + ) -> Self { + Self { + chip, + inner: pieces + .into_iter() + .map(|piece| piece.inner) + .collect::>() + .into(), + } + } +} + +/// A message piece with a bitlength of some multiple of `K`. +#[derive(Copy, Clone, Debug)] +pub struct MessagePiece + where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, +{ + inner: SinsemillaChip::MessagePiece, +} + +impl +MessagePiece + where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, +{ + /// Returns the inner MessagePiece contained in this gadget. + pub fn inner(&self) -> SinsemillaChip::MessagePiece { + self.inner.clone() + } +} + +impl +MessagePiece + where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, +{ + #![allow(dead_code)] + fn from_bitstring( + chip: SinsemillaChip, + layouter: impl Layouter, + bitstring: &[Value], + ) -> Result { + // Message must be composed of `K`-bit words. + assert_eq!(bitstring.len() % K, 0); + let num_words = bitstring.len() / K; + + // Each message piece must have at most `floor(C::Base::CAPACITY / K)` words. + // This ensures that the all-ones bitstring is canonical in the field. + let piece_max_num_words = C::Base::CAPACITY as usize / K; + assert!(num_words <= piece_max_num_words); + + // Closure to parse a bitstring (little-endian) into a base field element. + let to_base_field = |bits: &[Value]| -> Value { + let bits: Value> = bits.iter().cloned().collect(); + bits.map(|bits| { + bits.into_iter().rev().fold(C::Base::ZERO, |acc, bit| { + if bit { + acc.double() + C::Base::ONE + } else { + acc.double() + } + }) + }) + }; + + let piece_value = to_base_field(bitstring); + Self::from_field_elem(chip, layouter, piece_value, num_words) + } + + /// Constructs a MessagePiece from a field element. + pub fn from_field_elem( + chip: SinsemillaChip, + layouter: impl Layouter, + field_elem: Value, + num_words: usize, + ) -> Result { + let inner = chip.witness_message_piece(layouter, field_elem, num_words)?; + Ok(Self { inner }) + } + + /// Constructs a `MessagePiece` by concatenating a sequence of [`RangeConstrained`] + /// subpiece values. + /// + /// The `MessagePiece` is assigned to the circuit, but not constrained in any way. + /// + /// # Panics + /// + /// Panics if the total number of bits across the subpieces is not a multiple of the + /// word size, or if the required bitshift for any subpiece is greater than 63 bits. + pub fn from_subpieces( + chip: SinsemillaChip, + layouter: impl Layouter, + subpieces: impl IntoIterator>>, + ) -> Result { + let (field_elem, total_bits) = subpieces.into_iter().fold( + (Value::known(C::Base::ZERO), 0), + |(acc, bits), subpiece| { + assert!(bits < 64); + let subpiece_shifted = subpiece + .inner() + .value() + .map(|v| C::Base::from(1 << bits) * v); + (acc + subpiece_shifted, bits + subpiece.num_bits()) + }, + ); + + // Message must be composed of `K`-bit words. + assert_eq!(total_bits % K, 0); + let num_words = total_bits / K; + + Self::from_field_elem(chip, layouter, field_elem, num_words) + } +} + +/// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can +/// be used. +#[derive(Debug)] +#[allow(non_snake_case)] +pub struct HashDomain< + C: CurveAffine, + SinsemillaChip, + EccChip, + const K: usize, + const MAX_WORDS: usize, +> where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, + EccChip: EccInstructions< + C, + NonIdentityPoint = >::NonIdentityPoint, + FixedPoints = >::FixedPoints, + > + Clone + + Debug + + Eq, +{ + sinsemilla_chip: SinsemillaChip, + ecc_chip: EccChip, + Q: C, +} + +impl +HashDomain + where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, + EccChip: EccInstructions< + C, + NonIdentityPoint = >::NonIdentityPoint, + FixedPoints = >::FixedPoints, + > + Clone + + Debug + + Eq, +{ + #[allow(non_snake_case)] + /// Constructs a new `HashDomain` for the given domain. + pub fn new( + sinsemilla_chip: SinsemillaChip, + ecc_chip: EccChip, + domain: &SinsemillaChip::HashDomains, + ) -> Self { + HashDomain { + sinsemilla_chip, + ecc_chip, + Q: domain.Q(), + } + } + + #[allow(clippy::type_complexity)] + /// $\mathsf{SinsemillaHashToPoint}$ from [§ 5.4.1.9][concretesinsemillahash]. + /// + /// [concretesinsemillahash]: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash + pub fn hash_to_point( + &self, + layouter: impl Layouter, + message: Message, + ) -> Result<(ecc::NonIdentityPoint, Vec), Error> { + assert_eq!(self.sinsemilla_chip, message.chip); + self.sinsemilla_chip + .hash_to_point(layouter, self.Q, message.inner) + .map(|(point, zs)| (ecc::NonIdentityPoint::from_inner(self.ecc_chip.clone(), point), zs)) + } + + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + /// Evaluate the Sinsemilla hash of `message` from the private initial point `Q`. + pub fn hash_to_point_with_private_init( + &self, + layouter: impl Layouter, + Q: &>::NonIdentityPoint, + message: Message, + ) -> Result<(ecc::NonIdentityPoint, Vec), Error> { + assert_eq!(self.sinsemilla_chip, message.chip); + self.sinsemilla_chip + .hash_to_point_with_private_init(layouter, Q, message.inner) + .map(|(point, zs)| (ecc::NonIdentityPoint::from_inner(self.ecc_chip.clone(), point), zs)) + } + + /// $\mathsf{SinsemillaHash}$ from [§ 5.4.1.9][concretesinsemillahash]. + /// + /// [concretesinsemillahash]: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash + #[allow(clippy::type_complexity)] + pub fn hash( + &self, + layouter: impl Layouter, + message: Message, + ) -> Result<(ecc::X, Vec), Error> { + assert_eq!(self.sinsemilla_chip, message.chip); + let (p, zs) = self.hash_to_point(layouter, message)?; + Ok((p.extract_p(), zs)) + } +} + +/// Trait allowing circuit's Sinsemilla CommitDomains to be enumerated. +pub trait CommitDomains, H: HashDomains>: +Clone + Debug +{ + /// Returns the fixed point corresponding to the R constant used for + /// randomization in this CommitDomain. + fn r(&self) -> F::FullScalar; + + /// Returns the HashDomain contained in this CommitDomain + fn hash_domain(&self) -> H; +} + +/// Trait allowing circuit's Sinsemilla HashDomains to be enumerated. +#[allow(non_snake_case)] +pub trait HashDomains: Clone + Debug { + /// Returns the `Q` constant for this domain. + fn Q(&self) -> C; +} + +/// Gadget representing a domain in which $\mathsf{SinsemillaCommit}$ and +/// $\mathsf{SinsemillaShortCommit}$ can be used. +#[derive(Debug)] +#[allow(non_snake_case)] +pub struct CommitDomain< + C: CurveAffine, + SinsemillaChip, + EccChip, + const K: usize, + const MAX_WORDS: usize, +> where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, + EccChip: EccInstructions< + C, + NonIdentityPoint = >::NonIdentityPoint, + FixedPoints = >::FixedPoints, + > + Clone + + Debug + + Eq, +{ + M: HashDomain, + R: ecc::FixedPoint, +} + +impl +CommitDomain + where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, + EccChip: EccInstructions< + C, + NonIdentityPoint = >::NonIdentityPoint, + FixedPoints = >::FixedPoints, + > + Clone + + Debug + + Eq, +{ + /// Constructs a new `CommitDomain` for the given domain. + pub fn new( + sinsemilla_chip: SinsemillaChip, + ecc_chip: EccChip, + // TODO: Instead of using SinsemilllaChip::CommitDomains, just use something that implements a CommitDomains trait + domain: &SinsemillaChip::CommitDomains, + ) -> Self { + CommitDomain { + M: HashDomain::new(sinsemilla_chip, ecc_chip.clone(), &domain.hash_domain()), + R: ecc::FixedPoint::from_inner(ecc_chip, domain.r()), + } + } + + #[allow(clippy::type_complexity)] + /// Evaluates the Sinsemilla hash of `message` from the public initial point `Q` stored + /// into `CommitDomain`. + pub fn hash( + &self, + layouter: impl Layouter, + message: Message, + ) -> Result< + ( + ecc::NonIdentityPoint, + Vec, + ), + Error, + > { + assert_eq!(self.M.sinsemilla_chip, message.chip); + self.M.hash_to_point(layouter, message) + } + + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + /// Evaluates the Sinsemilla hash of `message` from the private initial point `Q`. + pub fn hash_with_private_init( + &self, + layouter: impl Layouter, + Q: &>::NonIdentityPoint, + message: Message, + ) -> Result< + ( + ecc::NonIdentityPoint, + Vec, + ), + Error, + > { + assert_eq!(self.M.sinsemilla_chip, message.chip); + self.M.hash_to_point_with_private_init(layouter, Q, message) + } + + #[allow(clippy::type_complexity)] + /// Returns the public initial point `Q` stored into `CommitDomain`. + pub fn q_init(&self) -> C { + self.M.Q + } + + #[allow(clippy::type_complexity)] + /// Evaluates the blinding factor equal to $\[r\] R$ where `r` is stored in the `CommitDomain`. + pub fn blinding_factor( + &self, + mut layouter: impl Layouter, + r: ecc::ScalarFixed, + ) -> Result< + ecc::Point, + Error, + > { + let (blind, _) = self.R.mul(layouter.namespace(|| "[r] R"), r)?; + Ok(blind) + } + + #[allow(clippy::type_complexity)] + /// $\mathsf{SinsemillaCommit}$ from [§ 5.4.8.4][concretesinsemillacommit]. + /// + /// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit + pub fn commit( + &self, + mut layouter: impl Layouter, + message: Message, + r: ecc::ScalarFixed, + ) -> Result< + ( + ecc::Point, + Vec, + ), + Error, + > { + assert_eq!(self.M.sinsemilla_chip, message.chip); + let blind = self.blinding_factor(layouter.namespace(|| "[r] R"), r)?; + let (p, zs) = self.hash(layouter.namespace(|| "M"), message)?; + let commitment = p.add(layouter.namespace(|| "M + [r] R"), &blind)?; + Ok((commitment, zs)) + } + + #[allow(clippy::type_complexity)] + /// $\mathsf{SinsemillaShortCommit}$ from [§ 5.4.8.4][concretesinsemillacommit]. + /// + /// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit + pub fn short_commit( + &self, + mut layouter: impl Layouter, + message: Message, + r: ecc::ScalarFixed, + ) -> Result<(ecc::X, Vec), Error> { + assert_eq!(self.M.sinsemilla_chip, message.chip); + let (p, zs) = self.commit(layouter.namespace(|| "commit"), message, r)?; + Ok((p.extract_p(), zs)) + } +} + +#[cfg(test)] +pub(crate) mod tests { + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + dev::MockProver, + plonk::{Circuit, ConstraintSystem, Error}, + }; + use rand::rngs::OsRng; + + use super::{ + chip::{SinsemillaChip, SinsemillaConfig}, + CommitDomain, CommitDomains, HashDomain, HashDomains, Message, MessagePiece, + }; + + use crate::{ + ecc::ScalarFixed, + sinsemilla::primitives::{self as sinsemilla, K}, + { + ecc::{ + chip::{find_zs_and_us, EccChip, EccConfig, H, NUM_WINDOWS}, + tests::{FullWidth, TestFixedBases}, + NonIdentityPoint, + }, + utilities::lookup_range_check::LookupRangeCheckConfig, + }, + }; + + use group::{ff::Field, Curve}; + use lazy_static::lazy_static; + use pasta_curves::pallas; + + use std::convert::TryInto; + + pub(crate) const PERSONALIZATION: &str = "MerkleCRH"; + + lazy_static! { + static ref COMMIT_DOMAIN: sinsemilla::CommitDomain = + sinsemilla::CommitDomain::new(PERSONALIZATION); + static ref Q: pallas::Affine = COMMIT_DOMAIN.Q().to_affine(); + static ref R: pallas::Affine = COMMIT_DOMAIN.R().to_affine(); + static ref R_ZS_AND_US: Vec<(u64, [pallas::Base; H])> = + find_zs_and_us(*R, NUM_WINDOWS).unwrap(); + } + + #[derive(Debug, Clone, Eq, PartialEq)] + pub(crate) struct TestHashDomain; + impl HashDomains for TestHashDomain { + fn Q(&self) -> pallas::Affine { + *Q + } + } + + // This test does not make use of the CommitDomain. + #[derive(Debug, Clone, Eq, PartialEq)] + pub(crate) struct TestCommitDomain; + impl CommitDomains for TestCommitDomain { + fn r(&self) -> FullWidth { + FullWidth::from_parts(*R, &R_ZS_AND_US) + } + + fn hash_domain(&self) -> TestHashDomain { + TestHashDomain + } + } + + struct MyCircuit {} + + impl Circuit for MyCircuit { + #[allow(clippy::type_complexity)] + type Config = ( + EccConfig, + SinsemillaConfig, + SinsemillaConfig, + ); + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + MyCircuit {} + } + + #[allow(non_snake_case)] + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let advices = [ + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + ]; + + // Shared fixed column for loading constants + let constants = meta.fixed_column(); + meta.enable_constant(constants); + + let table_idx = meta.lookup_table_column(); + let table_range_check_tag = meta.lookup_table_column(); + let lagrange_coeffs = [ + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + ]; + + // Fixed columns for the Sinsemilla generator lookup table + let lookup = ( + table_idx, + meta.lookup_table_column(), + meta.lookup_table_column(), + table_range_check_tag, + ); + + let range_check = LookupRangeCheckConfig::configure( + meta, + advices[9], + table_idx, + table_range_check_tag, + ); + + let ecc_config = + EccChip::::configure(meta, advices, lagrange_coeffs, range_check); + + let config1 = SinsemillaChip::configure( + meta, + advices[..5].try_into().unwrap(), + advices[2], + lagrange_coeffs[0], + lookup, + range_check, + ); + let config2 = SinsemillaChip::configure( + meta, + advices[5..].try_into().unwrap(), + advices[7], + lagrange_coeffs[1], + lookup, + range_check, + ); + (ecc_config, config1, config2) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let rng = OsRng; + + let ecc_chip = EccChip::construct(config.0); + + // The two `SinsemillaChip`s share the same lookup table. + SinsemillaChip::::load( + config.1.clone(), + &mut layouter, + )?; + + // This MerkleCRH example is purely for illustrative purposes. + // It is not an implementation of the Orchard protocol spec. + { + let chip1 = SinsemillaChip::construct(config.1); + + let merkle_crh = HashDomain::new(chip1.clone(), ecc_chip.clone(), &TestHashDomain); + + // Layer 31, l = MERKLE_DEPTH - 1 - layer = 0 + let l_bitstring = vec![Value::known(false); K]; + let l = MessagePiece::from_bitstring( + chip1.clone(), + layouter.namespace(|| "l"), + &l_bitstring, + )?; + + // Left leaf + let left_bitstring: Vec> = (0..250) + .map(|_| Value::known(rand::random::())) + .collect(); + let left = MessagePiece::from_bitstring( + chip1.clone(), + layouter.namespace(|| "left"), + &left_bitstring, + )?; + + // Right leaf + let right_bitstring: Vec> = (0..250) + .map(|_| Value::known(rand::random::())) + .collect(); + let right = MessagePiece::from_bitstring( + chip1.clone(), + layouter.namespace(|| "right"), + &right_bitstring, + )?; + + let l_bitstring: Value> = l_bitstring.into_iter().collect(); + let left_bitstring: Value> = left_bitstring.into_iter().collect(); + let right_bitstring: Value> = right_bitstring.into_iter().collect(); + + // Witness expected parent + let expected_parent = { + let expected_parent = l_bitstring.zip(left_bitstring.zip(right_bitstring)).map( + |(l, (left, right))| { + let merkle_crh = sinsemilla::HashDomain::from_Q((*Q).into()); + let point = merkle_crh + .hash_to_point( + l.into_iter() + .chain(left.into_iter()) + .chain(right.into_iter()), + ) + .unwrap(); + point.to_affine() + }, + ); + + NonIdentityPoint::new( + ecc_chip.clone(), + layouter.namespace(|| "Witness expected parent"), + expected_parent, + )? + }; + + // Parent + let (parent, _) = { + let message = Message::from_pieces(chip1, vec![l, left, right]); + merkle_crh.hash_to_point(layouter.namespace(|| "parent"), message)? + }; + + parent.constrain_equal( + layouter.namespace(|| "parent == expected parent"), + &expected_parent, + )?; + } + + { + let chip2 = SinsemillaChip::construct(config.2); + + let test_commit = + CommitDomain::new(chip2.clone(), ecc_chip.clone(), &TestCommitDomain); + let r_val = pallas::Scalar::random(rng); + let message: Vec> = (0..500) + .map(|_| Value::known(rand::random::())) + .collect(); + + let (result, _) = { + let r = ScalarFixed::new( + ecc_chip.clone(), + layouter.namespace(|| "r"), + Value::known(r_val), + )?; + let message = Message::from_bitstring( + chip2, + layouter.namespace(|| "witness message"), + message.clone(), + )?; + test_commit.commit(layouter.namespace(|| "commit"), message, r)? + }; + + // Witness expected result. + let expected_result = { + let message: Value> = message.into_iter().collect(); + let expected_result = message.map(|message| { + let domain = sinsemilla::CommitDomain::new(PERSONALIZATION); + let point = domain.commit(message.into_iter(), &r_val).unwrap(); + point.to_affine() + }); + + NonIdentityPoint::new( + ecc_chip, + layouter.namespace(|| "Witness expected result"), + expected_result, + )? + }; + + result.constrain_equal( + layouter.namespace(|| "result == expected result"), + &expected_result, + ) + } + } + } + + #[test] + fn sinsemilla_chip() { + let k = 11; + let circuit = MyCircuit {}; + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())) + } + + #[cfg(feature = "test-dev-graph")] + #[test] + fn print_sinsemilla_chip() { + use plotters::prelude::*; + + let root = + BitMapBackend::new("sinsemilla-hash-layout.png", (1024, 7680)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root.titled("SinsemillaHash", ("sans-serif", 60)).unwrap(); + + let circuit = MyCircuit {}; + halo2_proofs::dev::CircuitLayout::default() + .render(11, &circuit, &root) + .unwrap(); + } +} \ No newline at end of file diff --git a/halo2_gadgets_optimized/src/sinsemilla/chip.rs b/halo2_gadgets_optimized/src/sinsemilla/chip.rs new file mode 100644 index 0000000000..75bbaf7014 --- /dev/null +++ b/halo2_gadgets_optimized/src/sinsemilla/chip.rs @@ -0,0 +1,342 @@ +//! Chip implementations for the Sinsemilla gadgets. + +use super::{ + message::{Message, MessagePiece}, + primitives as sinsemilla, CommitDomains, HashDomains, SinsemillaInstructions, +}; +use crate::{ + ecc::{ + chip::{DoubleAndAdd, NonIdentityEccPoint}, + FixedPoints, + }, + utilities::lookup_range_check::LookupRangeCheckConfig, +}; +use std::marker::PhantomData; + +use halo2_proofs::{ + circuit::{AssignedCell, Chip, Layouter, Value}, + plonk::{ + Advice, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector, + TableColumn, VirtualCells, + }, + poly::Rotation, +}; +use pasta_curves::pallas; + +mod generator_table; +use generator_table::GeneratorTableConfig; + +mod hash_to_point; + +/// Configuration for the Sinsemilla hash chip +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct SinsemillaConfig + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + /// Binary selector used in lookup argument and in the body of the Sinsemilla hash. + q_sinsemilla1: Selector, + /// Non-binary selector used in lookup argument and in the body of the Sinsemilla hash. + q_sinsemilla2: Column, + /// q_sinsemilla2 is used to define a synthetic selector, + /// q_sinsemilla3 = (q_sinsemilla2) ⋅ (q_sinsemilla2 - 1) + /// Simple selector used to constrain hash initialization to be consistent with + /// the y-coordinate of the domain $Q$. + q_sinsemilla4: Selector, + /// Fixed column used to load the y-coordinate of the domain $Q$. + fixed_y_q: Column, + /// Logic specific to merged double-and-add. + double_and_add: DoubleAndAdd, + /// Advice column used to load the message. + bits: Column, + /// Advice column used to witness message pieces. This may or may not be the same + /// column as `bits`. + witness_pieces: Column, + /// The lookup table where $(\mathsf{idx}, x_p, y_p)$ are loaded for the $2^K$ + /// generators of the Sinsemilla hash. + pub(super) generator_table: GeneratorTableConfig, + /// An advice column configured to perform lookup range checks. + lookup_config: LookupRangeCheckConfig, + _marker: PhantomData<(Hash, Commit, F)>, +} + +impl SinsemillaConfig + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + /// Returns an array of all advice columns in this config, in arbitrary order. + pub(super) fn advices(&self) -> [Column; 5] { + [ + self.double_and_add.x_a, + self.double_and_add.x_p, + self.bits, + self.double_and_add.lambda_1, + self.double_and_add.lambda_2, + ] + } + + /// Returns the lookup range check config used in this config. + pub fn lookup_config(&self) -> LookupRangeCheckConfig { + self.lookup_config + } + + /// Derives the expression `q_s3 = (q_s2) * (q_s2 - 1)`. + fn q_s3(&self, meta: &mut VirtualCells) -> Expression { + let one = Expression::Constant(pallas::Base::one()); + let q_s2 = meta.query_fixed(self.q_sinsemilla2); + q_s2.clone() * (q_s2 - one) + } +} + +/// A chip that implements 10-bit Sinsemilla using a lookup table and 5 advice columns. +/// +/// [Chip description](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html#plonk--halo-2-constraints). +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct SinsemillaChip + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + config: SinsemillaConfig, +} + +impl Chip for SinsemillaChip + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + type Config = SinsemillaConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} + +impl SinsemillaChip + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + /// Reconstructs this chip from the given config. + pub fn construct(config: >::Config) -> Self { + Self { config } + } + + /// Loads the lookup table required by this chip into the circuit. + pub fn load( + config: SinsemillaConfig, + layouter: &mut impl Layouter, + ) -> Result<>::Loaded, Error> { + // Load the lookup table. + config.generator_table.load(layouter) + } + + /// # Side-effects + /// + /// All columns in `advices` and will be equality-enabled. + #[allow(clippy::too_many_arguments)] + #[allow(non_snake_case)] + pub fn configure( + meta: &mut ConstraintSystem, + advices: [Column; 5], + witness_pieces: Column, + fixed_y_q: Column, + lookup: (TableColumn, TableColumn, TableColumn, TableColumn), + range_check: LookupRangeCheckConfig, + ) -> >::Config { + // Enable equality on all advice columns + for advice in advices.iter() { + meta.enable_equality(*advice); + } + + let config = SinsemillaConfig:: { + q_sinsemilla1: meta.complex_selector(), + q_sinsemilla2: meta.fixed_column(), + q_sinsemilla4: meta.selector(), + fixed_y_q, + double_and_add: DoubleAndAdd { + x_a: advices[0], + x_p: advices[1], + lambda_1: advices[3], + lambda_2: advices[4], + }, + bits: advices[2], + witness_pieces, + generator_table: GeneratorTableConfig { + table_idx: lookup.0, + table_x: lookup.1, + table_y: lookup.2, + table_range_check_tag: lookup.3, + }, + lookup_config: range_check, + _marker: PhantomData, + }; + + // Set up lookup argument + GeneratorTableConfig::configure(meta, config.clone()); + + let two = pallas::Base::from(2); + + // Closures for expressions that are derived multiple times + // x_r = lambda_1^2 - x_a - x_p + let x_r = |meta: &mut VirtualCells, rotation| { + config.double_and_add.x_r(meta, rotation) + }; + + // Y_A = (lambda_1 + lambda_2) * (x_a - x_r) + let Y_A = |meta: &mut VirtualCells, rotation| { + config.double_and_add.Y_A(meta, rotation) + }; + + // Check that the initial x_A, x_P, lambda_1, lambda_2 are consistent with y_Q. + // https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial + meta.create_gate("Initial y_Q", |meta| { + let q_s4 = meta.query_selector(config.q_sinsemilla4); + let y_q = meta.query_advice(config.double_and_add.x_p, Rotation::prev()); + + // Y_A = (lambda_1 + lambda_2) * (x_a - x_r) + let Y_A_cur = Y_A(meta, Rotation::cur()); + + // 2 * y_q - Y_{A,0} = 0 + let init_y_q_check = y_q * two - Y_A_cur; + + Constraints::with_selector(q_s4, Some(("init_y_q_check", init_y_q_check))) + }); + + // https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial + meta.create_gate("Sinsemilla gate", |meta| { + let q_s1 = meta.query_selector(config.q_sinsemilla1); + let q_s3 = config.q_s3(meta); + + let lambda_1_next = meta.query_advice(config.double_and_add.lambda_1, Rotation::next()); + let lambda_2_cur = meta.query_advice(config.double_and_add.lambda_2, Rotation::cur()); + let x_a_cur = meta.query_advice(config.double_and_add.x_a, Rotation::cur()); + let x_a_next = meta.query_advice(config.double_and_add.x_a, Rotation::next()); + + // x_r = lambda_1^2 - x_a_cur - x_p + let x_r = x_r(meta, Rotation::cur()); + + // Y_A = (lambda_1 + lambda_2) * (x_a - x_r) + let Y_A_cur = Y_A(meta, Rotation::cur()); + + // Y_A = (lambda_1 + lambda_2) * (x_a - x_r) + let Y_A_next = Y_A(meta, Rotation::next()); + + // lambda2^2 - (x_a_next + x_r + x_a_cur) = 0 + let secant_line = + lambda_2_cur.clone().square() - (x_a_next.clone() + x_r + x_a_cur.clone()); + + // lhs - rhs = 0, where + // - lhs = 4 * lambda_2_cur * (x_a_cur - x_a_next) + // - rhs = (2 * Y_A_cur + (2 - q_s3) * Y_A_next + 2 * q_s3 * y_a_final) + let y_check = { + // lhs = 4 * lambda_2_cur * (x_a_cur - x_a_next) + let lhs = lambda_2_cur * pallas::Base::from(4) * (x_a_cur - x_a_next); + + // rhs = 2 * Y_A_cur + (2 - q_s3) * Y_A_next + 2 * q_s3 * y_a_final + let rhs = { + // y_a_final is assigned to the lambda1 column on the next offset. + let y_a_final = lambda_1_next; + + Y_A_cur * two + + (Expression::Constant(two) - q_s3.clone()) * Y_A_next + + q_s3 * two * y_a_final + }; + lhs - rhs + }; + + Constraints::with_selector(q_s1, [("Secant line", secant_line), ("y check", y_check)]) + }); + + config + } +} + +// Implement `SinsemillaInstructions` for `SinsemillaChip` +impl SinsemillaInstructions +for SinsemillaChip + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + type CellValue = AssignedCell; + + type Message = Message; + type MessagePiece = MessagePiece; + + type RunningSum = Vec; + + type X = AssignedCell; + type NonIdentityPoint = NonIdentityEccPoint; + type FixedPoints = F; + + type HashDomains = Hash; + type CommitDomains = Commit; + + fn witness_message_piece( + &self, + mut layouter: impl Layouter, + field_elem: Value, + num_words: usize, + ) -> Result { + let config = self.config().clone(); + + let cell = layouter.assign_region( + || "witness message piece", + |mut region| { + region.assign_advice( + || "witness message piece", + config.witness_pieces, + 0, + || field_elem, + ) + }, + )?; + Ok(MessagePiece::new(cell, num_words)) + } + + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + fn hash_to_point( + &self, + mut layouter: impl Layouter, + Q: pallas::Affine, + message: Self::Message, + ) -> Result<(Self::NonIdentityPoint, Vec), Error> { + layouter.assign_region( + || "hash_to_point", + |mut region| self.hash_message(&mut region, Q, &message), + ) + } + + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + fn hash_to_point_with_private_init( + &self, + mut layouter: impl Layouter, + Q: &Self::NonIdentityPoint, + message: Self::Message, + ) -> Result<(Self::NonIdentityPoint, Vec), Error> { + layouter.assign_region( + || "hash_to_point", + |mut region| self.hash_message_with_private_init(&mut region, Q, &message), + ) + } + + fn extract(point: &Self::NonIdentityPoint) -> Self::X { + point.x() + } +} \ No newline at end of file diff --git a/halo2_gadgets_optimized/src/sinsemilla/chip/generator_table.rs b/halo2_gadgets_optimized/src/sinsemilla/chip/generator_table.rs new file mode 100644 index 0000000000..8c75830f38 --- /dev/null +++ b/halo2_gadgets_optimized/src/sinsemilla/chip/generator_table.rs @@ -0,0 +1,175 @@ +use group::ff::PrimeField; +use halo2_proofs::{ + circuit::{Layouter, Value}, + plonk::{ConstraintSystem, Error, Expression, TableColumn}, + poly::Rotation, +}; + +use super::{CommitDomains, FixedPoints, HashDomains}; +use crate::sinsemilla::primitives::{self as sinsemilla, K, SINSEMILLA_S}; +use pasta_curves::pallas; + +/// Table containing independent generators S[0..2^k] +#[derive(Eq, PartialEq, Copy, Clone, Debug)] +pub struct GeneratorTableConfig { + pub table_idx: TableColumn, + pub table_x: TableColumn, + pub table_y: TableColumn, + pub table_range_check_tag: TableColumn, +} + +impl GeneratorTableConfig { + #[allow(clippy::too_many_arguments)] + #[allow(non_snake_case)] + /// Even though the lookup table can be used in other parts of the circuit, + /// this specific configuration sets up Sinsemilla-specific constraints + /// controlled by `q_sinsemilla`, and would likely not apply to other chips. + pub fn configure( + meta: &mut ConstraintSystem, + config: super::SinsemillaConfig, + ) where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, + { + let (table_idx, table_x, table_y) = ( + config.generator_table.table_idx, + config.generator_table.table_x, + config.generator_table.table_y, + ); + + // https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial + meta.lookup(|meta| { + let q_s1 = meta.query_selector(config.q_sinsemilla1); + let q_s2 = meta.query_fixed(config.q_sinsemilla2); + let q_s3 = config.q_s3(meta); + let q_run = q_s2 - q_s3; + + // m_{i+1} = z_{i} - 2^K * q_{run,i} * z_{i + 1} + // Note that the message words m_i's are 1-indexed while the + // running sum z_i's are 0-indexed. + let word = { + let z_cur = meta.query_advice(config.bits, Rotation::cur()); + let z_next = meta.query_advice(config.bits, Rotation::next()); + z_cur - (q_run * z_next * pallas::Base::from(1 << sinsemilla::K)) + }; + + let x_p = meta.query_advice(config.double_and_add.x_p, Rotation::cur()); + + // y_{p,i} = (Y_{A,i} / 2) - lambda1 * (x_{A,i} - x_{P,i}) + let y_p = { + let lambda1 = meta.query_advice(config.double_and_add.lambda_1, Rotation::cur()); + let x_a = meta.query_advice(config.double_and_add.x_a, Rotation::cur()); + let Y_A = config.double_and_add.Y_A(meta, Rotation::cur()); + + (Y_A * pallas::Base::TWO_INV) - (lambda1 * (x_a - x_p.clone())) + }; + + // Lookup expressions default to the first entry when `q_s1` + // is not enabled. + let (init_x, init_y) = SINSEMILLA_S[0]; + let not_q_s1 = Expression::Constant(pallas::Base::one()) - q_s1.clone(); + + let m = q_s1.clone() * word; // The first table index is 0. + let x_p = q_s1.clone() * x_p + not_q_s1.clone() * init_x; + let y_p = q_s1 * y_p + not_q_s1 * init_y; + + vec![(m, table_idx), (x_p, table_x), (y_p, table_y)] + }); + } + + /// Load the generator table into the circuit. + /// + /// | table_idx | table_x | table_y | table_range_check_tag | + /// ------------------------------------------------------------------- + /// | 0 | X(S\[0\]) | Y(S\[0\]) | 0 | + /// | 1 | X(S\[1\]) | Y(S\[1\]) | 0 | + /// | ... | ... | ... | 0 | + /// | 2^10-1 | X(S\[2^10-1\]) | Y(S\[2^10-1\]) | 0 | + /// | 0 | X(S\[0\]) | Y(S\[0\]) | 4 | + /// | 1 | X(S\[1\]) | Y(S\[1\]) | 4 | + /// | ... | ... | ... | 4 | + /// | 2^4-1 | X(S\[2^4-1\]) | Y(S\[2^4-1\]) | 4 | + /// | 0 | X(S\[0\]) | Y(S\[0\]) | 5 | + /// | 1 | X(S\[1\]) | Y(S\[1\]) | 5 | + /// | ... | ... | ... | 5 | + /// | 2^5-1 | X(S\[2^5-1\]) | Y(S\[2^5-1\]) | 5 | + pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + layouter.assign_table( + || "generator_table", + |mut table| { + for (index, (x, y)) in SINSEMILLA_S.iter().enumerate() { + table.assign_cell( + || "table_idx", + self.table_idx, + index, + || Value::known(pallas::Base::from(index as u64)), + )?; + table.assign_cell(|| "table_x", self.table_x, index, || Value::known(*x))?; + table.assign_cell(|| "table_y", self.table_y, index, || Value::known(*y))?; + table.assign_cell( + || "table_range_check_tag", + self.table_range_check_tag, + index, + || Value::known(pallas::Base::zero()), + )?; + if index < (1 << 4) { + let new_index = index + (1 << K); + table.assign_cell( + || "table_idx", + self.table_idx, + new_index, + || Value::known(pallas::Base::from(index as u64)), + )?; + table.assign_cell( + || "table_x", + self.table_x, + new_index, + || Value::known(*x), + )?; + table.assign_cell( + || "table_y", + self.table_y, + new_index, + || Value::known(*y), + )?; + table.assign_cell( + || "table_range_check_tag", + self.table_range_check_tag, + new_index, + || Value::known(pallas::Base::from(4_u64)), + )?; + } + if index < (1 << 5) { + let new_index = index + (1 << 10) + (1 << 4); + table.assign_cell( + || "table_idx", + self.table_idx, + new_index, + || Value::known(pallas::Base::from(index as u64)), + )?; + table.assign_cell( + || "table_x", + self.table_x, + new_index, + || Value::known(*x), + )?; + table.assign_cell( + || "table_y", + self.table_y, + new_index, + || Value::known(*y), + )?; + table.assign_cell( + || "table_range_check_tag", + self.table_range_check_tag, + new_index, + || Value::known(pallas::Base::from(5_u64)), + )?; + } + } + Ok(()) + }, + ) + } +} \ No newline at end of file diff --git a/halo2_gadgets_optimized/src/sinsemilla/chip/hash_to_point.rs b/halo2_gadgets_optimized/src/sinsemilla/chip/hash_to_point.rs new file mode 100644 index 0000000000..15a8600a5b --- /dev/null +++ b/halo2_gadgets_optimized/src/sinsemilla/chip/hash_to_point.rs @@ -0,0 +1,572 @@ +use super::super::{CommitDomains, HashDomains, SinsemillaInstructions}; +use super::{NonIdentityEccPoint, SinsemillaChip}; +use crate::{ + ecc::FixedPoints, + sinsemilla::primitives::{self as sinsemilla, lebs2ip_k, INV_TWO_POW_K, SINSEMILLA_S}, +}; + +use ff::Field; +use halo2_proofs::{ + circuit::{AssignedCell, Chip, Region, Value}, + plonk::{Assigned, Error}, +}; + +use group::ff::{PrimeField, PrimeFieldBits}; +use pasta_curves::{arithmetic::CurveAffine, pallas}; + +use std::ops::Deref; + +impl SinsemillaChip + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + pub(super) fn hash_message( + &self, + region: &mut Region<'_, pallas::Base>, + Q: pallas::Affine, + message: &>::Message, + ) -> Result< + ( + NonIdentityEccPoint, + Vec>>, + ), + Error, + > { + let (offset, x_a, y_a) = self.public_initialization(region, Q)?; + + let (x_a, y_a, zs_sum) = self.hash_all_pieces(region, offset, message, x_a, y_a)?; + + #[cfg(test)] + #[allow(non_snake_case)] + // Check equivalence to result from primitives::sinsemilla::hash_to_point + { + use crate::sinsemilla::primitives::{K, S_PERSONALIZATION}; + + use group::{prime::PrimeCurveAffine, Curve}; + use pasta_curves::arithmetic::CurveExt; + + let field_elems: Value> = message + .iter() + .map(|piece| piece.field_elem().map(|elem| (elem, piece.num_words()))) + .collect(); + + field_elems + .zip(x_a.value().zip(y_a.value())) + .assert_if_known(|(field_elems, (x_a, y_a))| { + // Get message as a bitstring. + let bitstring: Vec = field_elems + .iter() + .flat_map(|(elem, num_words)| { + elem.to_le_bits().into_iter().take(K * num_words) + }) + .collect(); + + let hasher_S = pallas::Point::hash_to_curve(S_PERSONALIZATION); + let S = |chunk: &[bool]| hasher_S(&lebs2ip_k(chunk).to_le_bytes()); + + // We can use complete addition here because it differs from + // incomplete addition with negligible probability. + let expected_point = bitstring + .chunks(K) + .fold(Q.to_curve(), |acc, chunk| (acc + S(chunk)) + acc); + let actual_point = + pallas::Affine::from_xy(x_a.evaluate(), y_a.evaluate()).unwrap(); + expected_point.to_affine() == actual_point + }); + } + + x_a.value() + .zip(y_a.value()) + .error_if_known_and(|(x_a, y_a)| x_a.is_zero_vartime() || y_a.is_zero_vartime())?; + Ok(( + NonIdentityEccPoint::from_coordinates_unchecked(x_a.0, y_a), + zs_sum, + )) + } + + /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + pub(super) fn hash_message_with_private_init( + &self, + region: &mut Region<'_, pallas::Base>, + Q: &NonIdentityEccPoint, + message: &>::Message, + ) -> Result< + ( + NonIdentityEccPoint, + Vec>>, + ), + Error, + > { + let (offset, x_a, y_a) = self.private_initialization(region, Q)?; + + let (x_a, y_a, zs_sum) = self.hash_all_pieces(region, offset, message, x_a, y_a)?; + + #[cfg(test)] + #[allow(non_snake_case)] + // Check equivalence to result from primitives::sinsemilla::hash_to_point + { + use crate::sinsemilla::primitives::{K, S_PERSONALIZATION}; + + use group::{prime::PrimeCurveAffine, Curve}; + use pasta_curves::arithmetic::CurveExt; + + let field_elems: Value> = message + .iter() + .map(|piece| piece.field_elem().map(|elem| (elem, piece.num_words()))) + .collect(); + + field_elems + .zip(x_a.value().zip(y_a.value())) + .zip(Q.point()) + .assert_if_known(|((field_elems, (x_a, y_a)), Q)| { + // Get message as a bitstring. + let bitstring: Vec = field_elems + .iter() + .flat_map(|(elem, num_words)| { + elem.to_le_bits().into_iter().take(K * num_words) + }) + .collect(); + + let hasher_S = pallas::Point::hash_to_curve(S_PERSONALIZATION); + let S = |chunk: &[bool]| hasher_S(&lebs2ip_k(chunk).to_le_bytes()); + + // We can use complete addition here because it differs from + // incomplete addition with negligible probability. + let expected_point = bitstring + .chunks(K) + .fold(Q.to_curve(), |acc, chunk| (acc + S(chunk)) + acc); + let actual_point = + pallas::Affine::from_xy(x_a.evaluate(), y_a.evaluate()).unwrap(); + expected_point.to_affine() == actual_point + }); + } + + x_a.value() + .zip(y_a.value()) + .error_if_known_and(|(x_a, y_a)| x_a.is_zero_vartime() || y_a.is_zero_vartime())?; + Ok(( + NonIdentityEccPoint::from_coordinates_unchecked(x_a.0, y_a), + zs_sum, + )) + } + + #[allow(non_snake_case)] + /// Assign the coordinates of the initial public point `Q` + /// + /// | offset | x_A | x_P | q_sinsemilla4 | + /// -------------------------------------- + /// | 0 | | y_Q | | + /// | 1 | x_Q | | 1 | + fn public_initialization( + &self, + region: &mut Region<'_, pallas::Base>, + Q: pallas::Affine, + ) -> Result<(usize, X, Y), Error> { + let config = self.config().clone(); + let mut offset = 0; + + // Get the `x`- and `y`-coordinates of the starting `Q` base. + let x_q = *Q.coordinates().unwrap().x(); + let y_q = *Q.coordinates().unwrap().y(); + + // Constrain the initial x_a, lambda_1, lambda_2, x_p using the q_sinsemilla4 + // selector. + let y_a: Y = { + // Enable `q_sinsemilla4` on the second row. + config.q_sinsemilla4.enable(region, offset + 1)?; + let y_a: AssignedCell, pallas::Base> = region + .assign_advice_from_constant( + || "fixed y_q", + config.double_and_add.x_p, + offset, + y_q.into(), + )?; + + y_a.value_field().into() + }; + offset += 1; + + // Constrain the initial x_q to equal the x-coordinate of the domain's `Q`. + let x_a: X = { + let x_a = region.assign_advice_from_constant( + || "fixed x_q", + config.double_and_add.x_a, + offset, + x_q.into(), + )?; + + x_a.into() + }; + + Ok((offset, x_a, y_a)) + } + + #[allow(non_snake_case)] + /// Assign the coordinates of the initial private point `Q` + /// + /// | offset | x_A | x_P | q_sinsemilla4 | + /// -------------------------------------- + /// | 0 | | y_Q | | + /// | 1 | x_Q | | 1 | + fn private_initialization( + &self, + region: &mut Region<'_, pallas::Base>, + Q: &NonIdentityEccPoint, + ) -> Result<(usize, X, Y), Error> { + let config = self.config().clone(); + let mut offset = 0; + + // Assign `x_Q` and `y_Q` in the region and constrain the initial x_a, lambda_1, lambda_2, + // x_p, y_Q using the q_sinsemilla4 selector. + let y_a: Y = { + // Enable `q_sinsemilla4` on the second row. + config.q_sinsemilla4.enable(region, offset + 1)?; + let q_y: AssignedCell, pallas::Base> = Q.y().into(); + let y_a: AssignedCell, pallas::Base> = + q_y.copy_advice(|| "fixed y_q", region, config.double_and_add.x_p, offset)?; + + y_a.value_field().into() + }; + offset += 1; + + let x_a: X = { + let q_x: AssignedCell, pallas::Base> = Q.x().into(); + let x_a = q_x.copy_advice(|| "fixed x_q", region, config.double_and_add.x_a, offset)?; + + x_a.into() + }; + + Ok((offset, x_a, y_a)) + } + + #[allow(clippy::type_complexity)] + /// Hash `message` from the initial point `Q`. + fn hash_all_pieces( + &self, + region: &mut Region<'_, pallas::Base>, + mut offset: usize, + message: &>::Message, + mut x_a: X, + mut y_a: Y, + ) -> Result< + ( + X, + AssignedCell, pallas::Base>, + Vec>>, + ), + Error, + > { + let config = self.config().clone(); + + let mut zs_sum: Vec>> = Vec::new(); + + // Hash each piece in the message. + for (idx, piece) in message.iter().enumerate() { + let final_piece = idx == message.len() - 1; + + // The value of the accumulator after this piece is processed. + let (x, y, zs) = self.hash_piece(region, offset, piece, x_a, y_a, final_piece)?; + + // Since each message word takes one row to process, we increase + // the offset by `piece.num_words` on each iteration. + offset += piece.num_words(); + + // Update the accumulator to the latest value. + x_a = x; + y_a = y; + zs_sum.push(zs); + } + + // Assign the final y_a. + let y_a = { + // Assign the final y_a. + let y_a_cell = + region.assign_advice(|| "y_a", config.double_and_add.lambda_1, offset, || y_a.0)?; + + // Assign lambda_2 and x_p zero values since they are queried + // in the gate. (The actual values do not matter since they are + // multiplied by zero.) + { + region.assign_advice( + || "dummy lambda2", + config.double_and_add.lambda_2, + offset, + || Value::known(pallas::Base::zero()), + )?; + region.assign_advice( + || "dummy x_p", + config.double_and_add.x_p, + offset, + || Value::known(pallas::Base::zero()), + )?; + } + + y_a_cell + }; + + Ok((x_a, y_a, zs_sum)) + } + + #[allow(clippy::type_complexity)] + /// Hashes a message piece containing `piece.length` number of `K`-bit words. + /// + /// To avoid a duplicate assignment, the accumulator x-coordinate provided + /// by the caller is not copied. This only works because `hash_piece()` is + /// an internal API. Before this call to `hash_piece()`, x_a MUST have been + /// already assigned within this region at the correct offset. + fn hash_piece( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + piece: &>::MessagePiece, + mut x_a: X, + mut y_a: Y, + final_piece: bool, + ) -> Result< + ( + X, + Y, + Vec>, + ), + Error, + > { + let config = self.config().clone(); + + // Selector assignments + { + // Enable `q_sinsemilla1` selector on every row. + for row in 0..piece.num_words() { + config.q_sinsemilla1.enable(region, offset + row)?; + } + + // Set `q_sinsemilla2` fixed column to 1 on every row but the last. + for row in 0..(piece.num_words() - 1) { + region.assign_fixed( + || "q_s2 = 1", + config.q_sinsemilla2, + offset + row, + || Value::known(pallas::Base::one()), + )?; + } + + // Set `q_sinsemilla2` fixed column to 0 on the last row if this is + // not the final piece, or to 2 on the last row of the final piece. + region.assign_fixed( + || { + if final_piece { + "q_s2 for final piece" + } else { + "q_s2 between pieces" + } + }, + config.q_sinsemilla2, + offset + piece.num_words() - 1, + || { + Value::known(if final_piece { + pallas::Base::from(2) + } else { + pallas::Base::zero() + }) + }, + )?; + } + + // Message piece as K * piece.length bitstring + let bitstring: Value> = piece.field_elem().map(|value| { + value + .to_le_bits() + .into_iter() + .take(sinsemilla::K * piece.num_words()) + .collect() + }); + + let words: Value> = bitstring.map(|bitstring| { + bitstring + .chunks_exact(sinsemilla::K) + .map(lebs2ip_k) + .collect() + }); + + // Get (x_p, y_p) for each word. + let generators: Value> = words.clone().map(|words| { + words + .iter() + .map(|word| SINSEMILLA_S[*word as usize]) + .collect() + }); + + // Convert `words` from `Value>` to `Vec>` + let words = words.transpose_vec(piece.num_words()); + + // Decompose message piece into `K`-bit pieces with a running sum `z`. + let zs = { + let mut zs = Vec::with_capacity(piece.num_words() + 1); + + // Copy message and initialize running sum `z` to decompose message in-circuit + let initial_z = piece.cell_value().copy_advice( + || "z_0 (copy of message piece)", + region, + config.bits, + offset, + )?; + zs.push(initial_z); + + // Assign cumulative sum such that for 0 <= i < n, + // z_i = 2^K * z_{i + 1} + m_{i + 1} + // => z_{i + 1} = (z_i - m_{i + 1}) / 2^K + // + // For a message piece m = m_1 + 2^K m_2 + ... + 2^{K(n-1)} m_n}, initialize z_0 = m. + // We end up with z_n = 0. (z_n is not directly encoded as a cell value; + // it is implicitly taken as 0 by adjusting the definition of m_{i+1}.) + let mut z = piece.field_elem(); + let inv_2_k = Value::known(pallas::Base::from_repr(INV_TWO_POW_K).unwrap()); + + // We do not assign the final z_n as it is constrained to be zero. + for (idx, word) in words[0..(words.len() - 1)].iter().enumerate() { + let word = word.map(|word| pallas::Base::from(word as u64)); + // z_{i + 1} = (z_i - m_{i + 1}) / 2^K + z = (z - word) * inv_2_k; + let cell = region.assign_advice( + || format!("z_{:?}", idx + 1), + config.bits, + offset + idx + 1, + || z, + )?; + zs.push(cell) + } + + zs + }; + + // The accumulator x-coordinate provided by the caller MUST have been assigned + // within this region. + + let generators = generators.transpose_vec(piece.num_words()); + + for (row, gen) in generators.iter().enumerate() { + let x_p = gen.map(|gen| gen.0); + let y_p = gen.map(|gen| gen.1); + + // Assign `x_p` + region.assign_advice(|| "x_p", config.double_and_add.x_p, offset + row, || x_p)?; + + // Compute and assign `lambda_1` + let lambda_1 = { + let lambda_1 = (y_a.0 - y_p) * (x_a.value() - x_p).invert(); + + // Assign lambda_1 + region.assign_advice( + || "lambda_1", + config.double_and_add.lambda_1, + offset + row, + || lambda_1, + )?; + + lambda_1 + }; + + // Compute `x_r` + let x_r = lambda_1.square() - x_a.value() - x_p; + + // Compute and assign `lambda_2` + let lambda_2 = { + let lambda_2 = + y_a.0 * pallas::Base::from(2) * (x_a.value() - x_r).invert() - lambda_1; + + region.assign_advice( + || "lambda_2", + config.double_and_add.lambda_2, + offset + row, + || lambda_2, + )?; + + lambda_2 + }; + + // Compute and assign `x_a` for the next row. + let x_a_new: X = { + let x_a_new = lambda_2.square() - x_a.value() - x_r; + + let x_a_cell = region.assign_advice( + || "x_a", + config.double_and_add.x_a, + offset + row + 1, + || x_a_new, + )?; + + x_a_cell.into() + }; + + // Compute y_a for the next row. + let y_a_new: Y = + (lambda_2 * (x_a.value() - x_a_new.value()) - y_a.0).into(); + + // Update the mutable `x_a`, `y_a` variables. + x_a = x_a_new; + y_a = y_a_new; + } + + Ok((x_a, y_a, zs)) + } +} + +/// The x-coordinate of the accumulator in a Sinsemilla hash instance. +struct X(AssignedCell, F>); + +impl From, F>> for X { + fn from(cell_value: AssignedCell, F>) -> Self { + X(cell_value) + } +} + +impl Deref for X { + type Target = AssignedCell, F>; + + fn deref(&self) -> &AssignedCell, F> { + &self.0 + } +} + +/// The y-coordinate of the accumulator in a Sinsemilla hash instance. +/// +/// This is never actually witnessed until the last round, since it +/// can be derived from other variables. Thus it only exists as a field +/// element, not a `CellValue`. +struct Y(Value>); + +impl From>> for Y { + fn from(value: Value>) -> Self { + Y(value) + } +} + +impl Deref for Y { + type Target = Value>; + + fn deref(&self) -> &Value> { + &self.0 + } +} \ No newline at end of file diff --git a/halo2_gadgets_optimized/src/sinsemilla/merkle.rs b/halo2_gadgets_optimized/src/sinsemilla/merkle.rs new file mode 100644 index 0000000000..998335785a --- /dev/null +++ b/halo2_gadgets_optimized/src/sinsemilla/merkle.rs @@ -0,0 +1,400 @@ +//! Gadgets for implementing a Merkle tree with Sinsemilla. + +use halo2_proofs::{ + circuit::{Chip, Layouter, Value}, + plonk::Error, +}; +use pasta_curves::arithmetic::CurveAffine; + +use super::{HashDomains, SinsemillaInstructions}; + +use crate::utilities::{cond_swap::CondSwapInstructions, i2lebsp, UtilitiesInstructions}; + +pub mod chip; + +/// SWU hash-to-curve personalization for the Merkle CRH generator +pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH"; + +/// Instructions to check the validity of a Merkle path of a given `PATH_LENGTH`. +/// The hash function used is a Sinsemilla instance with `K`-bit words. +/// The hash function can process `MAX_WORDS` words. +pub trait MerkleInstructions< + C: CurveAffine, + const PATH_LENGTH: usize, + const K: usize, + const MAX_WORDS: usize, +>: +SinsemillaInstructions ++ CondSwapInstructions ++ UtilitiesInstructions ++ Chip +{ + /// Compute MerkleCRH for a given `layer`. The hash that computes the root + /// is at layer 0, and the hashes that are applied to two leaves are at + /// layer `MERKLE_DEPTH - 1` = layer 31. + #[allow(non_snake_case)] + fn hash_layer( + &self, + layouter: impl Layouter, + Q: C, + l: usize, + left: Self::Var, + right: Self::Var, + ) -> Result; +} + +/// Gadget representing a Merkle path that proves a leaf exists in a Merkle tree at a +/// specific position. +#[derive(Clone, Debug)] +pub struct MerklePath< + C: CurveAffine, + MerkleChip, + const PATH_LENGTH: usize, + const K: usize, + const MAX_WORDS: usize, + const PAR: usize, +> where + MerkleChip: MerkleInstructions + Clone, +{ + chips: [MerkleChip; PAR], + domain: MerkleChip::HashDomains, + leaf_pos: Value, + // The Merkle path is ordered from leaves to root. + path: Value<[C::Base; PATH_LENGTH]>, +} + +impl< + C: CurveAffine, + MerkleChip, + const PATH_LENGTH: usize, + const K: usize, + const MAX_WORDS: usize, + const PAR: usize, +> MerklePath + where + MerkleChip: MerkleInstructions + Clone, +{ + /// Constructs a [`MerklePath`]. + /// + /// A circuit may have many more columns available than are required by a single + /// `MerkleChip`. To make better use of the available circuit area, the `MerklePath` + /// gadget will distribute its path hashing across each `MerkleChip` in `chips`, such + /// that each chip processes `ceil(PATH_LENGTH / PAR)` layers (with the last chip + /// processing fewer layers if the division is inexact). + pub fn construct( + chips: [MerkleChip; PAR], + domain: MerkleChip::HashDomains, + leaf_pos: Value, + path: Value<[C::Base; PATH_LENGTH]>, + ) -> Self { + assert_ne!(PAR, 0); + Self { + chips, + domain, + leaf_pos, + path, + } + } +} + +#[allow(non_snake_case)] +impl< + C: CurveAffine, + MerkleChip, + const PATH_LENGTH: usize, + const K: usize, + const MAX_WORDS: usize, + const PAR: usize, +> MerklePath + where + MerkleChip: MerkleInstructions + Clone, +{ + /// Calculates the root of the tree containing the given leaf at this Merkle path. + /// + /// Implements [Zcash Protocol Specification Section 4.9: Merkle Path Validity][merklepath]. + /// + /// [merklepath]: https://zips.z.cash/protocol/protocol.pdf#merklepath + pub fn calculate_root( + &self, + mut layouter: impl Layouter, + leaf: MerkleChip::Var, + ) -> Result { + // Each chip processes `ceil(PATH_LENGTH / PAR)` layers. + let layers_per_chip = (PATH_LENGTH + PAR - 1) / PAR; + + // Assign each layer to a chip. + let chips = (0..PATH_LENGTH).map(|i| self.chips[i / layers_per_chip].clone()); + + // The Merkle path is ordered from leaves to root, which is consistent with the + // little-endian representation of `pos` below. + let path = self.path.transpose_array(); + + // Get position as a PATH_LENGTH-bit bitstring (little-endian bit order). + let pos: [Value; PATH_LENGTH] = { + let pos: Value<[bool; PATH_LENGTH]> = self.leaf_pos.map(|pos| i2lebsp(pos as u64)); + pos.transpose_array() + }; + + let Q = self.domain.Q(); + + let mut node = leaf; + for (l, ((sibling, pos), chip)) in path.iter().zip(pos.iter()).zip(chips).enumerate() { + // `l` = MERKLE_DEPTH - layer - 1, which is the index obtained from + // enumerating this Merkle path (going from leaf to root). + // For example, when `layer = 31` (the first sibling on the Merkle path), + // we have `l` = 32 - 31 - 1 = 0. + // On the other hand, when `layer = 0` (the final sibling on the Merkle path), + // we have `l` = 32 - 0 - 1 = 31. + + // Constrain which of (node, sibling) is (left, right) with a conditional swap + // tied to the current bit of the position. + let pair = { + let pair = (node, *sibling); + + // Swap node and sibling if needed + chip.swap(layouter.namespace(|| "node position"), pair, *pos)? + }; + + // Compute the node in layer l from its children: + // M^l_i = MerkleCRH(l, M^{l+1}_{2i}, M^{l+1}_{2i+1}) + node = chip.hash_layer( + layouter.namespace(|| format!("MerkleCRH({}, left, right)", l)), + Q, + l, + pair.0, + pair.1, + )?; + } + + Ok(node) + } +} + +#[cfg(test)] +pub mod tests { + use super::{ + chip::{MerkleChip, MerkleConfig}, + MerklePath, + }; + + use crate::{ + ecc::tests::TestFixedBases, + sinsemilla::{ + chip::SinsemillaChip, + tests::{TestCommitDomain, TestHashDomain}, + HashDomains, + }, + utilities::{i2lebsp, lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions}, + }; + + use group::ff::{Field, PrimeField, PrimeFieldBits}; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + dev::MockProver, + pasta::pallas, + plonk::{Circuit, ConstraintSystem, Error}, + }; + + use rand::{rngs::OsRng, RngCore}; + use std::{convert::TryInto, iter}; + + const MERKLE_DEPTH: usize = 32; + + #[derive(Default)] + struct MyCircuit { + leaf: Value, + leaf_pos: Value, + merkle_path: Value<[pallas::Base; MERKLE_DEPTH]>, + } + + impl Circuit for MyCircuit { + type Config = ( + MerkleConfig, + MerkleConfig, + ); + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let advices = [ + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + ]; + + // Shared fixed column for loading constants + let constants = meta.fixed_column(); + meta.enable_constant(constants); + + // NB: In the actual Action circuit, these fixed columns will be reused + // by other chips. For this test, we are creating new fixed columns. + let fixed_y_q_1 = meta.fixed_column(); + let fixed_y_q_2 = meta.fixed_column(); + + // Fixed columns for the Sinsemilla generator lookup table + let lookup = ( + meta.lookup_table_column(), + meta.lookup_table_column(), + meta.lookup_table_column(), + meta.lookup_table_column(), + ); + + let range_check = + LookupRangeCheckConfig::configure(meta, advices[9], lookup.0, lookup.3); + + let sinsemilla_config_1 = SinsemillaChip::configure( + meta, + advices[5..].try_into().unwrap(), + advices[7], + fixed_y_q_1, + lookup, + range_check, + ); + let config1 = MerkleChip::configure(meta, sinsemilla_config_1); + + let sinsemilla_config_2 = SinsemillaChip::configure( + meta, + advices[..5].try_into().unwrap(), + advices[2], + fixed_y_q_2, + lookup, + range_check, + ); + let config2 = MerkleChip::configure(meta, sinsemilla_config_2); + + (config1, config2) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // Load generator table (shared across both configs) + SinsemillaChip::::load( + config.0.sinsemilla_config.clone(), + &mut layouter, + )?; + + // Construct Merkle chips which will be placed side-by-side in the circuit. + let chip_1 = MerkleChip::construct(config.0.clone()); + let chip_2 = MerkleChip::construct(config.1.clone()); + + let leaf = chip_1.load_private( + layouter.namespace(|| ""), + config.0.cond_swap_config.a(), + self.leaf, + )?; + + let path = MerklePath { + chips: [chip_1, chip_2], + domain: TestHashDomain, + leaf_pos: self.leaf_pos, + path: self.merkle_path, + }; + + let computed_final_root = + path.calculate_root(layouter.namespace(|| "calculate root"), leaf)?; + + self.leaf + .zip(self.leaf_pos) + .zip(self.merkle_path) + .zip(computed_final_root.value()) + .assert_if_known(|(((leaf, leaf_pos), merkle_path), computed_final_root)| { + // The expected final root + let final_root = + merkle_path + .iter() + .enumerate() + .fold(*leaf, |node, (l, sibling)| { + let l = l as u8; + let (left, right) = if leaf_pos & (1 << l) == 0 { + (&node, sibling) + } else { + (sibling, &node) + }; + + use crate::sinsemilla::primitives as sinsemilla; + let merkle_crh = + sinsemilla::HashDomain::from_Q(TestHashDomain.Q().into()); + + merkle_crh + .hash( + iter::empty() + .chain(i2lebsp::<10>(l as u64).iter().copied()) + .chain( + left.to_le_bits() + .iter() + .by_vals() + .take(pallas::Base::NUM_BITS as usize), + ) + .chain( + right + .to_le_bits() + .iter() + .by_vals() + .take(pallas::Base::NUM_BITS as usize), + ), + ) + .unwrap_or(pallas::Base::zero()) + }); + + // Check the computed final root against the expected final root. + computed_final_root == &&final_root + }); + + Ok(()) + } + } + + #[test] + fn merkle_chip() { + let mut rng = OsRng; + + // Choose a random leaf and position + let leaf = pallas::Base::random(rng); + let pos = rng.next_u32(); + + // Choose a path of random inner nodes + let path: Vec<_> = (0..(MERKLE_DEPTH)) + .map(|_| pallas::Base::random(rng)) + .collect(); + + // The root is provided as a public input in the Orchard circuit. + + let circuit = MyCircuit { + leaf: Value::known(leaf), + leaf_pos: Value::known(pos), + merkle_path: Value::known(path.try_into().unwrap()), + }; + + let prover = MockProver::run(11, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())) + } + + #[cfg(feature = "test-dev-graph")] + #[test] + fn print_merkle_chip() { + use plotters::prelude::*; + + let root = BitMapBackend::new("merkle-path-layout.png", (1024, 7680)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root.titled("MerkleCRH Path", ("sans-serif", 60)).unwrap(); + + let circuit = MyCircuit::default(); + halo2_proofs::dev::CircuitLayout::default() + .show_labels(false) + .render(11, &circuit, &root) + .unwrap(); + } +} \ No newline at end of file diff --git a/halo2_gadgets_optimized/src/sinsemilla/merkle/chip.rs b/halo2_gadgets_optimized/src/sinsemilla/merkle/chip.rs new file mode 100644 index 0000000000..59ed3d40fc --- /dev/null +++ b/halo2_gadgets_optimized/src/sinsemilla/merkle/chip.rs @@ -0,0 +1,554 @@ +//! Chip implementing a Merkle hash using Sinsemilla as the hash function. + +use halo2_proofs::{ + circuit::{AssignedCell, Chip, Layouter, Value}, + plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, + poly::Rotation, +}; +use pasta_curves::pallas; + +use super::MerkleInstructions; + +use crate::{ + sinsemilla::{primitives as sinsemilla, MessagePiece}, + utilities::RangeConstrained, + { + ecc::FixedPoints, + sinsemilla::{ + chip::{SinsemillaChip, SinsemillaConfig}, + CommitDomains, HashDomains, SinsemillaInstructions, + }, + utilities::{ + cond_swap::{CondSwapChip, CondSwapConfig, CondSwapInstructions}, + UtilitiesInstructions, + }, + }, +}; +use group::ff::PrimeField; + +/// Configuration for the `MerkleChip` implementation. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MerkleConfig + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + advices: [Column; 5], + q_decompose: Selector, + pub(super) cond_swap_config: CondSwapConfig, + pub(super) sinsemilla_config: SinsemillaConfig, +} + +/// Chip implementing `MerkleInstructions`. +/// +/// This chip specifically implements `MerkleInstructions::hash_layer` as the `MerkleCRH` +/// function `hash = SinsemillaHash(Q, 𝑙⋆ || left⋆ || right⋆)`, where: +/// - `𝑙⋆ = I2LEBSP_10(l)` +/// - `left⋆ = I2LEBSP_255(left)` +/// - `right⋆ = I2LEBSP_255(right)` +/// +/// This chip does **NOT** constrain `left⋆` and `right⋆` to be canonical encodings of +/// `left` and `right`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MerkleChip + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + config: MerkleConfig, +} + +impl Chip for MerkleChip + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + type Config = MerkleConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} + +impl MerkleChip + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + /// Configures the [`MerkleChip`]. + pub fn configure( + meta: &mut ConstraintSystem, + sinsemilla_config: SinsemillaConfig, + ) -> MerkleConfig { + // All five advice columns are equality-enabled by SinsemillaConfig. + let advices = sinsemilla_config.advices(); + let cond_swap_config = CondSwapChip::configure(meta, advices); + + // This selector enables the decomposition gate. + let q_decompose = meta.selector(); + + // Check that pieces have been decomposed correctly for Sinsemilla hash. + // + // + // a = a_0||a_1 = l || (bits 0..=239 of left) + // b = b_0||b_1||b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + // c = bits 5..=254 of right + // + // The message pieces `a`, `b`, `c` are constrained by Sinsemilla to be + // 250 bits, 20 bits, and 250 bits respectively. + // + // The pieces and subpieces are arranged in the following configuration: + // | A_0 | A_1 | A_2 | A_3 | A_4 | q_decompose | + // ------------------------------------------------------- + // | a | b | c | left | right | 1 | + // | z1_a | z1_b | b_1 | b_2 | l | 0 | + meta.create_gate("Decomposition check", |meta| { + let q_decompose = meta.query_selector(q_decompose); + let l_whole = meta.query_advice(advices[4], Rotation::next()); + + let two_pow_5 = pallas::Base::from(1 << 5); + let two_pow_10 = two_pow_5.square(); + + // a_whole is constrained by Sinsemilla to be 250 bits. + let a_whole = meta.query_advice(advices[0], Rotation::cur()); + // b_whole is constrained by Sinsemilla to be 20 bits. + let b_whole = meta.query_advice(advices[1], Rotation::cur()); + // c_whole is constrained by Sinsemilla to be 250 bits. + let c_whole = meta.query_advice(advices[2], Rotation::cur()); + let left_node = meta.query_advice(advices[3], Rotation::cur()); + let right_node = meta.query_advice(advices[4], Rotation::cur()); + + // a = a_0||a_1 = l || (bits 0..=239 of left) + // + // z_1 of SinsemillaHash(a) = a_1 + // => a_0 = a - (a_1 * 2^10) + let z1_a = meta.query_advice(advices[0], Rotation::next()); + let a_1 = z1_a; + // Derive a_0 (constrained by SinsemillaHash to be 10 bits) + let a_0 = a_whole - a_1.clone() * two_pow_10; + + // b = b_0||b_1||b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + // The Orchard specification allows this representation to be non-canonical. + // + // + // z_1 of SinsemillaHash(b) = b_1 + 2^5 b_2 + // => b_0 = b - (z1_b * 2^10) + let z1_b = meta.query_advice(advices[1], Rotation::next()); + // b_1 has been constrained to be 5 bits outside this gate. + let b_1 = meta.query_advice(advices[2], Rotation::next()); + // b_2 has been constrained to be 5 bits outside this gate. + let b_2 = meta.query_advice(advices[3], Rotation::next()); + // Constrain b_1 + 2^5 b_2 = z1_b + // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-bit-lengths?partial + let b1_b2_check = z1_b.clone() - (b_1.clone() + b_2.clone() * two_pow_5); + // Derive b_0 (constrained by SinsemillaHash to be 10 bits) + let b_0 = b_whole - (z1_b * two_pow_10); + + // Check that left = a_1 (240 bits) || b_0 (10 bits) || b_1 (5 bits) + // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-decomposition?partial + let left_check = { + let reconstructed = { + let two_pow_240 = pallas::Base::from_u128(1 << 120).square(); + a_1 + (b_0 + b_1 * two_pow_10) * two_pow_240 + }; + reconstructed - left_node + }; + + // Check that right = b_2 (5 bits) || c (250 bits) + // The Orchard specification allows this representation to be non-canonical. + // + // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-decomposition?partial + let right_check = b_2 + c_whole * two_pow_5 - right_node; + + Constraints::with_selector( + q_decompose, + [ + ("l_check", a_0 - l_whole), + ("left_check", left_check), + ("right_check", right_check), + ("b1_b2_check", b1_b2_check), + ], + ) + }); + + MerkleConfig { + advices, + q_decompose, + cond_swap_config, + sinsemilla_config, + } + } + + /// Constructs a [`MerkleChip`] given a [`MerkleConfig`]. + pub fn construct(config: MerkleConfig) -> Self { + MerkleChip { config } + } +} + +impl +MerkleInstructions +for MerkleChip + where + Hash: HashDomains + Eq, + F: FixedPoints, + Commit: CommitDomains + Eq, +{ + #[allow(non_snake_case)] + fn hash_layer( + &self, + mut layouter: impl Layouter, + Q: pallas::Affine, + // l = MERKLE_DEPTH - layer - 1 + l: usize, + left: Self::Var, + right: Self::Var, + ) -> Result { + let config = self.config().clone(); + + // We need to hash `l || left || right`, where `l` is a 10-bit value. + // We allow `left` and `right` to be non-canonical 255-bit encodings. + // + // a = a_0||a_1 = l || (bits 0..=239 of left) + // b = b_0||b_1||b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + // c = bits 5..=254 of right + // + // We start by witnessing all of the individual pieces, and range-constraining the + // short pieces b_1 and b_2. + // + // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-bit-lengths?partial + + // `a = a_0||a_1` = `l` || (bits 0..=239 of `left`) + let a = MessagePiece::from_subpieces( + self.clone(), + layouter.namespace(|| "Witness a = a_0 || a_1"), + [ + RangeConstrained::bitrange_of(Value::known(&pallas::Base::from(l as u64)), 0..10), + RangeConstrained::bitrange_of(left.value(), 0..240), + ], + )?; + + // b = b_0 || b_1 || b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + let (b_1, b_2, b) = { + // b_0 = (bits 240..=249 of `left`) + let b_0 = RangeConstrained::bitrange_of(left.value(), 240..250); + + // b_1 = (bits 250..=254 of `left`) + // Constrain b_1 to 5 bits. + let b_1 = RangeConstrained::witness_short( + &config.sinsemilla_config.lookup_config(), + layouter.namespace(|| "b_1"), + left.value(), + 250..(pallas::Base::NUM_BITS as usize), + )?; + + // b_2 = (bits 0..=4 of `right`) + // Constrain b_2 to 5 bits. + let b_2 = RangeConstrained::witness_short( + &config.sinsemilla_config.lookup_config(), + layouter.namespace(|| "b_2"), + right.value(), + 0..5, + )?; + + let b = MessagePiece::from_subpieces( + self.clone(), + layouter.namespace(|| "Witness b = b_0 || b_1 || b_2"), + [b_0, b_1.value(), b_2.value()], + )?; + + (b_1, b_2, b) + }; + + // c = bits 5..=254 of `right` + let c = MessagePiece::from_subpieces( + self.clone(), + layouter.namespace(|| "Witness c"), + [RangeConstrained::bitrange_of( + right.value(), + 5..(pallas::Base::NUM_BITS as usize), + )], + )?; + + // hash = SinsemillaHash(Q, 𝑙⋆ || left⋆ || right⋆) + // + // `hash = ⊥` is handled internally to `SinsemillaChip::hash_to_point`: incomplete + // addition constraints allows ⊥ to occur, and then during synthesis it detects + // these edge cases and raises an error (aborting proof creation). + // + // Note that MerkleCRH as-defined maps ⊥ to 0. This is for completeness outside + // the circuit (so that the ⊥ does not propagate into the type system). The chip + // explicitly doesn't map ⊥ to 0; in fact it cannot, as doing so would require + // constraints that amount to using complete addition. The rationale for excluding + // this map is the same as why Sinsemilla uses incomplete addition: this situation + // yields a nontrivial discrete log relation, and by assumption it is hard to find + // these. + // + // https://p.z.cash/proto:merkle-crh-orchard + let (point, zs) = self.hash_to_point( + layouter.namespace(|| format!("hash at l = {}", l)), + Q, + vec![a.inner(), b.inner(), c.inner()].into(), + )?; + let hash = Self::extract(&point); + + // `SinsemillaChip::hash_to_point` returns the running sum for each `MessagePiece`. + // Grab the outputs we need for the decomposition constraints. + let z1_a = zs[0][1].clone(); + let z1_b = zs[1][1].clone(); + + // Check that the pieces have been decomposed properly. + // + // The pieces and subpieces are arranged in the following configuration: + // | A_0 | A_1 | A_2 | A_3 | A_4 | q_decompose | + // ------------------------------------------------------- + // | a | b | c | left | right | 1 | + // | z1_a | z1_b | b_1 | b_2 | l | 0 | + { + layouter.assign_region( + || "Check piece decomposition", + |mut region| { + // Set the fixed column `l` to the current l. + // Recall that l = MERKLE_DEPTH - layer - 1. + // The layer with 2^n nodes is called "layer n". + config.q_decompose.enable(&mut region, 0)?; + region.assign_advice_from_constant( + || format!("l {}", l), + config.advices[4], + 1, + pallas::Base::from(l as u64), + )?; + + // Offset 0 + // Copy and assign `a` at the correct position. + a.inner().cell_value().copy_advice( + || "copy a", + &mut region, + config.advices[0], + 0, + )?; + // Copy and assign `b` at the correct position. + b.inner().cell_value().copy_advice( + || "copy b", + &mut region, + config.advices[1], + 0, + )?; + // Copy and assign `c` at the correct position. + c.inner().cell_value().copy_advice( + || "copy c", + &mut region, + config.advices[2], + 0, + )?; + // Copy and assign the left node at the correct position. + left.copy_advice(|| "left", &mut region, config.advices[3], 0)?; + // Copy and assign the right node at the correct position. + right.copy_advice(|| "right", &mut region, config.advices[4], 0)?; + + // Offset 1 + // Copy and assign z_1 of SinsemillaHash(a) = a_1 + z1_a.copy_advice(|| "z1_a", &mut region, config.advices[0], 1)?; + // Copy and assign z_1 of SinsemillaHash(b) = b_1 + z1_b.copy_advice(|| "z1_b", &mut region, config.advices[1], 1)?; + // Copy `b_1`, which has been constrained to be a 5-bit value + b_1.inner() + .copy_advice(|| "b_1", &mut region, config.advices[2], 1)?; + // Copy `b_2`, which has been constrained to be a 5-bit value + b_2.inner() + .copy_advice(|| "b_2", &mut region, config.advices[3], 1)?; + + Ok(()) + }, + )?; + } + + // Check layer hash output against Sinsemilla primitives hash + #[cfg(test)] + { + use crate::{sinsemilla::primitives::HashDomain, utilities::i2lebsp}; + + use group::ff::PrimeFieldBits; + + left.value() + .zip(right.value()) + .zip(hash.value()) + .assert_if_known(|((left, right), hash)| { + let l = i2lebsp::<10>(l as u64); + let left: Vec<_> = left + .to_le_bits() + .iter() + .by_vals() + .take(pallas::Base::NUM_BITS as usize) + .collect(); + let right: Vec<_> = right + .to_le_bits() + .iter() + .by_vals() + .take(pallas::Base::NUM_BITS as usize) + .collect(); + let merkle_crh = HashDomain::from_Q(Q.into()); + + let mut message = l.to_vec(); + message.extend_from_slice(&left); + message.extend_from_slice(&right); + + let expected = merkle_crh.hash(message.into_iter()).unwrap(); + + expected.to_repr() == hash.to_repr() + }); + } + + Ok(hash) + } +} + +impl UtilitiesInstructions for MerkleChip + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + type Var = AssignedCell; +} + +impl CondSwapInstructions for MerkleChip + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + #[allow(clippy::type_complexity)] + fn swap( + &self, + layouter: impl Layouter, + pair: (Self::Var, Value), + swap: Value, + ) -> Result<(Self::Var, Self::Var), Error> { + let config = self.config().cond_swap_config.clone(); + let chip = CondSwapChip::::construct(config); + chip.swap(layouter, pair, swap) + } + + fn mux( + &self, + layouter: &mut impl Layouter, + choice: Self::Var, + left: Self::Var, + right: Self::Var, + ) -> Result { + let config = self.config().cond_swap_config.clone(); + let chip = CondSwapChip::::construct(config); + chip.mux(layouter, choice, left, right) + } +} + +impl SinsemillaInstructions +for MerkleChip + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + type CellValue = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::CellValue; + + type Message = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::Message; + type MessagePiece = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::MessagePiece; + type RunningSum = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::RunningSum; + + type X = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::X; + type NonIdentityPoint = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::NonIdentityPoint; + type FixedPoints = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::FixedPoints; + + type HashDomains = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::HashDomains; + type CommitDomains = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::CommitDomains; + + fn witness_message_piece( + &self, + layouter: impl Layouter, + value: Value, + num_words: usize, + ) -> Result { + let config = self.config().sinsemilla_config.clone(); + let chip = SinsemillaChip::::construct(config); + chip.witness_message_piece(layouter, value, num_words) + } + + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + fn hash_to_point( + &self, + layouter: impl Layouter, + Q: pallas::Affine, + message: Self::Message, + ) -> Result<(Self::NonIdentityPoint, Vec>), Error> { + let config = self.config().sinsemilla_config.clone(); + let chip = SinsemillaChip::::construct(config); + chip.hash_to_point(layouter, Q, message) + } + + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + fn hash_to_point_with_private_init( + &self, + layouter: impl Layouter, + Q: &Self::NonIdentityPoint, + message: Self::Message, + ) -> Result<(Self::NonIdentityPoint, Vec>), Error> { + let config = self.config().sinsemilla_config.clone(); + let chip = SinsemillaChip::::construct(config); + chip.hash_to_point_with_private_init(layouter, Q, message) + } + + fn extract(point: &Self::NonIdentityPoint) -> Self::X { + SinsemillaChip::::extract(point) + } +} \ No newline at end of file diff --git a/halo2_gadgets_optimized/src/sinsemilla/message.rs b/halo2_gadgets_optimized/src/sinsemilla/message.rs new file mode 100644 index 0000000000..f8ba67daa4 --- /dev/null +++ b/halo2_gadgets_optimized/src/sinsemilla/message.rs @@ -0,0 +1,65 @@ +//! Gadget and chips for the Sinsemilla hash function. +use ff::{Field, PrimeFieldBits}; +use halo2_proofs::circuit::{AssignedCell, Cell, Value}; +use std::fmt::Debug; + +/// A [`Message`] composed of several [`MessagePiece`]s. +#[derive(Clone, Debug)] +pub struct Message(Vec>); + +impl From>> + for Message +{ + fn from(pieces: Vec>) -> Self { + // A message cannot contain more than `MAX_WORDS` words. + assert!(pieces.iter().map(|piece| piece.num_words()).sum::() < MAX_WORDS); + Message(pieces) + } +} + +impl std::ops::Deref + for Message +{ + type Target = [MessagePiece]; + + fn deref(&self) -> &[MessagePiece] { + &self.0 + } +} + +/// A [`MessagePiece`] of some bitlength. +/// +/// The piece must fit within a base field element, which means its length +/// cannot exceed the base field's `NUM_BITS`. +#[derive(Clone, Debug)] +pub struct MessagePiece { + cell_value: AssignedCell, + /// The number of K-bit words in this message piece. + num_words: usize, +} + +impl MessagePiece { + pub fn new(cell_value: AssignedCell, num_words: usize) -> Self { + assert!(num_words * K < F::NUM_BITS as usize); + Self { + cell_value, + num_words, + } + } + + pub fn num_words(&self) -> usize { + self.num_words + } + + pub fn cell(&self) -> Cell { + self.cell_value.cell() + } + + pub fn field_elem(&self) -> Value { + self.cell_value.value().cloned() + } + + pub fn cell_value(&self) -> AssignedCell { + self.cell_value.clone() + } +} diff --git a/halo2_gadgets_optimized/src/sinsemilla/primitives.rs b/halo2_gadgets_optimized/src/sinsemilla/primitives.rs new file mode 100644 index 0000000000..668558fed9 --- /dev/null +++ b/halo2_gadgets_optimized/src/sinsemilla/primitives.rs @@ -0,0 +1,368 @@ +//! Implementation of Sinsemilla outside the circuit. + +use group::{Curve, Wnaf}; +use halo2_proofs::arithmetic::{CurveAffine, CurveExt}; +use pasta_curves::pallas; +use subtle::CtOption; + +mod addition; +use self::addition::IncompletePoint; +mod sinsemilla_s; +pub use sinsemilla_s::SINSEMILLA_S; + +/// Number of bits of each message piece in $\mathsf{SinsemillaHashToPoint}$ +pub const K: usize = 10; + +/// $\frac{1}{2^K}$ +pub const INV_TWO_POW_K: [u8; 32] = [ + 1, 0, 192, 196, 160, 229, 70, 82, 221, 165, 74, 202, 85, 7, 62, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 240, 63, +]; + +/// The largest integer such that $2^c \leq (r_P - 1) / 2$, where $r_P$ is the order +/// of Pallas. +pub const C: usize = 253; + +// Sinsemilla Q generators + +/// SWU hash-to-curve personalization for Sinsemilla $Q$ generators. +pub const Q_PERSONALIZATION: &str = "z.cash:SinsemillaQ"; + +// Sinsemilla S generators + +/// SWU hash-to-curve personalization for Sinsemilla $S$ generators. +pub const S_PERSONALIZATION: &str = "z.cash:SinsemillaS"; + +pub(crate) fn lebs2ip_k(bits: &[bool]) -> u32 { + assert!(bits.len() == K); + bits.iter() + .enumerate() + .fold(0u32, |acc, (i, b)| acc + if *b { 1 << i } else { 0 }) +} + +/// Coordinate extractor for Pallas. +/// +/// Defined in [Zcash Protocol Spec § 5.4.9.7: Coordinate Extractor for Pallas][concreteextractorpallas]. +/// +/// [concreteextractorpallas]: https://zips.z.cash/protocol/nu5.pdf#concreteextractorpallas +fn extract_p_bottom(point: CtOption) -> CtOption { + point.map(|p| { + p.to_affine() + .coordinates() + .map(|c| *c.x()) + .unwrap_or_else(pallas::Base::zero) + }) +} + +/// Pads the given iterator (which MUST have length $\leq K * C$) with zero-bits to a +/// multiple of $K$ bits. +struct Pad> { + /// The iterator we are padding. + inner: I, + /// The measured length of the inner iterator. + /// + /// This starts as a lower bound, and will be accurate once `padding_left.is_some()`. + len: usize, + /// The amount of padding that remains to be emitted. + padding_left: Option, +} + +impl> Pad { + fn new(inner: I) -> Self { + Pad { + inner, + len: 0, + padding_left: None, + } + } +} + +impl> Iterator for Pad { + type Item = bool; + + fn next(&mut self) -> Option { + loop { + // If we have identified the required padding, the inner iterator has ended, + // and we will never poll it again. + if let Some(n) = self.padding_left.as_mut() { + if *n == 0 { + // Either we already emitted all necessary padding, or there was no + // padding required. + break None; + } else { + // Emit the next padding bit. + *n -= 1; + break Some(false); + } + } else if let Some(ret) = self.inner.next() { + // We haven't reached the end of the inner iterator yet. + self.len += 1; + assert!(self.len <= K * C); + break Some(ret); + } else { + // Inner iterator just ended, so we now know its length. + let rem = self.len % K; + if rem > 0 { + // The inner iterator requires padding in the range [1,K). + self.padding_left = Some(K - rem); + } else { + // No padding required. + self.padding_left = Some(0); + } + } + } + } +} + +/// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can +/// be used. +#[derive(Debug, Clone)] +#[allow(non_snake_case)] +pub struct HashDomain { + Q: pallas::Point, +} + +impl HashDomain { + /// Constructs a new `HashDomain` with a specific prefix string. + pub fn new(domain: &str) -> Self { + HashDomain { + Q: pallas::Point::hash_to_curve(Q_PERSONALIZATION)(domain.as_bytes()), + } + } + + /// $\mathsf{SinsemillaHashToPoint}$ from [§ 5.4.1.9][concretesinsemillahash]. + /// + /// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash + pub fn hash_to_point(&self, msg: impl Iterator) -> CtOption { + self.hash_to_point_inner(msg).into() + } + + #[allow(non_snake_case)] + fn hash_to_point_inner(&self, msg: impl Iterator) -> IncompletePoint { + let padded: Vec<_> = Pad::new(msg).collect(); + + padded + .chunks(K) + .fold(IncompletePoint::from(self.Q), |acc, chunk| { + let (S_x, S_y) = SINSEMILLA_S[lebs2ip_k(chunk) as usize]; + let S_chunk = pallas::Affine::from_xy(S_x, S_y).unwrap(); + (acc + S_chunk) + acc + }) + } + + /// $\mathsf{SinsemillaHash}$ from [§ 5.4.1.9][concretesinsemillahash]. + /// + /// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash + /// + /// # Panics + /// + /// This panics if the message length is greater than [`K`] * [`C`] + pub fn hash(&self, msg: impl Iterator) -> CtOption { + extract_p_bottom(self.hash_to_point(msg)) + } + + /// Constructs a new `HashDomain` from a given `Q`. + /// + /// This is only for testing use. + #[cfg(test)] + #[allow(non_snake_case)] + pub(crate) fn from_Q(Q: pallas::Point) -> Self { + HashDomain { Q } + } + + /// Returns the Sinsemilla $Q$ constant for this domain. + #[cfg(any(test, feature = "test-dependencies"))] + #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] + #[allow(non_snake_case)] + pub fn Q(&self) -> pallas::Point { + self.Q + } +} + +/// A domain in which $\mathsf{SinsemillaCommit}$ and $\mathsf{SinsemillaShortCommit}$ can +/// be used. +#[derive(Debug)] +#[allow(non_snake_case)] +pub struct CommitDomain { + /// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can be used + M: HashDomain, + R: pallas::Point, +} + +impl CommitDomain { + /// Constructs a new `CommitDomain` with a specific prefix string. + pub fn new(domain: &str) -> Self { + let m_prefix = format!("{}-M", domain); + let r_prefix = format!("{}-r", domain); + let hasher_r = pallas::Point::hash_to_curve(&r_prefix); + CommitDomain { + M: HashDomain::new(&m_prefix), + R: hasher_r(&[]), + } + } + + /// Constructs a new `CommitDomain` from different values for `hash_domain` and `blind_domain` + pub fn new_with_personalization(hash_domain: &str, blind_domain: &str) -> Self { + let m_prefix = format!("{}-M", hash_domain); + let r_prefix = format!("{}-r", blind_domain); + let hasher_r = pallas::Point::hash_to_curve(&r_prefix); + CommitDomain { + M: HashDomain::new(&m_prefix), + R: hasher_r(&[]), + } + } + + /// $\mathsf{SinsemillaCommit}$ from [§ 5.4.8.4][concretesinsemillacommit]. + /// + /// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit + #[allow(non_snake_case)] + pub fn commit( + &self, + msg: impl Iterator, + r: &pallas::Scalar, + ) -> CtOption { + // We use complete addition for the blinding factor. + CtOption::::from(self.M.hash_to_point_inner(msg)) + .map(|p| p + Wnaf::new().scalar(r).base(self.R)) + } + + /// $\mathsf{SinsemillaHashToPoint}$ from [§ 5.4.1.9][concretesinsemillahash]. + /// + /// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash + pub fn hash_to_point(&self, msg: impl Iterator) -> CtOption { + self.M.hash_to_point(msg) + } + + /// Returns `SinsemillaCommit_r(personalization, msg) = hash_point + [r]R` + /// where `SinsemillaHash(personalization, msg) = hash_point` + /// and `R` is derived from the `personalization`. + #[allow(non_snake_case)] + pub fn commit_from_hash_point( + &self, + hash_point: CtOption, + r: &pallas::Scalar, + ) -> CtOption { + // We use complete addition for the blinding factor. + hash_point.map(|p| p + Wnaf::new().scalar(r).base(self.R)) + } + + /// $\mathsf{SinsemillaShortCommit}$ from [§ 5.4.8.4][concretesinsemillacommit]. + /// + /// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit + pub fn short_commit( + &self, + msg: impl Iterator, + r: &pallas::Scalar, + ) -> CtOption { + extract_p_bottom(self.commit(msg, r)) + } + + /// Returns the Sinsemilla $R$ constant for this domain. + #[cfg(any(test, feature = "test-dependencies"))] + #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] + #[allow(non_snake_case)] + pub fn R(&self) -> pallas::Point { + self.R + } + + /// Returns the Sinsemilla $Q$ constant for this domain. + #[cfg(any(test, feature = "test-dependencies"))] + #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] + #[allow(non_snake_case)] + pub fn Q(&self) -> pallas::Point { + self.M.Q + } +} + +#[cfg(test)] +mod tests { + use super::{Pad, K}; + use pasta_curves::{arithmetic::CurveExt, pallas}; + + #[test] + fn pad() { + assert_eq!( + Pad::new([].iter().cloned()).collect::>(), + vec![false; 0] + ); + assert_eq!( + Pad::new([true].iter().cloned()).collect::>(), + vec![true, false, false, false, false, false, false, false, false, false] + ); + assert_eq!( + Pad::new([true, true].iter().cloned()).collect::>(), + vec![true, true, false, false, false, false, false, false, false, false] + ); + assert_eq!( + Pad::new([true, true, true].iter().cloned()).collect::>(), + vec![true, true, true, false, false, false, false, false, false, false] + ); + assert_eq!( + Pad::new( + [true, true, false, true, false, true, false, true, false, true] + .iter() + .cloned() + ) + .collect::>(), + vec![true, true, false, true, false, true, false, true, false, true] + ); + assert_eq!( + Pad::new( + [true, true, false, true, false, true, false, true, false, true, true] + .iter() + .cloned() + ) + .collect::>(), + vec![ + true, true, false, true, false, true, false, true, false, true, true, false, false, + false, false, false, false, false, false, false + ] + ); + } + + #[test] + fn sinsemilla_s() { + use super::sinsemilla_s::SINSEMILLA_S; + use group::Curve; + use pasta_curves::arithmetic::CurveAffine; + + let hasher = pallas::Point::hash_to_curve(super::S_PERSONALIZATION); + + for j in 0..(1u32 << K) { + let computed = { + let point = hasher(&j.to_le_bytes()).to_affine().coordinates().unwrap(); + (*point.x(), *point.y()) + }; + let actual = SINSEMILLA_S[j as usize]; + assert_eq!(computed, actual); + } + } + + #[test] + fn commit_in_several_steps() { + use rand::{rngs::OsRng, Rng}; + + use ff::Field; + + use crate::sinsemilla::primitives::CommitDomain; + + let domain = CommitDomain::new("z.cash:ZSA-NoteCommit"); + + let mut os_rng = OsRng::default(); + let msg: Vec = (0..36).map(|_| os_rng.gen::()).collect(); + + let rcm = pallas::Scalar::random(&mut os_rng); + + // Evaluate the commitment with commit function + let commit1 = domain.commit(msg.clone().into_iter(), &rcm); + + // Evaluate the commitment with the following steps + // 1. hash msg + // 2. evaluate the commitment from the hash point + let hash_point = domain.M.hash_to_point(msg.into_iter()); + let commit2 = domain.commit_from_hash_point(hash_point, &rcm); + + // Test equality + assert_eq!(commit1.unwrap(), commit2.unwrap()); + } +} \ No newline at end of file diff --git a/halo2_gadgets_optimized/src/sinsemilla/primitives/addition.rs b/halo2_gadgets_optimized/src/sinsemilla/primitives/addition.rs new file mode 100644 index 0000000000..3949addaa1 --- /dev/null +++ b/halo2_gadgets_optimized/src/sinsemilla/primitives/addition.rs @@ -0,0 +1,73 @@ +use std::ops::Add; + +use group::{cofactor::CofactorCurveAffine, Group}; +use pasta_curves::pallas; +use subtle::{ConstantTimeEq, CtOption}; + +/// P ∪ {⊥} +/// +/// Simulated incomplete addition built over complete addition. +#[derive(Clone, Copy, Debug)] +pub(super) struct IncompletePoint(CtOption); + +impl From for IncompletePoint { + fn from(p: pallas::Point) -> Self { + IncompletePoint(CtOption::new(p, 1.into())) + } +} + +impl From for CtOption { + fn from(p: IncompletePoint) -> Self { + p.0 + } +} + +impl Add for IncompletePoint { + type Output = IncompletePoint; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn add(self, rhs: Self) -> Self::Output { + // ⊥ ⸭ ⊥ = ⊥ + // ⊥ ⸭ P = ⊥ + IncompletePoint(self.0.and_then(|p| { + // P ⸭ ⊥ = ⊥ + rhs.0.and_then(|q| { + // 0 ⸭ 0 = ⊥ + // 0 ⸭ P = ⊥ + // P ⸭ 0 = ⊥ + // (x, y) ⸭ (x', y') = ⊥ if x == x' + // (x, y) ⸭ (x', y') = (x, y) + (x', y') if x != x' + CtOption::new( + p + q, + !(p.is_identity() | q.is_identity() | p.ct_eq(&q) | p.ct_eq(&-q)), + ) + }) + })) + } +} + +impl Add for IncompletePoint { + type Output = IncompletePoint; + + /// Specialisation of incomplete addition for mixed addition. + #[allow(clippy::suspicious_arithmetic_impl)] + fn add(self, rhs: pallas::Affine) -> Self::Output { + // ⊥ ⸭ ⊥ = ⊥ + // ⊥ ⸭ P = ⊥ + IncompletePoint(self.0.and_then(|p| { + // P ⸭ ⊥ = ⊥ is satisfied by definition. + let q = rhs.to_curve(); + + // 0 ⸭ 0 = ⊥ + // 0 ⸭ P = ⊥ + // P ⸭ 0 = ⊥ + // (x, y) ⸭ (x', y') = ⊥ if x == x' + // (x, y) ⸭ (x', y') = (x, y) + (x', y') if x != x' + CtOption::new( + // Use mixed addition for efficiency. + p + rhs, + !(p.is_identity() | q.is_identity() | p.ct_eq(&q) | p.ct_eq(&-q)), + ) + })) + } +} diff --git a/halo2_gadgets_optimized/src/sinsemilla/primitives/sinsemilla_s.rs b/halo2_gadgets_optimized/src/sinsemilla/primitives/sinsemilla_s.rs new file mode 100644 index 0000000000..739d5a793b --- /dev/null +++ b/halo2_gadgets_optimized/src/sinsemilla/primitives/sinsemilla_s.rs @@ -0,0 +1,14344 @@ +use super::K; +use pasta_curves::pallas; + +/// The precomputed bases for the [Sinsemilla hash function][concretesinsemillahash]. +/// +/// [concretesinsemillahash]: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash +pub const SINSEMILLA_S: [(pallas::Base, pallas::Base); 1 << K] = [ + ( + pallas::Base::from_raw([ + 0x5a91_eb91_2044_ea5f, + 0x29a0_5baf_bede_62b5, + 0x1431_d4ea_7d4a_fc7b, + 0x0db5_218b_e688_1f0f, + ]), + pallas::Base::from_raw([ + 0x17c2_4f76_bf8e_6483, + 0x944a_041c_2e65_ba01, + 0x9caf_6629_8493_d5d0, + 0x2f0f_40c2_f152_a01c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xce4a_e33e_a108_af91, + 0xe677_00ca_2464_9b8f, + 0xc8fd_33eb_3917_5404, + 0x2111_12b4_b3e1_9518, + ]), + pallas::Base::from_raw([ + 0x1d83_c293_f810_c5ee, + 0xb43c_744a_670e_19bc, + 0xa38a_3e79_cd5a_35fe, + 0x06c5_9939_93ad_b03b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x22e0_c475_a18f_6d24, + 0xf40c_b333_4b54_11c0, + 0x4661_a4e2_355b_9b33, + 0x25b3_2ccd_49f9_25a3, + ]), + pallas::Base::from_raw([ + 0xa67d_6b8d_b8fd_9757, + 0x4be1_ebb9_f945_ccd2, + 0x7d53_d0f3_23b4_4711, + 0x140f_f2ba_70d0_692c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa5fa_ef0f_0419_dabc, + 0x6539_ca12_938b_1826, + 0x60ff_465c_d02c_701f, + 0x1421_5a48_e118_32c9, + ]), + pallas::Base::from_raw([ + 0x011d_5d36_25ca_36dc, + 0x97b6_3b4b_53e2_ad56, + 0xc711_a0b9_0b58_03bd, + 0x1066_6957_becb_884d, + ]), + ), + ( + pallas::Base::from_raw([ + 0xcd48_cef6_4bed_0936, + 0x0e94_8b52_f738_61ce, + 0x9ab9_b595_d23f_059f, + 0x315c_f93b_9e63_151c, + ]), + pallas::Base::from_raw([ + 0xe257_7684_a31c_721a, + 0x81ef_1386_c070_a51d, + 0x2afb_9a52_d043_78ca, + 0x2c54_c0b3_8dfb_ad40, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1e62_9662_aa5a_3fdf, + 0x94fb_57dc_9d31_389b, + 0x63c5_542e_be6c_3ab7, + 0x1d74_51a5_7e92_5ec5, + ]), + pallas::Base::from_raw([ + 0x934c_1dc8_aaca_454b, + 0xc086_048c_4eef_c0dd, + 0xc49c_1472_164b_6fbc, + 0x0a4b_5139_9347_7ec7, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7e7c_cebd_ac44_118f, + 0xe055_272f_8429_ff1c, + 0x3c80_b5f8_24a7_5f24, + 0x0d3d_3c74_10d4_4668, + ]), + pallas::Base::from_raw([ + 0x79ea_b21b_b522_55cc, + 0x1268_61c0_de4f_0982, + 0xa02e_3186_23a5_5ae9, + 0x2b94_e712_9758_334f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0e77_383e_d712_9d2f, + 0x6283_df71_4b0f_9b18, + 0x0864_617c_d666_062c, + 0x197e_faba_7491_0b57, + ]), + pallas::Base::from_raw([ + 0x85b8_e2cb_12fd_c127, + 0x7a88_75b5_de27_45cd, + 0x8df5_6a97_ed99_d31b, + 0x34d9_5547_d6e9_56a9, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd21c_d292_6b24_b055, + 0xa416_7a07_7246_418e, + 0x3e29_11ab_ef54_9b47, + 0x305a_d0e8_b11c_ee66, + ]), + pallas::Base::from_raw([ + 0x9b39_73cf_a5bf_b938, + 0x12a2_0ca5_d138_80d6, + 0x8207_1242_c3b7_34a4, + 0x3e25_9102_6dbb_bfd8, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf6bc_e362_6b48_56be, + 0xdf5e_e6f1_5b66_3484, + 0xbc37_bdf1_4766_3e61, + 0x2a80_ed20_aecc_771f, + ]), + pallas::Base::from_raw([ + 0xaeaf_58cd_40d9_d753, + 0x6776_81a1_39a9_44eb, + 0xdb78_4220_d080_d1ae, + 0x3bed_9f53_2f89_c873, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa96d_351c_69e7_3b34, + 0xe5df_e62c_bb74_b432, + 0x3e0b_f886_c563_3909, + 0x1169_679d_516b_0f52, + ]), + pallas::Base::from_raw([ + 0x447f_2648_3a56_52ee, + 0x77d8_03b3_afe8_3912, + 0xbe1e_a779_988f_4fd3, + 0x24dc_58d8_46ef_f85d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x851d_c62e_bc87_5030, + 0x1957_510c_703c_5765, + 0xd439_8de8_3aae_c992, + 0x0a64_5164_e0e0_d1cb, + ]), + pallas::Base::from_raw([ + 0xd768_494c_1aa6_936d, + 0x0c89_d7ee_a548_c2a4, + 0x7e1f_87b7_9978_76b1, + 0x153f_b63d_3879_f182, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc565_0619_42a8_3439, + 0x7716_f57c_193a_3df2, + 0x1325_b0e1_8d7a_ea0a, + 0x05a9_1753_a68b_7601, + ]), + pallas::Base::from_raw([ + 0x2443_2316_26bd_d43c, + 0x959f_e764_a3f2_575f, + 0xd709_6a40_9799_144e, + 0x1c29_c630_347e_9508, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe843_6f46_1411_e72a, + 0x2c1a_4034_7249_2a59, + 0x1347_0a8d_938a_b691, + 0x25e3_34b5_cec9_3544, + ]), + pallas::Base::from_raw([ + 0xcec5_d233_9cad_4b3d, + 0xa40d_0761_a4cc_45ab, + 0x194c_d250_2d80_84b2, + 0x0160_64db_6159_2474, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe2f0_65b5_2537_5cc9, + 0x2528_5987_8c88_25ab, + 0x9252_31c1_bc9d_6a66, + 0x379c_2bd0_c190_9c7e, + ]), + pallas::Base::from_raw([ + 0xa0fb_efa7_6375_9430, + 0x430e_1e41_7b73_83f0, + 0xddea_123a_66f3_6e40, + 0x11a9_c254_99d5_9f1b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0b5a_9e1e_862e_edaa, + 0xc0a3_4382_61ff_61c6, + 0x4b41_f6dc_4801_f3a1, + 0x0c6f_c7b2_d365_6f82, + ]), + pallas::Base::from_raw([ + 0xf30d_50e8_3e60_3bb5, + 0x589f_b478_7d54_8e5f, + 0x06d5_b032_d1ea_0eeb, + 0x2492_a76d_96dc_fe8e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0719_4d69_3a4f_e5d9, + 0xc3f4_ced6_5ced_7022, + 0x1965_c5c3_f9ee_e6b7, + 0x0c17_5d45_3bb5_0e04, + ]), + pallas::Base::from_raw([ + 0x6e28_e65c_02a8_979a, + 0x9155_bdb2_213a_1ec2, + 0xa592_5dd2_ac1b_a08a, + 0x106e_3d4f_2d01_2990, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2af5_5769_718f_6d0a, + 0x0acc_0135_db3d_9c80, + 0x31f8_9d95_9313_5e05, + 0x26e5_eae3_20ed_b155, + ]), + pallas::Base::from_raw([ + 0xd75b_fdee_b95f_d1c9, + 0x1e63_a80a_818c_5374, + 0x4bb1_c3e6_0f0b_c040, + 0x1d05_882a_cd14_4b0c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7ec3_90f2_5e39_5b94, + 0x4230_0755_c68f_a549, + 0x9210_9733_af81_0407, + 0x310d_60d7_14eb_00d9, + ]), + pallas::Base::from_raw([ + 0xce04_43cb_4410_650a, + 0xb1c0_4053_f900_ef43, + 0x4087_e7a3_9312_1fdd, + 0x3ee2_559e_cdce_ec5c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa2a0_b2bb_c709_eb43, + 0x8ade_17ea_bf18_0624, + 0xbe3d_cee5_1eac_8187, + 0x1719_0481_09ec_ac8d, + ]), + pallas::Base::from_raw([ + 0x2abc_973a_cc15_a05b, + 0x50a8_df0b_570a_193a, + 0x87f1_e098_4f0e_1d44, + 0x1267_3ac5_b9fe_f8c5, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb461_d0fe_6d58_3369, + 0xccbc_110f_c982_9287, + 0x0263_da47_2626_a5f0, + 0x1289_d222_9c37_19c6, + ]), + pallas::Base::from_raw([ + 0x7f84_d109_f869_6212, + 0xe7b6_8435_60c9_f37e, + 0xc9ab_26a3_a8a3_b9aa, + 0x08a7_e5e9_d4ad_1e44, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd657_d70e_67a6_cffb, + 0x1bc2_8f7a_f5d8_d663, + 0x176c_cb97_5e2e_acdd, + 0x08dc_48f1_a91c_222a, + ]), + pallas::Base::from_raw([ + 0xc6aa_0ceb_40b6_128c, + 0x94a1_9ee5_c270_a217, + 0x9f79_f114_a1f4_083a, + 0x21f4_0941_aa55_b3d0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x84c6_e703_7113_c2c1, + 0x9549_5a9a_5e2c_f521, + 0xae9d_c0a2_1bc8_b85e, + 0x2bde_7809_257f_2d02, + ]), + pallas::Base::from_raw([ + 0xa114_c0a6_314e_d2f9, + 0x9be7_e608_3d7e_876f, + 0xc105_6823_d929_fd2a, + 0x15ac_6d2a_83e2_50bd, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb0fe_9fd5_a421_340b, + 0x48e4_f171_3c9e_cd26, + 0xe4f4_ac71_3d4a_95bc, + 0x312b_6128_1ab5_d033, + ]), + pallas::Base::from_raw([ + 0x405b_d400_afdb_5d9c, + 0xaaeb_8090_ef6b_9390, + 0x7469_c0b3_e23c_53dd, + 0x2e4f_21bf_b05d_4559, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa0bf_d82b_7612_2e84, + 0x6c78_ca0a_b548_2d27, + 0x75ae_5653_62af_eb1d, + 0x089b_3b7f_8bd4_ea08, + ]), + pallas::Base::from_raw([ + 0x7fca_3834_c670_2579, + 0xa062_31d2_085d_6258, + 0xb32f_6836_d782_eb34, + 0x3708_e831_35ae_10c4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9f6d_c014_6c05_c7fe, + 0x1119_b3b5_d67c_a65f, + 0x45bf_6d2e_f7e6_fe54, + 0x0141_08ac_9ab6_3e4f, + ]), + pallas::Base::from_raw([ + 0x57f9_36e7_a514_d173, + 0x3d4e_a9b8_283e_a3f2, + 0x0b45_9bbe_bf56_d8df, + 0x0096_be83_6417_c3ce, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbf84_7e6e_a60a_1e10, + 0x007d_0da9_59a1_8af7, + 0x9b66_6f39_805d_f26e, + 0x2dda_a8b1_c70d_4798, + ]), + pallas::Base::from_raw([ + 0x8afa_e4a1_2104_3215, + 0xfc45_281d_665c_435f, + 0xcf42_5d23_97a4_d0d6, + 0x203b_d5e4_3544_e416, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6355_a342_c769_b991, + 0xaf94_3561_7ca8_fba5, + 0x9bc6_1953_1bc6_867c, + 0x35c6_b4b2_7ce9_8c84, + ]), + pallas::Base::from_raw([ + 0x358b_52f2_aa05_d0e2, + 0x1e36_f404_1069_11a7, + 0xdfb1_775d_f512_925e, + 0x2f0e_4cff_2885_5660, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9bc6_45aa_659d_394f, + 0x63fb_f9d5_aff5_52c2, + 0x2a81_a1f9_157d_0ddf, + 0x0840_8d6d_792b_1bdd, + ]), + pallas::Base::from_raw([ + 0x6214_c9e0_a442_6fed, + 0x580c_2dc1_b2dc_4db0, + 0xdf6e_7ce6_7c07_8b91, + 0x149b_86c7_aa0a_5807, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2366_fb20_016a_d15d, + 0x7e11_a509_32be_92a2, + 0xdf46_3c90_d34d_85db, + 0x3b7d_3524_37fb_ddae, + ]), + pallas::Base::from_raw([ + 0xd701_78a2_e2e7_3b2c, + 0xeb76_7a60_49bb_fd17, + 0xa081_0c5a_7118_8504, + 0x30e1_da14_7a31_ac8a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x996c_1cc3_79a3_05e2, + 0x61d9_35a7_da7f_f9df, + 0xe77e_3c50_0e84_d1fe, + 0x119d_6098_c1df_c009, + ]), + pallas::Base::from_raw([ + 0xe83c_68f6_0bac_ad89, + 0xe3e4_5f6b_7f33_8aea, + 0xcb02_edb7_2703_0a11, + 0x0dfe_fcd8_c7a0_37d2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0249_e1ef_c0c1_2e3f, + 0xfd35_f750_fc1b_140e, + 0x34cc_fd25_ee0a_24a4, + 0x2d85_ea37_1450_e936, + ]), + pallas::Base::from_raw([ + 0xea03_7e87_7076_c6a6, + 0x1e86_054a_7181_9a98, + 0xc8b2_0a67_de55_ffdd, + 0x2fa0_f1dc_c560_6ead, + ]), + ), + ( + pallas::Base::from_raw([ + 0x149d_4058_8269_142a, + 0xc47e_8699_87cd_0c7e, + 0x9083_84e4_4ce8_c260, + 0x0d65_ccec_99ef_3d51, + ]), + pallas::Base::from_raw([ + 0x3d9f_bece_1b95_bb5c, + 0x7369_87ea_5b12_60ce, + 0x69c5_cc43_e099_3c2c, + 0x1296_2805_bcb2_5e3d, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfd0f_79ac_f857_70f6, + 0x2cad_5ad0_3b87_58f2, + 0xd79d_4eba_953a_f087, + 0x0074_778c_62d8_0363, + ]), + pallas::Base::from_raw([ + 0x4d53_43bd_75b7_04b2, + 0xfb69_64df_389f_bd6c, + 0x0999_4f51_36f1_c414, + 0x0776_f4ae_c6cb_4e85, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2207_b16e_8cac_9dc4, + 0x6fcc_2464_92c3_0265, + 0x1f05_e00a_14f4_b845, + 0x01a9_d4be_c204_e872, + ]), + pallas::Base::from_raw([ + 0xf783_69ae_24fb_81a8, + 0xf741_a8a2_38ae_9967, + 0x4408_0885_faae_b30d, + 0x03f7_7f1f_49b9_18e6, + ]), + ), + ( + pallas::Base::from_raw([ + 0xad73_e083_fd4b_c101, + 0xb299_c606_ca3b_197f, + 0x61c8_0000_0ba8_6506, + 0x1691_35f3_c4b5_4f76, + ]), + pallas::Base::from_raw([ + 0x8760_58ce_eb92_e28c, + 0x34f0_16e0_f86c_0e2d, + 0x86b0_495e_c47b_3f50, + 0x03f1_c42b_c4c5_85ff, + ]), + ), + ( + pallas::Base::from_raw([ + 0x017d_94b7_2cab_798c, + 0x8d6d_cb5b_18aa_f523, + 0x8e08_8b5d_8994_c20b, + 0x19ba_e178_e635_fe13, + ]), + pallas::Base::from_raw([ + 0xab27_84b4_994a_0119, + 0x9da4_fe0e_635c_7e33, + 0x1143_8112_caa2_afc4, + 0x3acf_da94_ded9_c4e7, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbbce_cb08_317a_55bc, + 0x36aa_ddcd_3238_f0e6, + 0xdf00_05b6_e6ab_d61e, + 0x2527_c64e_053d_087c, + ]), + pallas::Base::from_raw([ + 0x6be8_48ee_033a_68ef, + 0x55a3_f9ac_d4f4_cee1, + 0xf6dd_57ce_d465_28bf, + 0x1e27_ea7d_b509_cc96, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe5bc_12d9_9516_4280, + 0x2a00_0dc2_5125_5df6, + 0xa936_924b_164c_d882, + 0x34a9_358a_32a8_ce49, + ]), + pallas::Base::from_raw([ + 0x60e8_77e4_dfcb_aca2, + 0x0aa5_3e88_25f8_a521, + 0xe325_6cc5_83dd_f098, + 0x3bdb_fc5c_5aa3_cdb8, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6d95_f7ea_ea3d_16d8, + 0x03c0_4e9a_6bd6_5383, + 0xec05_bb17_c4fe_b283, + 0x289e_03a5_ed1f_3b07, + ]), + pallas::Base::from_raw([ + 0x2b6a_c52f_feb1_7686, + 0xbcb7_da92_4e49_8948, + 0xa995_cd81_d04d_dbd3, + 0x0c46_6c57_1159_224a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6093_9be7_a4d2_9f0c, + 0x1518_62a2_b799_0aa2, + 0x44a3_e62c_1ce7_8cc2, + 0x201e_25ea_427d_e5f3, + ]), + pallas::Base::from_raw([ + 0x5853_5ee9_27ff_6b4f, + 0xa9ba_b5b3_9592_7126, + 0x2c60_0708_e61e_5681, + 0x31d4_68f6_37a7_026d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x97ec_2bda_32f9_27b3, + 0x7e79_9f38_c361_ed87, + 0x588f_ac0b_d601_fa88, + 0x275f_7736_734d_27fe, + ]), + pallas::Base::from_raw([ + 0x6426_8a27_c545_d9b9, + 0x46a0_a23d_c423_01eb, + 0xac7d_e04c_dccf_c81c, + 0x3030_4778_8312_b499, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9491_a146_3215_8028, + 0xee63_c9c2_6401_e1ab, + 0xe68f_139c_e3e6_847b, + 0x0ce9_e803_9aca_842f, + ]), + pallas::Base::from_raw([ + 0xe09d_04d1_25c7_4609, + 0x3770_bbd0_c05e_f8ad, + 0xdd8b_8c88_a169_9567, + 0x2d99_3912_f127_a6e4, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe531_5459_6f63_dd08, + 0xf7a0_13fe_10a1_9fc0, + 0xd7b4_76dc_ac74_a040, + 0x08a0_75bf_2f3f_783f, + ]), + pallas::Base::from_raw([ + 0x1962_3209_80d2_b200, + 0x3da6_e1b6_5f7f_1977, + 0x6168_4dbf_f2db_9c35, + 0x3c21_4bf5_b381_9204, + ]), + ), + ( + pallas::Base::from_raw([ + 0x46fc_698d_e305_8e9d, + 0xed82_7429_e58b_0e53, + 0xff3b_2ab5_65d8_30e8, + 0x2ee6_898e_6f25_1e50, + ]), + pallas::Base::from_raw([ + 0x2fea_1665_b778_7f9e, + 0xa84c_eb52_339e_9083, + 0xfc32_2809_6906_9bf6, + 0x22a0_7570_d244_95a4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8b2e_1430_028d_9350, + 0xa9ad_41fb_bf86_26c0, + 0xecff_6798_0a63_b4a7, + 0x38ec_640b_0d84_abd2, + ]), + pallas::Base::from_raw([ + 0xda17_249d_9fd9_ddc4, + 0x3862_5d42_a318_2c13, + 0x74b0_4c68_5377_a262, + 0x1e61_c8ab_ab50_80ed, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9263_3bf4_9d92_db8c, + 0x5b42_7e02_12d6_fcc3, + 0xd20c_2b5b_3c99_9c68, + 0x34a7_46a8_736d_3635, + ]), + pallas::Base::from_raw([ + 0x484c_9af1_9fc0_7320, + 0x2362_4c04_4eb0_ddc2, + 0x27e8_3514_854d_3b73, + 0x3064_16e8_7ca7_c73b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x32d4_110d_cc45_356b, + 0x48f4_7f5e_fc03_07ae, + 0x7853_0e2b_f3ff_9683, + 0x36d6_91ab_da05_2e5f, + ]), + pallas::Base::from_raw([ + 0xbedb_da77_5302_ff2e, + 0x4a4c_5917_f279_7024, + 0x0a89_7f1c_02e3_d7f5, + 0x01ac_b0f3_614f_a0e4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4ed3_24c0_f046_d8ff, + 0xd81f_1aca_41bc_54b4, + 0x633d_70fd_3193_5850, + 0x2f15_eb03_9398_a90e, + ]), + pallas::Base::from_raw([ + 0xe7be_4499_5af7_37c9, + 0x073c_911b_0333_2ce7, + 0x6ea6_25ed_f5ff_2dda, + 0x0290_63f6_092d_d3df, + ]), + ), + ( + pallas::Base::from_raw([ + 0x15f2_260b_7092_c6c1, + 0x00fd_36e3_678b_2424, + 0x1463_36d5_403d_3290, + 0x26cc_d620_a8dd_a6be, + ]), + pallas::Base::from_raw([ + 0x24d6_05f8_d93c_5bf6, + 0xbe50_1490_75ca_0894, + 0x70ea_d543_4183_1118, + 0x197e_4293_be61_e514, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9b50_3271_5c73_4456, + 0x2aff_79cf_b329_881f, + 0x20ad_27f7_8677_2305, + 0x1fb2_df9c_0084_6254, + ]), + pallas::Base::from_raw([ + 0xd726_501c_633c_d28f, + 0xee75_e9bd_87c7_1d7e, + 0xe5a0_ffd1_3d7a_a3dc, + 0x3501_2a18_8aab_9aed, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa5e3_3bb7_1725_a038, + 0x1a6f_44fa_d126_caec, + 0xc682_0c7b_8b6b_3c11, + 0x1e69_1a87_f24b_4f92, + ]), + pallas::Base::from_raw([ + 0xef4b_cf4f_cd8d_ce42, + 0x9ed4_61f9_5a17_0c8a, + 0x885b_451a_cc3e_7b33, + 0x17e0_4dc9_ffb0_95b4, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe134_a4de_1af0_e032, + 0x6055_abca_fd65_aa29, + 0x8109_bd0f_7563_29f6, + 0x3d92_2e0c_6fcd_9ba6, + ]), + pallas::Base::from_raw([ + 0xa51d_b5ff_c901_49ed, + 0x8886_223c_be3a_44da, + 0x9cf3_0d83_8bc7_63f1, + 0x23d6_dc61_5576_d09a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x79e2_836e_2b2a_990c, + 0x982d_0f03_434f_7eb9, + 0x8bd2_b47f_e76c_d5ab, + 0x1560_a3d5_02ce_9002, + ]), + pallas::Base::from_raw([ + 0x38ca_701d_79f6_f538, + 0x0e6a_15a2_265a_5ca7, + 0x6941_bbbd_ce8a_3cdb, + 0x22ed_bcbd_725d_71c2, + ]), + ), + ( + pallas::Base::from_raw([ + 0xeb62_42b4_9b2b_17cc, + 0xdc45_c0bf_cfb3_b708, + 0x1ae9_71ab_fd1a_3f3e, + 0x3850_926a_6116_f9d5, + ]), + pallas::Base::from_raw([ + 0xdb46_22f9_99d5_1a3a, + 0xddcd_2618_7398_1ef3, + 0x3bb5_8508_b3d1_3894, + 0x2001_6ee8_a7ee_6a67, + ]), + ), + ( + pallas::Base::from_raw([ + 0x40e7_fdbf_ba4d_a620, + 0xf056_b930_22e3_f9ce, + 0x02b8_ee49_e734_7325, + 0x1d6f_f561_e1bc_8de3, + ]), + pallas::Base::from_raw([ + 0xb84e_027a_8189_369d, + 0x22f2_75da_0766_031d, + 0x4fe5_1687_c170_f2ec, + 0x3b2a_c069_39e1_17d1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6207_1eeb_79ef_b20c, + 0x0e39_0101_9c6f_6b1a, + 0xad9a_b2d4_0863_083d, + 0x24bb_0da6_d98e_430c, + ]), + pallas::Base::from_raw([ + 0xdc8e_7552_5328_2784, + 0x97f0_d36b_389a_5e23, + 0xdf59_27ec_4ddf_9bb4, + 0x205f_0b78_f827_4d40, + ]), + ), + ( + pallas::Base::from_raw([ + 0x01cd_96f6_680d_43ce, + 0xead1_e981_b589_56ef, + 0x7c6c_198f_61d3_59b6, + 0x2f8e_652c_a28e_caab, + ]), + pallas::Base::from_raw([ + 0xf893_e5c8_f34a_0ff0, + 0xc70a_cb32_4520_0e27, + 0xf8c6_36ea_ae29_6dc6, + 0x2328_82c1_1d67_4489, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd48e_2c48_68be_2a7f, + 0xfd76_3124_e97c_3785, + 0x2494_17a0_2862_5a76, + 0x1adb_8e39_5814_6bb6, + ]), + pallas::Base::from_raw([ + 0xc5b2_a933_33d8_2271, + 0xc234_101f_038d_cceb, + 0x14f3_b690_9f60_efab, + 0x27ba_de97_ba05_a771, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3781_a814_a454_5341, + 0xa839_4f11_8db0_db87, + 0x5f89_9340_4408_04e2, + 0x32c2_7af2_a581_5018, + ]), + pallas::Base::from_raw([ + 0x5072_470a_53d8_4521, + 0xf3ca_f426_f878_4d19, + 0x59c7_fc07_0d03_a314, + 0x05c9_4781_d82a_2a15, + ]), + ), + ( + pallas::Base::from_raw([ + 0x80d1_8205_7021_0e5c, + 0xc3c1_9d74_bb05_c3fd, + 0xb4d7_0972_795d_dc32, + 0x1703_d858_90b0_1ff2, + ]), + pallas::Base::from_raw([ + 0x48a1_ba30_d95b_8f1c, + 0xd8f0_0c72_cf1d_2306, + 0x13ac_ce4c_3128_9a52, + 0x3e18_5f4b_f046_df12, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6fe5_75df_15be_5ace, + 0x2c1f_ad80_7746_0b25, + 0x1fb9_8d91_95ff_e580, + 0x25d2_f86b_25f6_0374, + ]), + pallas::Base::from_raw([ + 0xe56d_dd9b_adaa_a04d, + 0x443a_5452_103d_3d0c, + 0x268f_e354_2b01_c30f, + 0x360a_d0af_c036_b6ec, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd5be_3219_7358_df23, + 0x36cd_6c6c_65c0_9b4d, + 0x2886_ca9d_7d3a_f212, + 0x0ad0_c100_2c7f_f83f, + ]), + pallas::Base::from_raw([ + 0x965c_3680_c268_ea30, + 0x90c2_1c88_c78f_7abd, + 0xadbd_7dfd_d32e_b481, + 0x3e32_3f87_fe7e_9cc8, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa927_c4f7_bcbc_65c9, + 0xb16b_54b4_356c_da95, + 0x467d_21ce_c0a1_946a, + 0x3a94_7a7a_8ba8_fcf3, + ]), + pallas::Base::from_raw([ + 0x6ad5_c82f_2e6b_802b, + 0x2134_5b22_9afa_da8c, + 0xfc8f_90bb_fa9c_a1d5, + 0x1760_5d23_3ef4_1cfb, + ]), + ), + ( + pallas::Base::from_raw([ + 0x745b_4736_84f8_b6ff, + 0x2fa7_72e6_7d28_f226, + 0x7f5c_281c_0a58_3d12, + 0x2101_b974_3906_2e3a, + ]), + pallas::Base::from_raw([ + 0xe616_1109_cf4e_3a62, + 0x250e_baad_119d_1842, + 0xfbd4_356a_24bc_0f49, + 0x197f_86a7_7da6_fbe4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5078_ccf0_65b4_a022, + 0xcadd_f679_760c_cec1, + 0x57f9_1931_9550_a0f2, + 0x1999_0351_202f_4b81, + ]), + pallas::Base::from_raw([ + 0xba4c_1093_d57d_9f32, + 0xa80a_f145_0a0f_e90f, + 0x640a_4328_7313_6af7, + 0x1965_465f_d2eb_c138, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4d75_75df_d86d_6ffc, + 0x8c7b_72c0_1499_bab5, + 0x8354_9fe9_4386_40f6, + 0x01a5_1723_6d7d_92c7, + ]), + pallas::Base::from_raw([ + 0x850e_9adf_152d_9d66, + 0xb87b_f256_43ee_366e, + 0x39dd_1caf_086d_f29c, + 0x2b2e_cbfa_0c18_5cfa, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2ff4_d2c3_2c0c_fb5e, + 0x5b66_20fb_1e78_2e48, + 0x164e_048f_a6a6_a139, + 0x1177_de98_7417_d289, + ]), + pallas::Base::from_raw([ + 0x4473_f63c_8b63_96f8, + 0x8813_7018_e47c_7668, + 0x6e54_658a_9365_58d6, + 0x1565_f32e_c124_4d16, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd3f8_b046_ad64_54ca, + 0x0783_8e28_f074_59fb, + 0x2c26_6e07_3920_1035, + 0x1ade_ae43_961e_58b4, + ]), + pallas::Base::from_raw([ + 0xcbac_9225_97cc_0885, + 0x0297_41f2_ecb8_bb41, + 0x52f7_8881_bca8_0ffe, + 0x2626_3bd7_d9ea_3997, + ]), + ), + ( + pallas::Base::from_raw([ + 0x239b_b01c_10ba_15ee, + 0xe0de_2e2f_e883_21d9, + 0x249e_a03d_0db2_8458, + 0x02a1_1010_c364_a8ea, + ]), + pallas::Base::from_raw([ + 0xbd63_66bd_80d3_bf55, + 0xb968_3e0e_df85_70d0, + 0xf00a_642f_04ed_e36b, + 0x37ff_d622_b30b_a504, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc31a_07a7_d2f5_79be, + 0x93e0_4a16_dd86_733b, + 0xbec9_12d1_386f_ac07, + 0x1991_25d9_b76c_e739, + ]), + pallas::Base::from_raw([ + 0x976a_9a8c_0d17_a731, + 0xaf6f_c9c4_4420_7256, + 0x7e5b_0ef8_957e_bf8d, + 0x1afe_401a_7512_9532, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3be4_fa65_6613_b0fa, + 0x3a1f_be61_2e26_94e4, + 0x0091_a6fc_3b5e_dd57, + 0x39fd_5dda_cf6b_ead4, + ]), + pallas::Base::from_raw([ + 0xf61e_8e10_a02d_938d, + 0x303d_5afb_e1b1_5c24, + 0x4f3b_c283_f1c5_9415, + 0x19ea_636b_25ee_6d68, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2f71_d6ff_1108_c379, + 0x3338_649b_c8f8_2416, + 0x29cb_7fba_0970_1000, + 0x3206_be8d_debb_c71c, + ]), + pallas::Base::from_raw([ + 0x4797_990c_97ff_3dc0, + 0xb0f3_243c_bc1b_97bc, + 0x8017_28af_4cf3_8a59, + 0x3f56_eb32_53df_1196, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5b66_3474_6e09_2e1f, + 0xc2e2_87f5_f9ef_0a82, + 0x2442_95dd_210c_66ca, + 0x1d88_ea2d_bc4c_95ae, + ]), + pallas::Base::from_raw([ + 0xe19d_8eb8_f861_3356, + 0x4b53_10d6_7f10_e971, + 0x1ae6_394e_2861_00bf, + 0x0bc9_988e_3ea3_35e3, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc3be_adb7_c20e_f2de, + 0xe8ca_eb5c_5b32_9708, + 0x7c0b_c8f5_a91d_631b, + 0x357d_7b0d_bb96_a87f, + ]), + pallas::Base::from_raw([ + 0xad73_6f6d_e6f6_ed85, + 0xa176_6fea_071e_6309, + 0x73ff_1119_546e_4400, + 0x37b5_a0a3_1732_6eb9, + ]), + ), + ( + pallas::Base::from_raw([ + 0x71fb_d7b6_ec81_457b, + 0xa3db_880b_8ebd_e2f8, + 0x6fae_9d5e_77f7_7282, + 0x3922_33fe_aae5_f4d8, + ]), + pallas::Base::from_raw([ + 0x10db_938c_736f_6d80, + 0x343d_e723_56d1_9731, + 0x78b3_c37a_9d5d_a468, + 0x1617_300e_9160_0a2b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x424a_8877_5bac_1aa3, + 0xa8c8_36e8_7e8c_c0e5, + 0xc371_2374_03fe_9264, + 0x26f1_c98a_7a66_a30e, + ]), + pallas::Base::from_raw([ + 0x51a3_b36e_0db2_44d9, + 0x3eea_1d88_887d_e035, + 0x25cb_a902_d51f_f3da, + 0x36e7_0b90_4548_6468, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2728_fdb5_0b61_d1c1, + 0xcb71_a070_6ed1_1521, + 0x0509_29be_1d07_ab6f, + 0x21d2_7b62_2b7e_c84c, + ]), + pallas::Base::from_raw([ + 0xe13a_1cfe_7ee0_cf80, + 0xae30_8c36_a8e1_6416, + 0xed99_fb9e_9ce0_6d84, + 0x34a7_cab8_9803_1842, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfdbb_35ef_3aeb_c28f, + 0x834b_2806_1e44_0e87, + 0xbd65_25e3_6607_150d, + 0x183c_3d98_886a_d987, + ]), + pallas::Base::from_raw([ + 0xefb2_e06e_bf3d_5ba9, + 0x8d2e_e783_371a_b957, + 0xca32_80bd_b0c7_07f8, + 0x1de1_6b88_08f5_68f0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x786d_bdc0_924e_ec5b, + 0xe210_6c1c_189c_4147, + 0x7a4b_a8ab_bb17_0ad5, + 0x27d2_5cca_176a_5ec8, + ]), + pallas::Base::from_raw([ + 0xbccc_549c_6a1a_38e3, + 0xffe7_d383_bcd5_f8c1, + 0x1e45_0633_f24b_c870, + 0x346e_0709_ab95_21c1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5e73_f44f_3cca_5a3e, + 0xa965_c7a3_dd6c_7e35, + 0x3ec6_eee4_1149_89dc, + 0x1083_dfea_d4be_6b07, + ]), + pallas::Base::from_raw([ + 0x294d_4b25_9ddb_cf9c, + 0x7ace_974e_8045_61c6, + 0x507e_843f_4d68_c0cd, + 0x0a2d_8f78_fec0_e925, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf05e_c78b_2d6d_b205, + 0x74ed_d93b_8da6_3fc5, + 0x2d14_ee3d_c962_e1b5, + 0x3d96_e779_a206_75e2, + ]), + pallas::Base::from_raw([ + 0x74b0_20fc_2442_53df, + 0x5e67_731c_8a84_c1e5, + 0x76b9_572f_ee83_9cca, + 0x20ec_4fc7_e584_793d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x618d_3a50_bf2f_9c6c, + 0x2c9e_12ad_d09d_53c9, + 0xc40e_e12a_9971_a79c, + 0x2193_5e49_8cdb_4af2, + ]), + pallas::Base::from_raw([ + 0x526d_a471_4847_0d71, + 0x91b6_ada6_7e06_44fd, + 0x0ed3_0fc8_f6f1_8eb0, + 0x3339_a9d2_a7ce_d503, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5083_6ba9_ebe2_c4d2, + 0x1a97_6e48_cf4e_b89f, + 0x8704_3936_d76c_17ad, + 0x29af_2213_94b4_1751, + ]), + pallas::Base::from_raw([ + 0xf644_cd10_7f09_50c3, + 0x8884_71c3_01ea_94bc, + 0x8937_b2cf_3be6_5258, + 0x2647_20f5_e202_98cb, + ]), + ), + ( + pallas::Base::from_raw([ + 0x95c7_2972_4e5a_6bdd, + 0xcc55_a697_2f1e_750b, + 0xb9b7_90c3_f913_c375, + 0x1ddf_0c2a_bcd6_126c, + ]), + pallas::Base::from_raw([ + 0xb261_7fe7_e51f_68c2, + 0xaf6a_f700_27e2_8cd1, + 0x22e8_5bb5_4a2c_2e77, + 0x04af_6cfe_6abe_1c31, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8ea6_8c4e_c05f_e2f3, + 0x0155_d4ad_ba7b_31ef, + 0xa5ea_74df_1d84_ead9, + 0x2523_0890_0a38_e897, + ]), + pallas::Base::from_raw([ + 0xa64a_883e_1163_2151, + 0xd107_d743_2477_f8ff, + 0x0daf_49f8_7158_36fc, + 0x3e81_0b63_687a_6435, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb8e8_f1d3_9d83_1327, + 0xeefd_129a_2f23_fb16, + 0x5ce7_95e9_417c_4b58, + 0x3579_508c_8c75_a170, + ]), + pallas::Base::from_raw([ + 0xe1b9_574c_e4b2_5e71, + 0xa37c_b44d_e87e_b515, + 0xf492_24b9_9d55_8ab8, + 0x09c4_997f_9e5b_f623, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5010_3905_13ee_f907, + 0x4719_6aaa_d39a_a899, + 0x54de_851b_e709_1a02, + 0x2784_a297_4957_e70e, + ]), + pallas::Base::from_raw([ + 0x24ee_caef_9885_dfab, + 0xd189_3009_5f27_f678, + 0x1fd3_6af4_15d9_091f, + 0x084b_ec81_b437_8e44, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb19f_74b4_e493_4f13, + 0xe3a0_e81b_f084_e23c, + 0xafb2_ef1f_aea3_231b, + 0x0281_e846_acc0_87ee, + ]), + pallas::Base::from_raw([ + 0xd975_d00f_7393_542f, + 0xf8ae_f91e_ec9e_59d0, + 0xd285_8c11_b9c5_280c, + 0x3b00_5d52_5add_f883, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7b66_98ea_e221_f3ec, + 0xaa52_8c99_81e3_ef9d, + 0x8594_5e1a_bb14_af22, + 0x005a_5676_d642_01e0, + ]), + pallas::Base::from_raw([ + 0xf403_21aa_0a31_021b, + 0x4b9d_4172_43e9_01ce, + 0xdcef_30a1_fffd_6fe4, + 0x115c_5d90_17e7_5c6b, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb50b_a31a_29ee_735f, + 0xe087_4244_55be_9f05, + 0x4b3d_5356_7f34_bb41, + 0x3592_dfa2_ef73_8fd5, + ]), + pallas::Base::from_raw([ + 0x5eea_a2b4_5eaa_ac7f, + 0xba64_a158_5df4_da51, + 0x64f2_1e18_e2a1_a18b, + 0x2744_472c_60b3_f4f4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1ab7_7d4c_5a3a_d076, + 0xecef_342e_138f_55a9, + 0xe581_d4cf_1cbd_f93b, + 0x0ea1_f359_9f7d_3d5c, + ]), + pallas::Base::from_raw([ + 0xfc25_aea5_9fe3_c15f, + 0xc8eb_d71a_f44f_64e0, + 0xaa6b_2e54_38b5_a5ed, + 0x1cd7_d950_1f7e_fb20, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb92c_cc75_d2f6_7ef9, + 0x7c8c_7050_bb4b_c76c, + 0xae06_dd71_3e7c_b705, + 0x12e7_b66e_0a99_9a6f, + ]), + pallas::Base::from_raw([ + 0xce73_174c_46de_1a90, + 0xf233_8c5c_dce6_3624, + 0x7311_d0f1_6107_caba, + 0x07c5_3cd8_2f6e_3bc1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x679f_68cb_0663_880c, + 0x22eb_3ab9_b8f2_c733, + 0x8c9a_cbf2_ee17_7ad5, + 0x0215_4cc2_96b2_c905, + ]), + pallas::Base::from_raw([ + 0xf9f8_7298_bb53_de4c, + 0xdf36_8c20_1ec4_1690, + 0x4964_d682_c5ab_2e0e, + 0x1c40_fd23_1fb7_cd69, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2119_8bfc_8956_b75d, + 0x991d_620b_35a4_b565, + 0x71ea_c445_814e_d86d, + 0x28c7_9db6_4739_3f32, + ]), + pallas::Base::from_raw([ + 0x14ce_2060_ae17_f113, + 0xbe45_fdc5_1246_fc50, + 0x8e1c_4319_10ac_be73, + 0x1312_53c6_1783_912e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x026d_767e_1005_dd37, + 0x0759_23a2_1a64_428a, + 0xeeae_4507_b79d_6a0e, + 0x37d5_760d_8173_256b, + ]), + pallas::Base::from_raw([ + 0x5214_d99e_936f_56c0, + 0x26a0_b50e_6731_2f58, + 0x3dfa_7029_6ec1_d238, + 0x170c_0df2_5f28_1a90, + ]), + ), + ( + pallas::Base::from_raw([ + 0x397a_3bcb_ba4d_bd68, + 0xf577_2816_06d1_1af5, + 0x0814_30b5_220c_1e6e, + 0x04ee_cac4_45e3_73c9, + ]), + pallas::Base::from_raw([ + 0x3de6_619d_1a4f_65d1, + 0x680e_a92b_eb54_6b7a, + 0x01d4_31ba_19fc_51bf, + 0x070c_35d1_13a7_0730, + ]), + ), + ( + pallas::Base::from_raw([ + 0x327a_43da_e34b_c50b, + 0x9227_b80c_f9ef_6317, + 0x207b_c331_727a_65f5, + 0x2f5b_368e_9519_0487, + ]), + pallas::Base::from_raw([ + 0x59da_f9c7_044b_8bc1, + 0x1ab3_944a_4c78_5084, + 0xb716_6a13_ed9f_a130, + 0x2165_91e0_c300_e0b9, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5fa9_1288_f21f_b95f, + 0xbbd2_13a8_837b_4744, + 0x0a4a_3270_6938_5d8c, + 0x2db9_f193_d5c5_c9c1, + ]), + pallas::Base::from_raw([ + 0x7bbd_9898_1a1a_9bff, + 0x4ad2_4d15_ef54_060e, + 0x8824_c7a3_915e_035e, + 0x04ea_a03b_8152_e316, + ]), + ), + ( + pallas::Base::from_raw([ + 0x718e_5117_e211_d00c, + 0x6820_04a7_e806_2c61, + 0xe14d_1252_02ee_8806, + 0x35d6_bbce_17ad_d8be, + ]), + pallas::Base::from_raw([ + 0x6d50_9095_573e_853e, + 0x9be9_bb45_bd5d_ff54, + 0xd142_d615_c2b2_e50a, + 0x0d21_45dc_b049_831c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8fe4_32aa_3577_54fb, + 0x4766_8513_94d3_898b, + 0x776f_85d0_c89d_75c4, + 0x1144_091e_1eb0_8134, + ]), + pallas::Base::from_raw([ + 0x398f_9036_dcb4_9364, + 0xaaa7_1f19_dd46_1c3e, + 0xda24_0bf3_7b5d_659e, + 0x1c09_8238_7bf2_3870, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6570_c993_e75f_d9fd, + 0x5595_d3ee_e187_37f9, + 0x0629_32a7_cec7_4b58, + 0x0202_816c_bc84_b4fa, + ]), + pallas::Base::from_raw([ + 0x1df0_9e9c_5fb9_da74, + 0xb847_f271_42f4_50ee, + 0xf499_343b_da75_dca6, + 0x1781_323b_7425_6726, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5aff_0d37_7f88_51d2, + 0x7353_2959_e87d_bfb3, + 0x634c_476c_fbb7_1c96, + 0x11df_8818_cefd_7683, + ]), + pallas::Base::from_raw([ + 0x35ac_fac4_4577_543d, + 0x5c8f_db44_1658_61fc, + 0x4b19_0f39_cf3c_fb30, + 0x27dc_c868_42a3_d6b8, + ]), + ), + ( + pallas::Base::from_raw([ + 0x706f_36b5_bce1_6334, + 0xed63_bc8e_d67e_8f0d, + 0x5389_8211_d9a1_148b, + 0x084e_1a66_ed1d_9f9a, + ]), + pallas::Base::from_raw([ + 0xa9e0_e924_9383_6ee5, + 0x54c4_d664_8f95_0861, + 0xe2de_beff_8d23_4306, + 0x2038_1989_0b48_2b10, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3afe_f81c_330c_8ad7, + 0x8f31_b38b_3cb7_7f46, + 0x0be8_a9e3_b4bc_f237, + 0x09ce_729b_7917_ae58, + ]), + pallas::Base::from_raw([ + 0xd199_ef86_4732_8de0, + 0x7b03_6744_a3f5_8144, + 0xa545_b8ef_580a_7994, + 0x1074_1c11_9769_07ab, + ]), + ), + ( + pallas::Base::from_raw([ + 0xcc3d_d69a_86da_b9af, + 0xd4e7_5a09_d52b_0b3e, + 0xe8ad_091d_8b10_7f75, + 0x2b43_cc2e_f8d8_f01e, + ]), + pallas::Base::from_raw([ + 0xbd14_0c51_b03d_0f63, + 0x1006_923c_4bcf_285d, + 0x7221_7a99_120c_7911, + 0x012f_2b79_8f53_056f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0beb_949f_a313_486c, + 0x0d5c_e4d6_059d_abf8, + 0x4991_4b41_76f5_2b16, + 0x1a14_5dc7_97b5_eb4c, + ]), + pallas::Base::from_raw([ + 0x9b0a_fb2b_b5fb_dc02, + 0xb72e_d001_8b32_e67e, + 0x8dc1_f4ac_e7f6_40a1, + 0x0a03_99e6_e2c1_35e8, + ]), + ), + ( + pallas::Base::from_raw([ + 0x24e5_3cfc_0a85_3bd6, + 0xc691_a440_5284_538a, + 0x285f_8587_a2ca_3f67, + 0x0049_29ba_589a_b1ac, + ]), + pallas::Base::from_raw([ + 0x47d0_0f51_b59e_11b5, + 0x034c_e6c5_4f24_d018, + 0xcdfa_fdf5_9352_2277, + 0x1740_01a4_cd80_e459, + ]), + ), + ( + pallas::Base::from_raw([ + 0x05f2_fba6_88e4_64cd, + 0xcc07_4f11_8393_de4e, + 0x8cd3_84d1_2d3a_f616, + 0x082e_c561_65bd_c5db, + ]), + pallas::Base::from_raw([ + 0x0ff4_d463_cdf6_1438, + 0xb9d4_8513_1297_820b, + 0x577a_ab49_6011_7b6f, + 0x189f_caa5_6f47_de49, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6a66_f234_459d_9c05, + 0x52b3_b4e7_046a_0ca2, + 0x5f0c_f5c7_ac0b_8673, + 0x2d4d_378b_8ab7_0f4d, + ]), + pallas::Base::from_raw([ + 0x25e8_c3f3_463b_7de3, + 0x1db7_6493_b323_dd0c, + 0x96a6_cbc7_69f8_7c7f, + 0x0433_09e4_d57b_65e1, + ]), + ), + ( + pallas::Base::from_raw([ + 0xcf05_4d04_c520_e0a0, + 0xd540_edc7_1ca1_3af1, + 0x4993_044b_77dd_0400, + 0x1b0e_a9cd_b77e_ff99, + ]), + pallas::Base::from_raw([ + 0x6928_0ea8_321c_cd3b, + 0x72ab_b895_a1a8_f03d, + 0xf3d3_938f_2c87_7734, + 0x1270_1769_ae73_6bcf, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3e52_a2f0_ad5f_a12d, + 0x4418_5ba6_38bc_b064, + 0xef8b_d3ef_5bf1_6622, + 0x3ef5_5ec2_16b2_28bf, + ]), + pallas::Base::from_raw([ + 0xb82b_cb14_f2f4_9c05, + 0x1ecb_aee5_356e_0958, + 0xd850_681c_85cd_d83a, + 0x1328_7a30_a3cd_c18a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x76c9_1531_675b_e4d2, + 0xbff3_efc0_f5e7_a3d5, + 0xbf16_1feb_c704_2e5a, + 0x0033_a85c_5e3e_212d, + ]), + pallas::Base::from_raw([ + 0xb3fe_295f_735b_1607, + 0x4c07_169f_2f85_8811, + 0x53af_cd3d_e314_e24b, + 0x33dd_2f1a_9d46_44fc, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8e41_0696_97e7_8171, + 0x26ff_1208_32bf_c795, + 0x0bd7_0aa4_1196_808a, + 0x3ae0_f344_40d2_99f5, + ]), + pallas::Base::from_raw([ + 0x2474_b70b_2dad_669a, + 0x4603_66a7_e898_713f, + 0xb369_5b24_0bd9_aeba, + 0x3c74_bb6d_465e_e2ea, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6e89_42a3_91dd_c2ea, + 0xc38a_28e1_fcf3_fba9, + 0xeb31_2651_5a79_f178, + 0x341f_5e3d_2d3a_415b, + ]), + pallas::Base::from_raw([ + 0x7449_ce64_375e_4ca8, + 0x0602_745a_0fb3_6d33, + 0x2958_f891_d370_9055, + 0x09b2_2212_2dc8_e043, + ]), + ), + ( + pallas::Base::from_raw([ + 0x06b4_c662_7f86_1a17, + 0x6892_8a55_09f7_1a30, + 0x0f16_7e0c_216f_c514, + 0x3d3a_af95_d02a_5c26, + ]), + pallas::Base::from_raw([ + 0xf1df_9dff_4331_9bc6, + 0x3728_ab5b_c9a6_f755, + 0x8c73_0235_2ed7_3f95, + 0x3f65_6e0c_c790_2ba0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9f45_5904_d062_3f0a, + 0x7823_029e_72a6_7bb6, + 0x99fb_f22c_510a_2be4, + 0x2e85_df2d_4723_8ae7, + ]), + pallas::Base::from_raw([ + 0x327d_317b_e878_66e3, + 0xa45e_75ef_95df_3e94, + 0x7b1a_c76d_c402_0f49, + 0x3a60_fbcd_a37c_815e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x17df_a08e_e2a4_a5c2, + 0x0d37_fdbf_7f39_3cc8, + 0x5f65_0aee_69a3_3aab, + 0x21cc_cd3f_512f_6fef, + ]), + pallas::Base::from_raw([ + 0x5501_f47b_1934_3bbb, + 0xc629_25da_839b_5703, + 0xe869_5dff_4ac7_98db, + 0x2616_31c0_e0e8_b1b3, + ]), + ), + ( + pallas::Base::from_raw([ + 0x11b8_7af9_9e00_27df, + 0xafd7_bf48_d6fc_a4f1, + 0xd540_1aff_42f4_c233, + 0x158d_6245_8d40_37b2, + ]), + pallas::Base::from_raw([ + 0x8b6f_fd47_6243_eb36, + 0x6c6d_2aa6_425d_192e, + 0x9148_af15_de60_624c, + 0x3e87_9938_3886_00a0, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfe07_2c77_e4e1_e7c2, + 0x93d8_3c8c_e662_68de, + 0x619f_5102_29bc_739b, + 0x3a66_5f24_c02b_af59, + ]), + pallas::Base::from_raw([ + 0xc557_f198_58c8_a876, + 0x7731_9895_7381_1af9, + 0x6b7d_918d_995d_93e6, + 0x307f_067d_c98e_9636, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa436_5475_6a68_c675, + 0x88f2_f299_794f_3553, + 0x7f11_45b8_74ae_62c7, + 0x0dee_f81a_0f27_b481, + ]), + pallas::Base::from_raw([ + 0x993e_47cf_7f5e_cf8c, + 0xe810_5d2c_1b5e_0447, + 0xcaad_b923_1a1d_ac82, + 0x147a_8969_4b2b_e230, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4723_e366_ae1e_6184, + 0xee04_327f_d719_ea9b, + 0x24db_be73_e152_f27d, + 0x2100_9976_6171_438d, + ]), + pallas::Base::from_raw([ + 0xefe4_8e4e_9b94_1267, + 0xacb1_8986_fab0_a084, + 0x7b10_5663_ae11_ea5e, + 0x274f_221d_771c_400c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5cb8_1790_8657_3d1e, + 0xdd05_63de_2edd_ddc4, + 0x78ba_e557_b388_ffeb, + 0x1a84_b9f1_4ad0_365d, + ]), + pallas::Base::from_raw([ + 0xd1da_bfb8_e966_236a, + 0x57d3_fa91_cbd6_0cc8, + 0xdc92_8096_0248_333a, + 0x376c_21b4_5247_997f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x13f0_c07a_dc21_f7b1, + 0x2a5c_69e9_05fe_854b, + 0x0573_d14d_1d7d_da4f, + 0x193e_e5da_b674_a49d, + ]), + pallas::Base::from_raw([ + 0x7fa1_74ca_714a_4b90, + 0xfe21_aa2e_52a5_a793, + 0x69dc_b536_0f5b_c791, + 0x1b16_b237_03d7_e26c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xeaf6_6865_27d4_b8e6, + 0x1aa0_aab4_2666_1fcf, + 0x1aa7_9baf_a2a0_5e73, + 0x3589_722c_8ec2_4085, + ]), + pallas::Base::from_raw([ + 0x4142_0491_d747_fb8f, + 0xc20b_63f7_728a_bf57, + 0xf61d_1974_ab2a_d20b, + 0x3a16_a2b3_28cd_a978, + ]), + ), + ( + pallas::Base::from_raw([ + 0x479a_9ac8_e884_e011, + 0x504e_93b0_cd03_ae26, + 0xc711_9e1f_1319_e977, + 0x3793_11e7_db77_15e7, + ]), + pallas::Base::from_raw([ + 0xf05b_ef48_6578_ec58, + 0x05b9_b52f_deca_50dd, + 0x6c92_c4fe_9bf6_95a2, + 0x2ae6_65e4_8ad7_556d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9418_f007_3a11_5039, + 0x84ba_f7e6_293c_aa1d, + 0x4be5_7bb5_6c37_e8e8, + 0x2e01_0c6c_ba9d_2f9e, + ]), + pallas::Base::from_raw([ + 0xb30c_63c5_793a_1373, + 0xc2a7_a868_e4da_a692, + 0x789f_e67b_b71f_b6dd, + 0x2812_c192_3126_8e2a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8a3e_8b6d_d6fb_bcb6, + 0xfab6_9586_3583_cbe2, + 0x3d2e_3c47_a4bf_b5a1, + 0x1715_0e76_b351_e99d, + ]), + pallas::Base::from_raw([ + 0x2ea8_22fa_65a4_7757, + 0x1f2f_e64a_c655_741c, + 0xd1aa_205a_f5ac_9570, + 0x27cf_dd88_0836_c8e9, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7d30_5f29_822a_7979, + 0x8082_4fc1_a364_96e2, + 0xf73d_1cf4_c77a_0f4d, + 0x1c9c_be3c_6a19_26be, + ]), + pallas::Base::from_raw([ + 0xaa17_af8c_1c76_1823, + 0x09d0_a290_baf6_e926, + 0xf61c_06f8_b5d6_c22b, + 0x172f_1a6d_fc32_d9cd, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5ad3_8743_b520_2fb9, + 0x083a_2695_50be_40d2, + 0xb911_1803_3f67_cde3, + 0x2846_f462_30cf_b810, + ]), + pallas::Base::from_raw([ + 0xe223_b5e7_e937_c54c, + 0xf2c5_77fd_b08b_3f21, + 0xb6c1_652b_7505_a7d3, + 0x2c8f_a498_2e60_e130, + ]), + ), + ( + pallas::Base::from_raw([ + 0x56bb_e185_4175_95f3, + 0xecc1_0053_9407_c202, + 0x90a5_c415_7630_fd2e, + 0x1f33_8ef2_6410_ce20, + ]), + pallas::Base::from_raw([ + 0x1a3b_5459_1a56_b7d4, + 0x2711_c42b_cfc8_bd25, + 0xb313_42d6_a462_1965, + 0x1e04_5727_6c6a_be28, + ]), + ), + ( + pallas::Base::from_raw([ + 0x33f0_122d_1fc2_e9d6, + 0xbbaf_07e2_6d0d_b5f4, + 0xada4_e1db_90f2_0928, + 0x320d_78d3_7e1f_9b51, + ]), + pallas::Base::from_raw([ + 0x6d4f_fe2f_b80e_79f9, + 0x1697_46f5_0160_9c4d, + 0x9c94_aa69_371f_e117, + 0x08ff_a47d_8297_7661, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0eed_415f_7d55_1715, + 0xf420_3b98_aa11_457b, + 0x9053_f3d5_4476_0afb, + 0x02c2_03ba_dea2_76a1, + ]), + pallas::Base::from_raw([ + 0x55d7_024a_e842_a5af, + 0xb58c_03ba_9deb_cd2f, + 0x0c6b_c8b6_ceaa_42b5, + 0x3b4e_7fab_6e35_5c3e, + ]), + ), + ( + pallas::Base::from_raw([ + 0xea9d_81f8_6a0d_dbe9, + 0xce0f_2809_3257_e6f1, + 0x0039_3b64_fd52_ff04, + 0x2942_5821_8d23_ef24, + ]), + pallas::Base::from_raw([ + 0xbb7f_710f_9517_dc5b, + 0xb9db_1453_43ab_48ca, + 0x94cd_60e3_1fe8_3451, + 0x2be1_7ce0_f798_be8b, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd971_ee8b_071f_3aa2, + 0x6542_046f_2f21_6648, + 0x7011_a0b4_c4c5_2feb, + 0x176a_75e4_305d_c7cb, + ]), + pallas::Base::from_raw([ + 0xb8fe_680b_ce75_ae24, + 0x8549_cbf6_c3d2_7b44, + 0xa8ee_92a7_6a40_c587, + 0x09e6_b0ba_013d_f131, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb80b_9e97_64fd_da65, + 0xb0c0_b411_0bc5_8a2d, + 0xac1b_b1a0_c6e1_5c55, + 0x36a4_4d41_d37a_70e8, + ]), + pallas::Base::from_raw([ + 0x6a77_efb2_81aa_e72f, + 0xc578_8686_9b58_faa7, + 0xab74_33bd_7e33_68dc, + 0x1792_ad2e_7f54_a000, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8792_f6ef_0395_a9a7, + 0xd375_7f3d_66c2_7229, + 0x6949_e876_d5c7_9010, + 0x176c_f116_0c7f_8ba7, + ]), + pallas::Base::from_raw([ + 0xf0f5_d8f0_a98d_c2bf, + 0x4118_e09e_ac6c_d9e9, + 0x4ed1_51e4_5f97_f657, + 0x13e7_3756_26b8_54e4, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbccd_3257_f3c2_06d0, + 0x3139_8eae_f991_a342, + 0x5a2d_4414_2fa3_5a6c, + 0x3cef_bcc7_13f6_3152, + ]), + pallas::Base::from_raw([ + 0xc806_3b97_017e_00ab, + 0x5e20_fdd5_44b5_acc3, + 0x6cce_c0f6_ef8f_18cd, + 0x14ee_4b77_ec6a_bfeb, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc096_2238_58db_eab3, + 0x5c0e_61b0_db10_ef29, + 0x6c9a_f1ed_8acb_1da4, + 0x3f76_fe73_c425_d939, + ]), + pallas::Base::from_raw([ + 0xdad4_82e8_b280_556f, + 0xc3f4_06de_7fdc_c54e, + 0x37ca_88b8_1d36_54b9, + 0x0261_f776_7244_a4d3, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7e1c_ca9a_a12a_8efe, + 0x948b_62a8_c522_ec9b, + 0x9dbb_049b_218c_3df8, + 0x1efd_3a6c_d255_af78, + ]), + pallas::Base::from_raw([ + 0xa4b5_0f75_4d65_0a1f, + 0x25b7_6c86_a75e_2c13, + 0xfd6c_6887_ef4d_1d53, + 0x0a59_0bd3_71bc_51f0, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdcee_cea8_b8d0_d650, + 0x4f2d_8b8d_6a29_f2d4, + 0x43d0_528f_eaf9_11eb, + 0x2404_4c52_546c_2b52, + ]), + pallas::Base::from_raw([ + 0x96c3_facd_1cf0_d9f6, + 0xca63_6712_9f50_6ad8, + 0x2f85_d005_fa5f_9b5b, + 0x069e_153f_9896_c130, + ]), + ), + ( + pallas::Base::from_raw([ + 0x686e_8212_4c61_d957, + 0xc7aa_9bde_a486_649e, + 0xd3c2_91e0_d4a3_7a8d, + 0x0107_e2e3_2c5f_28f3, + ]), + pallas::Base::from_raw([ + 0xab0b_45df_c3d7_591b, + 0xf7b5_854a_710b_f63d, + 0x544d_c8f1_2e0c_26f3, + 0x2d9b_95a7_30cc_ea85, + ]), + ), + ( + pallas::Base::from_raw([ + 0x513d_33c2_95ce_56f0, + 0xbd2b_2fdd_7fd7_7031, + 0x5d4f_47fe_09e2_e031, + 0x2f94_4842_7b80_54b4, + ]), + pallas::Base::from_raw([ + 0xdf6f_d665_d36a_7467, + 0x5dc9_191f_2fea_6b07, + 0xeca2_ef63_7140_6422, + 0x0171_50c0_96da_f72c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x367a_1d4f_87ce_2858, + 0x9d54_5c07_8137_cd19, + 0xdf1a_2072_738f_3bfd, + 0x114e_4120_52f0_25d4, + ]), + pallas::Base::from_raw([ + 0x18fd_28df_bf66_d5ea, + 0x8bbd_77f0_f949_9a75, + 0x6362_4f99_f3e8_453b, + 0x2780_edfe_8a38_8449, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7402_3082_0ea9_5ad4, + 0x63b2_7be5_3550_2ffd, + 0x91dc_cb3e_fbe2_60b6, + 0x25e2_bfb1_0f32_3477, + ]), + pallas::Base::from_raw([ + 0xadc0_3a71_39b4_094f, + 0xedf8_c60f_b4d1_c2bb, + 0x6911_0947_b702_9fcb, + 0x01f4_2fe0_a850_8065, + ]), + ), + ( + pallas::Base::from_raw([ + 0xff2d_cd62_e596_b4d0, + 0xc7d3_1ad4_5af5_0fc8, + 0xa0bf_73fa_b1a6_63fd, + 0x01c5_4695_7e3e_a005, + ]), + pallas::Base::from_raw([ + 0xefeb_aacf_938f_6779, + 0x6986_3f6e_07de_7291, + 0xc06e_8e67_ade6_e796, + 0x164b_abe7_7966_9ae9, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1c33_6e51_b527_8fe0, + 0x830f_0267_88b0_060b, + 0xe589_fe8a_6136_cdf4, + 0x04e2_bb36_ec30_315b, + ]), + pallas::Base::from_raw([ + 0xcde6_5cb5_9334_5bfb, + 0x10a1_fb83_4287_f5f4, + 0x37c7_a20c_4619_28c1, + 0x2adc_5a85_4126_6b62, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3932_ba35_42e2_61ba, + 0x3a1e_399e_82de_4dcd, + 0x3a5f_6dff_03ac_3c73, + 0x2cbd_15f4_53d9_16a9, + ]), + pallas::Base::from_raw([ + 0x2425_a35f_a98d_8937, + 0x0a66_a2d3_5d9f_588c, + 0xf832_8c5a_3951_852b, + 0x0848_6f66_b4a2_dd55, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa4c3_8380_3a4d_9564, + 0x109a_bd0c_31eb_54f2, + 0xa83d_57b4_b012_a211, + 0x3949_c80c_4cfd_3f85, + ]), + pallas::Base::from_raw([ + 0xfdcd_3f0d_1ef1_9e40, + 0x47f8_5477_4692_b481, + 0x60fc_0e1d_252f_8607, + 0x1547_2a08_45c3_dc19, + ]), + ), + ( + pallas::Base::from_raw([ + 0x042e_596d_48e5_7baf, + 0xd9d7_3041_cb15_f628, + 0x65ec_a378_4e88_0916, + 0x059f_7f0d_27e9_01da, + ]), + pallas::Base::from_raw([ + 0xd37d_77d4_4cf8_b443, + 0xbfa1_72a6_7d41_76ec, + 0xd8c8_b761_2524_7b4e, + 0x079b_6488_5c2d_24f5, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd29f_0703_a4b5_6dbe, + 0x75e2_6aa8_956b_46fc, + 0x84a4_49e7_02e2_2806, + 0x38bb_f1d6_ceac_121c, + ]), + pallas::Base::from_raw([ + 0xc772_1a73_3066_b6d1, + 0x1737_55f3_b53b_ec06, + 0x3c2a_bd62_3e1e_2c21, + 0x088d_d43b_6366_0116, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa116_6f77_055a_8bb6, + 0x6385_2d8c_5889_8da4, + 0x34b4_f4cd_754b_7717, + 0x1526_aa2d_f3e7_515f, + ]), + pallas::Base::from_raw([ + 0x4085_c51a_2657_e4b8, + 0x9679_5ea4_4624_8645, + 0xc804_03a1_e3d6_3998, + 0x199c_c2b0_2132_4b08, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa52a_db5d_d168_77c2, + 0x6b03_f922_0b52_af2e, + 0x7fc8_01e8_b4ef_22bd, + 0x12e5_67c2_ec81_2f1d, + ]), + pallas::Base::from_raw([ + 0x0309_4014_a4e8_c20b, + 0xe7bf_0cd6_a200_a8cc, + 0xc608_65fe_fb5b_94ea, + 0x0993_1445_3f68_41ac, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbfd7_fd57_8b6f_75d8, + 0xf07e_f86e_d33d_296a, + 0x5054_c978_d48e_ee12, + 0x0b3d_76cb_4f6f_c43a, + ]), + pallas::Base::from_raw([ + 0xbef7_625a_4eed_90eb, + 0x1f8d_bade_22fe_73f7, + 0xc960_ab94_3ae5_1bfe, + 0x0858_b2d8_865c_973e, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe2e4_3c37_843a_04dd, + 0x28e9_8a1e_6966_e7c0, + 0x6ac4_304f_d629_f5a1, + 0x2dfc_f1e1_ec0c_32fb, + ]), + pallas::Base::from_raw([ + 0xbd67_15f8_99d0_7f6e, + 0xcb85_5a99_89cc_47ff, + 0xdd2d_2426_6872_35c4, + 0x1968_0ec6_a3a3_fcb8, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa5ab_b96e_1f94_1076, + 0x51b5_b451_b97a_0268, + 0xd7e0_dc4f_58ed_e375, + 0x2bee_18a5_f76d_7e63, + ]), + pallas::Base::from_raw([ + 0xed7d_80be_441d_d7b4, + 0xa4c2_d30a_3c19_cf3b, + 0xac4f_739b_2e1f_91ed, + 0x3562_48da_e389_3d81, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7c79_fc8c_4dcb_a1c0, + 0x8162_1c35_b958_c1a1, + 0x0a41_6e19_3265_f04d, + 0x009e_4848_45e1_7f41, + ]), + pallas::Base::from_raw([ + 0x712d_9394_ab35_ebd1, + 0x6312_e11e_d8e1_49fb, + 0x84af_9933_77e5_1ade, + 0x3f42_3c6c_cd36_1dc9, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf371_6e56_7f23_b1e1, + 0x538f_3c4d_6a0d_4994, + 0xaf2a_6e2a_b089_32ec, + 0x2f3e_49c7_37a2_98af, + ]), + pallas::Base::from_raw([ + 0xc5eb_afba_dcc3_18cc, + 0x64f2_6b4c_6339_fd3d, + 0x5ef0_9554_fa27_b6cc, + 0x21dd_5ec1_51e4_e135, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc382_c4ea_f9e5_4b3c, + 0x47ea_f27a_b13e_c143, + 0x2a6c_3775_caaf_400f, + 0x08d1_cba6_65ee_26df, + ]), + pallas::Base::from_raw([ + 0xfdf2_f10d_f382_cd25, + 0x812b_4d72_4045_d2f3, + 0xc1c3_32e2_6422_084b, + 0x26a4_9ea4_f176_8cd3, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe132_91ea_94e5_9353, + 0x7d69_5680_fc7b_82e5, + 0xada6_bc9f_fcd7_fe02, + 0x2ad4_e94f_ac41_1335, + ]), + pallas::Base::from_raw([ + 0xf166_8557_d845_98ef, + 0xb6f8_78df_9c3a_5c6a, + 0x0993_e2b9_0073_8ccf, + 0x09b0_6867_4885_ae31, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4161_dea7_2c51_a176, + 0x147b_e228_82c1_8e17, + 0xf47c_b827_d4af_8bcd, + 0x30a0_5ec4_90a9_aa33, + ]), + pallas::Base::from_raw([ + 0x3209_f6db_3ea8_a841, + 0x1d9c_06c6_ea2d_6905, + 0x6afe_34c8_ac39_628f, + 0x35f8_03ed_ac2f_acde, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbe8d_dfdb_83eb_b5ed, + 0x185a_8a55_57ab_07c3, + 0x109c_99b1_64d5_ebb0, + 0x0243_40be_c0e7_c4f7, + ]), + pallas::Base::from_raw([ + 0x6b51_37b6_6b10_0439, + 0x6f2e_00fe_1a1d_1ecd, + 0xe2af_ff62_7ab4_5f97, + 0x31d1_3ebc_0bad_4368, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe52c_00c2_77e0_805b, + 0x289a_6cfc_00e4_613e, + 0xba6d_64e6_b80e_af43, + 0x028d_bb3a_a2eb_10e1, + ]), + pallas::Base::from_raw([ + 0xb5e0_5d84_b2c2_af71, + 0x8ca8_19c8_ed2d_d4b4, + 0xbf23_1285_894e_181d, + 0x13dd_55ec_af42_2820, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7cd5_9e64_f479_5578, + 0xb2e4_8301_4dad_1d12, + 0x69bc_3333_f3ae_7a7b, + 0x1d8b_bee3_8e56_ce73, + ]), + pallas::Base::from_raw([ + 0x62ed_e032_93f3_6d8e, + 0xe0ff_b670_42fd_4d90, + 0x28e5_b97c_3c9d_1811, + 0x1a6e_4ed7_56c2_b230, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0828_358e_7ca1_8645, + 0x4e14_04d0_c831_fc41, + 0xa2d9_42ef_1ded_82b9, + 0x2fb8_9978_33e6_41c9, + ]), + pallas::Base::from_raw([ + 0x1fa1_883b_1222_e351, + 0x8a92_e672_a65c_2998, + 0x2c95_70ae_0ce3_66ac, + 0x2182_c430_2f94_8f79, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7137_1420_909c_4455, + 0x79a8_72fa_e5ff_4895, + 0x64c5_8a11_259d_2d60, + 0x1b4a_243f_2c40_9d0b, + ]), + pallas::Base::from_raw([ + 0x24b7_097b_3e5c_77cf, + 0x103c_bbe4_8a2c_d3c1, + 0xfb53_ff35_bd2b_0ac1, + 0x3520_075c_da2f_93c3, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf70f_d61e_7311_192b, + 0x663f_6658_1f78_66bb, + 0xb223_dcf3_6099_76c2, + 0x22c3_ced3_c628_b166, + ]), + pallas::Base::from_raw([ + 0xc390_e15b_4656_71ea, + 0x839c_7a74_12c7_d85b, + 0x9eab_c165_478d_f2c2, + 0x0426_245e_279f_b951, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf32e_587b_08ac_a16a, + 0xef0f_0954_a0d1_18d6, + 0x663a_0d0e_0f93_711b, + 0x3ba9_d989_09c2_1586, + ]), + pallas::Base::from_raw([ + 0x67b0_fe69_78fa_f39b, + 0x4365_7c97_5d17_8817, + 0x3634_3954_9a26_d391, + 0x2912_61a5_3c05_d61d, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb213_f8dc_16d9_4935, + 0x6c08_5bb9_8b3f_b3d8, + 0x3c8e_7712_a323_61cb, + 0x1186_5328_9691_8dbf, + ]), + pallas::Base::from_raw([ + 0xdd0f_6a6f_c395_0461, + 0x402e_cdec_f30a_8b9a, + 0x63c0_5442_4a3d_9c82, + 0x1e3b_87b0_e601_4734, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd2bf_fe22_3f25_f8aa, + 0x3d13_dad2_88e3_899a, + 0xe40c_042b_1047_1796, + 0x20ef_355f_b2b9_b7a4, + ]), + pallas::Base::from_raw([ + 0x2584_fc90_05a4_4eb7, + 0xc73d_0faa_e860_285e, + 0xa3a9_e4b5_9dce_93f6, + 0x1002_4764_5a67_73fa, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb44d_d4ef_f05f_ee9d, + 0xd311_e35e_57c3_af04, + 0x6afa_e748_df35_ace0, + 0x2852_47bf_ed5c_af05, + ]), + pallas::Base::from_raw([ + 0x67e1_bbc4_0409_5959, + 0x1064_023c_f1ce_c606, + 0x12ea_7b93_7755_8d2c, + 0x14e6_9d5a_a6ca_0da4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3dad_64a1_41cc_bf58, + 0xcd45_cc95_e1bb_6d96, + 0x4f71_1071_c168_9f44, + 0x0f42_27ce_35ad_602c, + ]), + pallas::Base::from_raw([ + 0x54ef_0524_e72a_f65c, + 0x7baf_4d83_432e_dfbc, + 0x3479_4bfb_b7eb_9917, + 0x1bad_5ff2_bc75_3f5e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x17df_6323_6eb4_e4a0, + 0xec9a_1e58_c280_b973, + 0x5a8d_46ad_12ab_e217, + 0x0222_2ecc_4b1f_79a4, + ]), + pallas::Base::from_raw([ + 0x8dc6_c26b_627e_cfc3, + 0xcad4_3492_8c57_feac, + 0x4c7e_c57f_f201_761e, + 0x1a7a_19c9_ebec_a09f, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfbdd_d33a_def4_3e8f, + 0xd650_1c5d_0b0f_e30a, + 0x6fd5_fd34_c4c8_ef1b, + 0x337e_4fbe_c8b3_3a4e, + ]), + pallas::Base::from_raw([ + 0xfe9a_5311_7c98_6fb1, + 0x90fe_d9ee_d112_899c, + 0xa935_19db_c171_8814, + 0x1a32_d859_2e42_f132, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbd1f_570e_d595_ab4c, + 0x2e11_069e_dc17_bceb, + 0x5c63_4ef5_02d2_93e8, + 0x3be4_949d_9d7b_53c5, + ]), + pallas::Base::from_raw([ + 0xf087_456a_6cc0_0eac, + 0xb68e_6724_73b7_edeb, + 0x9548_e552_8391_9a99, + 0x0107_0f1f_04ae_b5d6, + ]), + ), + ( + pallas::Base::from_raw([ + 0x493a_9aea_3262_6e17, + 0x7b8e_4c15_74b2_e969, + 0x8053_61b6_b5f2_ca98, + 0x39d7_c501_1792_79a3, + ]), + pallas::Base::from_raw([ + 0x53d3_5c19_43dc_f2e9, + 0xba13_604f_47bf_3260, + 0x8324_b8e7_4d46_596a, + 0x1882_9bdb_b7d8_45eb, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0d27_0a51_f183_e9e8, + 0xd276_a7f9_3066_8f03, + 0xd48d_97b5_9e5d_88e9, + 0x0e8a_ee90_45a1_44e1, + ]), + pallas::Base::from_raw([ + 0x2818_4a9f_3fa0_bb40, + 0x710f_acd0_0450_af64, + 0x900f_4bdf_1974_6e9f, + 0x27a4_2da3_2115_76d4, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb689_cf40_37dc_668d, + 0x9377_0a01_142d_c428, + 0x3134_eac4_c878_08fd, + 0x3afc_9061_8065_68ed, + ]), + pallas::Base::from_raw([ + 0x60c7_7d3d_3fad_57b6, + 0x59f4_9ebc_34aa_59b3, + 0x9d63_d009_0c0a_7384, + 0x2559_c358_bc86_61ce, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5c09_5692_145c_79b4, + 0xda8b_bb5f_49bd_b0b4, + 0x4135_2c94_9dbf_9dd7, + 0x0453_dc79_9e74_8178, + ]), + pallas::Base::from_raw([ + 0xda01_9f05_07c7_9539, + 0xe639_45d5_2aef_6d32, + 0x4119_4ec6_4943_39e2, + 0x2ea2_ef55_be81_fd44, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf49f_4fa9_04be_f892, + 0x62dc_978b_3700_3ffb, + 0x6702_e625_4263_44a6, + 0x3b3b_e12c_dc8e_7efc, + ]), + pallas::Base::from_raw([ + 0x10bd_e67f_93e4_1090, + 0x8ccc_a11e_ecb0_991e, + 0x50dd_72f0_eb8a_e400, + 0x3f3c_db56_a787_8aad, + ]), + ), + ( + pallas::Base::from_raw([ + 0xff0a_53a6_55f4_b430, + 0x0fbe_41a2_841c_8e6b, + 0xe44a_6249_5534_24da, + 0x3d16_d183_cfd9_e4f3, + ]), + pallas::Base::from_raw([ + 0xf173_7a47_4a12_010d, + 0x3386_2a20_5ae3_010c, + 0x9475_ac8c_7cc9_6eb2, + 0x2d5e_edba_92e3_787e, + ]), + ), + ( + pallas::Base::from_raw([ + 0xec32_bdcc_3ab6_8bc8, + 0x5ef1_c68d_8205_6ba8, + 0x2a79_c6b1_760f_230a, + 0x0269_c007_4842_ac02, + ]), + pallas::Base::from_raw([ + 0xdea5_6a73_acf7_0dd7, + 0xefae_24fd_d605_d32c, + 0x7644_0179_5a72_8a8f, + 0x29bf_5935_ee3b_cf0d, + ]), + ), + ( + pallas::Base::from_raw([ + 0xafec_fca4_d928_57e0, + 0xa2ef_4a25_2d8d_462a, + 0x3f9c_1cd2_7b1f_8c08, + 0x3f06_e865_ae86_e74e, + ]), + pallas::Base::from_raw([ + 0x2bca_60c6_542a_5cce, + 0x2017_a0ca_ddf9_1f45, + 0x9ee1_6a4f_f103_b9cf, + 0x3795_5bec_4580_dd2a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8691_e4b4_c1cf_edc6, + 0xb4f6_468c_8120_fd13, + 0x05b1_7517_b826_dc89, + 0x0c61_3568_3cef_710c, + ]), + pallas::Base::from_raw([ + 0xb119_025f_6be2_1c59, + 0xe60e_c8d4_cc1a_34da, + 0x61ba_a8f8_bd7a_34ff, + 0x1c27_2a98_a433_d3ba, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc775_8f6e_229b_53c4, + 0x8187_14e3_1fd4_834f, + 0x9b72_1c14_5292_b1f4, + 0x2ad0_635e_fd85_69a0, + ]), + pallas::Base::from_raw([ + 0x359a_b6e7_a119_d83e, + 0x598f_fc63_1f64_1ef0, + 0x444e_4b2b_7792_20fd, + 0x03df_5258_e7c9_9279, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9134_5261_be30_5e2e, + 0xe6d4_a11d_a37a_2874, + 0xba1c_281f_b344_e7ec, + 0x160d_d9d7_58da_88b5, + ]), + pallas::Base::from_raw([ + 0xdade_8f8f_bbfe_566a, + 0x2000_6526_5423_f35f, + 0x0f19_01f2_71a6_22a5, + 0x32a0_503a_fecb_2320, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0683_5997_1217_55ba, + 0x2a57_c9d0_8730_2e1b, + 0x8f07_c57b_3fdc_d076, + 0x0849_b2af_ec11_13b5, + ]), + pallas::Base::from_raw([ + 0x13d6_5361_9c27_882d, + 0x84d9_028a_a60f_5d93, + 0xf1ee_437b_33fa_62f2, + 0x0c6f_0de4_3fdb_971d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6e23_4404_9793_ef67, + 0x7cd5_90e5_4c92_3c41, + 0x27dc_0ed1_1611_45fe, + 0x1ce0_4ef4_cfb4_c9b1, + ]), + pallas::Base::from_raw([ + 0xfd5b_9f8f_ddcf_d367, + 0x6a5d_c6bd_9379_c9c2, + 0xc297_9ece_527b_63b8, + 0x323d_873d_580f_2a72, + ]), + ), + ( + pallas::Base::from_raw([ + 0x44f9_82c7_c31d_7a89, + 0x9846_e859_178d_60b5, + 0xa50a_5197_85d6_70ac, + 0x0777_8711_9138_56b7, + ]), + pallas::Base::from_raw([ + 0x8fa1_5093_7040_054c, + 0xe0af_8de4_84a8_b394, + 0x6ed9_fa7f_468f_261d, + 0x26e0_8f4c_2f70_048a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1610_366a_bf41_9b15, + 0x5495_33a5_f504_49cf, + 0xff03_7486_34b9_7494, + 0x2ccd_de83_e509_6a23, + ]), + pallas::Base::from_raw([ + 0x9724_c725_1ffe_d7b2, + 0x014a_a2d1_b3b7_bba7, + 0xed28_d6ab_01bd_03b9, + 0x04d8_9a55_3cc8_37fd, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5053_2dc7_a8cb_ce3d, + 0x9332_681a_8979_6c93, + 0x1b1c_f355_e7aa_959f, + 0x0b04_c461_9897_ec8b, + ]), + pallas::Base::from_raw([ + 0x91c0_9815_eac2_6003, + 0xdf44_c95a_8b52_8f05, + 0xfb9d_53b9_32a2_96c5, + 0x05f7_a7c0_514d_9481, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe9e8_2ebd_f36a_c3aa, + 0xd63c_3aba_dde3_84c6, + 0xa065_4794_bbd5_57d2, + 0x2af1_74e3_b8eb_6bb4, + ]), + pallas::Base::from_raw([ + 0xc1b7_ec28_b61c_bc1c, + 0xa6df_1fd9_3b44_ada5, + 0x41b4_1fbd_54b7_40e1, + 0x07b6_b7b5_6200_d6ae, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf809_14b1_c60c_7c25, + 0xc96e_9d7a_d9d2_0bbb, + 0x9bf1_14b6_065a_6090, + 0x07f3_c075_3999_631c, + ]), + pallas::Base::from_raw([ + 0x6d84_a970_41fb_fe71, + 0x7367_f8cc_4832_6aed, + 0x9fa4_b09a_b998_980d, + 0x10b5_51f4_4081_9d9b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x13b2_211f_8488_06eb, + 0x8761_a8e5_68bd_a93f, + 0xfe79_18b0_b2c7_bedb, + 0x0eb8_9428_e7fa_66e6, + ]), + pallas::Base::from_raw([ + 0xa4ae_5cd1_74dd_020f, + 0x0a36_7483_d7bc_ebd5, + 0x02ee_e479_a589_b49f, + 0x381f_443f_f703_3d02, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbfe9_7299_65ea_ab6c, + 0x91d7_c643_d196_223e, + 0xc1c4_1ee0_2fba_0ee2, + 0x0391_a582_eed5_9211, + ]), + pallas::Base::from_raw([ + 0xf6f5_ac50_5fbe_870a, + 0x4532_ce4f_a3a9_10b7, + 0x2e04_95dc_076f_edfc, + 0x2566_6899_b070_c78e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x59d3_31f8_f73a_4699, + 0xb89e_a133_6b3e_ec84, + 0x67bb_8592_e68f_95da, + 0x0917_436e_03ed_fafc, + ]), + pallas::Base::from_raw([ + 0x2728_c13f_7768_6f7e, + 0x2615_d6e8_f42a_f8eb, + 0x5cc5_df77_1ffb_5726, + 0x00a0_eb9f_912d_b497, + ]), + ), + ( + pallas::Base::from_raw([ + 0x45a5_12f7_5fc1_61de, + 0x0fc6_e344_3c96_5554, + 0x4fea_37c5_2155_ebd0, + 0x07c2_cfac_599f_e891, + ]), + pallas::Base::from_raw([ + 0xd32a_935d_3c0c_02bb, + 0x6263_1e68_432b_5e9c, + 0x072c_bed2_1f3d_b393, + 0x28ed_e749_83dc_4b16, + ]), + ), + ( + pallas::Base::from_raw([ + 0x06de_4839_8da8_baee, + 0x68cc_20a7_0293_f4e0, + 0xde3f_a9e2_2477_7654, + 0x3fd6_fa54_176a_beb5, + ]), + pallas::Base::from_raw([ + 0x5353_f59f_e5a7_9799, + 0x1782_1d05_f83b_d46d, + 0x426d_078d_a05e_5fc3, + 0x278e_e806_7443_d6e7, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc062_01cb_8329_3f63, + 0x2612_bb03_e6c3_5ca8, + 0x6cf4_e8c9_485f_b2b2, + 0x1df7_2e99_3570_d89e, + ]), + pallas::Base::from_raw([ + 0x78a4_d3c2_1b6d_70a7, + 0x14d8_c82f_b0c1_2f69, + 0xf678_2daa_4988_4ae3, + 0x35d2_eef4_df12_fd4f, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb4ae_fdcc_23b0_19ed, + 0xac89_a1b7_c189_9999, + 0x3c5b_8165_4f7a_0a2d, + 0x0ab3_7d69_1765_1d3f, + ]), + pallas::Base::from_raw([ + 0xa130_f549_d03d_7b10, + 0xfe3e_fba5_af76_b8c7, + 0x479c_b292_d515_4d68, + 0x08ed_b127_7cd1_5737, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7250_3581_4ad1_9e66, + 0x9fad_6a01_94a2_09c1, + 0xea98_3f3f_0138_1a13, + 0x17b3_e97c_781f_885b, + ]), + pallas::Base::from_raw([ + 0x871a_12ed_525d_b6df, + 0xadc2_9db0_5909_a3d7, + 0x9807_df73_70cc_dace, + 0x31cc_cbe8_16e8_1804, + ]), + ), + ( + pallas::Base::from_raw([ + 0x42b6_c7a3_8516_301a, + 0x6485_544d_58aa_3fec, + 0xe105_5ca4_0c25_2a01, + 0x3ced_acc5_ade8_6885, + ]), + pallas::Base::from_raw([ + 0x4de5_edcc_e563_c20c, + 0x0879_53b7_6a36_36ea, + 0x1eda_cf9c_e6ca_a3a3, + 0x3f82_b415_4198_b3b1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2781_6103_15f5_7658, + 0xfcc0_b779_a51d_a582, + 0x99df_bff3_92f2_e1d7, + 0x1e09_2f1e_a84d_ae01, + ]), + pallas::Base::from_raw([ + 0x1e6f_2bc5_8ea2_2080, + 0x8ed9_b15d_0399_5f75, + 0x727a_eba9_910e_20af, + 0x38c6_dc55_925a_cbb1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x91fc_bc05_db22_2747, + 0x25bc_36c1_336b_0c2c, + 0x4df4_c949_dcfe_5263, + 0x2eec_785b_ec90_be5a, + ]), + pallas::Base::from_raw([ + 0x4d7e_fc93_c1b1_5b65, + 0x2def_facc_0fdc_986b, + 0x1250_98ef_caa3_a74d, + 0x0624_77fe_5983_41c1, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb63d_7e66_55d5_e3ea, + 0xfb16_b4cb_6165_cc87, + 0x36d2_6c4d_6ae2_1690, + 0x166a_c76c_81f8_449d, + ]), + pallas::Base::from_raw([ + 0x82e2_3968_9388_5c2b, + 0x1b87_9d98_85df_6a30, + 0x5407_156d_69ad_ce04, + 0x0906_bd2d_e5cc_fafa, + ]), + ), + ( + pallas::Base::from_raw([ + 0x737c_cc41_9796_7823, + 0x1007_28e0_1f6f_ec97, + 0xfc9b_08d3_ba9e_2d24, + 0x06f5_aae5_2940_c965, + ]), + pallas::Base::from_raw([ + 0x28f1_62cd_2885_43be, + 0x3f9e_680d_fcad_3865, + 0x8661_0321_76b3_13ce, + 0x14bd_d4cd_abb9_372f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6c25_b3d7_e74e_4edf, + 0x67fb_ae65_2ead_0439, + 0xc182_0837_bf0b_8a51, + 0x3bc9_ce19_4df3_7d84, + ]), + pallas::Base::from_raw([ + 0x6a05_2e74_98de_40b8, + 0xca78_a073_4c6d_7fc7, + 0x5a0a_a126_7469_ec62, + 0x2f17_851c_850f_c7ed, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3317_c03a_fd91_fc0d, + 0x380d_9ecf_fea8_a93c, + 0x7ecd_e1bc_3a9f_b86e, + 0x1770_3f7b_98a4_afe3, + ]), + pallas::Base::from_raw([ + 0xc54b_5020_c9a0_f2c7, + 0xc66b_05c3_adef_c98c, + 0xf38d_a7ae_8e47_7d10, + 0x3784_0c9a_9c1b_eff5, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5093_7efe_a739_821e, + 0x7ff8_6ca2_faa1_106e, + 0x252a_974b_7e9d_2cf1, + 0x1b69_d19c_d62b_b010, + ]), + pallas::Base::from_raw([ + 0xc16f_5a7f_12a2_844e, + 0xae53_d0bb_8ead_5714, + 0xa3c7_0af5_6666_4f67, + 0x3ecb_b98a_9351_1276, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbdc5_4853_e848_f0e9, + 0x1b28_67be_1e77_2485, + 0xe0f5_1554_daca_9e94, + 0x1186_da13_89c4_b670, + ]), + pallas::Base::from_raw([ + 0x1651_3d05_c776_2b00, + 0x20b2_e3a4_b95f_872b, + 0xbf7c_eb39_15fc_1332, + 0x0423_5acc_ac90_496a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x01b0_7e5d_d856_259f, + 0x7f43_208f_a155_b0c1, + 0x8928_e815_2193_8b73, + 0x178d_9714_a538_7eac, + ]), + pallas::Base::from_raw([ + 0xfcb4_658a_4c20_46bb, + 0x6bc7_e482_a235_c77f, + 0x44e8_f7b0_3fb7_88ff, + 0x3ce7_7772_6e04_3742, + ]), + ), + ( + pallas::Base::from_raw([ + 0x55ee_eaa0_d43e_91da, + 0xc90e_6024_78ca_5f27, + 0x6c2a_ec40_cbb6_e051, + 0x0bff_365c_4a48_be7c, + ]), + pallas::Base::from_raw([ + 0x145a_ef04_d8ea_8dc9, + 0x2d56_9b48_e5c8_414d, + 0x781c_943a_068f_fe73, + 0x3361_9f38_967a_b97b, + ]), + ), + ( + pallas::Base::from_raw([ + 0xacbe_71a5_7996_2bd4, + 0x3a08_766b_9cb9_e72b, + 0x1588_d82c_533d_b065, + 0x30d6_c1d1_7767_c3e6, + ]), + pallas::Base::from_raw([ + 0x5d52_0dac_efe4_6e17, + 0xd87a_651f_6eab_7d3f, + 0x6a75_591f_324e_b6db, + 0x1a5d_9a98_7774_d8e4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0225_72e9_ae01_ff6b, + 0xce5a_1ad2_ef36_212b, + 0x14c1_0f42_da3d_b3ad, + 0x2267_625e_16a9_0f0e, + ]), + pallas::Base::from_raw([ + 0x7bf4_5dd3_d6d6_71ec, + 0x01bb_0a02_1198_ccac, + 0x15d0_34ca_24fa_48fa, + 0x19a8_8c5b_dd76_c0e2, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfdd9_7cc1_34fa_1e2d, + 0x3714_b775_9ccf_8134, + 0xb76b_7ae3_d982_6b77, + 0x1ecb_f631_e1bd_91a2, + ]), + pallas::Base::from_raw([ + 0xb611_00ce_ead1_4ede, + 0x9f64_dc5c_9bf7_633c, + 0x315e_4d26_f06e_c2e6, + 0x024a_535a_5088_6f26, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9c75_4f1e_74b6_2341, + 0xc229_dbf2_cfa3_2ca1, + 0x8012_a006_e1b9_9678, + 0x289a_7df4_9f8e_2205, + ]), + pallas::Base::from_raw([ + 0xa292_86b7_14cc_854c, + 0x63c0_5a80_bab8_b239, + 0x77bc_a95b_3393_16df, + 0x0aa1_78e8_67c4_6e06, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6983_e305_abeb_b0fc, + 0x4047_8611_cde4_c624, + 0xcdee_dbfe_557f_b7ed, + 0x04e8_1c14_8e02_18d1, + ]), + pallas::Base::from_raw([ + 0xe556_8d33_21a8_0629, + 0x97f8_66e9_f2bb_ae53, + 0xcea5_b64e_5002_2bbb, + 0x1a34_52e0_acea_f51a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3405_d717_8cc7_2c52, + 0x28a7_a504_399a_1e1b, + 0xf170_e239_f7e9_c844, + 0x1cd3_6395_bb69_dc88, + ]), + pallas::Base::from_raw([ + 0x675b_0b51_04e6_8e2d, + 0x187f_6ecc_1ded_2163, + 0x1a53_421e_85e3_7079, + 0x158a_e3c7_752b_7751, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5500_883e_12a9_cfd7, + 0x9cd8_0402_12bd_4753, + 0x1f75_8954_f62b_dad5, + 0x0bbb_fa9c_c2fe_d2d0, + ]), + pallas::Base::from_raw([ + 0x19e6_d909_4824_a28d, + 0x8cd6_c4c0_9883_3e51, + 0xb646_194f_becc_6f59, + 0x3420_e2ec_d734_13e5, + ]), + ), + ( + pallas::Base::from_raw([ + 0x80a9_e5e3_1610_f69e, + 0xdd9c_4a92_1056_8e20, + 0xf86c_aeb6_c85c_4356, + 0x31ad_dfcc_5e4b_700d, + ]), + pallas::Base::from_raw([ + 0xe9ab_d914_79e1_df04, + 0xb7f0_23f3_36f6_74dc, + 0xa9d2_3371_7f13_8bb6, + 0x1f15_2617_cb34_976f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x960d_00a3_fb22_5078, + 0x9988_7156_0e61_3f5f, + 0xb2ba_af97_cca6_a3eb, + 0x2e7f_19e7_c704_b5fd, + ]), + pallas::Base::from_raw([ + 0x57bb_469f_0dd9_b209, + 0x9c40_8186_c84a_bd26, + 0x5413_30b7_2445_9760, + 0x3ea7_3777_ee4f_b850, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6241_5e2d_be88_0053, + 0xe65d_815b_0595_5f7a, + 0x6b5b_e7ed_43e0_5474, + 0x29f9_ef3c_013d_20ff, + ]), + pallas::Base::from_raw([ + 0x645c_e074_cff0_711a, + 0x9b08_6e5e_0ae6_30dc, + 0x1765_5ee6_7ff2_68df, + 0x2830_190f_e6ea_62a9, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf9a6_568b_9c61_4281, + 0x4bf3_f37b_d546_d5ce, + 0x47fc_7710_3474_7b14, + 0x3a46_2aa4_f76a_f9ef, + ]), + pallas::Base::from_raw([ + 0x480d_19d7_aeef_4c79, + 0xab60_e175_aefb_931d, + 0x4d55_85d0_c71f_a5b4, + 0x0a96_8e62_97d5_9535, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe436_903f_24ac_9df9, + 0x9300_ddf0_1da8_17a6, + 0x123e_6839_b4bc_932a, + 0x2718_8488_35fd_d77f, + ]), + pallas::Base::from_raw([ + 0x52e4_7abd_9b09_46bc, + 0xc085_3ece_0feb_e4a5, + 0xf3f8_35c8_347e_995d, + 0x19de_e913_2cc0_a224, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa453_83a9_b4e6_a758, + 0x9015_00b5_23c1_e447, + 0xe85e_c0c8_8626_5736, + 0x1461_a8e6_40c8_f85a, + ]), + pallas::Base::from_raw([ + 0x501d_08ea_fd72_b2c8, + 0xeb63_ec79_4f6d_f403, + 0xf1e3_f250_1c53_0d19, + 0x062e_d759_693d_19d8, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa2c4_a38d_b249_fc75, + 0x8ae4_0721_77c7_8590, + 0x03dc_4b04_8d5b_9ea5, + 0x0d89_0d38_e803_317d, + ]), + pallas::Base::from_raw([ + 0x3e10_ee60_9358_ada7, + 0x3509_48eb_0862_d70e, + 0x75aa_80df_185a_8c1c, + 0x37e7_c380_d027_a877, + ]), + ), + ( + pallas::Base::from_raw([ + 0xaafd_a565_f09a_06cf, + 0x91e9_f0c0_b668_00eb, + 0xba89_4a61_9a9a_5deb, + 0x35ab_6c2f_a72a_67a5, + ]), + pallas::Base::from_raw([ + 0xdd34_5830_bbc1_a26f, + 0xc3d2_140c_6fc3_9747, + 0x6580_4aaa_a4a3_f62c, + 0x2c8a_da2b_1b04_9e5f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x641e_246a_98ad_630f, + 0x72ab_91de_19b3_2e2c, + 0x5c92_774b_2169_b661, + 0x1e1a_c543_70ce_61cd, + ]), + pallas::Base::from_raw([ + 0x9cb2_3ff5_285b_8448, + 0x9bb4_a42b_f331_6727, + 0x62dd_a513_c86e_4720, + 0x2138_a0f0_4fd0_ae85, + ]), + ), + ( + pallas::Base::from_raw([ + 0x05ff_8a30_31c8_c2c0, + 0x320c_4e8a_b37d_13af, + 0x5fe1_6192_7de0_0a62, + 0x3239_6eec_c899_df01, + ]), + pallas::Base::from_raw([ + 0xf86a_5aa7_254c_db0b, + 0xfff8_6343_7c50_4001, + 0x907e_ce31_13c4_2834, + 0x0083_9120_fef6_4f04, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0da3_5615_e80b_7105, + 0xcd0d_35c7_83f5_ea68, + 0x1956_8f17_fd14_c784, + 0x3844_169d_8e21_5b80, + ]), + pallas::Base::from_raw([ + 0x1fcc_6ff4_1c9b_ed51, + 0x34e5_9481_5dd7_ec6b, + 0x0ba0_d316_7cad_7c5d, + 0x3ec0_c83c_398a_638e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x30e3_dd2a_caa8_9e23, + 0xca56_24c7_a921_34aa, + 0x569a_4c5d_be62_0c0a, + 0x0bb5_f435_f064_07e1, + ]), + pallas::Base::from_raw([ + 0xdfe8_e197_5e22_f53b, + 0x8dfa_bec5_7137_b1fb, + 0x01e1_9f98_b685_ae1a, + 0x28c1_1490_0528_c2c1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3dcc_f7ce_0ad8_e4f8, + 0x588e_312e_199e_763b, + 0x8c6a_a52f_0325_558d, + 0x29c4_ba69_c672_f4f3, + ]), + pallas::Base::from_raw([ + 0x19ec_f218_7171_52d7, + 0x06ee_7a55_fa38_ba4e, + 0xd8f0_a4f2_6c3c_fd95, + 0x3ef7_61af_4dbb_7f0c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1f0f_baad_04bb_5f75, + 0x4aef_02b6_56f4_e2f0, + 0x4c89_9bff_b8a0_8ea4, + 0x3f4e_7e2c_a1c7_708d, + ]), + pallas::Base::from_raw([ + 0x02f6_7a10_1c9c_9bbf, + 0x05f3_0cdb_8ba2_8ad6, + 0xc13a_c49a_1560_a8e2, + 0x1534_bedb_9435_5918, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfa87_6dff_b810_be23, + 0x72c7_0ce1_e075_2041, + 0x07c8_bf38_511f_3cef, + 0x1a6c_5f3e_ed1b_e25b, + ]), + pallas::Base::from_raw([ + 0x51dd_c0ae_85fa_65b0, + 0x835b_4dca_4cc3_3f47, + 0x1bd6_02bd_221a_6807, + 0x32c2_3d1a_8a90_ae9f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0eed_532c_a114_960d, + 0x6983_2d07_9745_030d, + 0x0cb8_8c0e_c597_8792, + 0x3ed4_46f3_babd_8fbc, + ]), + pallas::Base::from_raw([ + 0x2fa7_9562_7ce7_6ebb, + 0x7e21_5019_7c86_5e0b, + 0x6d0e_6986_0b16_2609, + 0x17ed_a803_03d2_9dbd, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5add_f07f_2875_4f6e, + 0xded2_1019_ce38_6e69, + 0xba11_5d0e_d146_4546, + 0x1cb8_adf5_6bf7_d57b, + ]), + pallas::Base::from_raw([ + 0x74b3_43e7_3067_5c9f, + 0xaabe_1a6c_c038_250c, + 0x8ef9_5802_7c5c_eb68, + 0x26f7_2308_b634_cbf3, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe697_9cd0_2e2a_09a3, + 0xbfb4_1ab4_b141_08b6, + 0x290d_dd81_2cb0_5761, + 0x2a6c_642d_8d10_e582, + ]), + pallas::Base::from_raw([ + 0x8536_8a2a_e13e_84a4, + 0x9ece_6cb2_499e_86d2, + 0xa6d1_0304_1f4e_6811, + 0x1749_3586_294f_c00b, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd000_f933_b288_c9ec, + 0x0008_fa27_b1fc_f86c, + 0x77b8_dc36_5ede_0af6, + 0x289d_ea2d_59eb_3f26, + ]), + pallas::Base::from_raw([ + 0x6ee9_9aad_2b6e_4dfc, + 0xa357_f9fb_63b5_5c8d, + 0xb99d_03c4_98a8_d710, + 0x2cfd_b980_9dc3_4f19, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4a3f_f24b_fabd_5cdf, + 0x7415_5bb3_3633_20e8, + 0xf605_add9_99a6_a3b6, + 0x0365_39f1_3da5_3f58, + ]), + pallas::Base::from_raw([ + 0x4aa5_e910_0459_c408, + 0xe4df_d4e4_dbfc_3099, + 0xece4_6d1e_47e1_8bae, + 0x2571_fbf8_3aaf_0f52, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc08b_755a_6fa6_56f0, + 0xac17_cbd2_3558_19a6, + 0x0e1d_63a9_76de_d59f, + 0x0f0d_bacc_068f_cd71, + ]), + pallas::Base::from_raw([ + 0x9ab6_8817_d366_c236, + 0x7ada_4276_3f88_5fd7, + 0x083d_de4f_9fe3_44ec, + 0x33de_e193_caf7_4d09, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3595_8bfd_2887_b46e, + 0x5f38_9101_f1be_4e28, + 0xf9d1_acba_99ed_08d5, + 0x34ae_a666_fa73_373e, + ]), + pallas::Base::from_raw([ + 0x8550_386d_b269_6cc2, + 0xace0_f26f_8504_9ec0, + 0x8e1e_d46d_9d3d_5454, + 0x0978_82d8_5b44_aeee, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1337_19a0_5435_a760, + 0xeb59_3ab7_0fce_fad0, + 0x066b_89a8_bf0d_853a, + 0x1a05_df20_1bf7_016e, + ]), + pallas::Base::from_raw([ + 0x69f0_568a_7e88_6ac6, + 0x90d1_fb1a_ccb8_880d, + 0x81f3_cb9b_0f87_03f4, + 0x28e5_0389_a936_6ecf, + ]), + ), + ( + pallas::Base::from_raw([ + 0x50bb_3f29_64dd_d715, + 0xd5aa_f9a6_dc3f_7706, + 0x6e6d_1433_c49c_3c3d, + 0x1a87_0e90_b006_4440, + ]), + pallas::Base::from_raw([ + 0x8f4a_8f0d_9ffe_2a67, + 0x3df7_ff37_8b3d_e8b2, + 0xe200_ac50_b2e5_224a, + 0x2ea8_be75_625f_19d6, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9352_f3ae_79df_84a6, + 0xd821_c76a_b91e_7cd2, + 0x1516_8b7d_afe5_369b, + 0x39b2_d1b3_ed5f_2801, + ]), + pallas::Base::from_raw([ + 0xe3c1_c8d3_4ac0_cded, + 0x2467_48bb_7fb8_12b2, + 0xf18d_46f6_9a23_e9ca, + 0x12b1_73bf_3cbd_5773, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0d92_cb07_476a_c74d, + 0x9179_b82a_91e5_9ea2, + 0xd5fc_3ab3_d692_f5e0, + 0x3dee_efd0_00c3_3a50, + ]), + pallas::Base::from_raw([ + 0xce37_7044_fa32_43cd, + 0x23ea_04ca_a975_a183, + 0x3aa3_b02e_ec62_b871, + 0x04a9_2111_c558_8ef0, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdc97_a102_7da5_665f, + 0xa1bb_5b5d_cefd_5818, + 0x7b4e_1a76_62cd_3571, + 0x3aa3_d99b_5276_2d70, + ]), + pallas::Base::from_raw([ + 0x94bd_7936_fec3_7759, + 0x7216_4b55_232e_5a3c, + 0x756b_e2a2_52bd_b271, + 0x33f2_bb74_d656_6f3b, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc8fd_7256_d6dc_d9b9, + 0xca03_c9d8_7f65_7f5e, + 0xfd39_43fa_2bf6_46db, + 0x22c1_594f_6399_a591, + ]), + pallas::Base::from_raw([ + 0xa3d3_1b89_58be_066c, + 0x3776_df4c_4371_d751, + 0x6b4f_1dcd_0000_7a87, + 0x3a6d_83e9_a433_e219, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2bac_92ca_749d_8ca1, + 0xcdff_b76a_d983_64c1, + 0x4a71_9e43_074b_e266, + 0x211d_199f_4af1_3d31, + ]), + pallas::Base::from_raw([ + 0x615c_f375_7d8a_3f72, + 0xe271_0a4e_2b25_e0cf, + 0xe07e_62f8_a9e1_180f, + 0x3668_5169_d28f_4663, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5026_b92b_a025_7211, + 0x0f61_be9a_2a03_558b, + 0x6eb5_4fce_c558_57ce, + 0x3193_20a0_380e_6bd7, + ]), + pallas::Base::from_raw([ + 0x6fe5_1d43_554a_f6a8, + 0x6ff0_48c0_b9c9_f972, + 0xb6f3_7bca_07a3_0477, + 0x01b4_080c_f397_6ea1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0c85_14b2_21c5_bf83, + 0x6a65_f7f8_f786_f1e3, + 0x3c45_3a2c_865b_f046, + 0x18e2_8c3f_fbc1_e707, + ]), + pallas::Base::from_raw([ + 0xb311_7e90_19e0_60bd, + 0x682c_b029_85d5_f279, + 0x2d07_1839_acf3_3c80, + 0x24fb_be5a_09e4_252a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3f38_1349_fd30_13dd, + 0x50a3_3f2c_64f9_acc4, + 0xbdfc_c29a_eaae_4852, + 0x08cd_e7f1_737e_4d1f, + ]), + pallas::Base::from_raw([ + 0xc9eb_95f1_0d2e_cff9, + 0x2e16_091e_e8fa_7cb8, + 0x2338_d545_b2b1_87df, + 0x2f9c_01cd_934a_39a5, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2f4f_2486_fa98_dd32, + 0x5ab3_50b9_9429_ab02, + 0x4d00_5b08_67f9_fb09, + 0x3779_1b28_6244_fb4c, + ]), + pallas::Base::from_raw([ + 0xf37a_1b41_ad70_19ed, + 0x3d58_6291_0404_eff3, + 0x3710_9d64_690b_7131, + 0x323d_fe2b_648a_0a01, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2c99_1b63_424c_90ac, + 0xcde0_e148_9201_19b1, + 0x6b0a_cdcd_a94e_23dd, + 0x0a83_ec85_5467_a25b, + ]), + pallas::Base::from_raw([ + 0xe2ae_b456_e029_5ff5, + 0xbbf2_157e_95d4_0f9e, + 0xfcf3_8385_5286_2123, + 0x09d4_c6b8_6321_1860, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5a98_269b_b68a_dd6a, + 0x1efe_b941_4639_b22a, + 0x3571_1c04_46ae_622b, + 0x0724_0c4e_61e2_c404, + ]), + pallas::Base::from_raw([ + 0x4ef2_32c5_b9e0_bf79, + 0x2849_2c6d_82b9_4679, + 0x6dc9_c162_d595_5ace, + 0x23a1_46c9_cb46_7e4b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0ea6_e467_1688_5744, + 0xd73a_c7a9_3c06_0f51, + 0x1b46_3eef_0911_0ff0, + 0x25f5_1a1b_8b2b_15b0, + ]), + pallas::Base::from_raw([ + 0x0e94_24d7_d847_cff0, + 0x9eca_1c26_c8d3_4d22, + 0x3e7c_44f4_a071_358a, + 0x02e0_3e55_9e85_2acc, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfaf6_e963_4f72_548c, + 0x75ba_6678_06cb_3018, + 0xdd5c_4dc1_f6a6_1ed0, + 0x358a_aa4a_969c_7d11, + ]), + pallas::Base::from_raw([ + 0x7192_b88c_0b41_26f7, + 0xdcbf_5faa_5173_d672, + 0x2844_3f7b_d7c5_386b, + 0x0326_622d_0d54_8882, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf31d_8620_1649_f08d, + 0x1fc2_50c9_c62e_1cdb, + 0x3fa2_7d7f_c12a_308b, + 0x087a_7698_3f2b_40d6, + ]), + pallas::Base::from_raw([ + 0x1dfe_55f8_f11a_cf1f, + 0xee33_c9fd_9995_34ef, + 0xc306_776a_68aa_2318, + 0x2ac3_5afb_37f0_64ac, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3ade_cfe8_e5e3_3497, + 0x6c41_930f_374a_cbed, + 0x12b0_f864_c7a8_8ad8, + 0x370f_dfb0_97f4_2558, + ]), + pallas::Base::from_raw([ + 0x7220_b1e4_1ab5_cf99, + 0x1632_92de_779c_623c, + 0xb034_0022_4b4a_86e7, + 0x309b_afa2_6ef2_925b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3406_5012_f9c0_5127, + 0xc828_f419_9900_4b75, + 0xf6d7_a11d_f480_e3f3, + 0x2d33_2d46_fc42_f207, + ]), + pallas::Base::from_raw([ + 0x2e46_6250_9e68_6ee3, + 0xabfd_de7c_df9e_5e25, + 0xfd57_0e95_4b02_75d5, + 0x2477_ef02_28ea_9a13, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbc3e_e22f_76ca_58be, + 0x3ea8_77d8_d59b_c50b, + 0xa88b_9b65_778b_d746, + 0x3ac6_7681_a682_1563, + ]), + pallas::Base::from_raw([ + 0x012d_26a1_75a2_a60c, + 0xd293_1675_5b9e_520e, + 0x7bb3_81b2_f90a_0804, + 0x33a1_bb0d_70de_7a89, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb0ca_c9ca_8444_ee90, + 0x968e_19ad_ff27_1d61, + 0x3f26_3e09_835a_f66b, + 0x05bd_71f7_da1c_0c64, + ]), + pallas::Base::from_raw([ + 0x61e9_dece_6f85_2582, + 0xe5d1_17ee_5df5_c9f5, + 0xa3d6_5b5f_bc5d_8147, + 0x3b58_829c_1b3c_754e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x39e8_9116_b59c_ef80, + 0xe8ee_3f15_868d_9a99, + 0x4b32_6add_a5d5_8444, + 0x14fe_9402_9ff7_9f66, + ]), + pallas::Base::from_raw([ + 0x8f66_4020_0012_b5cb, + 0x6fcc_c979_1537_fa13, + 0xd19e_3ecc_386a_8282, + 0x162c_c785_6d5c_74dd, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdcc0_2e8e_07fe_88af, + 0xbd3b_2a17_ffae_f834, + 0x0c6f_3298_640b_78d5, + 0x34a4_a683_e010_cf56, + ]), + pallas::Base::from_raw([ + 0x63f3_abee_7273_ea33, + 0x0537_702e_040b_92a5, + 0x547d_6ec3_8dd6_3bc9, + 0x0064_5b12_758a_1545, + ]), + ), + ( + pallas::Base::from_raw([ + 0xee8e_7e91_b30f_5175, + 0x9118_328b_98b1_2a4f, + 0x0c6b_7d43_ce19_2ef5, + 0x2683_6545_f707_77c2, + ]), + pallas::Base::from_raw([ + 0xd798_780b_8fd5_f829, + 0xc468_a05e_0278_eba4, + 0xb7fa_f13c_0064_7ac1, + 0x322a_6aee_3ba4_d176, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1e3b_4dd6_6189_13dd, + 0x248b_f2dd_da95_8f94, + 0x8216_4826_97e1_1826, + 0x21ac_9398_a5e7_dcec, + ]), + pallas::Base::from_raw([ + 0x772d_8d75_5b4c_3ff4, + 0x7677_d0f9_ffdf_bce3, + 0x10a3_b808_a28e_401f, + 0x3f6e_5b46_b349_c86e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5585_d887_f1be_367c, + 0xaf07_cc5a_a53a_b659, + 0x923e_92e1_35a8_1746, + 0x3bf2_268c_f24d_f183, + ]), + pallas::Base::from_raw([ + 0x6fe6_0473_5e78_a74d, + 0x39a1_9091_b746_8447, + 0xa594_c45e_b7ed_cbeb, + 0x0bc9_13da_4cca_ecc3, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1dd4_2c2c_c537_4903, + 0xa6cd_7de7_d439_ae61, + 0x15db_897f_3b7a_4b07, + 0x385c_81f8_ce51_3dd7, + ]), + pallas::Base::from_raw([ + 0x0ed8_0b1c_199e_8dc9, + 0x79e2_6722_5b1d_39ed, + 0x8b04_7941_6c0b_072f, + 0x1507_a637_5325_d934, + ]), + ), + ( + pallas::Base::from_raw([ + 0xaf31_86eb_84ad_4635, + 0xa779_6331_ccd3_0336, + 0xf128_6ce2_c67c_671f, + 0x00c1_b469_5cb2_7c0a, + ]), + pallas::Base::from_raw([ + 0x7c4f_be0e_1cae_a3da, + 0xc17b_1bad_0248_be18, + 0xbddd_f903_c50f_6c61, + 0x2f3f_845e_eaa9_03e7, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7338_6610_264b_147f, + 0xe78a_eae6_e018_7005, + 0x909e_4f4d_6e92_b8fc, + 0x20ec_33c4_98b4_3f28, + ]), + pallas::Base::from_raw([ + 0x29a8_eaa1_5507_3eea, + 0x729d_227c_2a31_1b0f, + 0xb154_9c99_6dee_651b, + 0x3060_2ac3_8a24_17f5, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3fa1_0f32_fc38_d1e7, + 0x1130_716c_d00c_2e12, + 0x8b7b_e7ee_bb9c_cf07, + 0x03dc_0d4e_7c25_b4c8, + ]), + pallas::Base::from_raw([ + 0xcc37_2d2f_1cf0_494e, + 0xf281_1971_693f_a7cb, + 0x71c8_5723_0112_d789, + 0x0b3a_4062_de68_d35a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2e57_965d_8fe6_3cf5, + 0x1e7d_0375_96cf_b42f, + 0xe4eb_acfa_e071_f221, + 0x18c8_30aa_25a9_b96b, + ]), + pallas::Base::from_raw([ + 0x1e35_c955_b145_dba5, + 0x38bf_a239_4c66_8c55, + 0x2b79_b6c0_0ae1_44e5, + 0x3306_70f2_2a1a_de18, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0337_c17d_8089_522c, + 0x8cf3_b7f9_e509_1e93, + 0x30c2_0095_5bf9_2882, + 0x1803_6e94_3474_308f, + ]), + pallas::Base::from_raw([ + 0x83ac_e960_14ab_f3bb, + 0x39e9_4c89_6758_a248, + 0x3b82_4eaf_babb_2278, + 0x05fc_dc8a_770e_abbe, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4d15_af9f_a6dc_11ec, + 0x9688_b4f7_fa83_b756, + 0xa900_9592_c556_2c12, + 0x3131_f9b2_c23e_76a9, + ]), + pallas::Base::from_raw([ + 0x6377_bc0b_418d_17d1, + 0xbdc7_c62f_a1a8_205e, + 0xb808_e58c_dc75_27e1, + 0x3ce3_9a6a_42ea_0aa0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x919c_3661_1b95_aae0, + 0x21c0_adf5_8570_1f29, + 0x15d8_a824_0a43_7e95, + 0x2487_0e7f_975b_e250, + ]), + pallas::Base::from_raw([ + 0x2b8a_f942_d325_4f8f, + 0xb8bf_f293_9d0e_2803, + 0x2b36_1401_fde5_3611, + 0x095f_973d_1e25_6fe0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x25b5_ca69_afd7_aff7, + 0x5731_761b_b667_965f, + 0xf0db_8000_e078_d5f6, + 0x105f_d651_9a6f_486c, + ]), + pallas::Base::from_raw([ + 0xf6ed_7c18_4edf_3a25, + 0xf716_d2fc_b54c_26c1, + 0x8303_3072_def8_c70a, + 0x3c72_43cc_c6f9_d8a8, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd29a_f540_a1af_2913, + 0x86f8_1775_ed00_f09a, + 0xc978_728d_7996_fcb6, + 0x15aa_c4cf_5c52_2bcf, + ]), + pallas::Base::from_raw([ + 0xccad_8b65_5bf6_65a2, + 0x197e_4429_fe3d_8a0a, + 0xbc26_5a82_cbc8_f7ef, + 0x0963_7be9_0c33_cc7a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x75dc_37ce_9ca5_f163, + 0x3ce4_4f1c_f801_0d67, + 0x1933_916f_d304_ae70, + 0x09c9_2882_3da7_3ed0, + ]), + pallas::Base::from_raw([ + 0x141a_785b_eb35_8d29, + 0x10a1_7521_6bb8_ca36, + 0xcd51_d77e_9964_8226, + 0x1d96_15b1_2538_b56c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7c71_c1bd_6f57_54d3, + 0xc9a0_533d_95df_b16c, + 0xc945_1160_48eb_86c6, + 0x05a8_f8f3_8c44_0d2e, + ]), + pallas::Base::from_raw([ + 0x11d8_5995_cf88_f460, + 0x44eb_7f8a_f7bb_3f42, + 0xbe8c_bb43_79ee_ea5b, + 0x02dc_b98f_ed50_2f86, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4aa2_aebe_4d5d_a915, + 0x3425_6cb2_b933_62ec, + 0xd229_ad1e_6a44_6607, + 0x35fd_e2b2_e65f_cbf8, + ]), + pallas::Base::from_raw([ + 0x9a36_e5cc_e10a_7cc0, + 0x7d41_adbf_6442_8e9e, + 0xc64a_aab0_e77d_ddc3, + 0x28b6_ce44_dd17_b086, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1729_f03e_c757_de09, + 0x8714_d598_3cae_ddd6, + 0x46bc_981b_76f8_5cbe, + 0x1b7c_7a5d_98d4_34b6, + ]), + pallas::Base::from_raw([ + 0xfb61_8881_0f97_fafc, + 0x769e_1253_4744_7d00, + 0xe166_94e5_6b37_ac66, + 0x0e0d_592c_aaa4_40d6, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb477_691c_7b9a_4e74, + 0x1c61_eaa0_b273_998a, + 0x9295_2b04_a869_8738, + 0x18ae_d1c7_f94d_31cb, + ]), + pallas::Base::from_raw([ + 0x63b4_a102_91a0_3aa0, + 0x8456_6a59_cd00_e2e8, + 0xd9ac_8f76_f4fb_99ea, + 0x249a_c33c_1f19_006c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa611_c5c5_9dd9_c129, + 0xb449_431e_460c_3b9a, + 0x8aca_28de_50ac_6407, + 0x2be1_f2a6_a7a7_ef47, + ]), + pallas::Base::from_raw([ + 0xa14c_46c9_87ca_1f52, + 0x40fd_f4bb_a4f9_9362, + 0x99f2_8ce2_fff0_5d47, + 0x2339_e672_20fd_6223, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb07e_39ee_d553_19df, + 0x2e2e_a2b6_cd63_7b9e, + 0xb230_1c5c_5876_4d85, + 0x3101_8876_853f_6e11, + ]), + pallas::Base::from_raw([ + 0x6d1c_01bb_fd88_f740, + 0xac45_ec78_810e_f0f9, + 0x9178_89e7_5ed9_7c53, + 0x2e02_e772_12fb_a864, + ]), + ), + ( + pallas::Base::from_raw([ + 0x482b_ce95_a904_fbd8, + 0x099f_45ef_d9d4_df4e, + 0x6e70_1656_036c_32a5, + 0x0a38_cfd7_47d5_12df, + ]), + pallas::Base::from_raw([ + 0x570e_1101_ac76_4879, + 0x99d6_ff14_205e_e41a, + 0xc0cb_4ce1_8364_ca7d, + 0x3f21_3a0f_2127_88ab, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd491_d705_998e_3189, + 0x7bbe_badc_663c_142a, + 0x1d71_9890_63cb_18bf, + 0x24f8_a1c5_2bcc_e57b, + ]), + pallas::Base::from_raw([ + 0x1052_3fab_b946_6f52, + 0x4621_6150_6b1b_7617, + 0xe22b_b133_5f66_15bb, + 0x00f7_be2e_c34c_d4e3, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9b7c_bac0_1918_11a9, + 0xe14a_1c33_bf27_13cd, + 0x9c34_676f_4ed5_0771, + 0x3bc3_b8f9_e4c7_cbdf, + ]), + pallas::Base::from_raw([ + 0xc0c3_e7a5_3a19_2bbe, + 0x9697_bec0_d8a8_605b, + 0xf832_dc8b_d08d_48b0, + 0x0ad3_7d64_7a36_db47, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1a49_dcca_d569_77a0, + 0x2769_7440_2b48_e461, + 0x7635_b7b2_f6c1_424a, + 0x2be6_10f4_a289_b89f, + ]), + pallas::Base::from_raw([ + 0x3e61_c167_3ef3_fc04, + 0x704f_268b_ce47_f0ed, + 0x1445_3f5f_d2de_d836, + 0x2cc9_d834_4ecd_1dd8, + ]), + ), + ( + pallas::Base::from_raw([ + 0x11be_a2a7_7c43_e243, + 0xaecc_1a58_bb49_1e6a, + 0xad18_3b6c_844b_4f3e, + 0x3af2_29a8_1e78_8c01, + ]), + pallas::Base::from_raw([ + 0xe5c9_8811_3857_46dd, + 0x18a2_37a6_3f4b_065b, + 0x95eb_77a3_8a4f_506e, + 0x0f19_e7b4_bb81_9e9f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4289_8695_120d_ccc9, + 0x7c47_76b5_b28f_500d, + 0x7387_a0f6_6257_0e43, + 0x3e0a_1a75_0403_726f, + ]), + pallas::Base::from_raw([ + 0xa4b8_617f_f5a5_e2d0, + 0xbf01_7517_8dc0_be8f, + 0x3e12_8a0f_5763_aace, + 0x1019_05b6_1b1b_c5f2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6a1c_6336_c37a_69b0, + 0xab01_b8f5_4c9c_c938, + 0x4b70_77c2_8858_0ff1, + 0x219d_9462_2657_9313, + ]), + pallas::Base::from_raw([ + 0xf0ca_ec54_481d_b8b4, + 0x9caa_706e_8ced_f819, + 0x892e_4cf8_342d_d927, + 0x241c_1980_c2a8_9519, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2257_583e_eb36_4cc3, + 0x2729_3e7e_d528_5088, + 0x7eca_1457_60f9_6fcd, + 0x366d_41fd_d540_9bb6, + ]), + pallas::Base::from_raw([ + 0x8bfe_2959_1e82_19f9, + 0xbbcf_1722_efec_e216, + 0x9560_fd43_03ee_a5fe, + 0x01a2_ccf4_056f_faa8, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf4b5_ad0b_5218_a94c, + 0x3a24_9090_dd6d_9354, + 0x98dc_0f69_de87_c57c, + 0x3dda_357c_5ff1_403e, + ]), + pallas::Base::from_raw([ + 0x5119_459b_c85d_b13a, + 0x2bfc_720c_90b0_7f40, + 0x8537_0f1d_f88b_d581, + 0x37db_226e_c00e_a298, + ]), + ), + ( + pallas::Base::from_raw([ + 0x63b3_23a9_39a1_77d2, + 0xe62f_9748_713d_01cc, + 0x2508_9050_a0a1_59bf, + 0x3d7c_7c78_e9b8_851d, + ]), + pallas::Base::from_raw([ + 0x3984_54cb_b4b2_6fce, + 0xc06e_f1b1_b11f_d4b9, + 0x4456_781c_e7d7_a297, + 0x04cd_8194_f1b8_f453, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1848_83cb_b617_7af4, + 0x2d57_546c_a4fe_c3d5, + 0x984d_e3a8_9232_dc0a, + 0x0fe5_7d7a_b313_f576, + ]), + pallas::Base::from_raw([ + 0x675f_4ab1_700e_2db7, + 0xb044_6bdd_3a0d_7127, + 0x8d66_c6a3_c174_8080, + 0x339e_ae89_0552_92fb, + ]), + ), + ( + pallas::Base::from_raw([ + 0x28c6_46e7_5497_b6ce, + 0xa3fa_753a_0908_d101, + 0x1f09_d0f5_065d_2adc, + 0x178a_d1dd_216e_1573, + ]), + pallas::Base::from_raw([ + 0x030e_a306_b041_1e57, + 0x762a_03ec_df28_bda9, + 0xa2c1_f35a_ecc9_b51c, + 0x037f_7114_803f_e131, + ]), + ), + ( + pallas::Base::from_raw([ + 0x59ea_b5bb_9453_8366, + 0x5fd1_07f8_c2f0_3024, + 0x491f_9e0a_77a0_85f2, + 0x1948_5bba_5ad6_3346, + ]), + pallas::Base::from_raw([ + 0xe07d_8570_5ff6_f740, + 0xa7eb_9f4b_f78f_ebfb, + 0x4ec7_d8d5_ba74_87e2, + 0x152f_bf0c_1ad3_a448, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8951_a4b7_b88a_e304, + 0xfbba_f494_63d6_2de5, + 0x52dc_f4cf_46fb_b604, + 0x3867_61af_f0a2_8253, + ]), + pallas::Base::from_raw([ + 0xa79e_a333_0c3d_f255, + 0x98c0_1806_eff3_7bfa, + 0xce04_7c7b_36b8_bcd6, + 0x3c8b_a7f4_c363_edea, + ]), + ), + ( + pallas::Base::from_raw([ + 0xaf3d_f661_71ab_0aef, + 0xf42a_df27_feb7_ccb0, + 0xf652_096a_95e0_714e, + 0x12d2_2f2e_bb9f_dc00, + ]), + pallas::Base::from_raw([ + 0x2bd3_e13b_745c_7e1a, + 0x30d5_8bed_1c3b_4731, + 0x4fe9_de60_156d_0e6c, + 0x1e75_82ca_046c_a5e1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4b26_3b3a_2d30_be4e, + 0xa303_e1b5_ef9e_9c4e, + 0x1a0c_d3c7_1f78_d34d, + 0x0bc6_cbe2_2b93_1a09, + ]), + pallas::Base::from_raw([ + 0x5ef8_540a_c950_78e6, + 0x5d90_b70b_8565_5e85, + 0xed53_c717_36e6_4c78, + 0x1686_468a_3079_9166, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0d0d_dd14_b731_9e48, + 0x712e_bc82_5391_3fdc, + 0x5fcf_dda4_b51e_08ba, + 0x2f3d_06eb_2d44_fe78, + ]), + pallas::Base::from_raw([ + 0xa569_8634_a0b3_f32d, + 0xc8a6_e6d0_0d30_9550, + 0x89e7_8274_9620_9f58, + 0x05b3_f104_5b90_6405, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6556_9fab_c93f_5ef1, + 0x8c77_fb45_e5b0_3ff0, + 0x1462_4404_a661_0d5d, + 0x1431_fd82_8c12_8ca9, + ]), + pallas::Base::from_raw([ + 0xe0f4_ffe0_7a81_532a, + 0x6254_e069_7645_e573, + 0x3642_fa4b_19fd_822a, + 0x2485_d135_0391_bb52, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5768_e306_b266_4925, + 0xd2ef_fda1_e729_f652, + 0x21c6_d47c_319c_a9c0, + 0x2401_0c36_e54d_b3ee, + ]), + pallas::Base::from_raw([ + 0x4951_90fc_eb1b_0b80, + 0xf0c2_c315_ee04_91f2, + 0x61bc_9d08_1307_b3d6, + 0x024a_b73e_3607_a264, + ]), + ), + ( + pallas::Base::from_raw([ + 0x107f_387e_54f4_ef3f, + 0x290a_32da_0a43_f56b, + 0x8b9f_8656_4e83_0909, + 0x277f_0f45_b6ae_6203, + ]), + pallas::Base::from_raw([ + 0x546b_2865_f5cc_4c0e, + 0x8270_55d6_07bf_0010, + 0x3f7e_68bd_fe61_0371, + 0x1687_eb45_0f4d_7235, + ]), + ), + ( + pallas::Base::from_raw([ + 0x78a6_4eac_316d_2648, + 0x753e_2521_a67f_50aa, + 0x6e78_79a5_f11f_46fd, + 0x05d9_18ff_b1da_6eb7, + ]), + pallas::Base::from_raw([ + 0xbab9_5863_9bc9_3ae9, + 0xc37b_3f46_04cf_5755, + 0x7de4_ae79_9f29_d2df, + 0x047b_e1a6_060c_00a3, + ]), + ), + ( + pallas::Base::from_raw([ + 0x49c8_71c2_61df_7cb6, + 0x5380_ca6c_e9ea_0b1a, + 0xd05a_4403_41b8_fe0e, + 0x080c_d18c_035f_3d1c, + ]), + pallas::Base::from_raw([ + 0x5274_e1b3_a894_244a, + 0xdedc_74af_a21e_7fb2, + 0xefb1_5f0d_99ee_54b4, + 0x164c_3262_4eb6_dbf6, + ]), + ), + ( + pallas::Base::from_raw([ + 0x720a_ab53_d588_6877, + 0xceaa_5cf8_1671_dfae, + 0x6d4b_9e16_5405_52e7, + 0x1c6c_47fa_233f_06be, + ]), + pallas::Base::from_raw([ + 0x61f5_3c0a_f4a4_aa3f, + 0x5258_2334_b4f0_389a, + 0xc8a1_db30_50aa_f8fe, + 0x26dc_db34_c46c_475c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3208_2970_3840_3e89, + 0x90a1_593b_19f1_4fe3, + 0x09f3_ee71_e6d7_1be4, + 0x0e1f_8f25_134a_535e, + ]), + pallas::Base::from_raw([ + 0xd519_b8f9_b1ac_7612, + 0x91f7_850e_9074_5846, + 0x8846_3611_08cf_2b28, + 0x13ea_a62b_73e0_0d4c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4f39_a353_445a_c66f, + 0xb59d_a767_e1ba_84b7, + 0x3717_9c04_0954_afa8, + 0x2fcf_f236_d66a_b6d8, + ]), + pallas::Base::from_raw([ + 0x681a_f295_0d6a_597b, + 0x5642_9cae_6025_720a, + 0x8a44_998d_765c_574d, + 0x12ee_e67e_1017_6ed2, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa310_66f1_af04_bfee, + 0x2537_ea36_fb79_6590, + 0xb3a7_25f6_64e1_75b9, + 0x0788_937e_9e35_d05e, + ]), + pallas::Base::from_raw([ + 0xb54e_df7e_8f9f_a0a2, + 0xe362_2000_9b73_a921, + 0xf8db_8c46_cd4f_aefb, + 0x2519_43ee_b590_deda, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa88e_2b82_de18_77cb, + 0x8b94_f5d7_e46c_d059, + 0x87a7_0381_3254_1fda, + 0x22b7_728f_f5f3_2e91, + ]), + pallas::Base::from_raw([ + 0x673d_7869_fa50_f997, + 0x9719_2499_f4d2_51a3, + 0x1c21_1da8_cfc7_b1d3, + 0x2bcb_9b60_77ac_9937, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa685_8e35_0e91_aeb6, + 0x97a5_6a16_423e_dae8, + 0xf9d3_8da2_28a0_0e54, + 0x1e9b_88fb_12aa_573a, + ]), + pallas::Base::from_raw([ + 0x9be1_63b7_2d0c_c322, + 0x6650_4cd4_3970_8ced, + 0xd3b1_ffca_c43e_ab37, + 0x1146_b28a_9f97_60fe, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdada_d29e_a8b1_8934, + 0x1250_940f_3606_539b, + 0xde2b_b139_6cf1_8876, + 0x11e8_cf63_9f28_6365, + ]), + pallas::Base::from_raw([ + 0x5015_5f8c_2102_5aa6, + 0xa1a1_d888_4567_1dfb, + 0x4927_0835_d236_0fb2, + 0x2753_5582_b996_b87b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8cc5_e492_e0b4_ca8f, + 0xb28f_29f7_b0c6_3c48, + 0x7e38_ef10_32a2_771e, + 0x3313_c543_e637_32ad, + ]), + pallas::Base::from_raw([ + 0x7669_4d57_eb2c_730c, + 0x0574_9ec0_b3f7_fe08, + 0x20a7_2453_e449_f1a4, + 0x2070_c656_4f60_8012, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7ec2_1a43_236a_004c, + 0xa075_0e43_5d13_56db, + 0xf3fe_38de_a0d9_767d, + 0x0337_f093_b562_0110, + ]), + pallas::Base::from_raw([ + 0x62ce_7184_7406_5d24, + 0x6bbb_c9a5_81d5_55af, + 0xfa2d_4ee6_6018_beea, + 0x280b_c654_e7e7_dca0, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb0e1_913d_b0c2_44fb, + 0xaaa4_d9c4_218e_5938, + 0x3546_9b6b_5fd6_28c6, + 0x2395_9107_43d3_b222, + ]), + pallas::Base::from_raw([ + 0x5caa_129b_4b4c_e99e, + 0xef45_ecaa_5313_85ec, + 0x9c0f_a2cc_c8c2_fb2b, + 0x189c_8131_f518_b80a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x032c_426c_b013_a4dd, + 0xe11b_5e5a_3317_d9a0, + 0x1492_b9fd_830d_3b80, + 0x05f1_9d94_cf5c_55d4, + ]), + pallas::Base::from_raw([ + 0x2c4c_4704_673d_d003, + 0xc48e_29af_97d8_8d07, + 0xd127_dd3d_91d8_53df, + 0x0d94_c052_f2d8_6405, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0281_7bc9_d4b9_3609, + 0xfd46_6a0d_2dd4_a592, + 0xd7bf_d5ad_053c_c494, + 0x3e23_096d_c906_06f6, + ]), + pallas::Base::from_raw([ + 0xca1b_a2ad_cf48_d0bb, + 0x4888_2a9e_e612_5ebd, + 0xa857_f300_33cd_faaa, + 0x0c0d_d8b5_2910_9f77, + ]), + ), + ( + pallas::Base::from_raw([ + 0x63aa_2c76_cd16_571b, + 0x6708_27ee_f35d_9eee, + 0xf732_370c_ed30_8f28, + 0x2ce7_690f_9c45_b228, + ]), + pallas::Base::from_raw([ + 0xcf08_8fe5_d2da_2200, + 0x6b75_35a5_3b79_5a4a, + 0x6cdd_78c6_35eb_3d58, + 0x088b_da57_e794_bed4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4f92_90f2_3582_04fa, + 0xbc25_9bd5_ce7a_6e0f, + 0xe944_7dba_fd99_596a, + 0x2151_a369_bbea_a627, + ]), + pallas::Base::from_raw([ + 0xc11e_3f08_4d99_83d1, + 0x3a2d_880e_3a7b_619b, + 0xa9ce_df46_75f2_11fc, + 0x153b_e140_8a41_79a8, + ]), + ), + ( + pallas::Base::from_raw([ + 0x16b4_ed0b_32aa_4e15, + 0x5d02_9826_e003_163e, + 0xa674_90a0_12f9_fec2, + 0x13b1_0df3_13d4_e0de, + ]), + pallas::Base::from_raw([ + 0xfac3_5814_0dbf_6fc9, + 0x8d97_dbe3_f3dd_64d8, + 0xf604_80c5_b973_198e, + 0x0e44_6ba1_c4c7_30fe, + ]), + ), + ( + pallas::Base::from_raw([ + 0x53e5_02ce_d667_b3fc, + 0xbd6f_f292_2d35_b631, + 0x511e_7681_0c78_b289, + 0x35d9_b968_f2eb_c3a6, + ]), + pallas::Base::from_raw([ + 0x4b3b_f3a7_1759_3f8d, + 0x4820_7638_9976_7ebf, + 0x1549_6703_964e_4f60, + 0x153d_81a1_43f0_33f0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x31ba_52fc_3de5_ff01, + 0x376a_2b41_b9d1_ece2, + 0x62fc_8331_dce8_2da0, + 0x118f_5744_f6ac_a780, + ]), + pallas::Base::from_raw([ + 0xb1e6_df68_1ea7_f7a0, + 0x40ad_d363_1deb_f495, + 0x41bc_dabb_085f_cb8b, + 0x364f_f020_42c1_2247, + ]), + ), + ( + pallas::Base::from_raw([ + 0x36b4_1638_0c9e_d00e, + 0x51e0_bfdc_10a4_d1db, + 0x913c_5835_4ab5_aeab, + 0x0fab_7d64_6a29_1389, + ]), + pallas::Base::from_raw([ + 0x3321_18a5_c438_99ab, + 0xaf16_ef69_ec9a_ffa2, + 0xc1b2_59aa_af0b_d100, + 0x0575_6607_ef7c_7347, + ]), + ), + ( + pallas::Base::from_raw([ + 0x74e0_0818_faa1_9834, + 0xfb98_2b7c_6bed_60fd, + 0xdde3_235a_2ebd_4674, + 0x215a_5274_fda3_f4b6, + ]), + pallas::Base::from_raw([ + 0x106c_9783_c54d_cdce, + 0xe128_7246_1a71_5454, + 0xd4f3_6b85_c93a_1dc3, + 0x0c6e_92c5_d9f9_4b70, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1b65_d726_c0c0_4615, + 0xbf8b_e863_197b_7eda, + 0xaad7_e0b1_2452_bfd1, + 0x3511_415c_a5d8_9c89, + ]), + pallas::Base::from_raw([ + 0xd833_0a78_4996_6f63, + 0x30e1_6447_4b6b_f6d4, + 0xcaff_7c50_1359_107c, + 0x0952_ff5e_91f1_5c61, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0d03_79de_a34a_e974, + 0xcc94_561f_8673_d71a, + 0x131a_c0e3_d11b_fdbf, + 0x25f8_c019_6ac7_41d8, + ]), + pallas::Base::from_raw([ + 0xc14d_3404_894d_304e, + 0x1683_69a9_6ab3_16e9, + 0x3735_bc70_cd9c_eda8, + 0x37b4_443a_f523_48a5, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9956_1c6e_192e_7568, + 0x65e7_be87_d79a_44a8, + 0xc477_1369_88f7_cfa5, + 0x2e08_d2d1_1f6a_f91f, + ]), + pallas::Base::from_raw([ + 0x8a16_6878_cf4d_afeb, + 0xabb3_04d6_e705_de6c, + 0x0859_c814_89dc_15a7, + 0x274d_0775_a079_b46f, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdb3d_11a8_4fa8_eb13, + 0xea3a_7ea8_9ad3_d6af, + 0x9981_af35_3af1_4cae, + 0x2b38_c01e_7ae8_dfd9, + ]), + pallas::Base::from_raw([ + 0x830d_dec0_3c06_8077, + 0x422d_fa90_51e2_eb38, + 0xb8f7_0d0d_d10c_76bc, + 0x059f_5d67_0e51_88ac, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd910_97f8_e2bd_e10e, + 0x2f49_4c52_8f28_53a0, + 0x18ce_d481_b3ab_923b, + 0x25d9_e3db_a718_8482, + ]), + pallas::Base::from_raw([ + 0xd5a7_8005_ba95_0986, + 0xcaff_2367_d76d_2556, + 0xfd49_0e15_d2f5_1094, + 0x04ec_51a9_732e_c101, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9f75_b055_2d08_c237, + 0x8f7b_40f6_b94b_551f, + 0xff0a_1d65_6512_02d4, + 0x3259_3507_44ad_2a0e, + ]), + pallas::Base::from_raw([ + 0xc3b0_007b_5635_87fc, + 0x67fe_664c_1556_008b, + 0x3933_d14e_7c90_a14b, + 0x08dc_b066_37f5_9ef5, + ]), + ), + ( + pallas::Base::from_raw([ + 0x66d6_5691_d7f4_6f1e, + 0x5774_25e4_a039_ed7a, + 0xfe27_c3f5_c7dd_098b, + 0x1daf_b77a_22f1_a231, + ]), + pallas::Base::from_raw([ + 0xa3bf_e610_9f9d_ac9b, + 0xa538_e6f2_5314_865c, + 0xd48c_5619_13f2_6135, + 0x267d_66bd_ebc3_5567, + ]), + ), + ( + pallas::Base::from_raw([ + 0x22c0_55b7_005a_fd9a, + 0xfbe9_5fb4_544d_dfc1, + 0x48d5_2641_43d1_c6cc, + 0x37c3_a72a_ea7a_3c08, + ]), + pallas::Base::from_raw([ + 0x77e1_afd5_eb2f_d2a4, + 0xbfcf_76bf_248c_529c, + 0x8589_07aa_9ffa_a44a, + 0x288a_b3ad_a23d_9811, + ]), + ), + ( + pallas::Base::from_raw([ + 0x847f_2f2e_b09a_a283, + 0x2874_cbee_5fec_a088, + 0x34d9_fc8d_9af2_8672, + 0x3e01_114f_d579_ff35, + ]), + pallas::Base::from_raw([ + 0x5504_d237_2254_c60b, + 0x8f8f_14cd_6ba7_b727, + 0x7539_9f3f_1180_d507, + 0x1977_5697_8320_c24b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x21d8_677d_a4ef_3754, + 0x970f_79ca_c4fe_71cc, + 0x9202_5638_93e7_a2f0, + 0x276c_3165_597d_8172, + ]), + pallas::Base::from_raw([ + 0xcddb_83d9_a9a1_4fee, + 0x05fa_2b1e_95df_ce2c, + 0xd58c_11ef_e1b6_7c84, + 0x01c7_cbc6_23e0_fde1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5dc4_11b7_be41_9bf8, + 0x6102_61de_71fd_509e, + 0x93df_5fe5_4b6f_15c1, + 0x2b17_a62a_ef55_fc8c, + ]), + pallas::Base::from_raw([ + 0x13e9_27e7_5428_fe43, + 0x3d07_6e3a_5358_d948, + 0x321d_9085_07a0_7394, + 0x2648_17bc_3f85_f5f9, + ]), + ), + ( + pallas::Base::from_raw([ + 0xda55_668c_c5c9_544a, + 0x7dd5_42c2_06f1_4ae9, + 0xb88a_afeb_3d1c_ee22, + 0x1a12_25c2_42a6_d6dc, + ]), + pallas::Base::from_raw([ + 0xd8d8_02d6_7061_f4e7, + 0x1a21_e3e6_ed3e_e0e2, + 0xcbe5_9238_89e5_856b, + 0x0390_44ab_2e17_bd89, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3ae8_41df_e3b7_290b, + 0xf13f_ecde_fd80_6170, + 0xac39_a24e_4ce5_3f97, + 0x26ce_75c5_b2d5_e122, + ]), + pallas::Base::from_raw([ + 0xe473_f056_dc79_115d, + 0x76e3_15a5_ad62_0dae, + 0xa313_6c1e_483c_9721, + 0x3e6d_83ea_86d5_d926, + ]), + ), + ( + pallas::Base::from_raw([ + 0xae2f_741a_d983_16f1, + 0x382d_5858_cf9d_44bd, + 0x384e_98ab_f72d_0fbe, + 0x30f3_d34b_406d_3804, + ]), + pallas::Base::from_raw([ + 0xc245_5362_2247_8063, + 0x3e17_2608_60b4_bb4b, + 0xb86d_dafc_c500_27d6, + 0x2fe8_4841_5f29_2c18, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7b6a_1128_3c3c_80fa, + 0x9ca2_b9c2_dff3_f3b4, + 0x340f_f261_7ed1_3780, + 0x3e0c_e5ac_ec7d_2bec, + ]), + pallas::Base::from_raw([ + 0xd741_2e2b_3d37_fb89, + 0x6247_866a_c8b1_fed8, + 0x6f6b_0239_2805_590c, + 0x0ced_382c_9268_f3f4, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf517_dcec_62e9_e3ab, + 0xef0f_7eeb_1b0c_6735, + 0x8b97_f27c_70f9_37cf, + 0x3b8f_7d37_f83c_eefc, + ]), + pallas::Base::from_raw([ + 0xc89c_a8e6_7126_cae0, + 0xbb34_8af9_7905_e208, + 0x454a_e56d_15e2_6ff5, + 0x1e45_8d1f_2c82_bdf4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8f21_d255_acaa_5b90, + 0xe729_e5a0_0840_e7fb, + 0x56c6_9b75_0b63_eb5d, + 0x1eaa_c9b9_4cc3_82f7, + ]), + pallas::Base::from_raw([ + 0xa370_d467_d4f0_adf4, + 0x5ec1_c5cf_197c_0be2, + 0xc1cf_51f4_f03f_6d42, + 0x09fb_0444_4edc_27ff, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb12c_0d2f_205c_31fe, + 0xeea6_f71c_6bfb_1591, + 0x67af_0a9c_f750_03b2, + 0x0398_bc66_1d37_47e7, + ]), + pallas::Base::from_raw([ + 0xcfcb_c59b_a71c_e886, + 0x13c5_dcea_abed_f8e3, + 0x94cc_f92b_e337_137f, + 0x0684_13f8_7199_260e, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf82d_26f7_308d_982b, + 0xa463_f795_5164_d737, + 0x943b_0cdb_2a01_2793, + 0x36dc_43c7_d977_77d2, + ]), + pallas::Base::from_raw([ + 0x28d0_729c_c900_9cd4, + 0x95ce_751e_83b7_6438, + 0x7f75_8d0c_1ee4_d82f, + 0x0484_975c_8703_23b2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x573b_6407_c295_bc03, + 0x3d81_4362_3653_b276, + 0x715a_5538_1619_6299, + 0x1a2d_544c_2924_13dd, + ]), + pallas::Base::from_raw([ + 0x35eb_0d37_a82a_ebb9, + 0xc43b_bf74_7668_ff2b, + 0xb086_e27f_cabe_e36f, + 0x2aa3_498b_0d5e_e868, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbdc2_bfad_362e_4e45, + 0xe6a5_1dc4_a99d_9290, + 0x7a7b_06f1_60f6_7d5d, + 0x19cd_8877_65a7_d9d8, + ]), + pallas::Base::from_raw([ + 0x8d51_6305_64ff_936f, + 0xfadb_c64d_5013_97ef, + 0xc69f_7c0f_10e4_e5bc, + 0x2d2a_dbea_d1b6_989d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x583a_b4b1_cd55_7263, + 0xaae4_f810_312f_8c56, + 0x2f23_2a1d_07d9_df4d, + 0x2fba_b5a3_c4c6_773a, + ]), + pallas::Base::from_raw([ + 0x73e5_4412_d35d_bef2, + 0x3ece_87a1_67ec_e5a8, + 0x9223_f63f_59e9_6781, + 0x0ee5_e010_f598_055d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2969_6dcf_d0f2_45a4, + 0x8277_fcbf_73c3_66a0, + 0x6841_c274_1f99_74cf, + 0x3709_2e4c_18e6_289b, + ]), + pallas::Base::from_raw([ + 0x838f_2b53_2d37_f282, + 0x81dd_5e60_c4b0_6016, + 0xfc7c_b034_4511_4e5d, + 0x32ff_9c63_4da9_341f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9ff0_ae31_0044_5d6d, + 0x7cd8_e22c_968c_f64e, + 0x6ec7_7099_fd3f_bc2d, + 0x03ed_d409_4ace_757a, + ]), + pallas::Base::from_raw([ + 0xb949_d0f4_aa7d_daab, + 0x9579_75a6_e421_ce51, + 0xe387_f7c9_68d4_199a, + 0x1076_83d8_acc2_378c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7c36_ca16_0d02_f992, + 0xb3c4_7744_bd2a_cf1b, + 0xd4fa_877a_2a8e_237d, + 0x1394_a3da_f7c5_819a, + ]), + pallas::Base::from_raw([ + 0xb613_5e52_1b63_455a, + 0x4bf9_1fa0_ac38_e15a, + 0x81d9_9f64_1be8_55b6, + 0x2224_1b8b_d75d_1421, + ]), + ), + ( + pallas::Base::from_raw([ + 0xceb4_5f69_b989_fe1b, + 0xb7e7_93ed_c797_8d45, + 0x3b07_5a63_4103_0c39, + 0x2d76_b68f_6c00_d2e6, + ]), + pallas::Base::from_raw([ + 0x3ac8_1f11_ce1f_0c3e, + 0x5065_af29_2ab5_050c, + 0x8ac1_305a_a9ba_43f1, + 0x1697_81ba_e7d9_3067, + ]), + ), + ( + pallas::Base::from_raw([ + 0x20fe_f089_d835_a48f, + 0xcb23_7766_d607_2a64, + 0x18ef_0b57_dd7c_3b7c, + 0x1a3d_5f57_8f57_a4a0, + ]), + pallas::Base::from_raw([ + 0x2574_2597_3e32_c989, + 0xde95_cb1a_38b5_9aee, + 0x5e69_c45a_328e_ae08, + 0x12fa_3870_5145_9017, + ]), + ), + ( + pallas::Base::from_raw([ + 0x54cc_b71d_b115_00f3, + 0xeae2_7b08_c955_4e07, + 0xd7ac_a091_6e33_815d, + 0x0924_e273_11dd_a853, + ]), + pallas::Base::from_raw([ + 0x6d61_f93c_cce9_942a, + 0x2d9e_5dd1_7e83_e833, + 0x8410_7aea_6d67_f750, + 0x3cbe_6e62_757c_a1a5, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7496_11d0_b797_7376, + 0x91ee_1646_4ccd_ec34, + 0xa71a_ee11_a82a_121a, + 0x0b9f_4bbf_d185_de95, + ]), + pallas::Base::from_raw([ + 0xd157_72cc_ea7e_a26d, + 0xd171_58db_8708_6d27, + 0x6093_1fae_1eea_2b4d, + 0x34dc_4613_b828_dd99, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1fbd_03d8_1ee5_f96f, + 0x371c_9782_b691_077a, + 0xf52d_f5a7_19bf_5ce5, + 0x02d6_cab2_451d_9d53, + ]), + pallas::Base::from_raw([ + 0x19b8_5371_5e90_f9c3, + 0x6e1e_109e_62b7_e02b, + 0x4302_b72c_1d4a_5b35, + 0x10b0_be6d_e221_46f8, + ]), + ), + ( + pallas::Base::from_raw([ + 0x24cf_34c7_81aa_7322, + 0x3247_bd2e_b1c4_5ee3, + 0xf9ae_4378_0413_ea39, + 0x0d16_dcb8_7865_78a6, + ]), + pallas::Base::from_raw([ + 0x9d41_e502_3ba7_b1c8, + 0xfe5e_2c86_fc66_1a81, + 0xe729_c0d6_427e_0fc3, + 0x05f3_05ca_4971_05cb, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1afa_a868_252f_7b9d, + 0x9e19_617b_52e9_b42c, + 0xd5f4_449d_e9e3_f183, + 0x322a_0e11_63ce_20e8, + ]), + pallas::Base::from_raw([ + 0xb391_cb0a_6351_bf73, + 0x610b_1f7b_4f5a_6acd, + 0x3a76_9f31_cd20_aa26, + 0x3ce3_28a2_561b_bf67, + ]), + ), + ( + pallas::Base::from_raw([ + 0x30c7_9e39_fd40_3fb2, + 0x1d82_1256_916f_f504, + 0xfe73_efde_65d6_2b6b, + 0x2994_4742_c6a3_4de2, + ]), + pallas::Base::from_raw([ + 0xaa6e_9941_b2a5_4d17, + 0x50e2_4496_1b34_d4ca, + 0x6b06_42c4_a199_cb45, + 0x01f3_609d_eefd_a00c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4bab_06a5_e077_4a9e, + 0xa330_3b42_52bd_5399, + 0x4bb4_1f7b_e729_9520, + 0x3e21_edef_d0eb_fab4, + ]), + pallas::Base::from_raw([ + 0x464c_145f_cb64_99e5, + 0xdf35_61b7_1aca_4810, + 0x04bc_58fc_3756_5635, + 0x1c58_279a_6bbf_5750, + ]), + ), + ( + pallas::Base::from_raw([ + 0x99f4_f0fe_9dc0_ca44, + 0x02fb_d682_b042_1702, + 0x2efe_f131_2968_d890, + 0x15e2_222e_6db1_4410, + ]), + pallas::Base::from_raw([ + 0x67a0_83e2_c99a_0667, + 0x23ab_d14a_6bd2_87fb, + 0xabdd_eb1e_2a5d_fdb0, + 0x0b60_960a_be90_667e, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc740_684e_ba5a_4faf, + 0x4c22_ce95_137c_c36c, + 0x281a_a42b_c45a_2b1a, + 0x376b_146b_97e0_9a59, + ]), + pallas::Base::from_raw([ + 0xc4ef_bdb0_982a_e289, + 0xf554_a7b6_564c_83fc, + 0x8a67_d20f_be31_f8df, + 0x2c37_9f03_4178_6df6, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2ab6_2fa8_4b1f_8e4f, + 0x5604_7652_51e0_d7d4, + 0x861d_2eb4_6261_0b9b, + 0x31c4_e0bd_450e_6e50, + ]), + pallas::Base::from_raw([ + 0xd8d3_3e9e_f72a_be1e, + 0xb803_ee7b_7f4a_6cef, + 0x9a3c_b438_0c39_4da6, + 0x1228_8e27_13b3_b5f0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1d8c_119e_1fea_0f3e, + 0xcf48_8423_afc0_a753, + 0x0b6e_dc85_421d_1af4, + 0x261f_f1dd_ff1f_5d8e, + ]), + pallas::Base::from_raw([ + 0x0199_5750_d19e_6caa, + 0xad1d_82df_b749_8ed4, + 0x247b_d709_b691_31b7, + 0x2539_b879_15a5_f780, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7589_fb80_f8e8_939a, + 0x9ebd_0810_d45a_bccd, + 0xfdba_79ca_0ccc_9b44, + 0x1aa0_1aea_8f5d_c980, + ]), + pallas::Base::from_raw([ + 0xa555_fa27_6b83_e30b, + 0x578f_1dd3_8ca4_8a1b, + 0x1df7_d102_f86b_370d, + 0x394a_4663_4e33_5391, + ]), + ), + ( + pallas::Base::from_raw([ + 0x20a6_ffb4_b2a0_6005, + 0x92eb_8f71_fb24_c4e2, + 0xaa8d_0967_96f7_84f8, + 0x16e3_1607_8d0e_330d, + ]), + pallas::Base::from_raw([ + 0xdd44_1a60_d432_5070, + 0x4b11_771a_1b21_7b5b, + 0xcd9b_504e_26da_2f48, + 0x300a_ebe3_2a72_477e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x744f_4c87_d1d4_508c, + 0xb3ec_6058_d948_a573, + 0xc4cc_b479_43b9_9fea, + 0x292e_928f_7ceb_3ff2, + ]), + pallas::Base::from_raw([ + 0x87ce_4476_03d0_27ad, + 0x9bb9_90ca_e688_0b20, + 0xc92d_41da_e465_745a, + 0x0223_81f5_2d29_279c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0762_3807_30d6_1de3, + 0x1edd_e3f3_3771_cc05, + 0x1b7f_051c_c2b6_4ab3, + 0x0453_ef02_8a0a_0e93, + ]), + pallas::Base::from_raw([ + 0x4af4_208c_5106_b83f, + 0xa12f_0bb8_1911_e9b2, + 0x8d23_55e3_13ca_6284, + 0x199a_c626_77dd_e42d, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa5d5_078b_65a5_31cb, + 0xd6d4_d396_40a8_dead, + 0x7395_10a3_2e84_194a, + 0x0746_7e7f_2d24_b2da, + ]), + pallas::Base::from_raw([ + 0x2aca_d55d_1851_65e5, + 0x0fb2_146a_77c9_c7ad, + 0x4a21_f9c7_5a25_793d, + 0x051f_b653_7e5c_669a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5c77_2c3e_2739_e3fc, + 0xfef3_5127_c5dc_20fd, + 0x93d1_092b_43f0_b004, + 0x3610_1ab4_3a30_2e81, + ]), + pallas::Base::from_raw([ + 0xa206_2c8a_6927_2961, + 0x95ae_1eda_33fe_553c, + 0x0c55_8ed6_13c3_1d9f, + 0x2679_b3ad_0bb1_f518, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4c99_4a2d_9356_6587, + 0xdeeb_0d82_95d1_2d60, + 0xe1c2_2593_45ec_8dc4, + 0x2a35_d2f2_3772_88eb, + ]), + pallas::Base::from_raw([ + 0xdd98_203f_af9d_ee4d, + 0x5c66_2c8c_c521_19b8, + 0xa63e_cdcb_8480_514e, + 0x18da_f4d5_332a_1baa, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd84c_d0ad_ccfa_70c3, + 0x007c_d209_cc31_09a0, + 0x78ed_585e_3225_fa0f, + 0x039e_aa43_428a_bea0, + ]), + pallas::Base::from_raw([ + 0xb8f7_04f9_aa61_c65c, + 0xb502_1eee_d781_1fd1, + 0x5e3a_d1e1_0641_c3b6, + 0x286d_2d99_97fb_09c8, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2f16_78b3_f543_9c73, + 0x9f1b_1cdd_d30a_111b, + 0x1601_479f_9b32_834d, + 0x17c1_c1b6_9420_721b, + ]), + pallas::Base::from_raw([ + 0x5b68_0b72_90c2_201a, + 0xf9f9_cb4d_b01a_bc23, + 0x1d4e_6bd4_f26a_e504, + 0x389d_e069_5988_822f, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb1cc_a546_a26c_bd86, + 0xd581_36f0_8d48_7530, + 0xc022_5c61_93c4_2f07, + 0x3900_59d2_1cdc_6bd4, + ]), + pallas::Base::from_raw([ + 0x6b0c_72de_318e_22d4, + 0xc25b_9e3a_542f_b6b6, + 0xa7a8_06e6_da9f_7440, + 0x3c2a_71ce_4326_8d4d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x50e1_11a2_0489_f122, + 0x07db_1ada_fd5f_8ecb, + 0xf6a6_51ee_1f90_02b9, + 0x25dc_8f85_41ca_57ca, + ]), + pallas::Base::from_raw([ + 0x8241_e6e0_c4ed_eedc, + 0xe1db_5a05_57a0_5392, + 0x4b07_d4a1_6ee5_32a3, + 0x1d1a_0c97_70ed_f432, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb26b_68f6_0086_71f4, + 0x75db_a00b_0b9e_4307, + 0xd9a7_65b7_c82f_711f, + 0x2765_f4c0_27cb_e853, + ]), + pallas::Base::from_raw([ + 0xb2a7_d024_e97a_b65b, + 0x1df5_ee26_7727_2a6d, + 0x6c0e_4551_c789_273d, + 0x27a1_7d99_2875_639d, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdb10_fcfe_4260_d68d, + 0x7154_7c95_a7be_2663, + 0x1258_286a_d0ce_a1a3, + 0x27df_52ac_88e9_47ea, + ]), + pallas::Base::from_raw([ + 0x3d4e_5b53_00c3_0fb7, + 0x93f6_bee4_3688_6e32, + 0xbce4_cf1c_bdd9_555d, + 0x2046_32cf_c355_10e2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8b7c_28f8_d7ec_c2a6, + 0x3484_bab7_04d2_2a17, + 0x68eb_5538_e6d8_bfcd, + 0x0408_8d25_0509_fd2f, + ]), + pallas::Base::from_raw([ + 0x57f8_018e_752e_b908, + 0xc709_94e2_3360_b3e9, + 0x31b5_e0c5_969a_1364, + 0x241d_3b9c_b64c_1c51, + ]), + ), + ( + pallas::Base::from_raw([ + 0x070f_a907_6332_8b37, + 0x80b7_7fc9_f7d1_454e, + 0xa851_ba95_b01c_9b9f, + 0x104f_1754_7220_df3c, + ]), + pallas::Base::from_raw([ + 0x7dc8_7e53_05e7_19fb, + 0x338f_f6d5_fbd5_8b87, + 0xf8c5_e720_14f7_325f, + 0x0309_fefc_0c51_fe88, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb33e_8147_9cf8_1580, + 0x3d43_f778_431a_39d8, + 0xca34_2567_f8af_16d1, + 0x3113_430a_214b_0cdb, + ]), + pallas::Base::from_raw([ + 0x13c9_5973_0b4f_4399, + 0xe534_1a6e_c65c_a2b0, + 0xcb52_a623_3314_e4d3, + 0x2ced_c552_f9fc_da21, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe2f2_d321_bc0b_2ca7, + 0xb252_6d8b_b9f2_415b, + 0xbf60_2a8f_52fe_1001, + 0x3d68_f289_95eb_8ebc, + ]), + pallas::Base::from_raw([ + 0x15bc_988c_f260_e0ae, + 0xd3fe_7253_a715_73b4, + 0x5d6e_a086_5a30_55b9, + 0x08ef_9afd_ec67_40fe, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4fa6_d6c3_91be_c514, + 0xb5a8_3229_b864_ecb8, + 0x3559_3f27_4198_2167, + 0x1402_abd3_64b4_91e4, + ]), + pallas::Base::from_raw([ + 0xad4f_91b5_5f82_9cbf, + 0x6078_24ce_bd17_46c1, + 0x8941_5aad_ad35_5caf, + 0x1e1d_54b1_8e31_aa02, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf268_ffe3_ff17_92f0, + 0xd30d_826d_c124_7ab7, + 0xd835_dea8_0451_546a, + 0x07f3_787f_5b1b_6728, + ]), + pallas::Base::from_raw([ + 0x06be_7c30_22da_5b48, + 0x6119_b0af_e57e_eece, + 0xffb2_ef93_3bd0_ebed, + 0x0169_f72c_db1b_2fcb, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe456_2834_294b_888d, + 0xbcfd_513b_64f2_5684, + 0xf08e_b8fb_c4ab_d423, + 0x0fb6_00e3_c86b_b49f, + ]), + pallas::Base::from_raw([ + 0xe7bc_fc0e_3b3f_f8de, + 0xbea6_5eea_f9ae_d71e, + 0x70df_ec85_cd8a_1b58, + 0x19f6_5367_896c_d8a6, + ]), + ), + ( + pallas::Base::from_raw([ + 0x34fa_bfcf_3c22_1b33, + 0x044b_883c_d5c6_097b, + 0xbfb0_715a_00cc_0c7c, + 0x0679_54d0_2044_c9f5, + ]), + pallas::Base::from_raw([ + 0xd919_1bda_61df_8d07, + 0x6d2a_a446_a685_4475, + 0x92bc_bfbb_a58a_db13, + 0x065a_ecfb_ba1c_4f39, + ]), + ), + ( + pallas::Base::from_raw([ + 0x86c3_eb48_7c88_bd57, + 0xdb8b_d4d0_11d2_f88e, + 0xfad8_69ea_a7e4_4dcf, + 0x3022_30ed_6e9c_6e28, + ]), + pallas::Base::from_raw([ + 0x0fbe_eda5_bba2_91f6, + 0x92ea_6a64_ab91_359a, + 0x2e65_7270_4b8b_8373, + 0x30fd_7673_e584_02f4, + ]), + ), + ( + pallas::Base::from_raw([ + 0xea84_2eab_a955_bf3a, + 0xa20d_8ca2_651d_19b9, + 0xab2e_3150_8ceb_fb7a, + 0x3aca_f491_8373_e853, + ]), + pallas::Base::from_raw([ + 0x44ca_a4f6_92ec_ca97, + 0x2b2e_4b9a_22ac_d3d3, + 0xbc30_279e_ff7b_e6aa, + 0x3b91_6c71_845b_1715, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1202_b03b_21d1_d047, + 0x03ae_5752_aa54_b5be, + 0xc925_4108_2eaf_2a3c, + 0x13ad_e51c_5d29_0b7a, + ]), + pallas::Base::from_raw([ + 0x8041_8cdc_5c36_974c, + 0xd604_0473_1a81_c29f, + 0x23a2_ba06_9389_1b6d, + 0x266f_9adc_4b16_36a1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9afd_aa5e_40ca_0c02, + 0xa358_984d_a414_8a51, + 0xacd4_cc7c_9942_0f78, + 0x2187_c185_232c_318b, + ]), + pallas::Base::from_raw([ + 0x8a81_3d5d_499f_4580, + 0xa513_113e_dfa2_d826, + 0xeb8d_fef7_f22d_f466, + 0x2031_fd3b_4359_99d0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x038e_6561_3318_309a, + 0xfbbc_cc98_6867_1618, + 0xc078_f45f_0b90_87ed, + 0x30f2_e8ef_849c_bdbb, + ]), + pallas::Base::from_raw([ + 0xa36b_5a01_49ef_da91, + 0x7a95_de96_1b20_9374, + 0xecca_06aa_0f3b_5ab8, + 0x31dd_63b8_64cb_de15, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4796_b297_f98e_93b3, + 0xf493_b341_1ed9_021e, + 0x1959_49f0_5a11_c922, + 0x35c5_a032_4cc5_8786, + ]), + pallas::Base::from_raw([ + 0xfe71_cea7_2663_1804, + 0x3b35_545d_e5bb_07ed, + 0x1e0c_8c02_6408_9be0, + 0x24b2_4b27_139e_827e, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf33d_521e_4818_701b, + 0x0ed0_6b9b_4fb1_12eb, + 0xd38d_f6b1_d568_5738, + 0x2f83_54a3_e364_1f2d, + ]), + pallas::Base::from_raw([ + 0x2f57_b58b_8e5d_489c, + 0x88b1_ab17_4dc3_61c5, + 0xfffe_6160_1707_a091, + 0x0797_75d9_3fd9_4026, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7d09_4b08_9f06_34e0, + 0x3260_4273_2097_b4f3, + 0x2d95_5d56_8f05_4ae3, + 0x2094_83e1_a0b0_a837, + ]), + pallas::Base::from_raw([ + 0x7432_cb32_c68b_2165, + 0x7bd9_2165_7fb0_2d3f, + 0x0f8e_cd9e_6143_ed8a, + 0x1e80_5fa9_b94a_ec49, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5113_896c_8344_ca04, + 0x3ef8_8140_14dc_b062, + 0x4412_402a_64e3_8636, + 0x2ca2_c0ca_7fdf_3aab, + ]), + pallas::Base::from_raw([ + 0xaed4_11b6_dd39_334e, + 0xacbf_64e8_3c88_59f5, + 0xc68a_900f_bbf1_8b0f, + 0x35c2_a197_59b7_ad1c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3d3e_18af_68d8_62fa, + 0xe3d5_0649_7f05_6b13, + 0x4e45_faad_be7b_7fd3, + 0x1859_da3e_20cd_e957, + ]), + pallas::Base::from_raw([ + 0x645d_a090_f36a_e749, + 0xd41d_b3b2_a046_2af1, + 0xa96d_2c5e_6597_3c58, + 0x141e_1418_8f51_e0d0, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdabb_00be_7a67_fef0, + 0x5c7b_cf1c_6ad7_edda, + 0x936c_f456_316c_55a1, + 0x0d3a_6bba_e167_7f06, + ]), + pallas::Base::from_raw([ + 0xd39b_8075_0060_baa3, + 0xe8e3_40fa_3051_61af, + 0x195d_c44b_0585_652d, + 0x00d3_796b_0ba9_07d1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0f72_b95e_c033_1d81, + 0xc509_70b4_8e36_3af0, + 0x8d67_3023_a716_fcc4, + 0x012d_485d_fb24_6814, + ]), + pallas::Base::from_raw([ + 0xed91_6bb8_2624_17de, + 0x426d_028d_0170_49d3, + 0x398f_1550_b9e3_0d8c, + 0x0918_c246_acb5_fdee, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1293_b9c9_0725_4044, + 0x4088_0cdf_1a2f_acdc, + 0x4de3_cd80_f405_03be, + 0x21c9_dc8c_e021_5c1d, + ]), + pallas::Base::from_raw([ + 0x2a0b_32e0_23d9_a3bb, + 0x8dfd_3300_2c4d_a85d, + 0x90fa_fa54_eadc_032c, + 0x2b22_e6b4_d976_bb94, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7f67_fb22_3643_6df5, + 0x8cb7_a940_6905_bff4, + 0x0700_85c6_481f_f20a, + 0x2c67_6bf1_c4b8_6158, + ]), + pallas::Base::from_raw([ + 0x7cf1_8c00_4a9c_1a9e, + 0xf931_e68f_a15f_b06c, + 0x4b5a_0021_677e_9587, + 0x2fb0_f64a_a35e_6c60, + ]), + ), + ( + pallas::Base::from_raw([ + 0x980c_1135_0270_595f, + 0xda07_a0c2_5480_8a60, + 0xb0aa_67e1_dd15_8881, + 0x2075_e446_9ffc_c6fb, + ]), + pallas::Base::from_raw([ + 0x44d0_d6f6_aa0a_1932, + 0x862b_52b6_f304_e01b, + 0x690d_6e48_b517_135e, + 0x22e5_932c_6392_e33f, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf261_d24d_d55e_fa46, + 0xe3a2_8ce1_2ece_2d1d, + 0xb6c3_ed0f_5de4_1a3c, + 0x1cb0_da91_1a87_cfa0, + ]), + pallas::Base::from_raw([ + 0x5442_5655_5336_1f42, + 0x089b_c245_a048_38f5, + 0x8dcc_4e66_7941_5a9e, + 0x34c6_80fb_1dd7_661b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2d65_9072_d56e_5b18, + 0x7987_24b4_22bd_dbf3, + 0xa346_5bd1_1778_6f23, + 0x3961_400e_4b13_e385, + ]), + pallas::Base::from_raw([ + 0xfd5e_6292_aed2_5d3c, + 0x4875_16bc_b3cb_783f, + 0x8e9f_fe18_293c_5856, + 0x257e_cda9_99c3_d0cf, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8313_e47e_aa56_0f12, + 0xa288_d1ce_19d0_c1ce, + 0x0de7_47c8_24b5_d9ce, + 0x1ef9_17f6_6050_f9b8, + ]), + pallas::Base::from_raw([ + 0x51b3_c417_aae3_d512, + 0x308c_26cb_3ef9_b520, + 0x9e24_fee2_0510_c124, + 0x10c4_5920_4a45_09fa, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5354_70f6_26b1_1d0b, + 0x366e_2b98_c3f8_51b4, + 0xc02a_1aef_b019_705e, + 0x0ef2_f878_dec4_3a29, + ]), + pallas::Base::from_raw([ + 0xbbe1_eafd_c8f8_d6fb, + 0xba82_354a_c062_0fc1, + 0x4984_289a_d39b_917c, + 0x32b0_046e_bbd5_1e14, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe5af_e709_3216_cf73, + 0xce7c_ea62_2592_e580, + 0x073b_be3d_9d7b_463d, + 0x0205_976f_e9c1_b9d7, + ]), + pallas::Base::from_raw([ + 0x1eef_2946_b9ab_16dc, + 0x5513_ea0e_5cab_a5c4, + 0x0174_f38f_88f9_1425, + 0x16b7_f6c6_6548_4de9, + ]), + ), + ( + pallas::Base::from_raw([ + 0x07cb_2e9c_7762_5c85, + 0x33f6_d324_7351_1aec, + 0xf7f0_ac0e_f1f1_e7ae, + 0x3e3f_c594_4e97_90ef, + ]), + pallas::Base::from_raw([ + 0xf286_21ff_e28c_23ad, + 0x44e4_45d2_2eb5_fae6, + 0xddc6_91ad_5bdf_57f0, + 0x36bf_156f_395d_dca9, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd524_a165_4426_7d9a, + 0x1f66_2e96_84b2_aeab, + 0x2c89_6845_13b1_47ef, + 0x38c9_c6de_31d0_54f1, + ]), + pallas::Base::from_raw([ + 0x05ac_032b_5a68_e21d, + 0x13bb_ddc0_606c_20ab, + 0x3026_8ada_227c_8b3a, + 0x2d5b_3fbe_7b71_ff46, + ]), + ), + ( + pallas::Base::from_raw([ + 0x11b6_85e3_842e_c92b, + 0x9944_f4db_a624_498b, + 0x9fc0_d641_b040_6a97, + 0x1647_4f51_124c_377f, + ]), + pallas::Base::from_raw([ + 0x1168_60f3_af34_2dae, + 0x7805_c248_f3c3_fcc5, + 0x4813_d152_d1d3_098a, + 0x208f_c6df_823b_e5f9, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc169_7a99_835e_be3e, + 0x5ef1_b7bb_3199_1aca, + 0x423b_d93b_fab8_c937, + 0x2967_35a3_0418_0bdd, + ]), + pallas::Base::from_raw([ + 0xbdb7_c009_07a2_d1da, + 0x368c_8c5b_b543_e0a1, + 0xc213_9520_868c_748e, + 0x0f8a_171f_46c2_fe7c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6adb_fb16_aac0_e0c1, + 0xe670_9f7f_a329_f25c, + 0x0345_3350_36cd_ada1, + 0x067c_220e_1a5d_efb7, + ]), + pallas::Base::from_raw([ + 0xeffb_7a08_1fd4_52ca, + 0x91c5_539e_3995_fa41, + 0x949f_e270_b209_2bc2, + 0x0c94_eb80_956c_63f5, + ]), + ), + ( + pallas::Base::from_raw([ + 0x76ce_f08f_c4b9_3ce9, + 0xcd65_00bb_d889_d19b, + 0xcb76_bc91_e514_c0ba, + 0x2676_b3ed_f17f_cc35, + ]), + pallas::Base::from_raw([ + 0xa6ee_7cfc_440d_3bb1, + 0xf9f0_fa37_559f_b568, + 0x5c75_9922_987b_e0fd, + 0x18ba_9b25_9fb9_1ecc, + ]), + ), + ( + pallas::Base::from_raw([ + 0x88aa_9070_b7aa_f1e5, + 0xed19_124a_2b33_bb67, + 0xe56e_e569_c5d2_f813, + 0x3c6d_3d48_689c_6545, + ]), + pallas::Base::from_raw([ + 0x2dd3_3c98_6d92_2737, + 0x4a38_67c9_cc68_d3c5, + 0x0dd8_104b_2350_8489, + 0x1220_a703_5129_d555, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf34f_b7dc_c29e_e3f6, + 0x68ff_419f_787f_d6b4, + 0xe113_6044_b650_fd24, + 0x0f07_bb4e_3f0a_06bb, + ]), + pallas::Base::from_raw([ + 0xaf2f_3c9b_30d6_4868, + 0xb876_d0bd_eebc_8d41, + 0xd5e1_cfea_a531_971d, + 0x049f_733f_9545_e7b0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0730_f7ca_1dba_2634, + 0x229c_c86c_ac49_a95d, + 0x299f_5487_67f0_5bff, + 0x1ff0_6c6f_bbc9_7265, + ]), + pallas::Base::from_raw([ + 0x42d4_85b3_c343_fb9e, + 0xa6a0_b509_d7f4_32ed, + 0xea60_7525_3e54_c341, + 0x2c00_143d_8e9b_261e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8fda_58ae_2efc_f247, + 0xee35_0022_54f7_e10e, + 0x736e_f067_a1d8_6854, + 0x171b_73f6_f381_7bdb, + ]), + pallas::Base::from_raw([ + 0xc34f_b482_2878_1165, + 0xf1a5_16af_a161_fc8f, + 0x98c7_8cbd_6d9f_f721, + 0x3b38_7dd3_b99f_11c1, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf52c_8ddc_ff5b_01c2, + 0x523d_70df_bd93_ca28, + 0x9c29_d3a5_c4c1_3774, + 0x31cd_fecb_38de_73c3, + ]), + pallas::Base::from_raw([ + 0xc68c_3f28_26b1_9cb8, + 0xfa8b_7593_0610_fe60, + 0xdef2_d495_2f51_9984, + 0x2221_6cb6_ddad_b185, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6bed_8edf_f3c0_d24e, + 0x99ef_37b9_cdba_a0d4, + 0xaf57_7449_65fd_54db, + 0x2704_ec4e_2419_ca44, + ]), + pallas::Base::from_raw([ + 0x8cdf_2bd0_b67e_b1c2, + 0x58f8_a33d_b426_e86b, + 0x653b_af62_fe66_967f, + 0x2326_7f58_4dce_4708, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6312_f284_adb7_2234, + 0x774f_0aa2_fa1c_ab96, + 0x7b2f_4ce7_137c_5695, + 0x0864_b203_83da_e0d2, + ]), + pallas::Base::from_raw([ + 0x731d_3ed1_53fe_b76c, + 0xcaf0_32bc_d5c9_4c3e, + 0xc4b6_0b28_83b2_aac6, + 0x2125_d63c_b9cf_c0b5, + ]), + ), + ( + pallas::Base::from_raw([ + 0x53e9_3f17_a1d9_9759, + 0x5bbd_c73e_e01b_a020, + 0xacf7_bba6_f9d3_2f75, + 0x1952_c1c8_1635_6a75, + ]), + pallas::Base::from_raw([ + 0x66ec_f0b6_1af3_d4e0, + 0x571c_0924_ed6e_1a90, + 0x7705_b5de_1fc0_14c0, + 0x1fd7_5a22_f673_ea80, + ]), + ), + ( + pallas::Base::from_raw([ + 0x51e1_ea40_8c91_aad6, + 0x9db3_dc4a_6dd7_5da3, + 0x565d_5a06_e81f_9a96, + 0x0b2a_fb0f_ed99_5390, + ]), + pallas::Base::from_raw([ + 0xcb85_e6f7_24eb_eaa3, + 0x0ae7_3db8_9527_b9dc, + 0xcbe3_28e1_951c_7a68, + 0x2e2f_ae1a_239e_1571, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf958_eaa2_64c1_89d5, + 0xfd66_b7d1_121c_4ff7, + 0x34b3_6c0c_e96d_541b, + 0x0e73_8609_6c3b_0025, + ]), + pallas::Base::from_raw([ + 0x2cbd_19de_7f3b_b8ec, + 0x049d_9874_0dc9_177f, + 0xdc76_e494_dc8b_6c50, + 0x3c16_ea6c_a968_ab1a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x915a_adcc_89b7_daf3, + 0x2909_ff1c_0e1f_9785, + 0xd5bd_a128_019a_7ef2, + 0x3d37_fb8f_9eea_8d51, + ]), + pallas::Base::from_raw([ + 0x09be_e328_a080_f138, + 0xe673_5890_07fe_2120, + 0x2113_15e1_5d7c_9110, + 0x190f_69f4_eefd_5477, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9887_d028_c814_459b, + 0x0bfe_9714_c977_9a7d, + 0x09bc_a123_a070_2c37, + 0x2ab5_d63f_55f9_33ef, + ]), + pallas::Base::from_raw([ + 0x7704_3175_669a_cbaa, + 0xd2d9_3256_279c_aaaf, + 0x163b_f414_992e_0d03, + 0x2dd3_ad25_0723_378a, + ]), + ), + ( + pallas::Base::from_raw([ + 0xaa70_af4d_2c62_1ee1, + 0x82bf_76f9_a44f_e16a, + 0xd823_dfb3_28e9_1bf4, + 0x0e2e_86a7_8fef_50ce, + ]), + pallas::Base::from_raw([ + 0x96df_97dd_95e6_9e90, + 0xa340_7d75_e959_bcd6, + 0x8806_08c5_d89f_9255, + 0x2de3_b3e1_6dc1_2b33, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0c96_2934_58ec_b2cb, + 0x0d7d_864d_7d1e_560e, + 0xec28_d3a6_3cdd_c555, + 0x0de0_eb00_0263_741a, + ]), + pallas::Base::from_raw([ + 0x1cc8_207d_937b_6f0e, + 0x9797_e7ee_dc7e_c8c6, + 0x2286_088a_e441_e77a, + 0x242f_4811_c54f_c5a4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x02e6_08b8_b421_3091, + 0x04f9_d82c_26af_83fb, + 0x4d9c_e544_ba2f_2525, + 0x3f8e_e248_4851_a53b, + ]), + pallas::Base::from_raw([ + 0x9dac_0a3c_f93d_2ab4, + 0x7ed7_5569_f31b_ed67, + 0xb530_b73f_14e7_8731, + 0x320a_6858_c7ef_b1d8, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf1c7_9c73_c18c_5bef, + 0xb4dc_fb27_be5d_af33, + 0x00fb_3437_a413_9ab8, + 0x27a9_6926_feb8_4658, + ]), + pallas::Base::from_raw([ + 0xf94f_9504_a805_5901, + 0xd816_70b5_5f8c_c186, + 0xd80a_41c9_3fca_d44c, + 0x35fb_6dfa_6996_faf9, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbad4_1cc8_8afd_dd08, + 0x49d2_bda3_3181_eb27, + 0x6bd9_a42e_e764_f287, + 0x1bf9_7fbc_aad6_3dd3, + ]), + pallas::Base::from_raw([ + 0x9e70_14b6_b93f_3479, + 0x2ecd_3acc_85d9_0a8e, + 0x9adc_da24_387e_73bf, + 0x1e46_208e_ee13_e6a1, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbefc_4b37_32e2_12e7, + 0x7994_0a4e_1943_82c7, + 0x31b5_2141_ba54_76cb, + 0x0dd7_b8d9_11c6_097e, + ]), + pallas::Base::from_raw([ + 0x878f_37be_45a6_24f7, + 0x9e03_f710_b58d_290e, + 0x9deb_aec0_79ca_08f8, + 0x00c9_3eac_1500_b8bd, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd808_eb89_0309_42fb, + 0xbcff_47e4_811f_e691, + 0x48a7_d2b6_7422_9156, + 0x228e_a752_e00e_fdbf, + ]), + pallas::Base::from_raw([ + 0xf199_ca0a_c24c_d4ca, + 0x6b0f_07a7_0752_8621, + 0x8d45_6536_032d_db36, + 0x0288_3b48_40b1_3378, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe693_43b4_9f9e_5b1b, + 0x89d5_ce6a_32fd_13c7, + 0x9ed7_6f5c_5aa1_9a97, + 0x2ec4_33c9_00e1_bbd1, + ]), + pallas::Base::from_raw([ + 0xeac3_ed86_403e_abe1, + 0xf6fe_9a5e_dfb8_8a4e, + 0x8f6a_6351_97b0_db20, + 0x1e2c_043d_0511_cfa2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9ab7_f215_baa3_94d4, + 0x1d8f_3e16_e38f_a26f, + 0x4159_fc19_7738_7456, + 0x00ab_08a1_f3c0_25db, + ]), + pallas::Base::from_raw([ + 0x7127_4247_2253_84aa, + 0xbb16_d239_fc1e_a68b, + 0x0e3b_b7c2_0be4_3570, + 0x100e_d9d7_3b76_9c8e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x42c3_aec9_48e9_24ff, + 0x9b63_1a8f_ca4b_f414, + 0x9ef2_f7fe_060e_a416, + 0x25b4_8d9f_f095_81d1, + ]), + pallas::Base::from_raw([ + 0x8ae4_2d3a_35a0_766e, + 0x104d_f6d8_e9cd_72d6, + 0x71df_5cb2_0423_2963, + 0x0899_58d7_7854_6236, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa5dc_0bad_a65b_67bf, + 0xf29f_e8a2_2244_b057, + 0x5511_3eee_8db8_b190, + 0x1210_c119_2f7b_d485, + ]), + pallas::Base::from_raw([ + 0x4b62_e8e5_60e9_df23, + 0xe625_32a0_2269_5edb, + 0x4afe_a317_bfa2_f9e4, + 0x0134_67f6_1d26_4b88, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd1d5_3c8c_9f78_209d, + 0x9713_2e54_9dc7_f6d1, + 0x9999_feb6_19e2_ce8f, + 0x1827_19c1_45b2_ad83, + ]), + pallas::Base::from_raw([ + 0x6440_07e8_0d1b_ebca, + 0xb59a_b45a_21c2_c8aa, + 0x37cd_166a_131b_bde9, + 0x3e88_79f1_0539_11ae, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2128_47a7_80b3_afd1, + 0xd62d_98a3_729d_147b, + 0xac76_cd5b_7780_e455, + 0x1371_d855_6493_7957, + ]), + pallas::Base::from_raw([ + 0x03c5_35ac_7821_d861, + 0x1897_7fb8_d4d3_21ff, + 0x57ec_5e9d_09d2_d94c, + 0x2933_8896_3d25_c33d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4c64_9009_4f5b_b733, + 0x2f25_149e_b638_123d, + 0x583b_d5e6_3c07_fe56, + 0x38d0_e3be_cf9a_d8fc, + ]), + pallas::Base::from_raw([ + 0x9482_7610_0282_f357, + 0x9035_4a5d_fd55_d474, + 0xdf90_065f_46d7_3461, + 0x1ee2_e8e9_a7f7_1162, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc2cf_20da_8325_f9f6, + 0x4158_322b_3ab5_e06c, + 0x55ea_0370_e75f_deaf, + 0x0491_5155_2307_b345, + ]), + pallas::Base::from_raw([ + 0x3a6c_b7c7_58d2_1156, + 0x3ee5_aca9_420a_c8d4, + 0x824e_78fc_93b0_042b, + 0x2300_ec5f_8679_d667, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4534_4de7_cd4a_1286, + 0x7000_9fbe_d545_bd95, + 0x56f3_45dc_329c_fe4d, + 0x1b11_0f10_4c22_8670, + ]), + pallas::Base::from_raw([ + 0x587e_67ee_4d03_0b13, + 0x26d6_4ee0_cfa1_e3fd, + 0x31a4_3dbc_e45b_3166, + 0x384c_1a74_cf99_7d87, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa739_b815_03af_5bde, + 0xfe73_4fc3_5916_3751, + 0x87ae_e2ab_a571_0f68, + 0x2880_7f09_fbdb_6069, + ]), + pallas::Base::from_raw([ + 0x959a_ef52_08c3_91fe, + 0x9283_4894_b296_c35c, + 0x4a5c_c47f_99f0_5f73, + 0x04a5_23d4_9550_e5f2, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbbaa_a155_8473_a2f0, + 0x848d_48dd_594a_7097, + 0xe3dc_ec5c_faee_ddc1, + 0x3a8c_2c5a_d7c4_d92e, + ]), + pallas::Base::from_raw([ + 0x3bbc_7750_d4fe_2be1, + 0x70ba_0c48_e53b_baff, + 0xa804_5683_c10d_6804, + 0x1094_6631_c67f_0dfe, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3602_21c5_3677_ccd7, + 0x9de8_46d9_609e_11b3, + 0xc600_0f59_0fc5_e51e, + 0x12e5_6f18_ba2a_83f3, + ]), + pallas::Base::from_raw([ + 0xb472_c933_636e_7cfd, + 0x747b_505e_b1f1_252b, + 0x944f_8b8b_0fbe_9e69, + 0x081b_05b2_cc35_1fcd, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf69f_2736_a2e6_02cc, + 0x40b6_25b7_d5c4_621f, + 0x09ba_e2a7_4bc9_bc80, + 0x19b0_3942_3298_a1b4, + ]), + pallas::Base::from_raw([ + 0xab25_9c06_92dd_6ddd, + 0x8930_0e85_715a_2677, + 0x1518_4d35_6d96_7b91, + 0x1afc_017e_c025_f777, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbd9c_a11d_c9d1_47c4, + 0xa729_70e7_74ea_f665, + 0x4ab2_7f51_e350_8d9b, + 0x1b80_855f_52e2_65f1, + ]), + pallas::Base::from_raw([ + 0x47cd_3ef3_25be_4c31, + 0xe4c1_3895_f52a_ad9b, + 0x3533_9da3_9399_7f27, + 0x2388_7e28_f45a_cf6f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0863_f044_e1a6_11e2, + 0x79e4_5575_8e7a_78f8, + 0xc8fc_9c15_0f54_30d6, + 0x3a7a_8e36_84de_274d, + ]), + pallas::Base::from_raw([ + 0x03a3_2fb5_bab3_3a4d, + 0x0bd2_34a6_5c64_7867, + 0x972b_19ad_d013_d7c1, + 0x1612_2c94_e22e_f260, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdf78_184d_c553_f8fb, + 0x95e8_ea38_3755_2814, + 0x1603_092f_6d39_db38, + 0x2769_e6ea_c901_bb9f, + ]), + pallas::Base::from_raw([ + 0x6aae_1ad2_22b6_2d45, + 0x8341_704f_980d_1ea9, + 0x7317_b503_da60_23a6, + 0x16b1_5baa_a77c_92e6, + ]), + ), + ( + pallas::Base::from_raw([ + 0x35ad_694f_d3f3_93e5, + 0xb977_6bab_ecc1_635d, + 0x673e_2552_a80f_847f, + 0x282a_562e_6ffd_6c2d, + ]), + pallas::Base::from_raw([ + 0xcd61_300f_af9b_7633, + 0x5f78_7bdd_6fb9_767a, + 0xf8e6_3e0b_a3cb_a82e, + 0x3c29_8b6c_70f6_5826, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf978_920b_4033_85f0, + 0x4741_dfed_87e7_f4ed, + 0x9af7_9a50_a097_b040, + 0x09f2_0401_7a7f_3d55, + ]), + pallas::Base::from_raw([ + 0x484c_a7c7_1e1d_cfde, + 0x435e_fe17_c8a5_cbc1, + 0xe981_12aa_7104_5057, + 0x1d2c_9905_b8c5_8026, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8a4e_ddc1_e2a4_e687, + 0x5bff_5ce2_018e_858f, + 0xc6e5_662f_83a1_e56a, + 0x02a2_000d_679b_2dba, + ]), + pallas::Base::from_raw([ + 0x2f6a_f6ea_7c76_b2a0, + 0xb8c7_4bfa_9b69_4056, + 0xfb79_16ad_496d_1e14, + 0x1df4_ca05_f14d_04e2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6088_f6ef_e336_ea5c, + 0x7497_110a_3ab5_54c8, + 0xbb4a_8a7e_90ab_1b70, + 0x16ca_05a8_a644_e307, + ]), + pallas::Base::from_raw([ + 0xbc6b_4bf6_eee0_03cf, + 0x9497_c239_5d17_314c, + 0xa528_8c4e_96df_57eb, + 0x05ec_2cd2_70b2_c32f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1d4b_5aba_2d51_6844, + 0x0ebb_e763_14ac_cff0, + 0xcae2_52c3_1950_5947, + 0x106f_7769_986b_58ec, + ]), + pallas::Base::from_raw([ + 0xec32_de24_bd53_2691, + 0xaf82_b179_9fba_b8aa, + 0xadf7_592d_33f3_4d51, + 0x1dd7_ab82_5380_22db, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7701_5dcc_356c_94f9, + 0xe75b_8f5f_f89f_73ab, + 0x934a_7c89_81e2_4737, + 0x0952_ea05_e704_9ee3, + ]), + pallas::Base::from_raw([ + 0x7bdb_c92d_9d50_75f7, + 0x9985_21f0_5f28_9ed4, + 0x920d_4ffe_7a28_6fb2, + 0x19b4_edf6_4b69_3292, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2d93_48b7_18f9_b3ac, + 0x1ee6_c3d8_07e9_cb72, + 0x27f7_47e3_bd7b_0b59, + 0x3344_264e_0e0f_39af, + ]), + pallas::Base::from_raw([ + 0x5392_14e5_9f27_7f85, + 0xbab5_f15c_75c7_fa84, + 0x5e92_a501_1bcb_a88a, + 0x39be_7bc4_6e98_ebef, + ]), + ), + ( + pallas::Base::from_raw([ + 0xeffc_5cb8_5798_7d36, + 0x4231_e383_e08a_1c06, + 0x83f4_c3e7_99b4_5fe5, + 0x1bc4_4ad9_628d_b324, + ]), + pallas::Base::from_raw([ + 0xb051_73ed_1a60_83bc, + 0x5d3f_b49d_377c_550f, + 0x9ec4_1f7e_c634_c956, + 0x2ea2_a4bd_7949_e352, + ]), + ), + ( + pallas::Base::from_raw([ + 0xce11_b86d_18ab_15c5, + 0xfdfd_58bb_e7cf_ebbd, + 0x011a_1665_1ade_a741, + 0x1867_7683_03fc_dcbe, + ]), + pallas::Base::from_raw([ + 0x8c65_a7b3_7436_63a6, + 0x0895_f62c_82b4_bcca, + 0x697f_2648_19e5_bf42, + 0x1c9e_8a89_693a_c155, + ]), + ), + ( + pallas::Base::from_raw([ + 0x20f1_50c1_b583_d962, + 0x5b7c_4fdd_6bf3_33a4, + 0x6616_f214_6b56_0695, + 0x3c09_152a_ae39_f9d7, + ]), + pallas::Base::from_raw([ + 0x8d4c_d15c_11e4_5f76, + 0x3ee6_fba2_a365_7a95, + 0x57df_944d_2990_ae68, + 0x3e40_9606_dd7f_fcfd, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0f1d_cbaa_c64e_207f, + 0x24ce_0096_04e8_74af, + 0x8206_a341_4f16_3b2b, + 0x1841_ffa5_9560_5383, + ]), + pallas::Base::from_raw([ + 0x7445_4a84_172d_dfb7, + 0x4e61_663d_e974_8c87, + 0x49ac_c46f_7652_ae10, + 0x35f9_eb31_2da0_3d40, + ]), + ), + ( + pallas::Base::from_raw([ + 0x71b2_cf29_e621_7cd2, + 0x85fb_6de4_0cdc_c772, + 0xfcb0_2d60_0a46_877a, + 0x0dcd_cba8_2583_6234, + ]), + pallas::Base::from_raw([ + 0xa5e7_e058_d611_327b, + 0x1f29_dadc_d446_02dd, + 0x50d2_0616_22aa_60e5, + 0x1bc1_4ad7_6911_5d28, + ]), + ), + ( + pallas::Base::from_raw([ + 0x76ad_98d4_ab3b_419f, + 0xffad_8c50_6e25_97ff, + 0x3a06_bbb6_43ea_1ebd, + 0x3da3_b0f3_6ffb_c5f7, + ]), + pallas::Base::from_raw([ + 0xf747_ed0e_6bf0_296d, + 0x08b5_9863_d358_93b0, + 0xfec9_a301_a77f_064e, + 0x18e9_b59e_d7dc_0386, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1965_7b9b_65a0_684f, + 0x2138_8c2d_19c4_1b69, + 0xae4f_210b_d783_87fa, + 0x2e4a_f156_17f9_aa1b, + ]), + pallas::Base::from_raw([ + 0x7e5a_da88_7dbb_23ac, + 0x076a_81da_aa1a_cea5, + 0x46f5_2c4a_4629_12c7, + 0x377c_3c47_a79a_cd00, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd9e6_9b4f_775a_8960, + 0x3cf8_686d_3a8c_2253, + 0x007c_16e7_38dd_f650, + 0x275c_d87f_d1a9_affb, + ]), + pallas::Base::from_raw([ + 0x2eac_fae8_83be_39dc, + 0xa147_cb90_545e_9792, + 0xb894_660b_0ca5_2156, + 0x102c_7bd5_54e6_2a1f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3d50_5225_818a_d743, + 0x7610_a4da_2296_4451, + 0x4ac1_dba3_15cd_e138, + 0x2a63_e9ea_93b4_8b68, + ]), + pallas::Base::from_raw([ + 0x602f_7b8a_987c_a0ae, + 0xf26d_d9ff_d12b_9ca2, + 0x7434_3696_6cde_5174, + 0x1d77_2b01_598d_98f6, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9ee6_79fa_2503_f12d, + 0xe3a0_67a8_ab7c_c1a7, + 0x4a04_cfd6_a900_5d0d, + 0x20b6_e71f_03ce_350e, + ]), + pallas::Base::from_raw([ + 0xff99_352e_01d9_e9c0, + 0x1cff_eb28_f38b_918a, + 0x3f5a_211d_6b2f_6a4b, + 0x35fc_1ac5_b933_3f78, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfd29_ac30_0d2e_c13b, + 0x6d86_8828_a090_269d, + 0x617b_b4e3_df1d_6e3f, + 0x1062_44bd_2099_3d22, + ]), + pallas::Base::from_raw([ + 0xba4b_6207_7d83_b053, + 0x752b_22b3_0554_a5b4, + 0x9399_8320_f3a0_12c4, + 0x3e32_1295_f9a7_53f3, + ]), + ), + ( + pallas::Base::from_raw([ + 0x21b0_2a13_67da_2df6, + 0xd95e_f536_2abb_71e5, + 0xc466_cf97_819a_bb8d, + 0x0ac1_d2f5_a9ef_d18d, + ]), + pallas::Base::from_raw([ + 0x652c_72da_e56f_053d, + 0x3e16_df3c_dd09_25f8, + 0x4486_dc64_f0ae_ae8e, + 0x1083_d1d6_afcc_3d28, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd66d_903f_1504_cb22, + 0x7d74_1d95_24fd_f1a2, + 0x0420_1142_5b05_701d, + 0x2581_a1db_a4a6_28f9, + ]), + pallas::Base::from_raw([ + 0x31f5_3161_9435_04e8, + 0x56b5_086a_e923_27ee, + 0x0470_dba6_f453_d40b, + 0x37b7_3cbb_6a46_8bc3, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5a64_fae6_2a62_095a, + 0x1a5d_b99a_a3cc_09e1, + 0x5c87_bf4d_b4f1_d4cf, + 0x3554_e145_5ac7_ce25, + ]), + pallas::Base::from_raw([ + 0x62fb_29aa_b259_653c, + 0x1079_e028_f669_bf99, + 0xc7b5_e1a2_b66a_56d3, + 0x1da9_6c27_0679_663d, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa38a_7597_05e4_c595, + 0x93b5_16bf_3679_9104, + 0xdcf3_617c_06b7_eca9, + 0x1c04_9649_6246_d90a, + ]), + pallas::Base::from_raw([ + 0xcb9b_62de_2098_b010, + 0x0b11_bddc_e6c4_3c5b, + 0x0e06_9619_2171_a4b5, + 0x0ca0_f9bd_545e_b563, + ]), + ), + ( + pallas::Base::from_raw([ + 0xabcc_ccbb_5ce1_d68c, + 0x6f7c_af47_e50d_a282, + 0xf36a_135e_abaa_806f, + 0x0c6e_bade_1095_aae8, + ]), + pallas::Base::from_raw([ + 0x759c_cc9e_69e6_b3e2, + 0xf9dd_54df_05a2_63bf, + 0x89da_d73c_cf47_946b, + 0x0312_d8af_2b83_bc52, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3d63_f419_3209_4084, + 0xa82e_0939_a10d_d3bd, + 0x09a4_2d48_e016_ca24, + 0x22d5_2560_48fb_a5d6, + ]), + pallas::Base::from_raw([ + 0x205f_917b_e385_f08e, + 0xe8ba_59ea_1582_96c5, + 0x07db_5f05_1202_61f4, + 0x1b27_fe10_cf0d_2d81, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc3c5_2026_c4c0_cdbc, + 0xe5c9_a075_9804_c4f1, + 0x333d_a34f_899b_6d01, + 0x0893_9b6f_69ee_9ff1, + ]), + pallas::Base::from_raw([ + 0x5738_5c90_a60b_6a29, + 0xcbfe_0af0_9889_9ee3, + 0xe56c_4f49_ed05_94c2, + 0x1a8c_87df_a7e4_fed0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9bcc_8617_524a_7052, + 0x9ed1_bc3e_a53b_8039, + 0xe83b_0a28_98c1_b3ae, + 0x2156_b879_f378_005f, + ]), + pallas::Base::from_raw([ + 0xe445_4fd6_1a4c_0c03, + 0xfd2d_77ee_9e45_d137, + 0x364c_d7d1_beca_462e, + 0x0454_61a0_6d06_0224, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa6dc_e40b_7742_c8df, + 0x77f6_fb9f_2f5e_4cbb, + 0x8971_a6f2_c8a1_01f6, + 0x06f6_891d_e68d_557e, + ]), + pallas::Base::from_raw([ + 0xe136_eb32_f32c_9360, + 0x43cc_807a_1583_94e3, + 0xb883_68d8_bd3f_0e85, + 0x39c7_f743_a3ff_d99b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4d92_8024_dec5_a188, + 0xc6fc_a92b_d386_353e, + 0x5812_c85d_f51b_7666, + 0x3622_96ae_b985_ad75, + ]), + pallas::Base::from_raw([ + 0xaf74_4347_0ebc_cc5b, + 0x8664_df03_bd4f_e04e, + 0xbb7c_dfe1_90ed_a6e1, + 0x04b7_49f0_6ac1_8d6d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x47cb_7e3a_61e6_5d64, + 0x2668_8f4b_611d_168b, + 0x851e_b1c6_f081_270a, + 0x0bcd_6577_07d9_c4d3, + ]), + pallas::Base::from_raw([ + 0xb8a7_9c1d_0164_6e59, + 0xaca0_0a81_3cbe_8d14, + 0x9be3_d622_a4d4_2261, + 0x228f_44ec_ff21_0aaf, + ]), + ), + ( + pallas::Base::from_raw([ + 0x01e8_32eb_32fc_77d8, + 0xa37b_d49f_cf1e_6d1d, + 0x4ade_7626_003a_6902, + 0x2810_18ba_6873_285b, + ]), + pallas::Base::from_raw([ + 0x3b22_bd8c_3f6a_e0f7, + 0x647a_d4e9_cc9e_9df9, + 0x599f_264e_e5e2_861a, + 0x1bc7_4dbb_c711_e11b, + ]), + ), + ( + pallas::Base::from_raw([ + 0xea31_502c_f1b8_0e15, + 0xc33d_5160_cd48_b428, + 0xcc23_c910_de17_adec, + 0x07de_f9ce_1aa5_9c47, + ]), + pallas::Base::from_raw([ + 0xa4ae_eeba_b464_5932, + 0xb123_1450_1087_4d65, + 0xd74f_2873_e2b3_fbd6, + 0x37f7_28e5_56e4_462f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7e30_9f95_bb4b_c5b4, + 0x6062_f47d_7261_c580, + 0x8e3d_4f2a_6d15_8590, + 0x01d5_e294_1638_a398, + ]), + pallas::Base::from_raw([ + 0x2d94_25e0_5176_6252, + 0x661f_3a26_6d6d_2f2b, + 0xe226_6037_2ff4_b8dd, + 0x1217_12c3_778e_e4a5, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8089_bc3e_77ce_6f7c, + 0xa6b2_22eb_d80f_7fc0, + 0xe9b8_b790_943e_8250, + 0x1095_0660_66b4_15b1, + ]), + pallas::Base::from_raw([ + 0xecaf_5823_aa30_216c, + 0x1bd3_7176_ae8c_c867, + 0x6646_5d17_3a00_4c92, + 0x1708_b8da_bbac_5fa2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0f5a_b3f4_1ed5_0406, + 0x3ed6_b824_6b8e_8193, + 0xefc8_17a7_c166_72ef, + 0x03d9_e65c_55e0_6fed, + ]), + pallas::Base::from_raw([ + 0xd266_0a0a_3a15_efd9, + 0x3158_3160_0238_603a, + 0xd08a_576e_ef04_0407, + 0x12b5_fc79_3386_02ad, + ]), + ), + ( + pallas::Base::from_raw([ + 0xafb4_5038_861a_b261, + 0x3336_2512_0ea2_ddef, + 0xd0d2_1e8a_bde9_c3f0, + 0x3142_5607_0f44_6667, + ]), + pallas::Base::from_raw([ + 0xd47d_2bf6_b0cd_9526, + 0xdfb6_5162_03ea_5997, + 0x6bc8_e93a_d9f1_3d86, + 0x36e3_7b1e_879f_a3fe, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9641_b725_3871_63f8, + 0x2f14_e213_977d_fb89, + 0x872e_0f0f_818e_2401, + 0x2705_4742_9401_5255, + ]), + pallas::Base::from_raw([ + 0xe914_98e9_e277_bf3e, + 0xbc1c_1336_46cc_0de8, + 0x810d_265f_9dbb_24c2, + 0x073c_4fa7_0c22_d417, + ]), + ), + ( + pallas::Base::from_raw([ + 0x72e0_8476_4f57_cbdb, + 0xaade_43be_ed5a_ec74, + 0x6b7a_19e7_38aa_efa8, + 0x3fe1_03e4_b203_9c38, + ]), + pallas::Base::from_raw([ + 0xf7b0_a409_9047_5ffc, + 0xf227_8b45_596c_d47f, + 0x8e89_3c33_0061_9b20, + 0x1e9a_0a12_9951_b344, + ]), + ), + ( + pallas::Base::from_raw([ + 0x273e_1d1d_25f4_def7, + 0x2003_845a_3171_beb1, + 0x6d17_8165_5f29_f2d4, + 0x0870_5e7f_5d5b_29c0, + ]), + pallas::Base::from_raw([ + 0x5593_5829_fa5d_7cff, + 0xf32a_41cd_f789_5d7e, + 0x1bf2_ea43_d1a2_e73d, + 0x020f_bf08_0453_d654, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc7cc_99c7_2db8_9a13, + 0xe869_f872_1a3e_457f, + 0xf8d5_9b75_f9e9_5e27, + 0x2a3c_dfb9_b0ec_7221, + ]), + pallas::Base::from_raw([ + 0xda92_9b87_c8b3_43ae, + 0xb33c_22e3_1aca_23f0, + 0x7ef6_c020_43d5_029b, + 0x0f16_75e5_fb1b_6016, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdaa7_c49c_581e_a8f5, + 0x5e24_60df_0bf6_68a4, + 0x04e2_ec29_9caf_6146, + 0x2438_e7ee_c6b6_6289, + ]), + pallas::Base::from_raw([ + 0xd94c_c550_0990_5ce3, + 0x9149_39c9_7c98_acbb, + 0x90d6_a0d5_e69d_004f, + 0x0e30_9799_430a_005e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x16d2_ba71_df3d_ba37, + 0xf987_3a39_9072_46f3, + 0x7309_0fc7_2e2c_8978, + 0x3e6a_ca9f_7693_dbd8, + ]), + pallas::Base::from_raw([ + 0x14bb_c667_e12f_8242, + 0xb8ab_dadf_64c7_ce8e, + 0x1a94_5bc1_7bb2_ea2a, + 0x012d_bbf6_af71_3aec, + ]), + ), + ( + pallas::Base::from_raw([ + 0x240d_00da_c9d7_36ac, + 0x5e01_5424_1ed5_6054, + 0xb77b_8e76_3e42_9cfb, + 0x1443_95ec_38cd_baef, + ]), + pallas::Base::from_raw([ + 0xa1d5_463f_18ef_f4f8, + 0xac13_6a2b_6eec_e912, + 0x175b_2f1a_655b_abb6, + 0x11f8_e577_8862_dc12, + ]), + ), + ( + pallas::Base::from_raw([ + 0x917d_91a5_1b8e_9f26, + 0x96a7_d4b3_e2db_2ef9, + 0x6467_2893_ebec_80a5, + 0x0415_c72c_6add_a7ad, + ]), + pallas::Base::from_raw([ + 0x31fe_78e5_541c_b69d, + 0x6fd0_a42e_a164_a907, + 0x779b_fe38_59e7_a99d, + 0x1502_f313_f9ea_cc53, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6299_68d0_f509_962c, + 0x0fd8_546c_a5b9_a1e8, + 0x4f71_7137_88ee_864d, + 0x21be_39d7_bb78_98ff, + ]), + pallas::Base::from_raw([ + 0x2e30_597c_56a1_b4fa, + 0x1a02_ad53_8381_07e1, + 0x4d02_2a9e_1cfd_6d33, + 0x1f3c_e37d_51aa_b4c1, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd2e8_277a_9787_ad9b, + 0x06b8_ef87_3d81_def3, + 0xcb3f_a058_0cdc_9294, + 0x3e0e_b866_b610_dad5, + ]), + pallas::Base::from_raw([ + 0x416f_988d_290b_fce1, + 0xe99d_9c46_9eea_a7fc, + 0xe826_d37c_968c_f31a, + 0x303a_5065_2996_bc3e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x970d_c43d_b7f9_6596, + 0x8bbe_a004_7bbe_b37a, + 0xffbf_c07c_4f86_ff20, + 0x2eb0_e870_2a5b_817b, + ]), + pallas::Base::from_raw([ + 0xcdec_df92_c73a_cf15, + 0xa349_b8f4_4bc1_1a4b, + 0x4ca2_5c82_1a35_0486, + 0x0c35_a8ee_8f63_ce4e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3330_feff_82c5_909d, + 0xa037_ccf5_1576_8f65, + 0x04b4_e5bf_d165_ab7e, + 0x3d8f_d30d_2e98_701f, + ]), + pallas::Base::from_raw([ + 0x76cf_354c_613f_6916, + 0xb3ac_8cfc_ef68_b8e4, + 0x121e_23ec_fb6d_3348, + 0x049c_4676_6965_007e, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd28b_5af8_208a_ad16, + 0x85cc_0c01_2522_9ec1, + 0x0010_00b7_fcba_b744, + 0x2f16_3cb5_e159_50da, + ]), + pallas::Base::from_raw([ + 0x8204_a5a6_854c_8f0c, + 0xaec2_50ed_9f9c_1bf3, + 0xa678_973b_4ca8_cfba, + 0x2060_78c2_1062_adf6, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb572_bfa2_ae44_bae7, + 0x4739_7be5_ed58_65d9, + 0x12d8_2660_d7d5_cd9d, + 0x3802_91b0_d0fb_11f4, + ]), + pallas::Base::from_raw([ + 0xda9e_f004_e34f_00ed, + 0xd1c7_c9c5_f145_719e, + 0x5d07_b071_e032_9c77, + 0x111f_1bf3_5aca_cc6c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9d4e_fee9_dbec_f74b, + 0x54b6_6b98_6ed1_1c40, + 0xc69d_e967_1f2a_9277, + 0x381e_f7c2_0901_6f19, + ]), + pallas::Base::from_raw([ + 0x78cd_6291_4c3d_4870, + 0x969d_b03f_4366_a7c9, + 0xc63f_edfc_ad98_8cca, + 0x0871_c3f1_196c_ed39, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbf95_5ae0_cd25_88e4, + 0x1a5d_be45_5bc1_85c6, + 0x800f_fb1f_93e7_1b70, + 0x0499_6852_1fa4_93b8, + ]), + pallas::Base::from_raw([ + 0x0888_a02b_46a4_447f, + 0xbf4f_428f_9cb9_8c28, + 0x8137_0703_14ff_9de2, + 0x1387_9076_4310_b410, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd12e_b4c0_0f6b_07df, + 0x4867_9cb7_f377_39f4, + 0xd28d_9c85_b17d_4400, + 0x0bd9_47e1_a875_e349, + ]), + pallas::Base::from_raw([ + 0xda62_8d27_e56c_e1ba, + 0x3fb9_f8d5_96b8_9fd8, + 0x5de3_b929_f658_6237, + 0x1535_62a4_2638_2eb6, + ]), + ), + ( + pallas::Base::from_raw([ + 0x36af_3b03_bde6_af5c, + 0x4fa4_3315_d7f6_0dc6, + 0x7067_a020_a491_9197, + 0x1c32_47f1_b306_ddee, + ]), + pallas::Base::from_raw([ + 0xc652_bb5a_650f_f696, + 0xf53e_890c_d2fe_64e1, + 0x9dbd_c120_670e_ac51, + 0x0786_cce0_4423_b08a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9b52_5a04_5c02_abe2, + 0xab88_03cd_3c69_fc10, + 0x4f5c_04e7_4967_f822, + 0x3d7c_ba8b_8d24_c973, + ]), + pallas::Base::from_raw([ + 0xc36f_6da9_c20e_8ca7, + 0x3ab8_0cd3_18c7_054d, + 0xea00_c48b_f1fb_88d8, + 0x2a85_cd4f_78e9_75d5, + ]), + ), + ( + pallas::Base::from_raw([ + 0xac5b_f95e_d4b6_11a2, + 0x136d_4d81_80c7_2c43, + 0x1fb6_df88_d28c_8d4e, + 0x246a_fe1b_17d5_6011, + ]), + pallas::Base::from_raw([ + 0x3668_7b33_4756_4972, + 0x1467_dd2d_360d_bc64, + 0x410c_df5f_93a9_6258, + 0x3588_8b68_24c6_e8a0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5dc2_2a72_4a62_d492, + 0x2660_9b2b_a750_8d8c, + 0x5059_5ae6_1b53_8471, + 0x266a_34b1_9765_cdff, + ]), + pallas::Base::from_raw([ + 0x7fbe_5bdb_d358_a2cb, + 0x40a4_f7fd_1ca8_a76a, + 0x491d_3273_a50e_004a, + 0x3568_0e4a_dc4f_62ad, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbfdb_990c_f977_fd4e, + 0x6437_369d_ca6d_cc9e, + 0xc7b5_d048_b550_51ee, + 0x250b_73ec_71d5_4645, + ]), + pallas::Base::from_raw([ + 0x41ae_e46b_4412_b381, + 0x9e9e_e500_6ddc_61fb, + 0xec2f_6df5_bbce_cf94, + 0x1793_e215_0b16_603d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6f88_6d79_770c_6247, + 0x1500_0374_0fa1_e182, + 0xf376_03b2_1769_2f82, + 0x3d1f_7368_0af5_b7ec, + ]), + pallas::Base::from_raw([ + 0x81b6_a75a_e4f9_729e, + 0xe735_aeaa_341f_ae17, + 0x4eff_b7c5_e1aa_bb0a, + 0x0223_c1d4_4c5d_c34a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x419d_f14d_7945_7532, + 0xd780_283f_4e02_c512, + 0xf0fc_47a1_756c_c536, + 0x1ce3_0702_a06b_ed82, + ]), + pallas::Base::from_raw([ + 0xaa20_9753_1479_6860, + 0xb77b_9671_d950_53a0, + 0x804e_e63e_09dd_84ba, + 0x09e1_711b_96dd_a806, + ]), + ), + ( + pallas::Base::from_raw([ + 0x607f_ba34_a589_c019, + 0x79bd_bab6_5d82_5998, + 0xcaca_a4bd_7fbb_d938, + 0x3694_ca85_2cf8_e54d, + ]), + pallas::Base::from_raw([ + 0xf555_28c0_3fb3_7e54, + 0x4a49_b781_11bb_66e4, + 0x94e4_77e7_59c5_515b, + 0x369b_a0f2_0b09_fe48, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa903_4843_3c41_e761, + 0x334f_6c0e_07da_b2ae, + 0xfa76_35b1_0775_e8e1, + 0x1fdd_43d1_901d_1921, + ]), + pallas::Base::from_raw([ + 0xb042_af7e_11f5_e1a3, + 0x72dc_c343_0584_0ada, + 0xec1f_22f3_40a0_7a4a, + 0x3d4d_cd14_0c46_afa7, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6516_53a1_ad5a_b863, + 0x3109_c5a4_3f3a_7299, + 0x4a26_89fe_ac51_8cd9, + 0x32cb_28e5_306f_4b25, + ]), + pallas::Base::from_raw([ + 0xce22_41f1_7770_0732, + 0xe9c2_68bf_1477_f81a, + 0xb643_c5f5_bed9_f57c, + 0x1ab3_8e0c_8fed_3eaf, + ]), + ), + ( + pallas::Base::from_raw([ + 0xad37_f47d_cf5b_c929, + 0xfec4_68cb_3ca6_b620, + 0x388c_5260_e2cd_625d, + 0x3e14_2094_bf47_b461, + ]), + pallas::Base::from_raw([ + 0xe191_3bcc_b9c4_dbd6, + 0xf56b_018e_cbd3_94fe, + 0x7a84_d88b_b2d8_d148, + 0x1095_d08b_31d1_571a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4c24_da73_a43b_18a4, + 0x192f_ad49_886d_4f1c, + 0xb9ca_2200_29b0_fd9e, + 0x23c3_cd8e_910e_3f61, + ]), + pallas::Base::from_raw([ + 0x446a_7b2c_0c53_995b, + 0xf640_9e82_f746_d6c6, + 0xd49a_f1a5_d660_f046, + 0x289f_6688_c8f7_a7da, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9322_2f89_f469_19ea, + 0x23ca_ce6d_d52f_41af, + 0x7b9f_0036_a0b5_b7ec, + 0x36a9_0ecc_3af7_1bf8, + ]), + pallas::Base::from_raw([ + 0xac78_4d5a_2fed_e96e, + 0x83c7_0ec2_e5cc_48a1, + 0x623c_7acd_dabe_6588, + 0x262e_4d3c_689f_55c2, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc48b_b03e_d3dd_130e, + 0xc8da_668d_e3e8_c4c0, + 0x8496_8dfb_620e_4d78, + 0x21d2_8b83_ef14_3a07, + ]), + pallas::Base::from_raw([ + 0xa607_41ba_7c9f_512b, + 0xe9af_6b59_b23f_048f, + 0xb49b_ffe3_9a97_dd6b, + 0x073c_ef7a_eab5_e67c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbb8c_2ebd_02ef_5c02, + 0x9f3e_fb6b_a8a3_d5e7, + 0xdd5e_afd6_8224_7afd, + 0x0a4d_0696_34c2_1d49, + ]), + pallas::Base::from_raw([ + 0xde8b_2693_7445_2117, + 0x8827_3e13_9ac8_9175, + 0xaf4d_3f11_60fa_ce22, + 0x372e_f7de_420c_f5b0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x007a_3171_e694_ad4f, + 0x0b63_7d43_b499_6483, + 0x3f41_d9a4_71d9_e31e, + 0x2304_4159_f767_5fe2, + ]), + pallas::Base::from_raw([ + 0xc689_d9d9_98f0_22c3, + 0x3843_04a4_96ef_f3ea, + 0xcdf7_07fb_a786_9698, + 0x00ff_aa52_52f3_1075, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc7ed_5950_3bbd_ae0c, + 0x0771_0a4e_f363_a8cf, + 0xb854_a41d_2524_2222, + 0x361c_d998_8c71_f7d1, + ]), + pallas::Base::from_raw([ + 0xd1c3_f5a8_e31d_e85e, + 0x61f9_1fe1_a2ed_72f5, + 0xd02a_3047_b442_7a6b, + 0x28a9_2ac8_140a_7f61, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe69b_8ff5_68db_09ef, + 0xa955_a884_45b6_1b1d, + 0x0181_0ff8_0bc0_0a37, + 0x3f77_7487_2da1_b634, + ]), + pallas::Base::from_raw([ + 0xbafc_6197_5f58_119e, + 0x8ec8_6817_94cf_4a02, + 0xf79d_b4f7_ef61_586b, + 0x1ecb_0eff_dee9_b332, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2e7c_1b51_5d9a_6b02, + 0x6db8_5ed9_bc41_5b63, + 0x9e61_c761_f278_31e1, + 0x1d63_7f07_1de7_68c5, + ]), + pallas::Base::from_raw([ + 0xb695_013b_a88a_e8fc, + 0x6087_861a_5652_9bf5, + 0x37e4_54e2_449e_c3d0, + 0x32f1_ac34_a889_a6b1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x45c6_e979_95c8_8f9d, + 0x9177_13bd_364f_3511, + 0x18b5_d48a_f737_4cec, + 0x1acb_50aa_82bc_21f3, + ]), + pallas::Base::from_raw([ + 0xa0b5_ac1e_0e73_137c, + 0xe9cc_3b69_7c2a_33dd, + 0x3843_fbd6_837f_46b9, + 0x3af2_c307_dabd_f022, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf320_517d_b185_80af, + 0x4ee4_765d_dab8_aefa, + 0x6b87_9ec8_cb2c_34ec, + 0x1804_0e61_d02c_9aa7, + ]), + pallas::Base::from_raw([ + 0x7799_9a31_ca66_44d5, + 0x5c57_40bb_df52_8c37, + 0x3c6f_fa5b_9973_5cc2, + 0x2d42_7fcb_2de3_02b2, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb702_1f4e_248b_d314, + 0x45a6_d47d_1e77_fc6f, + 0x71de_177e_9447_c0b3, + 0x2c36_6f8e_fc81_8295, + ]), + pallas::Base::from_raw([ + 0xd417_641c_3ef7_59be, + 0x46f0_a493_376d_27ea, + 0xcc58_89f3_a398_3e94, + 0x24ca_8204_9524_6a0c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4d41_08a1_fdf9_022d, + 0x445b_c1f6_ff38_ab2a, + 0x2c34_468f_08c5_67cd, + 0x27c9_224e_b005_f2fb, + ]), + pallas::Base::from_raw([ + 0xb67f_e386_3ad8_5858, + 0x35e7_89c1_3270_0ae0, + 0xaf43_ea8e_be9c_72c2, + 0x35ff_b97b_a593_0142, + ]), + ), + ( + pallas::Base::from_raw([ + 0x679c_9c77_fa3e_adea, + 0x050c_617e_c159_e9dd, + 0xe4d0_ad0b_279e_5d53, + 0x1c13_85a7_acca_d2d0, + ]), + pallas::Base::from_raw([ + 0xa605_758c_76d8_f99e, + 0x245c_09b6_233e_c109, + 0x9951_123f_fade_c94f, + 0x0e06_cb32_e11a_db9e, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdda6_c646_97fa_e3a1, + 0x57f4_3e80_51b9_d2bc, + 0x05ac_1626_2bfa_455d, + 0x27e6_b214_c46d_b4ea, + ]), + pallas::Base::from_raw([ + 0xe0df_3924_7265_cae4, + 0x09c0_1eb2_c3d4_7a5b, + 0x4b07_d6dd_a718_dc3c, + 0x046e_e6b1_16a4_d680, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8827_884a_0d84_93fb, + 0xb641_d4be_17e5_2a25, + 0xd1bb_d65e_ca13_f1e5, + 0x14e6_8375_9dbd_f9b7, + ]), + pallas::Base::from_raw([ + 0xc5bc_27f2_043e_09d6, + 0x4383_1bd8_740d_18bc, + 0x4d28_b951_db51_646c, + 0x2ff2_925c_ae35_4106, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd2ab_b8ea_a809_c065, + 0xa526_4c4f_d558_42f7, + 0x36bd_6833_52ed_686a, + 0x3e03_a120_a2d9_028e, + ]), + pallas::Base::from_raw([ + 0xadcd_15a2_2b64_bcff, + 0x8b41_fbcf_fa41_1d4e, + 0x6275_e729_aac4_1718, + 0x05f4_d1dd_a528_76ca, + ]), + ), + ( + pallas::Base::from_raw([ + 0x259f_45df_db18_4292, + 0x85a3_9f21_bd27_88a2, + 0x7741_d050_9d2b_a469, + 0x3628_e48a_fad3_7caf, + ]), + pallas::Base::from_raw([ + 0xf38c_a509_52c2_edd4, + 0xdbc8_3dce_fa5d_964f, + 0x67e5_1358_704a_dc0d, + 0x2dd7_037f_7772_b72a, + ]), + ), + ( + pallas::Base::from_raw([ + 0xde93_38bd_92bb_3d57, + 0x944b_7238_c860_9441, + 0xe03d_5bc0_6ceb_4255, + 0x2889_0bf6_1603_be43, + ]), + pallas::Base::from_raw([ + 0x598d_dfbe_ff5b_91c4, + 0x9017_7d24_b040_66d8, + 0x29fe_24aa_ca62_9592, + 0x0e1b_b56d_d00f_ee5d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x80f2_b1d9_cd31_a2c3, + 0xfeca_8134_5364_e82b, + 0x667e_8405_4786_7826, + 0x196a_2aa5_dfcc_2404, + ]), + pallas::Base::from_raw([ + 0x44f2_ea4e_50cb_7b22, + 0xa203_aca9_842b_2587, + 0x9c3f_525e_e03f_487d, + 0x1dbb_0fc6_d624_36c8, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd9f8_f81d_1726_2ea7, + 0x48c2_218d_3eba_8bbc, + 0x6a31_acc9_902a_abeb, + 0x1086_be57_c6c6_9382, + ]), + pallas::Base::from_raw([ + 0x9b21_c4ee_4438_3fb0, + 0xffbb_187c_d2c6_7113, + 0xbbdf_86a0_0d9c_9df9, + 0x1900_0dbd_6915_ef39, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb87e_e6a1_7150_f083, + 0x1dd3_172a_c7ca_bb4c, + 0x450d_6085_7553_2aa7, + 0x0e87_4af5_a4ea_bea9, + ]), + pallas::Base::from_raw([ + 0x27d9_3005_6a39_bdf0, + 0xaffc_ea8f_faac_2475, + 0x27ce_f92b_ac1e_26d1, + 0x1dce_ecf2_93f2_e855, + ]), + ), + ( + pallas::Base::from_raw([ + 0xec32_f124_f67c_b6a6, + 0xe76f_c0a9_bb04_6fc7, + 0x08c9_2fb9_a869_9d4b, + 0x0aa4_f0ca_3613_1f19, + ]), + pallas::Base::from_raw([ + 0xc77a_383a_f5f9_298c, + 0x6c2a_0c57_8f61_7a6a, + 0xa8fb_83f4_8769_5723, + 0x0dac_9338_2fb1_7d74, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa4ae_ed26_9bd7_f7af, + 0xcf45_5590_be79_8165, + 0xbb66_a5c2_ca07_ba02, + 0x2f71_97fb_33e0_2f3b, + ]), + pallas::Base::from_raw([ + 0xc6f5_fa79_26dc_55ff, + 0xb1d7_1d00_792c_b875, + 0x4300_1b82_b34b_fc81, + 0x2585_89f9_249d_35ff, + ]), + ), + ( + pallas::Base::from_raw([ + 0xca86_6954_84ea_c326, + 0x64d8_b454_7605_528f, + 0x47a1_1398_f1c8_6914, + 0x0626_f333_6694_8b96, + ]), + pallas::Base::from_raw([ + 0xcb9a_c695_8cdf_ac1a, + 0x51a2_395d_f28c_5a56, + 0x2982_997a_0143_8762, + 0x20a0_3634_e587_cdea, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4a22_3815_ad6a_af87, + 0xb23d_c239_fe51_f114, + 0xe744_6fbc_1d76_71f9, + 0x008f_b3c4_f277_6241, + ]), + pallas::Base::from_raw([ + 0xef35_ffbc_38cf_2be1, + 0x6406_1067_6e4b_152a, + 0xcd39_1b65_f743_55ca, + 0x33a9_ac54_8faa_2dc4, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb43e_6160_16b7_ac1a, + 0x2d17_45fa_33dd_dbbe, + 0xfcf2_c7dc_158c_b996, + 0x222d_d6b4_22f5_41da, + ]), + pallas::Base::from_raw([ + 0xd15f_8eb6_6b9a_1971, + 0x735e_b437_8b76_de8c, + 0x7a9e_fd09_e21c_cb2f, + 0x2605_6891_36d0_47e3, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa728_670e_b27d_0c27, + 0x0ffa_859b_d86e_09f4, + 0xfc12_1ddd_5bca_1d3e, + 0x3ed5_f5ac_9270_3915, + ]), + pallas::Base::from_raw([ + 0x2d66_ad9c_f3b0_9eb5, + 0x0f76_4a1b_3975_493f, + 0x311c_7d39_de6c_80e4, + 0x10c5_a910_538e_2161, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd8ef_2e55_08fe_449e, + 0x9795_f0b4_9b38_21dc, + 0x436d_3359_1956_ff92, + 0x2ef0_c2a1_ff67_562f, + ]), + pallas::Base::from_raw([ + 0xea61_5ac0_2396_252c, + 0xd3c3_e572_248f_f4ec, + 0x0891_5193_35ef_15f8, + 0x0e0b_7ddf_c509_d053, + ]), + ), + ( + pallas::Base::from_raw([ + 0x595f_7475_529b_3ad1, + 0xc202_93a2_e114_ef1a, + 0xa318_7931_4b87_03f2, + 0x25b5_defe_56df_8131, + ]), + pallas::Base::from_raw([ + 0x3207_683d_ce75_c89a, + 0x0aa3_95cc_ee66_6a3e, + 0x1d75_0d4b_db70_3a63, + 0x21a8_8c14_6d5d_50f2, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf1a0_a157_fe44_54d0, + 0xe58f_ea83_b9a1_8e48, + 0xe75f_1ebe_b321_bd75, + 0x2ff0_cde1_263d_eb99, + ]), + pallas::Base::from_raw([ + 0x78ad_13cb_3871_d58b, + 0xd70b_f90a_dedf_4a15, + 0xce59_469b_4a86_1855, + 0x07d0_b9f2_d8b0_f8b3, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1186_5fc8_3d94_8160, + 0xef33_cdf6_7d9d_f8ac, + 0x77fe_c81f_1b87_0816, + 0x33e4_5c8e_81bc_f208, + ]), + pallas::Base::from_raw([ + 0xa79c_f57d_5f8f_8039, + 0x46bd_dbab_182f_1e8d, + 0x121e_1e62_c3e6_2d1b, + 0x2aab_8626_7a08_126b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7309_5f4e_9257_4e26, + 0x800a_92c5_65c6_d0ab, + 0x959b_7396_8c33_df3e, + 0x1712_0abc_db9a_666e, + ]), + pallas::Base::from_raw([ + 0xa4ce_db71_dcbf_dda4, + 0x23a9_bb12_cc45_7125, + 0x4984_6598_5044_5012, + 0x15b9_a823_452d_151a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1881_58c9_d4f8_37b3, + 0x35e1_f53a_12ea_d4bf, + 0x43b0_1080_7105_9672, + 0x12a1_bb18_f6ae_aff4, + ]), + pallas::Base::from_raw([ + 0xd0c9_3a8c_6405_aeff, + 0x7c79_db4b_21ed_b868, + 0xfe64_ab4d_3285_580d, + 0x21f2_6e9b_4e96_4ad6, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5ead_7427_d8bc_93be, + 0x1074_df9b_a240_dc10, + 0x5e02_f458_b883_e475, + 0x1658_9488_1a71_48ba, + ]), + pallas::Base::from_raw([ + 0xcf20_d6a3_30b6_e493, + 0xc050_fc5b_3e2a_b5ca, + 0xcad1_ca64_521c_4b4d, + 0x2d9b_99eb_4fa9_4da9, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbf05_9e52_20ab_1fc0, + 0x8a7d_612b_8a1e_0e3a, + 0xeade_4244_f4cd_b38c, + 0x3331_7f8e_8124_54f2, + ]), + pallas::Base::from_raw([ + 0xc888_9b91_0593_4c5e, + 0xe569_c70e_0a7c_97f6, + 0x44d5_b9c8_f891_48f4, + 0x2312_5a5b_68f5_c72f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x94e4_bf9b_f2a6_1428, + 0xa590_5768_df89_f310, + 0x5f26_eac4_e6d7_bf48, + 0x1777_8461_3a31_42ef, + ]), + pallas::Base::from_raw([ + 0xd4c5_a93d_01a9_e944, + 0x00f3_0bb3_f382_1ab4, + 0x3e30_b05e_9001_115c, + 0x13aa_867d_0d62_d501, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0d77_6ee6_83d5_6884, + 0xc690_3133_0cb0_7fad, + 0x03f9_fa71_d83c_b6ac, + 0x385b_2c3f_69f2_2e4e, + ]), + pallas::Base::from_raw([ + 0xe52a_da77_f926_479c, + 0x4032_18dd_a3f9_f7d5, + 0x698a_ca67_0fe0_3b63, + 0x1be6_e46f_45a9_0d73, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb54f_2488_b34c_23f3, + 0x20f7_4d0f_dcf3_556c, + 0xb629_c46b_87ca_5183, + 0x3e05_1b07_97e5_4d7d, + ]), + pallas::Base::from_raw([ + 0x2845_df6b_a7f4_c4a9, + 0x01d8_7bb3_efc5_3212, + 0x7438_846f_f0c3_68eb, + 0x054c_4ccd_9568_783e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0e03_2b41_edf2_601e, + 0xca56_6380_2656_a1ab, + 0xa9c1_73f4_5b36_4aba, + 0x370b_f6a4_76fe_98c7, + ]), + pallas::Base::from_raw([ + 0xe83a_b9c8_7073_23c8, + 0x68cb_12fc_922c_e463, + 0x1d58_cba7_aae2_cd1d, + 0x1641_dcbe_d773_85ac, + ]), + ), + ( + pallas::Base::from_raw([ + 0x33d9_50b2_0786_8cb1, + 0x5546_4d24_79b0_1881, + 0xcef8_ae63_70b3_e7cf, + 0x1edd_0d6f_5654_df9f, + ]), + pallas::Base::from_raw([ + 0xca19_5fd2_3bf8_6d7d, + 0xd888_778b_481d_0c87, + 0x7718_4cb6_9a9c_a004, + 0x18cd_8422_c539_1785, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd3d0_061d_3c40_38a9, + 0x35d2_ba6f_b33a_337d, + 0x2b5a_ccd0_a048_8c8c, + 0x3177_51f9_70f0_55c8, + ]), + pallas::Base::from_raw([ + 0x7da0_2d46_e5dc_cf44, + 0x1948_7204_00fd_ac87, + 0xd35d_2f05_7952_09ed, + 0x067b_66e4_8fd4_40ae, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbe67_f5c1_c53b_6219, + 0x0c4d_d3eb_49dc_5141, + 0x1985_6e06_c83d_dc72, + 0x0580_4e34_6c60_f39d, + ]), + pallas::Base::from_raw([ + 0xd970_b5f8_e84e_2eca, + 0x8dee_bae7_6faa_0b0a, + 0xce36_460d_4c75_cfdd, + 0x0c0b_30a8_4eb6_c2ff, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd211_b3b4_85da_f337, + 0x7caf_c1c9_d176_8b9b, + 0x4f29_6d08_6aa0_6691, + 0x1469_1756_396e_e450, + ]), + pallas::Base::from_raw([ + 0x908c_5ebe_6cb7_9318, + 0x485e_123c_8a7a_1993, + 0xba9c_8297_dc30_544d, + 0x0b1b_b3c8_67ad_579f, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfdbb_126a_9c89_8e38, + 0xeeb0_b9e3_24c7_e16a, + 0x2a63_649b_a4bd_3976, + 0x2cbe_8ae3_38cf_2e67, + ]), + pallas::Base::from_raw([ + 0x02c8_e297_4287_df10, + 0xfd4b_303a_ffcb_5e36, + 0xfe24_5d64_99c2_2680, + 0x2ad4_f003_ac6a_0942, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6366_45e4_5cf2_472e, + 0x581b_fa20_8c70_06f3, + 0x7f42_fc2f_a68c_53b6, + 0x3e5f_5c59_a596_3e85, + ]), + pallas::Base::from_raw([ + 0x3fae_7cd7_1795_7498, + 0x1897_f70d_330f_17cd, + 0x08bc_c4a8_93be_9624, + 0x16b6_5a70_880a_1196, + ]), + ), + ( + pallas::Base::from_raw([ + 0xecb8_a34e_6cfa_6400, + 0x872b_a68d_1737_b069, + 0x692b_8b56_4d30_00d6, + 0x1ac8_6b32_c2bd_28f7, + ]), + pallas::Base::from_raw([ + 0xcd15_73d7_4ab8_ecb6, + 0x10d8_d456_d06f_8959, + 0xc4d7_94db_40b8_a6a6, + 0x0fc4_1405_f622_01e8, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb13c_4e21_fb86_cc29, + 0x6dd4_2256_220b_a841, + 0x485d_d357_9109_44f6, + 0x13e6_37f3_e85c_6236, + ]), + pallas::Base::from_raw([ + 0x393e_d2d6_d195_2520, + 0x8714_20e0_7d4d_41d9, + 0x4f97_e4c9_1b4d_d5a0, + 0x0ee1_6e26_9356_7999, + ]), + ), + ( + pallas::Base::from_raw([ + 0x18f0_e818_b67f_78c4, + 0xc78e_8276_5cfe_52d7, + 0x1029_108e_3372_5249, + 0x1ce7_779f_3e32_15bb, + ]), + pallas::Base::from_raw([ + 0x0a3c_2c79_69f4_7def, + 0x924a_5bd3_c10a_b277, + 0xa684_5102_3fda_00de, + 0x2848_c67f_8b86_351e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x785a_0070_6ef4_0a19, + 0xfc9e_e16d_1e79_450c, + 0x21b0_be13_8f13_ee31, + 0x205a_6e79_eb30_e515, + ]), + pallas::Base::from_raw([ + 0x109b_7b36_54cd_ff92, + 0xe219_fd76_bb44_d664, + 0x26dc_47f5_a4c6_cfd4, + 0x1396_0022_9638_8e73, + ]), + ), + ( + pallas::Base::from_raw([ + 0x284f_eee3_dedb_7e0a, + 0xeba3_d6d6_ab74_8634, + 0x206d_133e_7387_88b3, + 0x06e2_9444_0819_67d2, + ]), + pallas::Base::from_raw([ + 0x32f2_7745_dd5d_fa26, + 0x61b9_ff69_431d_6c7e, + 0x7b5e_c381_d868_ecdd, + 0x396d_b442_1b1f_998e, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf09f_2925_09b3_435a, + 0x5dd8_7f72_700e_6289, + 0x428f_036b_3027_e4e7, + 0x3ba1_38ab_852a_c4c2, + ]), + pallas::Base::from_raw([ + 0x31eb_6ef7_e3a6_63c9, + 0x66fb_a519_222a_5eef, + 0xf056_ea85_d9a5_5f2d, + 0x0f9d_1a6e_42c7_af7b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2ed8_b60f_5a7d_5c4d, + 0x3c41_ec81_bb2b_0bd4, + 0x5162_51c1_cfd3_cc57, + 0x1628_f847_8492_257a, + ]), + pallas::Base::from_raw([ + 0x1028_dea0_df49_3ad2, + 0x9f64_8ee0_54ba_1f5d, + 0xa5c3_00a6_334b_5071, + 0x22ec_d647_4c9d_8dcb, + ]), + ), + ( + pallas::Base::from_raw([ + 0xba78_884f_a618_b4c9, + 0x2490_cfbd_ef1c_b8de, + 0x6262_4555_49b8_a7cf, + 0x22de_6c00_52d8_822e, + ]), + pallas::Base::from_raw([ + 0xb574_d8b6_4eb4_f13b, + 0x3ae5_7da8_c6fd_7491, + 0xd951_849a_c56a_d2eb, + 0x3d95_2743_8af5_bb67, + ]), + ), + ( + pallas::Base::from_raw([ + 0x423b_21c9_a9ae_e502, + 0xb560_584f_a159_372a, + 0xbde3_a652_e154_27f3, + 0x13e9_25ee_106a_6648, + ]), + pallas::Base::from_raw([ + 0x21c0_83c5_f051_3cd4, + 0x1aa4_9c06_bbfb_670f, + 0x003e_2620_a935_dec4, + 0x06d0_2c9b_fefc_d300, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1bac_349a_3968_df9d, + 0x4f7a_9588_dd94_8cf4, + 0x9c25_b2b9_425b_41da, + 0x34bc_c380_6706_0422, + ]), + pallas::Base::from_raw([ + 0x2e43_e508_767d_ab6a, + 0x8f2e_1357_30de_d522, + 0x1e5d_425e_7b9c_b97b, + 0x3b61_c068_3a4c_a1d1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2f22_1515_dc8d_33b3, + 0x8744_45d2_f4cf_5744, + 0xd439_9e9c_7a24_704b, + 0x0a48_d0cd_b7aa_cb6d, + ]), + pallas::Base::from_raw([ + 0x3265_a994_76ed_b4f8, + 0xad35_4f4c_83db_34fb, + 0x8c76_979a_0183_f56b, + 0x24c0_3c4a_383c_f34b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0264_48de_6bde_8f36, + 0x47df_c98c_065b_94cc, + 0x1bae_0086_8f7b_65d4, + 0x3725_ede4_49fa_fc27, + ]), + pallas::Base::from_raw([ + 0x0784_2c74_c2a0_abad, + 0x98b0_6931_a1fa_c65c, + 0xa0a2_f3f5_c5a0_d649, + 0x34e4_0ab4_f07a_f6aa, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc04a_82a5_985a_802d, + 0xf0dc_11c2_0ff2_f8e3, + 0xf99c_8842_c343_ab5b, + 0x1efe_d560_4943_bc1e, + ]), + pallas::Base::from_raw([ + 0x2b73_38af_72b1_7c87, + 0x7a76_3e90_131f_458a, + 0xf558_1a8c_ede7_797e, + 0x2160_887f_54e5_5248, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa806_7929_bd78_8219, + 0x4502_b7c2_0868_4ba6, + 0x1824_a713_12fa_8662, + 0x1d5f_eb68_ec9c_82c0, + ]), + pallas::Base::from_raw([ + 0xb50c_6fd8_a0ff_197e, + 0x1ecb_67c8_1a98_17ef, + 0x3802_668e_c3e2_e9ad, + 0x171f_bfe8_b8ea_bdc5, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3804_32cb_1aa3_0b20, + 0x7206_9e76_128a_dbf5, + 0xfe30_6281_13f9_413e, + 0x3d95_faf6_a3c0_a23d, + ]), + pallas::Base::from_raw([ + 0x5c91_686d_7b70_5f0c, + 0xa432_25ab_ab76_f79a, + 0x1534_bae8_b052_0e10, + 0x1351_24f7_7d93_8a2d, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd8a5_d43e_0733_a49a, + 0x59fc_166c_4df8_685b, + 0x6b2c_730b_481e_d580, + 0x0b74_b4c9_b3a5_8739, + ]), + pallas::Base::from_raw([ + 0xf7c3_af4c_034b_e968, + 0x280b_4dd1_da46_b742, + 0x817c_6343_92a5_2bf1, + 0x20c6_7219_0fa4_fd21, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9b9a_5d85_cc8c_cea9, + 0x8a3a_db6c_635f_69e0, + 0xd9e9_bb8c_1560_1184, + 0x1dcc_4260_292a_b5ee, + ]), + pallas::Base::from_raw([ + 0x1291_c646_f66d_6966, + 0xd0b2_d860_6baf_fadc, + 0x5fe4_42f8_21f2_398e, + 0x1b97_d042_35eb_620c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9111_47ed_8621_b0cb, + 0x15d9_a0c5_9075_13d4, + 0x55b6_8241_f6b5_5714, + 0x388f_21cd_bdba_18a8, + ]), + pallas::Base::from_raw([ + 0xd7f4_b6ed_57dd_0be9, + 0xc659_dca1_c72f_718f, + 0x673b_b001_0480_c230, + 0x089f_3734_b014_2b1d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3fef_e211_66f0_64d3, + 0xab1e_a692_2b23_fe74, + 0xabcc_16b8_5b13_edd3, + 0x2f1e_5892_5bfd_9702, + ]), + pallas::Base::from_raw([ + 0xe111_5ff5_cd6b_6e0c, + 0x3b8e_6060_6a62_c9d3, + 0x2a71_2c0a_2329_962a, + 0x398a_1f1d_e5c2_a3b5, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb1f7_5faf_516c_5527, + 0x1834_e411_a954_5d74, + 0x5a3d_8f22_5192_48e5, + 0x0c9b_5c3b_99e5_e2e4, + ]), + pallas::Base::from_raw([ + 0xc519_1b97_cd4c_9178, + 0x3d1f_2cfd_ec1d_9f37, + 0x77ea_1ab5_a674_9b12, + 0x1c54_7537_1a50_9219, + ]), + ), + ( + pallas::Base::from_raw([ + 0xeb48_bb7c_bdf0_eca3, + 0x3818_af8f_3f8a_23f9, + 0x1225_2bc6_8251_52c2, + 0x20f2_231c_6b91_756b, + ]), + pallas::Base::from_raw([ + 0xe56c_b770_41bc_64df, + 0x6507_3efb_ec7c_88eb, + 0x4db8_fc10_e0e3_e9a3, + 0x25f5_aa8d_7ed0_e37a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x989d_1174_c5b5_1592, + 0xbc22_de23_d318_4f3d, + 0x0d74_e3e1_c30c_9216, + 0x3c9f_46e6_9974_ef0e, + ]), + pallas::Base::from_raw([ + 0xca71_bc9f_274e_a68f, + 0xda0a_f4d4_b787_77b0, + 0xe011_197f_5425_5452, + 0x02bc_1f24_5fad_07ce, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf66e_e106_a081_b017, + 0xbda0_9fb9_141a_ed66, + 0x8d9b_85d3_a5b2_218f, + 0x3368_44df_9aa0_5205, + ]), + pallas::Base::from_raw([ + 0x21cb_9ec2_7a50_283e, + 0x6526_eb8d_8a98_355e, + 0x4625_6651_5a5b_5bd2, + 0x0dcc_cfa2_eabb_31aa, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdf01_ebdc_d5d5_1078, + 0xdce7_4ffe_4597_40a9, + 0xec30_c721_88d4_926f, + 0x25f4_e3ba_66d0_0bd2, + ]), + pallas::Base::from_raw([ + 0x1aa4_cf28_305d_bd48, + 0xf1ac_ed41_29c5_2fce, + 0x6a79_ae99_419d_2023, + 0x287a_ccc2_aba7_dda2, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf6c9_67b1_61e4_3701, + 0xba59_4171_cf75_314a, + 0x2366_fd0c_9845_d326, + 0x07ea_75ef_2e55_1043, + ]), + pallas::Base::from_raw([ + 0x5f6a_b363_1d50_02b3, + 0x42a7_f1d3_8df7_1886, + 0xb175_5fda_e244_41b1, + 0x06d8_b881_47e9_e884, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7760_0ee1_2469_f797, + 0x9d28_9411_63cf_2100, + 0x94f5_d9bc_1e38_d7ac, + 0x0d9d_a8ed_2348_f1d8, + ]), + pallas::Base::from_raw([ + 0x9dcb_1045_7ef9_4381, + 0xf26f_888e_41de_250d, + 0x4db4_569e_a5be_98ff, + 0x2c6e_26fe_e783_b1b6, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8252_2117_8b68_e77e, + 0x62b6_4c04_daaf_0edb, + 0x56fc_ef6a_c5ce_9b21, + 0x0060_2183_26a9_b20f, + ]), + pallas::Base::from_raw([ + 0x9381_f756_c758_57be, + 0x2c64_8722_e9b2_aad0, + 0xd21d_fa97_22b7_899f, + 0x37b3_34ab_36d1_3b97, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1dca_d2a7_c9ac_257e, + 0xf481_bd41_dfde_561e, + 0x7fb9_5d59_e14f_9ba1, + 0x164f_e5ae_3a75_5ff1, + ]), + pallas::Base::from_raw([ + 0xd85d_2380_04ed_04b9, + 0xf0a2_f4ca_240b_67be, + 0x328d_6539_b137_e8c0, + 0x1f91_7ad2_3c72_2938, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7c1a_1f69_511d_ddc8, + 0xafef_22d0_8a93_c6bb, + 0x6b7d_de6e_5dfb_8e86, + 0x0418_14fb_9b1b_1fdf, + ]), + pallas::Base::from_raw([ + 0xc232_8d8d_5ade_ee19, + 0xfccd_233e_1faa_328c, + 0xb74a_eb62_bfd2_085d, + 0x349b_497a_e2da_b674, + ]), + ), + ( + pallas::Base::from_raw([ + 0xee76_492e_de05_8c40, + 0x9667_cea9_6321_0dfd, + 0x9ffb_dfe7_bbaf_4d78, + 0x024a_7f47_bbf2_edfb, + ]), + pallas::Base::from_raw([ + 0x2798_9453_54c0_b6a6, + 0x0258_04f1_e97c_9306, + 0xa589_745b_f70f_31f8, + 0x2980_753e_d2a0_1bd5, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc217_79ea_fb8d_cf1f, + 0xc7d4_3d68_74e8_1a99, + 0xdf4e_e87a_3da7_0225, + 0x2674_6664_7ec0_66ec, + ]), + pallas::Base::from_raw([ + 0x9a08_438d_3e66_71ff, + 0x2597_f5fd_7ef0_e033, + 0x3490_d2b3_16e4_336b, + 0x0af1_68b8_2ce5_50d0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9ee7_3ace_ad81_d7b9, + 0x7f7d_4d51_c420_2287, + 0x7ea4_8cda_f72c_21d5, + 0x3ba5_ab89_7c05_9262, + ]), + pallas::Base::from_raw([ + 0x65a7_2df6_e7af_1f62, + 0x5018_7079_ed87_6e01, + 0xdd49_7bac_b2f8_f533, + 0x2305_b1e1_10ca_8348, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7bb8_856e_6f81_0866, + 0x2910_87a6_3558_342c, + 0x0197_f6a1_a5ec_2aee, + 0x2e3f_0e84_be82_a936, + ]), + pallas::Base::from_raw([ + 0x24d2_4f23_defe_d26e, + 0xa6a3_fdcb_e123_531f, + 0xf554_544f_9059_9b7a, + 0x1b2b_2c74_2c05_d788, + ]), + ), + ( + pallas::Base::from_raw([ + 0x77aa_8cc5_e75f_61b7, + 0x7ab0_d22c_2c7a_225d, + 0x261a_0bae_d904_6233, + 0x23ca_5427_dfe6_fe96, + ]), + pallas::Base::from_raw([ + 0x3965_f126_8be5_f4f1, + 0xfbbc_f05d_19e0_4468, + 0xcf20_7a1a_dda3_da90, + 0x2dc1_38b3_e758_ae9f, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe4cd_d4bb_51f1_3a97, + 0x0ae0_b307_d947_f166, + 0x95c1_0d80_9554_61dd, + 0x2663_0dcd_f58c_a26b, + ]), + pallas::Base::from_raw([ + 0xec69_76ce_7f25_e7fb, + 0x21f1_ef67_603e_b2b9, + 0xb538_45ae_65dc_d7ef, + 0x006b_e5d2_3475_d4f0, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd937_daf5_c74d_37b5, + 0x7b6f_ad60_d7b0_78d4, + 0xeaa2_48f9_cc19_2e8e, + 0x1bff_9849_3f9c_61f3, + ]), + pallas::Base::from_raw([ + 0x1eb4_de71_6993_79b8, + 0x1631_ada7_84b0_c628, + 0x00af_5200_06fa_74cc, + 0x1497_0f48_9ac5_2a83, + ]), + ), + ( + pallas::Base::from_raw([ + 0x585d_8ca2_b07b_c36c, + 0x6a0e_125b_b3fb_3153, + 0x1656_99d8_a31c_48e5, + 0x0c90_2843_2d3d_2ecd, + ]), + pallas::Base::from_raw([ + 0x37b6_c3aa_0a9f_0553, + 0xe75f_7c83_3b31_0906, + 0x0b28_1359_fedd_80f0, + 0x2aea_f21d_78e4_2ae7, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf905_b492_f098_abbb, + 0x91e7_32ad_153d_fbb9, + 0x23b7_9b3d_c26f_956e, + 0x30a8_6999_b4de_0802, + ]), + pallas::Base::from_raw([ + 0xab98_1e4a_ef1a_ce80, + 0x3e6a_1ab7_cc7f_76f7, + 0xfcfc_ac90_b0a9_24bd, + 0x290c_7b1a_b364_d9f0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x89d2_5903_1495_2310, + 0xb8f6_4793_e3fe_9b02, + 0x1298_314c_1048_dded, + 0x35f6_6aef_3f87_5f47, + ]), + pallas::Base::from_raw([ + 0x7f09_c9ec_4e0b_9a00, + 0x02e5_58a8_8b63_c5e8, + 0xdff2_a2ab_eb34_d24d, + 0x182a_04c6_61a5_3ea5, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7749_9a48_5a8d_c3c0, + 0x7301_8bb7_d60b_54a8, + 0xbd74_5aec_e134_f4d3, + 0x0ce6_4340_9896_9f25, + ]), + pallas::Base::from_raw([ + 0xb214_a94a_a5e8_5ac9, + 0x3a7b_00db_2c5b_71bf, + 0x1f1f_97e9_4a95_3ea7, + 0x0fdc_1f13_90f2_bdc3, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfd7b_3712_3634_9249, + 0xb29c_31f6_a23e_e901, + 0x428c_09b7_ab6a_19b0, + 0x3edb_6eac_7f4a_7fc3, + ]), + pallas::Base::from_raw([ + 0x41ae_5f62_33d0_39fe, + 0x351b_2fad_6a83_3fae, + 0xb027_de87_dc3c_abd6, + 0x1c3c_6b89_8ff4_fbe6, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8579_eab4_36d7_7379, + 0x6976_f60e_a7ad_6a4f, + 0x4d44_0486_bd7f_82e0, + 0x33df_087b_d169_6b33, + ]), + pallas::Base::from_raw([ + 0xd8ae_253e_7704_e1b1, + 0x6c80_3c98_5ca9_cb72, + 0x4f76_0a94_7173_e72a, + 0x1749_3c2a_a576_b53e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0d8d_9ce3_bee5_d2a8, + 0x3b11_1e32_1416_7ec7, + 0x866a_5644_f7bc_2429, + 0x2f53_e65c_d670_6b69, + ]), + pallas::Base::from_raw([ + 0xc9bc_a98d_5327_54b9, + 0x1c20_0a22_3360_ac52, + 0x8fd1_441f_3249_5f67, + 0x0891_e7a6_7b7a_a04c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x33ac_b0c2_3336_e65e, + 0xe9bd_f7b7_365f_b280, + 0x3fdb_301c_b124_359c, + 0x225c_f8a4_48fb_0bfc, + ]), + pallas::Base::from_raw([ + 0x7bd0_525c_7c01_37f5, + 0x25cc_2e9e_5ef5_3cac, + 0x0abf_86e7_33f6_5865, + 0x25ec_f837_9bd5_34fd, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1959_74df_433a_0d39, + 0xf92a_d555_fe66_45c1, + 0xde22_fe69_6b67_05a3, + 0x30c6_33cd_5289_cf7f, + ]), + pallas::Base::from_raw([ + 0x2de2_06ae_215b_cdb9, + 0x1dba_733a_5d1c_f06f, + 0x7795_0562_7c29_07b6, + 0x2e05_a443_58f8_295f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8c8d_3e5a_0e9e_27e2, + 0x33a7_ac34_3383_9ad5, + 0x813a_ef88_67ca_27f2, + 0x0351_82c5_aa57_93ea, + ]), + pallas::Base::from_raw([ + 0xe37a_d328_0519_9e75, + 0x9492_ecb8_7225_4d8f, + 0xe440_0d58_32d9_3b4f, + 0x23d7_f634_31de_0e04, + ]), + ), + ( + pallas::Base::from_raw([ + 0xed93_fc4d_8b7c_d2c7, + 0x2ff9_e82a_2813_1039, + 0x9b41_402f_8edd_7482, + 0x2de8_408a_e87c_5526, + ]), + pallas::Base::from_raw([ + 0x8482_7aee_9078_6f5b, + 0xfca5_1acc_7aa9_8a6f, + 0x3b26_c0d8_1fb1_7641, + 0x1e97_cfa5_3430_ce4f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x28d8_3f6f_dd30_2e4b, + 0x8b33_1616_df48_b162, + 0xeb66_7f0b_abbe_d5c6, + 0x1bb4_9f7d_2f00_992d, + ]), + pallas::Base::from_raw([ + 0x8f1b_2585_5975_5b8f, + 0x992a_5645_ac44_49a4, + 0xc7f8_a778_378a_878e, + 0x2388_d495_aa9a_2f14, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb3ee_832b_cca6_e15c, + 0x302e_0976_d09c_f5b0, + 0x3b73_c911_07ca_0590, + 0x1033_729d_e32a_20e5, + ]), + pallas::Base::from_raw([ + 0x824d_03c9_62ab_fb97, + 0x3376_05c3_fca4_da48, + 0xd938_5b92_c1b1_eb7b, + 0x04e9_d53b_9dd7_0353, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2a31_ceea_2bfe_1b89, + 0x459c_7864_3eed_fab2, + 0x8015_ebda_9a08_21f7, + 0x3176_5d48_ed2e_8f38, + ]), + pallas::Base::from_raw([ + 0xbbd2_e12e_2200_a567, + 0xee7e_8b8e_60c9_3161, + 0x4450_13a8_b51e_a17f, + 0x3fb3_ea3d_413f_600f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5792_e1bb_65af_5499, + 0xf38e_ecce_d9d1_90db, + 0x782e_777f_aef9_a1ed, + 0x3275_1af6_d47e_89f4, + ]), + pallas::Base::from_raw([ + 0x0558_8657_d79c_9fac, + 0x4cfc_fa59_c797_3faa, + 0x46ec_529c_3e3a_e025, + 0x2e88_9fc7_1cfe_b590, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4f36_5a89_27df_98cf, + 0xdc4d_ad2a_e1df_90d4, + 0xe8c7_f8c9_f534_a3a6, + 0x1f53_db72_546f_88b1, + ]), + pallas::Base::from_raw([ + 0xc521_f710_64f3_458e, + 0x55f4_b813_b4eb_ca44, + 0x1fbe_fca1_69c2_c1f7, + 0x3270_72d0_0569_db08, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe51c_bf5e_4534_c30a, + 0x9f11_38fe_5b82_ac9b, + 0x31e5_618e_64c9_6740, + 0x1626_a008_8065_ae1f, + ]), + pallas::Base::from_raw([ + 0x4e4f_35ca_0362_3b2b, + 0x589e_4ec7_4b43_ca66, + 0xadfc_a852_9739_1779, + 0x3ec9_c8c2_307f_b4fc, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe558_1772_2b26_fcc5, + 0x8cd2_78c7_57d7_bc6b, + 0x216f_0ed7_5683_8c13, + 0x2bad_b214_0ca1_67ef, + ]), + pallas::Base::from_raw([ + 0x7b8e_54ac_bde4_5d74, + 0xf12b_ee08_f085_ef2b, + 0x59be_cb38_9bb3_7cba, + 0x10c2_e53a_d289_3205, + ]), + ), + ( + pallas::Base::from_raw([ + 0x05bf_6073_a2cc_922b, + 0xaa40_a1b1_c161_a6f5, + 0xe7aa_a199_d7b3_f2be, + 0x2ec8_895a_ff5b_5376, + ]), + pallas::Base::from_raw([ + 0x21a9_2a6f_5c37_ad34, + 0x9ed0_8f7e_4c30_d354, + 0x0c92_ae6b_73d0_ddf0, + 0x02af_e3c9_f6d8_8524, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa364_1c63_2ef7_445f, + 0x1601_4441_ab17_06e9, + 0x5e0c_e5dd_d268_6d5c, + 0x2962_bd56_585a_49b2, + ]), + pallas::Base::from_raw([ + 0x8f33_3912_6908_c490, + 0xb337_01a1_f6fb_f2c6, + 0x5b5d_ed95_d5a1_587e, + 0x175a_30c9_9d0b_dcfa, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf982_0b5c_aa89_84ec, + 0x8cd5_6f8a_04f7_0000, + 0x45bf_1e43_42e6_c668, + 0x2b33_5ef2_cfc8_0c83, + ]), + pallas::Base::from_raw([ + 0xd28e_8c9a_a973_eb4c, + 0xfc8d_633c_c565_5eb8, + 0x49f8_808f_b1f8_2460, + 0x26df_2492_c732_e56c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xcc02_e14a_ed10_15df, + 0x7ab2_e6da_6148_1b32, + 0xf1b8_ccf5_7959_7c1f, + 0x1f39_1c76_d2a5_87d4, + ]), + pallas::Base::from_raw([ + 0x6161_8e3c_c900_c078, + 0x8051_d2da_5db8_ca06, + 0xf00c_bba6_4a81_7e59, + 0x0212_f279_9961_28a4, + ]), + ), + ( + pallas::Base::from_raw([ + 0xba12_bea4_1ed1_352d, + 0xc72a_1238_3074_4e06, + 0x774b_4680_7bce_521e, + 0x1b55_f307_4483_1944, + ]), + pallas::Base::from_raw([ + 0x4339_05b5_7199_f756, + 0x5018_a2d2_8374_18cd, + 0xc369_3735_482c_e33f, + 0x06d1_33c4_e935_ed22, + ]), + ), + ( + pallas::Base::from_raw([ + 0x17d2_e055_42f8_9a6b, + 0x63c4_572c_834a_9911, + 0x7c98_197d_30e9_06b9, + 0x1bb8_0e9c_7b1c_a773, + ]), + pallas::Base::from_raw([ + 0x757f_5677_f5dd_82dd, + 0x6030_5392_57bb_182b, + 0x22e8_e711_493b_46f2, + 0x2462_9d6c_1107_6339, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc227_0007_0d48_67fa, + 0x5a14_a6ed_8e5a_164c, + 0xf31b_979b_e4fa_a4dc, + 0x2260_9c32_ae8a_2fe1, + ]), + pallas::Base::from_raw([ + 0xcbbe_cd59_36ba_6c7a, + 0x768f_7786_ddb9_39e0, + 0x8ee0_8603_e206_e123, + 0x14f1_bb3e_19ad_9852, + ]), + ), + ( + pallas::Base::from_raw([ + 0x36cc_d0fe_865e_1f8a, + 0xd12a_6df1_2f9b_0183, + 0x8fc1_7986_8075_3758, + 0x230a_a792_feb2_5fa6, + ]), + pallas::Base::from_raw([ + 0x2489_a5cd_0cc0_bae0, + 0x6f0d_c5d3_924f_4676, + 0xea2a_6502_9734_9b71, + 0x2596_75b5_6bcd_40ed, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc111_adf6_14ce_6cb0, + 0x4aba_0e21_bab9_0092, + 0x99e9_d9dc_1d39_ead0, + 0x1d84_4ec6_9215_ea84, + ]), + pallas::Base::from_raw([ + 0x665f_32ce_34d6_3a28, + 0x166d_7faf_b4f3_51da, + 0x71f2_1dc9_4a3a_0837, + 0x1cbd_dd75_d802_7a07, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbb16_0c4f_b407_3444, + 0x4f4f_3c61_ac00_a4f7, + 0x3c3a_271a_012b_988b, + 0x1b4d_4160_2bd3_ae10, + ]), + pallas::Base::from_raw([ + 0x61f2_9112_22f7_b25b, + 0xd2e6_eee7_d3df_2364, + 0x156e_24e6_a1ef_9b96, + 0x2cc4_b4f6_2b98_9419, + ]), + ), + ( + pallas::Base::from_raw([ + 0x90ad_379e_cf99_5052, + 0xfdd3_5b06_452e_f10f, + 0x3231_9fe1_ccd6_5847, + 0x37d6_0e5c_6f9b_157d, + ]), + pallas::Base::from_raw([ + 0xef5e_4b6d_8a11_3611, + 0xfff2_8aed_9719_e68f, + 0x0022_f704_2bc5_4b86, + 0x24d1_1739_87a0_150f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x67b6_a004_5b76_df29, + 0xd780_7758_5dd1_72c5, + 0xfd94_bada_1b47_6147, + 0x055a_ef12_3bb1_7975, + ]), + pallas::Base::from_raw([ + 0xc40d_7914_b6fd_0059, + 0xcd4b_03e5_9621_c0cf, + 0x5804_3959_aa1b_5cb4, + 0x399e_7d93_333c_9f5c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe987_36b6_6ff3_a8f3, + 0xb633_6b7c_e2d0_1c29, + 0x72b5_e138_e871_8594, + 0x39e3_1af0_f759_2d94, + ]), + pallas::Base::from_raw([ + 0x2584_aad2_a9e9_6991, + 0x4d67_c9b6_57a0_00f8, + 0x1fef_7c39_245d_f66b, + 0x39a5_61f5_b60e_13ce, + ]), + ), + ( + pallas::Base::from_raw([ + 0x12a8_2bb3_679d_2e7d, + 0x7923_949d_cf92_266e, + 0xb997_e0d1_8eda_a746, + 0x2722_d4bf_937a_919d, + ]), + pallas::Base::from_raw([ + 0xdcc8_3afd_5c79_7391, + 0x25a7_833d_a733_98ac, + 0xd82e_903f_c00f_3f8b, + 0x20b2_7ae6_263f_b41a, + ]), + ), + ( + pallas::Base::from_raw([ + 0xef97_ec2a_52ec_aaab, + 0x6456_0e73_b84c_0532, + 0xf42e_0f1e_2164_9dbc, + 0x362d_769b_3e3b_429b, + ]), + pallas::Base::from_raw([ + 0xa849_45d4_8229_07c7, + 0x8319_319a_9dfd_eb24, + 0x1b53_33bd_832f_9d47, + 0x1a8d_4605_440e_14eb, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2cfe_f055_520e_c484, + 0xd39b_1ad9_5e10_8589, + 0x1aaf_3ba7_8102_4b08, + 0x39c3_c628_22fe_6009, + ]), + pallas::Base::from_raw([ + 0x45f8_894e_2232_67ac, + 0x4296_0ed4_94f2_d879, + 0x91c3_5975_0a2e_a628, + 0x0055_3f58_2ea2_b9d8, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9dd7_97f4_738d_a24c, + 0x2759_7dca_3390_3158, + 0x2569_cf2d_55e1_3988, + 0x3a35_4403_855a_ecc7, + ]), + pallas::Base::from_raw([ + 0x8cd4_53df_2421_8d58, + 0x3eb8_606a_39df_7bba, + 0xe1b6_b3cd_016d_2c04, + 0x2ac0_1e01_122f_2859, + ]), + ), + ( + pallas::Base::from_raw([ + 0x05ee_bec3_ee74_bbef, + 0x9235_beb6_d261_7620, + 0xbe1b_e89b_c34f_92fd, + 0x3e52_f01c_752c_b88b, + ]), + pallas::Base::from_raw([ + 0xf3a9_51ca_cd8a_af53, + 0xc74a_97e4_097e_c343, + 0x092e_9e05_bffc_9a22, + 0x3d5a_5fc8_11e3_ba39, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0ce5_3d2c_d538_3a58, + 0x3ef1_eb32_6ca3_e72f, + 0x3c78_7688_7698_fc9b, + 0x3e74_cad7_49d2_c6c6, + ]), + pallas::Base::from_raw([ + 0x1f1c_faed_0c97_13d5, + 0x3c44_cdd9_d43b_1878, + 0xa99e_49e2_45a6_8dbe, + 0x3d13_8bac_6757_2e90, + ]), + ), + ( + pallas::Base::from_raw([ + 0x83f7_da35_d76e_c72f, + 0x5d1b_36e6_359e_06e5, + 0x4467_a198_0e08_1507, + 0x3416_e9d4_12cf_fc58, + ]), + pallas::Base::from_raw([ + 0x2730_0907_8ed4_7765, + 0xb022_0ab4_912b_7d90, + 0xb416_bc2b_6319_e9c9, + 0x10fa_d4cc_a19a_342c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa6ce_2794_9121_884f, + 0x92a8_a18f_02e8_8e44, + 0xcadf_04d1_3995_43e9, + 0x35f2_987c_a27e_1776, + ]), + pallas::Base::from_raw([ + 0x4f73_1ea3_e5d8_f863, + 0xbefa_b1c9_dacb_8028, + 0x91bd_d4ef_ab4e_02d4, + 0x39ea_3c5e_6cb8_47ec, + ]), + ), + ( + pallas::Base::from_raw([ + 0xba0e_d638_9040_a526, + 0xade3_6a2d_1046_ce2f, + 0x07c4_031f_7d63_bfda, + 0x3705_7fe5_eff1_6d4b, + ]), + pallas::Base::from_raw([ + 0xcb3d_da9a_230f_7d4f, + 0x3ec2_fac7_acc4_e22c, + 0x09bc_83c8_6022_52ca, + 0x01d7_c169_7b90_1850, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0316_2275_7062_aa8b, + 0x06e7_bf78_c301_eb68, + 0x5a45_ea8c_518d_e8fe, + 0x1a8b_f176_fdd8_5a16, + ]), + pallas::Base::from_raw([ + 0x1de4_ed97_6261_3da9, + 0x7c2d_265b_75a5_c58e, + 0xdecf_84e1_13a6_493c, + 0x1853_c9ac_fc18_1ad9, + ]), + ), + ( + pallas::Base::from_raw([ + 0x18f8_f690_da5c_857a, + 0x8baf_5e54_f47a_4c66, + 0xc596_5adb_9056_e487, + 0x0b41_5e4f_c50b_4723, + ]), + pallas::Base::from_raw([ + 0xf758_8e8b_d1c1_5233, + 0x3bb8_512d_7b1e_a04e, + 0x3782_0e10_b3da_73da, + 0x2c2a_1026_015a_e0af, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc491_0da2_ab40_38fa, + 0x8919_0bf6_2117_4bb0, + 0x11b6_3346_fe70_1c31, + 0x01e3_fd81_583f_9350, + ]), + pallas::Base::from_raw([ + 0xdc61_17c0_98c1_601e, + 0x1ab5_a27b_e45e_f81e, + 0x52a2_3f16_f37e_1612, + 0x0b0a_7f80_97e2_e6cf, + ]), + ), + ( + pallas::Base::from_raw([ + 0x520b_062f_e78a_e7a4, + 0x3cfd_2ac9_15c5_e15e, + 0x6f6f_0d82_41c1_d542, + 0x3d45_bbb4_6013_9f32, + ]), + pallas::Base::from_raw([ + 0xb1ac_5c97_c97a_3879, + 0xef0c_c0f2_56ab_5321, + 0x622a_376f_e184_60c6, + 0x0529_7efd_374c_32b5, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc226_4973_4c5f_c60a, + 0xda93_2f28_b15a_50e6, + 0xcce7_e413_9c75_ced4, + 0x2a0a_7edf_84f3_ca0c, + ]), + pallas::Base::from_raw([ + 0x97e9_ae4d_cf51_e9c6, + 0x7763_6a76_34f8_eb62, + 0xc1db_731f_15d6_84d7, + 0x28ae_45a0_2811_2280, + ]), + ), + ( + pallas::Base::from_raw([ + 0x48e1_7904_526f_f906, + 0xd5fd_96af_3118_d25d, + 0x16f2_1379_295a_3e85, + 0x056e_6682_1209_f065, + ]), + pallas::Base::from_raw([ + 0x4d09_3b0c_18f0_4853, + 0xf28c_aecf_c757_36ed, + 0x0124_3cba_966f_a69b, + 0x2bf0_69d1_a8f4_e195, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa2ab_2ca3_3f7a_8c18, + 0x5fbf_9a9e_292c_3fee, + 0x42d2_fb9b_62d4_bf6f, + 0x1513_87c4_5019_b2a8, + ]), + pallas::Base::from_raw([ + 0x45de_0a99_709d_8f92, + 0x013b_c62b_f18c_2d51, + 0xdc17_e477_e451_a5b5, + 0x189b_e9b3_2748_ccfa, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb1d2_10c0_c338_3697, + 0xdde9_1ff0_0a98_9f1e, + 0x57b1_5511_fac4_ae0b, + 0x3415_5509_eda9_fb3a, + ]), + pallas::Base::from_raw([ + 0x0336_a0e4_4d04_f2a0, + 0x0c38_8030_f51d_1168, + 0xdb88_0218_359b_52b6, + 0x1669_7954_3729_1904, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf754_4d39_2e15_d646, + 0x042c_85b5_e75c_bffe, + 0xe38b_c7e1_c3a3_371e, + 0x1f78_4954_3955_3ffd, + ]), + pallas::Base::from_raw([ + 0xb3b0_03ef_ce95_fe55, + 0x3447_f020_3e43_a221, + 0xd3ec_5b00_f101_a4a7, + 0x1daf_fb55_fdca_e9bb, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd022_b2e4_3b43_5771, + 0x1f98_8073_0dae_5fa9, + 0xa19c_1dae_ee9d_2279, + 0x2d7c_10f1_1f8a_fbe2, + ]), + pallas::Base::from_raw([ + 0xe53e_4dc4_e5c6_9e51, + 0x2af4_1c7b_e73c_bfc3, + 0xf0e0_1136_6a8b_7018, + 0x0b77_44c8_8c77_fcbf, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9ed7_be62_3b40_bcb9, + 0x3e31_f660_58df_eb96, + 0x0158_90c1_6331_430d, + 0x21ad_7eae_f186_64a9, + ]), + pallas::Base::from_raw([ + 0x4997_85d6_de57_f60b, + 0x3a83_9c1d_c46b_2861, + 0xaea4_ad0e_e1cd_df9a, + 0x2472_290e_1d0b_53b2, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa73e_3284_1eb0_d3cf, + 0x5dd2_c1b8_16cb_cd57, + 0xac16_7859_a9c2_66b6, + 0x39d0_2f80_ee2f_5fdc, + ]), + pallas::Base::from_raw([ + 0xf33d_b523_81f4_08e5, + 0xa267_d464_ddb6_94ce, + 0xde0d_2c54_83af_3f7a, + 0x3105_d9d7_4eb0_8afc, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0f06_d3f2_ae51_741c, + 0xb747_9d5c_a4b9_5a80, + 0x7839_36ab_4715_89b0, + 0x17e1_7cf7_cf73_abce, + ]), + pallas::Base::from_raw([ + 0x228e_32f0_7e39_0ca6, + 0x92fe_35e7_744d_16a8, + 0x235f_5e1c_a1d9_dfa7, + 0x2adb_4c5b_3bd6_6726, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5819_6066_3266_3841, + 0x24da_b91e_6b28_9f04, + 0xb79a_cf49_63a4_4105, + 0x21da_e5c6_0cb6_7703, + ]), + pallas::Base::from_raw([ + 0xa223_487e_6406_576e, + 0x86b6_9d9e_7467_7eb2, + 0x7e2e_531d_048b_6ae2, + 0x1796_bb64_46b9_0567, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6b90_eb45_561d_04dc, + 0xe743_9460_131c_ae9a, + 0x2735_4e72_d3b1_3274, + 0x181b_8996_1ce6_dbf2, + ]), + pallas::Base::from_raw([ + 0x4393_a574_6e7d_ebcb, + 0x17b9_57b0_374b_7321, + 0xf48f_1499_0aeb_f4ff, + 0x2af0_a0cc_80d8_ad59, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8a86_d29c_9a81_c5a9, + 0x2393_6d2f_411a_1f97, + 0x50f6_242a_40fa_9525, + 0x05b0_faf1_c315_0a4b, + ]), + pallas::Base::from_raw([ + 0xc70c_9a93_ab83_1c95, + 0x6ed2_9ae9_ceea_efb1, + 0xc7fe_68b2_22e1_cf03, + 0x381d_cfd0_1918_44b4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7d5a_3127_5dbe_b7d9, + 0x79e0_3de5_25ce_dab6, + 0x935a_02dc_9fdc_eab4, + 0x2b4d_4719_7811_4448, + ]), + pallas::Base::from_raw([ + 0x7673_78df_e2fe_1eb2, + 0xbe91_9301_c9aa_5f18, + 0x60ee_48c0_4796_a28e, + 0x2f24_3e8c_7b7f_4693, + ]), + ), + ( + pallas::Base::from_raw([ + 0x58ac_fcec_3b6a_6302, + 0x778c_ceb2_8a49_ffb4, + 0x8c28_f55d_2771_8a04, + 0x3c4e_cb86_6f24_7db3, + ]), + pallas::Base::from_raw([ + 0x7fbe_d4df_7b3f_fc86, + 0x8540_6cc9_d470_f2ed, + 0x0bef_5e3d_830e_0220, + 0x2c60_c874_457a_d78e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2fb1_b9b7_f918_5676, + 0x3b9c_0985_f979_e385, + 0x3c64_e299_b19e_df3f, + 0x38c6_03c7_f4c2_e21a, + ]), + pallas::Base::from_raw([ + 0xb328_4e4b_3bc3_34f9, + 0x1a14_4a9d_9ca6_9b21, + 0x8d0c_c279_4992_b9b6, + 0x0ce6_19ca_d57c_1085, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9555_d1a8_a695_617a, + 0x9f38_43fe_8064_4c5e, + 0xa495_35cb_8d85_c2e7, + 0x2d01_2113_eb7c_976f, + ]), + pallas::Base::from_raw([ + 0xb4b7_72bd_6491_50b5, + 0x45dd_d92f_bdeb_1fd5, + 0xedaa_6443_41d8_1b41, + 0x2966_c1c6_12e0_0292, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6095_00b2_ffd5_9f23, + 0xc0c2_61ef_b258_de48, + 0xcbcd_b97b_51c2_00ac, + 0x0bef_2f3a_5b4a_70a6, + ]), + pallas::Base::from_raw([ + 0x69f9_ffcf_df73_c226, + 0x3c86_f51d_5b51_5ddf, + 0x0826_406c_ef5e_e020, + 0x05f0_31fe_225f_e8c9, + ]), + ), + ( + pallas::Base::from_raw([ + 0x314d_d52e_0919_736c, + 0xf554_b5ea_55e9_39ff, + 0x7775_a5d8_d07f_9f27, + 0x18b9_43fb_57be_afd4, + ]), + pallas::Base::from_raw([ + 0xf563_1374_8745_63e7, + 0xbd1c_a9d6_622e_8cd2, + 0xd060_6006_611b_cc4e, + 0x360d_025e_38f6_02a0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x399e_2e74_4e2d_d3b9, + 0x43e3_74f8_8b83_0cba, + 0x4289_a1c4_48cd_f4e0, + 0x196d_f315_b594_9716, + ]), + pallas::Base::from_raw([ + 0xc288_6cd6_673f_4deb, + 0x21f2_2728_b89f_4ed3, + 0x3439_6d66_d9a9_48fe, + 0x1089_5fb4_40fe_bb75, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4316_a351_fc82_dd4a, + 0xc959_7950_4cfc_ed50, + 0x7273_943a_84cf_d15e, + 0x15aa_aa6c_c1b9_6ac1, + ]), + pallas::Base::from_raw([ + 0x1b99_d676_512b_796d, + 0xce39_dff1_cba5_dbbb, + 0x421d_4fa7_1462_6d44, + 0x34ad_9d2a_dfaf_5b69, + ]), + ), + ( + pallas::Base::from_raw([ + 0x49ad_a6d1_104a_6eef, + 0xcf8b_024f_cfb5_b7cf, + 0x840c_cc9d_3a3b_cc91, + 0x29b9_5744_64e1_ddfc, + ]), + pallas::Base::from_raw([ + 0x0b87_716a_708c_c280, + 0xf93d_800f_6f21_4435, + 0x1d51_3c67_2d6a_83ff, + 0x0ae6_d491_c1bd_f3bc, + ]), + ), + ( + pallas::Base::from_raw([ + 0x475e_41b0_f1fc_c172, + 0x09b7_1408_69c4_0bb0, + 0xc7ed_2eaa_6b58_0c14, + 0x1d5a_ddce_4556_7eae, + ]), + pallas::Base::from_raw([ + 0xb805_a989_ed0a_19a9, + 0x18f0_a092_4a48_ce6a, + 0x377c_c390_6667_48ba, + 0x0101_7bec_5e9b_8784, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc424_869d_df4a_4fd1, + 0xcb89_e9c6_1506_8e77, + 0x6573_5f9d_0d64_ad1e, + 0x1a16_1910_f3ef_d2a8, + ]), + pallas::Base::from_raw([ + 0x1c2a_ec44_2e96_e2b9, + 0xe5e2_6cbb_e793_3cd1, + 0x4152_f5a5_afcf_0b33, + 0x18bd_33f6_2cd7_afc2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6cef_2ed4_0f12_62a3, + 0x629f_be6a_9a08_13b6, + 0xe188_e97a_fd70_5e9a, + 0x1c81_abae_085e_bbdd, + ]), + pallas::Base::from_raw([ + 0x6b64_e32d_d83d_14cc, + 0x051c_e812_b7d3_8e72, + 0x7c9c_e5ff_9dab_955d, + 0x2c11_dcb2_7ed5_553a, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa486_1053_6543_7c5c, + 0xbaef_3528_5949_99b5, + 0x6ebc_2d21_8bd5_7856, + 0x1194_f3a8_8f8d_20d8, + ]), + pallas::Base::from_raw([ + 0xc583_16cf_02c6_0b44, + 0xbae8_db73_6695_4f2c, + 0x1321_fe4b_4bc8_ff74, + 0x1f90_ec5d_5cdb_66c9, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3e78_8bbc_500b_061b, + 0xa39f_04d5_7d40_d1b4, + 0x1075_ff0a_e695_322e, + 0x29e8_f7f2_17eb_d76f, + ]), + pallas::Base::from_raw([ + 0xbf38_0b79_6b13_1fc9, + 0x6464_a70d_f53b_4597, + 0x7026_66e6_dd97_4644, + 0x12fe_b0d1_9689_e650, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfd13_2e00_4e5d_4971, + 0xc505_01d2_b5ea_6072, + 0x9202_b952_2e65_9ed7, + 0x3ab2_84aa_1043_7b11, + ]), + pallas::Base::from_raw([ + 0x4de0_8d20_70f7_f5e1, + 0x550f_b47d_5648_c508, + 0xf74a_5a30_6073_9c33, + 0x3b52_c98c_59db_48ec, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb565_72c3_4d9d_f266, + 0xd4cb_8d31_2f6e_e625, + 0xcd7e_2193_6f0a_a501, + 0x3173_b0b5_e1b9_bcb7, + ]), + pallas::Base::from_raw([ + 0x7bb7_0367_d936_382b, + 0x90d5_03e0_1a24_9fa0, + 0x1d1a_fceb_21eb_295a, + 0x2a42_5586_0ab7_f2de, + ]), + ), + ( + pallas::Base::from_raw([ + 0xea5b_ff65_c9da_428a, + 0xa4cb_a520_eadb_5354, + 0xd80d_5043_0b03_7bdb, + 0x30f5_493e_17bf_d471, + ]), + pallas::Base::from_raw([ + 0xa752_0e7f_dd99_dba5, + 0x5d12_6ed0_d9a3_2a25, + 0xf835_77c5_523e_9c66, + 0x3096_0d36_5f12_d42a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6698_5d13_ac44_6aa6, + 0xb773_2f26_9677_6e12, + 0x1fb7_b640_8501_50b1, + 0x335c_045c_e3ac_a829, + ]), + pallas::Base::from_raw([ + 0x1134_b8f8_bddf_ae01, + 0xfc14_9622_110b_9e4d, + 0x292a_9dfe_fed2_495a, + 0x2d23_4774_78d6_9f8c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe697_a384_bfe0_e7b9, + 0x9600_e548_7eec_8f61, + 0x5cf6_d911_d00d_4080, + 0x2f72_9fef_7fb4_9c3d, + ]), + pallas::Base::from_raw([ + 0x6f27_44cd_0eb3_ccea, + 0x20a3_f582_850f_75ee, + 0x0a5d_de13_44a9_f626, + 0x35ce_7274_5aba_1199, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7745_e733_21ad_a466, + 0x7e96_b0af_9e4e_eac1, + 0x9076_89d2_d2fe_4108, + 0x0ad7_74ec_279a_de59, + ]), + pallas::Base::from_raw([ + 0xeb87_26d2_fa30_f945, + 0xf59f_fb36_952a_758e, + 0xf46c_d397_277b_f15b, + 0x0e42_e600_a788_4e1e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x42d7_c6e8_1a54_7d39, + 0xb171_695f_50a8_bd62, + 0xfe87_5f93_5739_16ba, + 0x2b7b_81a1_9e1a_f147, + ]), + pallas::Base::from_raw([ + 0x5479_f6bc_eb58_7cf0, + 0x55a1_827c_f39e_06ab, + 0x2a9c_0fc4_d3e9_aaae, + 0x1934_b567_9c19_e682, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe18f_eb78_018f_21fb, + 0xcfa5_4075_0fa5_0007, + 0x05e3_f7d5_9d0f_5e8b, + 0x2cc9_4cc1_fe1a_3754, + ]), + pallas::Base::from_raw([ + 0xc2d2_f324_8b4c_ae1c, + 0x447f_7745_c5a9_dedd, + 0x3c21_26cb_32f6_9c68, + 0x08d2_cff6_fba5_5d5b, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb5d8_5390_444c_c212, + 0xe08f_cf9d_bef6_3c10, + 0xe6cc_866a_fb3c_80e3, + 0x1973_ffe7_d02f_0fa4, + ]), + pallas::Base::from_raw([ + 0x433d_1974_b639_e380, + 0x10e3_a8f5_d79c_3bb2, + 0xc48b_7633_c798_f597, + 0x2b49_6a88_af43_e434, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6be3_5a9e_4646_9b0a, + 0x21fe_d657_d107_f630, + 0x99d3_0abd_ab7b_8d62, + 0x3509_1866_4a0e_e32f, + ]), + pallas::Base::from_raw([ + 0x0454_ba7b_2b38_8723, + 0x994c_fd44_0535_add3, + 0x5c3e_e7d5_a694_1082, + 0x3e6b_3a1f_16bb_57e2, + ]), + ), + ( + pallas::Base::from_raw([ + 0xffb6_e87a_f4f9_273d, + 0xa3b4_9290_844f_d1f0, + 0xd9d9_9177_ac41_03a2, + 0x1ba6_f2f8_98ad_5de0, + ]), + pallas::Base::from_raw([ + 0x2199_f2d0_0e0a_20d2, + 0x47bf_5d92_1c69_7f48, + 0xb50d_409d_b342_4e95, + 0x0fc8_6805_b969_8fa4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x36ee_72e6_26d6_16f4, + 0xa85c_131e_8f2e_e5d1, + 0x544d_49b6_d5fe_bc77, + 0x0900_2d79_10df_23fd, + ]), + pallas::Base::from_raw([ + 0x768f_c641_47a7_920b, + 0xafed_4d26_3791_7ef3, + 0x25ef_bc62_b972_a83c, + 0x0a20_b646_a0fe_e655, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb83e_09de_6703_6a2c, + 0xc6e4_2d86_9a1c_8a6f, + 0x26cf_9928_a140_3757, + 0x2ea1_20a4_bb61_d62b, + ]), + pallas::Base::from_raw([ + 0xc7aa_5256_b121_eafd, + 0x37a9_d899_91a1_fb45, + 0x0c6b_29a8_5134_4bbd, + 0x0047_cc6a_f224_fb19, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9c0e_5b82_9469_8c2f, + 0x246a_ab8d_57f7_eb14, + 0x6acc_27f8_d28b_015c, + 0x14bc_c3c2_2e2e_b6bb, + ]), + pallas::Base::from_raw([ + 0xbcfd_9f65_9803_ac84, + 0x9065_8c67_7b1a_f1a8, + 0x779e_13d7_fbca_9d34, + 0x0cde_f9bd_324e_1df5, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2fb5_5ddd_b490_61f1, + 0xe18d_c4f6_f48c_ff60, + 0x209e_1749_1f95_8e22, + 0x0651_e474_e0d8_11c6, + ]), + pallas::Base::from_raw([ + 0xadb6_45c2_ec3c_cbbb, + 0x35a0_eac6_b2d3_0210, + 0x4ad9_2ead_3d43_a194, + 0x2e39_0f7a_bc18_85b6, + ]), + ), + ( + pallas::Base::from_raw([ + 0x23ca_ecf8_4a99_8786, + 0x48da_db27_2bd6_3318, + 0x9d89_40f9_b2d3_7799, + 0x17fa_61d6_05a5_8987, + ]), + pallas::Base::from_raw([ + 0xc88f_47e1_1d26_8daf, + 0xfb0d_a012_55ff_62bc, + 0xac10_cf88_df83_2cca, + 0x32f1_0c77_19eb_ab29, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3195_102f_c327_9b31, + 0xacfe_b6c5_89a5_d878, + 0x9367_2ab2_d8e8_a423, + 0x14ac_50c6_6669_ba07, + ]), + pallas::Base::from_raw([ + 0x8454_fc70_093a_e70e, + 0x83cf_e218_71ea_9716, + 0x8f87_513a_2560_8897, + 0x3042_845c_65f3_82fd, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe3bd_44e1_cf62_b948, + 0xaca4_8396_f097_f8f9, + 0xaae7_28f2_e533_dd59, + 0x205a_0002_d292_790e, + ]), + pallas::Base::from_raw([ + 0x3747_573a_6340_da58, + 0xc462_be08_e666_c34c, + 0x932b_a9bd_22ef_0dd1, + 0x3c05_7dd9_7523_55c2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x21bc_7af6_3b01_c7e9, + 0x6252_732a_b6ba_dc77, + 0x8936_7ad2_a6da_3eaf, + 0x3423_225a_a8b2_648c, + ]), + pallas::Base::from_raw([ + 0xbf9f_96b4_d37e_cdbd, + 0x9788_5731_a6b5_c21e, + 0x8e53_deb0_53bd_d03f, + 0x0267_f659_8e41_9d57, + ]), + ), + ( + pallas::Base::from_raw([ + 0x16ce_c525_99d8_4e32, + 0x0be9_87cf_f76b_791a, + 0x64e9_f724_3ded_90cb, + 0x3f4b_7c45_f7fd_0a5b, + ]), + pallas::Base::from_raw([ + 0x504d_f52d_0a5e_6f9b, + 0x0ffd_7f14_c471_c25e, + 0x05f5_f6a6_f4e4_3235, + 0x03ed_4df6_1b9b_8e2f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4d55_7ecd_d9bc_b076, + 0x5408_529b_d207_78e4, + 0x58de_ce33_83ef_9bd5, + 0x08e2_3b7f_80c4_f1ce, + ]), + pallas::Base::from_raw([ + 0x2439_550a_1d3f_5fe7, + 0x6f35_49ba_c229_e7de, + 0x4ff1_8163_5458_c34c, + 0x18b0_1033_f86c_5b2d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7932_981f_1490_acb6, + 0xae32_d1c6_69f1_ba87, + 0x487d_3e38_98de_ad2f, + 0x3360_9d96_1cb9_e4bb, + ]), + pallas::Base::from_raw([ + 0x2df5_c107_9de0_42d9, + 0x3a5e_e2d8_4f95_338e, + 0x0f61_1e54_8e5d_6071, + 0x314a_f599_b686_4f6a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x12ec_74da_fb34_4c9c, + 0x86e7_c394_398c_b2b0, + 0xb350_5e55_0f41_55b3, + 0x3882_c274_d10f_9604, + ]), + pallas::Base::from_raw([ + 0x0475_5416_a740_3cc8, + 0x7bc7_5c57_18be_8417, + 0x7225_cf87_5def_f0bb, + 0x354b_a5af_e4b7_8ab5, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb928_8c66_ae60_cf6b, + 0x8a01_3bed_8367_6133, + 0xf9d3_d4e7_a269_61af, + 0x1590_bf3d_b717_8a0f, + ]), + pallas::Base::from_raw([ + 0x2988_6de5_e09d_b63b, + 0xffee_8de1_593f_5931, + 0x6c10_c99c_784a_4ab0, + 0x08b8_0c40_7a4e_c18f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x07ef_5d82_7684_6a64, + 0xf266_7542_3316_83e4, + 0x63e8_e5b2_b6e0_98a8, + 0x115d_36de_a116_3915, + ]), + pallas::Base::from_raw([ + 0xf5cb_ccd4_6981_b817, + 0x739c_6e08_5a3a_acc1, + 0x43c3_a2dc_0dc8_4a1b, + 0x3061_050b_bec9_728c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa784_97ba_84c4_9897, + 0x0bdf_4a54_6f11_477b, + 0x78d4_5bc7_61b4_4fc4, + 0x009c_265a_01e4_bd27, + ]), + pallas::Base::from_raw([ + 0x83d6_88e2_3124_442f, + 0xdaf5_4110_7337_7228, + 0x0476_e74e_a35f_a96c, + 0x1cbc_b10c_31c5_a6ce, + ]), + ), + ( + pallas::Base::from_raw([ + 0x815f_09ef_7425_c941, + 0xddea_5b91_af1f_b16e, + 0x44c4_8ec0_6e4c_4d5b, + 0x3eb2_6944_09dd_347a, + ]), + pallas::Base::from_raw([ + 0x5075_853b_087d_36e1, + 0xa637_4ced_4fb9_8cd2, + 0x8f41_0471_fa56_764d, + 0x2a49_2949_e50a_5431, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2a44_94c5_80ca_c7b1, + 0x5c80_bc62_be0c_4c20, + 0x3e9b_c8a6_76ee_a29d, + 0x31e9_f489_5373_0252, + ]), + pallas::Base::from_raw([ + 0x0cc5_99cc_2d44_795f, + 0x5f34_2d7a_6dbb_d769, + 0x1e03_83ac_5bb1_3b70, + 0x3576_b8fa_eecc_5390, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0d73_8802_f12c_9a24, + 0x574b_d31a_7a88_fc47, + 0xfc84_3f6a_5d93_3592, + 0x1386_f0fb_f8be_9c27, + ]), + pallas::Base::from_raw([ + 0x5a23_540a_296c_f044, + 0x6192_3af4_1508_7a98, + 0x5995_b4cf_15cc_2810, + 0x1706_3276_50a0_f036, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2653_f155_85de_a90b, + 0xd63d_94d0_8aa1_482f, + 0x6a82_4715_8027_d155, + 0x1df5_02c5_50b8_da4c, + ]), + pallas::Base::from_raw([ + 0x1c94_bebe_da62_eb37, + 0x0d7d_52bc_04b8_1267, + 0x8c2f_be33_eb00_04e4, + 0x0440_0a84_ea35_612f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5c1b_8f52_5659_4536, + 0x9b79_4d4d_2397_825f, + 0xbbde_a8df_0f7d_ba1c, + 0x3d29_b2f0_082a_5564, + ]), + pallas::Base::from_raw([ + 0x997a_e0be_a8e8_c381, + 0xd8fb_2e89_3946_7e27, + 0x23e9_4e6b_32ae_749c, + 0x37be_d4a7_96d3_6ef1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2b55_fd72_49a0_af15, + 0x84b6_ef86_00bf_2219, + 0xa742_27da_4188_25e1, + 0x2de3_dd50_d348_4b87, + ]), + pallas::Base::from_raw([ + 0x7c51_9935_0187_19af, + 0xe12b_c342_f5ad_3380, + 0x4771_3997_d6b6_6fdb, + 0x2781_cf1a_f080_88bd, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd6b5_8567_e42e_6228, + 0xba55_c9ee_caab_1684, + 0xb098_b03e_c0e2_bfa9, + 0x1658_92f6_2d6c_ab72, + ]), + pallas::Base::from_raw([ + 0x7cce_f3c2_7004_750a, + 0x6764_b8ec_87af_0fbd, + 0x954a_3e23_b88f_10da, + 0x016b_7c55_ed75_6eb4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1572_3792_f441_6a81, + 0xf995_d329_4d87_564f, + 0xb0d2_1f0e_cc5c_2ee8, + 0x27d0_c30b_d4bd_22ba, + ]), + pallas::Base::from_raw([ + 0xdb2c_fdc7_ab47_0473, + 0x6cf3_d13d_2256_03b9, + 0x2032_3b49_b053_23ca, + 0x3874_12f6_3a24_2a92, + ]), + ), + ( + pallas::Base::from_raw([ + 0x996a_0c38_d044_b83c, + 0xde09_0618_5b92_cda5, + 0x4643_0a67_d500_a255, + 0x27f5_9685_eb77_ac7e, + ]), + pallas::Base::from_raw([ + 0x059a_236e_dfd1_5edf, + 0xe561_315f_eae6_1f63, + 0xbb05_a594_85a4_b33a, + 0x3285_ddba_14ac_1ed8, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe549_c0d0_0ceb_9dc3, + 0xd273_7cf6_b421_aa39, + 0x97d6_32a0_5a8f_3a2b, + 0x3c24_5f47_8e0c_a36c, + ]), + pallas::Base::from_raw([ + 0xf20b_8a37_aa4a_7d8b, + 0x8189_e00c_5620_23da, + 0x5aff_d154_2f27_8147, + 0x03a2_d69b_1690_1c15, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1db8_ec71_2a43_63b8, + 0x8920_3a49_6826_c060, + 0xab52_38e8_da30_0add, + 0x1fba_c064_05f9_fd83, + ]), + pallas::Base::from_raw([ + 0x35ca_286a_fd3e_c748, + 0x4d74_e271_dbd4_2a3a, + 0x1af7_48ff_e310_304b, + 0x38e5_4864_6bbe_709c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9c3f_6480_c417_46a5, + 0x7455_11cb_9655_ec10, + 0x0b30_d995_92df_c3ba, + 0x04b5_d208_aa2d_5695, + ]), + pallas::Base::from_raw([ + 0xe3fa_148a_db80_2f0b, + 0x692a_dedb_cc21_f27b, + 0xe5bd_3253_815e_0161, + 0x06ae_836a_74b7_d7b3, + ]), + ), + ( + pallas::Base::from_raw([ + 0x23a6_7fe7_633a_8233, + 0xfb9a_4eca_7e71_4fac, + 0x950d_ef5f_a5bd_24cd, + 0x187a_4853_68dd_e36c, + ]), + pallas::Base::from_raw([ + 0x4ad9_6376_9666_3e54, + 0x935b_6072_4744_7eab, + 0x40d0_ef01_0cf4_ba17, + 0x2bd6_8f8f_e07b_ef4e, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe269_82a7_147b_8c18, + 0x607b_e6c4_b9b9_be5d, + 0xe32d_307b_b3db_e0ef, + 0x3a76_010b_80d3_c8b0, + ]), + pallas::Base::from_raw([ + 0x06a9_146c_a674_8492, + 0xce4b_2d6b_49aa_731b, + 0x795b_873d_4a5a_95bb, + 0x19e5_778d_9876_7a46, + ]), + ), + ( + pallas::Base::from_raw([ + 0x02c9_e528_8369_fdca, + 0x546f_8eed_d946_62ec, + 0xa4c4_8b26_12bd_a088, + 0x3551_abbc_cb49_2c53, + ]), + pallas::Base::from_raw([ + 0xdfbe_b704_29a1_b4f7, + 0x6943_7350_0ca9_1961, + 0x4cf2_bc5d_2dd1_5705, + 0x2341_167f_494d_0476, + ]), + ), + ( + pallas::Base::from_raw([ + 0xaec8_67d5_8bab_356f, + 0x5a07_3489_e797_2666, + 0xe593_e00b_8588_5887, + 0x3298_8eb7_8bd8_1bcc, + ]), + pallas::Base::from_raw([ + 0xca3c_443b_d127_bdbe, + 0x53f6_db87_cbf9_232f, + 0x925e_3c81_1375_408d, + 0x362f_d3bb_da48_9c11, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6010_85d0_794c_e3ac, + 0x162b_7969_422e_ff0a, + 0xfa12_7c4b_9718_324d, + 0x05eb_74ba_ac18_6eb0, + ]), + pallas::Base::from_raw([ + 0xfa9c_6176_37ad_dbd4, + 0xd77b_985f_9626_c949, + 0x53e4_deb7_34a6_4e7a, + 0x1133_2731_91bb_93cf, + ]), + ), + ( + pallas::Base::from_raw([ + 0x96c9_fb17_08b4_1634, + 0xa9a6_bc5e_2d24_cb92, + 0x834c_a22b_fd16_3870, + 0x3e56_ae12_1141_fc99, + ]), + pallas::Base::from_raw([ + 0x50bb_4dbc_163f_9f93, + 0x1e25_6f15_ff60_3b93, + 0x3e61_aa6f_b66c_8a6b, + 0x00e2_da21_06b8_e3c0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x42dd_8b79_23b6_b513, + 0xea85_bf04_b59a_779d, + 0xb0a9_fa50_a01a_72dd, + 0x08a0_0b38_4174_1ff4, + ]), + pallas::Base::from_raw([ + 0x64cb_e014_3f1e_ff9b, + 0xa145_12f2_67e7_15b9, + 0xd5fc_3acc_8d1b_3611, + 0x2717_cf1f_7f6d_ee6b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x11e3_925e_0cdb_c33d, + 0xbc8e_d434_51c3_4778, + 0x7304_853c_9743_9b31, + 0x1ada_7ad3_956c_621d, + ]), + pallas::Base::from_raw([ + 0x550f_ed05_7f72_917b, + 0x2345_cf15_73cd_6078, + 0xc00f_bede_8499_f921, + 0x3935_21fc_eb2e_3365, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0adc_cc3c_16a3_f235, + 0x2676_dcb3_8d85_ac03, + 0xb7c2_fa06_3232_3f54, + 0x1b00_c04e_8168_266c, + ]), + pallas::Base::from_raw([ + 0x22b8_f975_c6b2_6381, + 0x64a0_f521_877f_6589, + 0x0de4_3934_6246_b2b2, + 0x10ab_0d63_c867_c968, + ]), + ), + ( + pallas::Base::from_raw([ + 0x72ff_4d9f_054e_cb10, + 0x28cc_c6c5_1071_3c53, + 0x4144_3ca1_3640_7bcf, + 0x3ca1_8a54_8248_dce3, + ]), + pallas::Base::from_raw([ + 0x74b5_8607_c4f6_23a9, + 0x52cf_86ce_4024_b4e8, + 0x0c1f_22de_8fa9_9eb8, + 0x06d1_9d7c_2817_2812, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3429_9ecb_1a25_b41a, + 0x4c20_e2cd_430f_4f53, + 0xd3ac_8de7_5dfa_6385, + 0x315d_549f_290d_49aa, + ]), + pallas::Base::from_raw([ + 0x950a_7a7a_7391_01da, + 0xd06e_4417_2b59_15c5, + 0x8872_d56b_c32c_8a5b, + 0x1f28_a45d_64f9_644c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xec62_b82b_3f9c_dd5a, + 0x53e4_68e0_ba89_260f, + 0xbf71_b840_6644_8a63, + 0x1a7d_2578_300e_a6ca, + ]), + pallas::Base::from_raw([ + 0xdfd2_eae1_7952_3443, + 0xe753_284b_9058_3e43, + 0x3850_e370_e03e_b2ec, + 0x24f2_1e42_10b5_41ca, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3960_69f3_d739_056b, + 0x2f84_257d_240a_a99b, + 0xcbbc_6a3e_1d2e_3e47, + 0x32f2_11fd_60f5_6c88, + ]), + pallas::Base::from_raw([ + 0x8993_dc0a_f3e7_01d6, + 0x3c49_7f08_3d5f_1ffb, + 0x7c69_98c3_762e_4080, + 0x25ff_cd09_47d8_4962, + ]), + ), + ( + pallas::Base::from_raw([ + 0x00eb_df7a_a05e_0505, + 0x32d7_73fe_8a8d_769a, + 0xa0d7_4ae0_ba94_c75d, + 0x028f_6828_bf19_6ab8, + ]), + pallas::Base::from_raw([ + 0x1240_0761_dba1_cb3f, + 0xf439_7e09_5aee_c121, + 0x9a39_26be_cdaf_c085, + 0x11d1_6e6d_2222_d955, + ]), + ), + ( + pallas::Base::from_raw([ + 0xeb43_eac8_a45e_50fe, + 0x76e1_3155_9fb9_e4ca, + 0x8f66_7104_3f1b_0d78, + 0x0dc9_64a3_6226_3ba5, + ]), + pallas::Base::from_raw([ + 0x2443_a280_98f7_d9ea, + 0x7873_9d11_13fc_182d, + 0xd62c_6c84_30df_277e, + 0x3981_79f2_29f0_77ad, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3f3b_83e1_e18b_41a6, + 0xe879_b7d0_d499_1f0e, + 0x3a14_5291_dc57_a3a4, + 0x1106_64a3_a51b_428d, + ]), + pallas::Base::from_raw([ + 0x9acb_835d_d793_fc7e, + 0x695e_bbbc_7b1d_69ac, + 0x418d_bfef_2441_77eb, + 0x0e03_0d59_8861_c631, + ]), + ), + ( + pallas::Base::from_raw([ + 0x676c_1bbb_3d79_4a17, + 0x86f7_40b1_a30b_1824, + 0x0414_8138_2c6e_d6cd, + 0x2460_267a_aca9_b888, + ]), + pallas::Base::from_raw([ + 0x4b8e_28e4_c2de_91a3, + 0x0eb9_23e6_bb90_72e7, + 0x84a5_c9a1_9518_d64b, + 0x2028_854f_c7fe_f563, + ]), + ), + ( + pallas::Base::from_raw([ + 0xec55_0b1b_b4b3_5edc, + 0x9bd6_e713_a2a0_44f1, + 0x1bb7_d52f_e215_849b, + 0x2a3b_4a5a_60a8_e589, + ]), + pallas::Base::from_raw([ + 0x37db_8209_47f9_907b, + 0x9606_a95f_fed0_ec37, + 0xa4a5_f491_06db_2a91, + 0x2dc3_8e45_8a44_967b, + ]), + ), + ( + pallas::Base::from_raw([ + 0xac40_3499_d9f9_eb6c, + 0x026d_a7c4_f3a1_2e88, + 0x7dfa_04ff_1867_a66b, + 0x0980_4e29_e424_13a9, + ]), + pallas::Base::from_raw([ + 0x51b5_218c_3a0a_055f, + 0x5ddc_c1fd_7461_34bb, + 0x3696_cdf0_cf53_c400, + 0x3594_1364_798f_0c20, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe6a7_7cfc_25cb_3242, + 0x2c31_d7f5_8837_2988, + 0x562e_9094_a407_ac85, + 0x3ab1_effb_2622_e180, + ]), + pallas::Base::from_raw([ + 0xb424_bdcf_597c_6557, + 0xeda3_48f9_ba67_2219, + 0x8c1a_e37b_63f2_d3cd, + 0x26f7_ed5a_5a2f_099a, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd297_863e_dded_7212, + 0xf91b_2b7e_3e36_bebc, + 0xaeff_d446_04bf_08b9, + 0x2467_95dd_ab7c_c74e, + ]), + pallas::Base::from_raw([ + 0xc713_53de_e975_f704, + 0xb276_043d_644d_0408, + 0x5878_7fed_3075_dead, + 0x2771_e939_209b_ae7a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x06dd_9ad7_373d_99f9, + 0x8a73_6054_b608_fa6a, + 0x3411_7ed3_2293_33b5, + 0x2e33_7d8a_2021_02de, + ]), + pallas::Base::from_raw([ + 0xc3ee_16d0_a73b_d633, + 0xd25f_08e9_2b65_b288, + 0xc26a_08ce_301a_28b9, + 0x3935_358f_146f_39be, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3f5b_b50c_f6ef_ce5b, + 0xb76d_32e4_5f67_4c5d, + 0xcaa1_7ff1_2bd7_bb4b, + 0x1b8d_cefd_b94d_06b2, + ]), + pallas::Base::from_raw([ + 0xa469_b014_2c83_2078, + 0x4d17_ae69_2a58_478f, + 0xcc08_c0ee_6947_386b, + 0x1428_6ba3_312d_4851, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2f3d_b3e9_a41a_0337, + 0x03a4_7f23_3f67_a26d, + 0x28c2_fecf_bfc3_355b, + 0x2bbc_960a_bd2e_f481, + ]), + pallas::Base::from_raw([ + 0xc311_0952_b121_2186, + 0x2e87_5201_48d5_fc53, + 0x875c_9ff6_a96a_da2c, + 0x0223_4935_d415_a5e5, + ]), + ), + ( + pallas::Base::from_raw([ + 0x36f0_8415_22e2_56fe, + 0x6d55_4c96_1cb1_f126, + 0xefbe_e6c5_dceb_0fd6, + 0x3de2_b1ec_d97a_180c, + ]), + pallas::Base::from_raw([ + 0x5304_ab95_aae6_9b66, + 0x60ed_3893_0c80_7993, + 0xd9b4_3e8d_6ff5_4a3e, + 0x18fc_8e36_16ba_3090, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0822_d885_4dfe_69f0, + 0x505e_1f45_ccf1_dc38, + 0x628a_640a_7eec_8b84, + 0x0127_23d3_14c8_9c01, + ]), + pallas::Base::from_raw([ + 0x7e4e_f3cb_d5d4_7736, + 0x850b_2fbd_d6e5_5bac, + 0x6c2f_3bc2_18f3_9602, + 0x2535_1370_28dc_f11f, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa134_90eb_3ad2_a6fe, + 0x0b67_1163_084d_310a, + 0x86df_15cb_08ca_1613, + 0x2186_0967_8d1c_6167, + ]), + pallas::Base::from_raw([ + 0x58de_954b_613d_9043, + 0x4cdf_a072_9d4a_5cde, + 0xd2a4_f058_b900_7509, + 0x2660_6d63_e85e_94fb, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1bee_c383_512e_3059, + 0x75e4_6def_af35_b531, + 0x25cb_866c_89fa_7469, + 0x2300_a9c8_709c_c635, + ]), + pallas::Base::from_raw([ + 0x30fe_ba0a_d8b6_dd3a, + 0xfe7b_289a_3c6c_a17b, + 0x7a6a_763f_4f92_0595, + 0x0669_e3fe_787d_f81c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2317_af7e_2bc1_fb5a, + 0x4fa0_a83c_0f76_c57a, + 0xad4d_1850_e918_2af4, + 0x3095_294e_9909_a724, + ]), + pallas::Base::from_raw([ + 0xb04c_712b_c022_15ee, + 0xf118_0f20_c588_375d, + 0xaabd_ded2_9f7d_bf93, + 0x170b_a10e_7ff6_dc04, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4614_ca37_6411_165d, + 0xfe5e_19ff_1cf2_d2ea, + 0xfd57_702b_75b7_f29b, + 0x2d1b_3d96_4eb5_321a, + ]), + pallas::Base::from_raw([ + 0x03b0_6fb1_8c52_94d7, + 0xd282_d2a7_a658_83af, + 0x136d_b5bc_b3ef_254e, + 0x2e8c_5ef8_61fe_9aeb, + ]), + ), + ( + pallas::Base::from_raw([ + 0x79cc_d5aa_18d7_fe0f, + 0x21fb_e42a_7f84_9fc5, + 0x6a28_f36b_52a4_f91c, + 0x011d_e672_2dda_0469, + ]), + pallas::Base::from_raw([ + 0x4f07_9730_2c53_dc9c, + 0x6c9d_a709_b02b_cf85, + 0x6bf0_cda1_2708_3753, + 0x02ad_a1fa_4596_eb60, + ]), + ), + ( + pallas::Base::from_raw([ + 0x74f1_d926_d45e_11b7, + 0x1c48_974b_0e37_87a2, + 0x574f_54b1_2560_ee08, + 0x1a17_a6cd_ff9b_fa8b, + ]), + pallas::Base::from_raw([ + 0x65bd_430a_18c2_6fdd, + 0x06a7_f226_5fff_44b4, + 0x600f_2717_320e_d4a6, + 0x0c3b_4e95_ac95_a06b, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb18b_afc6_26e6_2669, + 0x34de_90c8_407d_290b, + 0x47c9_8071_fe58_680e, + 0x1274_01e0_c920_4b16, + ]), + pallas::Base::from_raw([ + 0x332d_e7a5_2f92_fc45, + 0xc917_cdc6_686b_1a9d, + 0xb86c_76fe_72e0_8b68, + 0x0b4f_39ca_545a_b70f, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc417_3670_bcb9_cc4a, + 0xbd5c_f1b8_0b64_2192, + 0x1897_6a02_8e09_c2fb, + 0x187e_651a_79a4_8732, + ]), + pallas::Base::from_raw([ + 0xae69_5d7d_5294_c1af, + 0x1934_654c_e813_bf54, + 0x642c_b26f_293d_c2dd, + 0x092c_e031_517b_da63, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7662_8a07_ecc9_bc79, + 0x8cb2_1f86_aaa6_5bb3, + 0xbb09_cf23_8a56_d226, + 0x1c1a_5d7b_19a6_351a, + ]), + pallas::Base::from_raw([ + 0xfe9f_8731_fe5c_71af, + 0xdd8d_8082_7660_c6cf, + 0xa2fb_bb46_607d_b4f6, + 0x066c_dec4_29fa_02f4, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfeba_3899_4dcd_fab9, + 0x0185_bee0_53a8_b2e9, + 0x2d18_4ae3_6719_da6c, + 0x38de_de43_97fc_4e8c, + ]), + pallas::Base::from_raw([ + 0xaf8c_1a56_9749_5a96, + 0x1eaf_43ef_5661_3ff8, + 0xfe65_2cce_44c4_fced, + 0x24ca_f69b_2d91_f2f4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x050e_2315_3fe8_888a, + 0x5cd6_7594_d7fb_eada, + 0x5890_89ac_5ffa_169e, + 0x2c7a_db4e_0279_0a77, + ]), + pallas::Base::from_raw([ + 0xace5_6f4c_4889_310b, + 0x36cb_a3e6_49a8_2d97, + 0x0a3f_5f71_4d72_02ef, + 0x34e9_12c6_ec66_b3d8, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8d2d_8ca6_933c_4438, + 0xd7f0_b609_54f3_3f61, + 0xe93b_e7cc_13ed_f94c, + 0x114a_be4c_6b55_af86, + ]), + pallas::Base::from_raw([ + 0x1837_62f1_7010_22b7, + 0x4811_6b3d_d2a2_a698, + 0xea41_857f_a3f3_188e, + 0x1fb5_1838_65ba_7743, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa082_8c60_d8a3_f578, + 0x0a25_684f_653f_6c8f, + 0x2b4d_9dc4_dc66_7726, + 0x36c8_2048_d681_651d, + ]), + pallas::Base::from_raw([ + 0xe26c_9196_230a_c29c, + 0x08fb_0378_bc18_071b, + 0x75a0_8f44_c578_a727, + 0x15ee_7196_24dc_9c10, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfeaa_76bf_d7f3_cc18, + 0x7d70_d0be_ff2b_e9a5, + 0xbfb7_ade3_819f_e899, + 0x0a80_597f_8201_89a2, + ]), + pallas::Base::from_raw([ + 0x7c0c_7cf8_5e73_abcb, + 0xefb0_3940_c106_04ee, + 0x85e4_c588_3d14_ef43, + 0x27fe_fbb2_9d0a_97b7, + ]), + ), + ( + pallas::Base::from_raw([ + 0x47c6_a888_684f_bdbe, + 0x5427_2144_0e19_5279, + 0xf6b3_7923_849c_acea, + 0x34f3_4173_3333_34f1, + ]), + pallas::Base::from_raw([ + 0x0a04_1869_d2aa_dad4, + 0x34a7_9c4d_f7a6_b66f, + 0x7b7b_e1e9_121b_6d3a, + 0x2421_7f52_b1bb_09bc, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfe01_fde7_3ddc_40c0, + 0x6731_87df_9de0_27de, + 0x2150_0dc8_2124_84f9, + 0x1764_90fe_4fbf_05d1, + ]), + pallas::Base::from_raw([ + 0x3e96_ffcc_83de_2760, + 0xffd3_6ccc_6e69_1191, + 0xe3ce_955b_54a6_1af7, + 0x091d_70c1_c2a0_1886, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdf0c_1b18_d13e_6075, + 0x3660_d9cd_6d7c_a28c, + 0xc8db_742a_5e85_5a70, + 0x0856_63df_fcde_b718, + ]), + pallas::Base::from_raw([ + 0xdfc6_3b44_bfcb_4ba9, + 0xe1ab_077a_c6da_e5a3, + 0x2b4a_31f8_1d0a_106f, + 0x26e9_9d38_3c41_ba1a, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa55b_b719_22c8_8eb6, + 0x2ba9_93d9_f776_01fd, + 0xef9d_38c3_4590_b955, + 0x393c_90d8_8ab2_8885, + ]), + pallas::Base::from_raw([ + 0x2367_9c6c_8698_dc18, + 0xc375_1a89_e204_ed77, + 0xa2cc_f60a_2cc0_42c5, + 0x3b1a_7e64_31f5_efc9, + ]), + ), + ( + pallas::Base::from_raw([ + 0x01aa_2003_93d8_1228, + 0xd565_deb4_6165_426a, + 0x1359_c8b2_23c3_a219, + 0x1741_223b_656d_fbea, + ]), + pallas::Base::from_raw([ + 0x68a2_aa8d_99d2_6617, + 0x3248_d5e3_8e95_e05c, + 0x4347_e264_3653_a95c, + 0x3b6a_ab30_009e_2a40, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc401_caf5_b80b_d7b7, + 0xd475_062f_bbbc_8de4, + 0xf888_a322_55a9_d44e, + 0x1a01_8bfb_3b21_464d, + ]), + pallas::Base::from_raw([ + 0x5d8f_724e_1cd2_4386, + 0x78a3_535f_8640_efd4, + 0x47ce_290a_4905_53a1, + 0x2a91_7aea_3b73_4a4c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x034e_89dd_99f2_6e09, + 0x45b5_bd00_e4da_62a9, + 0xebf3_dbff_ba88_271e, + 0x02c8_2b9c_ea98_af0f, + ]), + pallas::Base::from_raw([ + 0x3aca_a2de_6568_bd4f, + 0x5544_cffc_3aed_6643, + 0x52f8_d105_2659_131d, + 0x011f_4744_f248_a534, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe504_9c54_5183_a68e, + 0xb2d8_76d0_fd04_ddfd, + 0xe266_0cda_4417_fa06, + 0x2bc0_f5d1_292f_66ad, + ]), + pallas::Base::from_raw([ + 0x98fa_34c1_c7e4_4d04, + 0x86f0_df5a_c92c_6a54, + 0x458a_25bc_e1c6_6a86, + 0x36c6_ddb7_9a5f_7421, + ]), + ), + ( + pallas::Base::from_raw([ + 0x309b_b8ce_eaf9_5df5, + 0x0149_4b0f_a4ba_ea01, + 0xa124_4665_cce6_d172, + 0x3412_acee_8559_e4cd, + ]), + pallas::Base::from_raw([ + 0xa00c_8037_9921_039c, + 0x26ec_3ff0_2626_f929, + 0x7f42_5184_1ec7_6115, + 0x0d32_4da9_5111_de3f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0623_4914_8426_c348, + 0x923b_758a_75fb_c2ae, + 0x8563_479a_8a1c_3a18, + 0x0222_f189_6b30_9026, + ]), + pallas::Base::from_raw([ + 0x6383_b87a_3a58_9f66, + 0x5d68_22db_c04c_0804, + 0x3cd5_300d_b8c8_5e26, + 0x1ed6_87af_f5a9_7f81, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4976_a9d9_23f4_e05a, + 0x89a9_d356_80f3_5037, + 0xfcce_d80b_20ff_a679, + 0x2eaa_c96a_e572_8c2e, + ]), + pallas::Base::from_raw([ + 0xe326_9ec8_35b1_a894, + 0xe334_6c5d_38d5_f584, + 0x58d6_995b_9eb6_1568, + 0x3b13_1337_181e_2cab, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd626_3c8c_8fab_225e, + 0x6111_af8a_c249_0cd6, + 0xf2ef_6c29_147c_86d6, + 0x27f5_4f89_3962_c0ad, + ]), + pallas::Base::from_raw([ + 0xa842_3c35_185c_5986, + 0x70ae_f850_779c_bfa3, + 0xf628_f488_1fc8_b76e, + 0x0a9c_f292_fe41_9dfa, + ]), + ), + ( + pallas::Base::from_raw([ + 0x537c_08e1_3a1c_f53f, + 0x78df_4265_7b2b_fc61, + 0x2500_a58a_eac6_35c6, + 0x0fcf_c9c2_bc99_f123, + ]), + pallas::Base::from_raw([ + 0x3147_0e47_8fbe_1b4a, + 0x991e_e318_b485_e3b9, + 0x633d_47dd_3042_bb0e, + 0x3360_8103_99b0_a527, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd7a2_1d17_f989_6972, + 0x5e8b_6e6b_30d7_aa2b, + 0x8454_73ae_ee97_a305, + 0x3116_2156_7cc1_18e5, + ]), + pallas::Base::from_raw([ + 0xfccf_5d9f_b11e_df81, + 0x2e12_7758_c03e_b200, + 0x5f04_7db9_eeaa_1921, + 0x3a86_fc45_77b3_f524, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9e93_1cc8_c148_8493, + 0x900f_cf13_2569_e392, + 0x765e_58e9_48b5_d022, + 0x2ede_c32e_ef69_64c0, + ]), + pallas::Base::from_raw([ + 0x5695_ebac_bfeb_ebd4, + 0x98cc_74a8_fd64_21ff, + 0x27d8_5ecb_1931_3238, + 0x1053_65b5_5423_da3c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf222_9818_2d70_d3ed, + 0xdf8e_0ddb_b98b_fbeb, + 0x44ec_a0c2_57ef_550b, + 0x12e2_838e_4f99_a03a, + ]), + pallas::Base::from_raw([ + 0x6655_b07c_8d8e_d971, + 0x9d54_b9e8_f799_c635, + 0xd3ac_f7ab_31b0_0ae0, + 0x3db7_b034_049d_ceb4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x10ba_763a_c8c6_f730, + 0xae79_86b4_9150_c67a, + 0x82ed_80f2_e411_a974, + 0x04e0_22d6_0b2c_6589, + ]), + pallas::Base::from_raw([ + 0x088c_79a2_e8ad_c3e4, + 0xa2b1_bfb2_60e1_d64f, + 0x2153_eadf_50f5_03a8, + 0x0471_d795_1f45_c106, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa0af_6834_540a_1d2a, + 0xbc22_530b_a609_337d, + 0xb27e_7401_ff92_1206, + 0x0f66_0b47_5fce_7a48, + ]), + pallas::Base::from_raw([ + 0x0d2d_0ce4_5d34_1e91, + 0x5ed6_ed9b_6248_73e5, + 0xdb0d_b388_856f_04a8, + 0x3909_13e5_6b4d_ecad, + ]), + ), + ( + pallas::Base::from_raw([ + 0x931f_ebf4_3432_8d85, + 0xb4ac_dfb4_13f3_778f, + 0x2198_10f0_065a_4746, + 0x274b_25e6_d0ee_28ac, + ]), + pallas::Base::from_raw([ + 0x85ff_5dcf_2af8_0d1e, + 0x4fcf_da75_73d5_6aaa, + 0xfebf_10d6_f655_de17, + 0x0fc4_ed1e_e2fb_3daa, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2c88_682b_0807_fb47, + 0xe572_e261_762a_9833, + 0x92a5_94b7_dd97_e2d6, + 0x290c_0f08_ae67_92f1, + ]), + pallas::Base::from_raw([ + 0x9f3e_a22a_9fbd_e2f2, + 0x7828_c11a_b548_3f9d, + 0x889f_be4d_6071_fc49, + 0x2127_232a_a434_31ff, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdf50_276a_cd2f_7fd2, + 0x6221_308a_0532_4c09, + 0x1c28_3f54_3fb6_99e4, + 0x2691_bf94_093a_d903, + ]), + pallas::Base::from_raw([ + 0x6af0_82ff_ff8c_9733, + 0x5ff9_faf0_e36e_d8c0, + 0xce94_f44a_1788_600f, + 0x1318_d65d_1c35_c49b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9163_559a_be89_bb1c, + 0x5251_bfcf_7749_01a7, + 0x3efb_6de2_8193_cf1e, + 0x1942_18c3_fe5b_6949, + ]), + pallas::Base::from_raw([ + 0x7174_f4c0_cc3b_ede0, + 0x735b_e3c2_887f_4687, + 0x93ab_3df4_5697_0520, + 0x0d9d_d803_f612_8651, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7f74_e033_1fc3_eca4, + 0xd55f_7848_5f07_c856, + 0x4a3f_5542_4f34_fd79, + 0x0a48_7764_e6bb_64fc, + ]), + pallas::Base::from_raw([ + 0x7746_ae1b_b07c_53a9, + 0x0cd8_26a8_b54e_f182, + 0x64d2_d590_c7a7_2ef5, + 0x34e7_9a7c_db19_a72f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9ee7_822c_9974_c6fe, + 0x6d79_96e6_ecf8_df62, + 0x3263_2b2e_f5df_4af0, + 0x10f9_be27_b481_30e5, + ]), + pallas::Base::from_raw([ + 0x1272_0f17_8ab2_623a, + 0xa050_ea3b_6f25_0848, + 0x01f1_8495_9947_e6d4, + 0x3e25_d531_e842_b508, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1c20_5dac_8ff2_38cb, + 0x4c0e_7a71_7346_0cd7, + 0x5cb8_b1a1_4c49_19e7, + 0x1017_cc43_7f6c_a31c, + ]), + pallas::Base::from_raw([ + 0xc7a7_9329_b0d7_cfab, + 0xf910_054e_b22e_57ef, + 0xa27e_9be2_3ef3_e39a, + 0x221a_8fd3_dea3_471c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8da5_c8aa_c156_511d, + 0x3df5_2950_01ad_9519, + 0x654e_3cc1_fefe_95d2, + 0x1826_50e8_f7f3_c594, + ]), + pallas::Base::from_raw([ + 0x6a14_5790_4fe2_27cc, + 0xdbdc_600a_387f_90de, + 0xb47f_a736_8906_d3bd, + 0x2919_7af4_2853_5c8b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0e60_4790_c292_a15b, + 0x568d_bf3d_b502_70c0, + 0x54ae_c935_bb45_d7de, + 0x30eb_56d9_3c62_e58a, + ]), + pallas::Base::from_raw([ + 0x940f_2e00_ced7_14f4, + 0xf3d0_1a90_5dd8_c6bb, + 0x2188_66b3_369c_ec8f, + 0x16ad_3c60_929e_f022, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0c17_e5c3_6446_c41f, + 0xbc36_aeb7_6459_3101, + 0xe6b3_222b_f89f_1cbb, + 0x0a55_07f7_2694_65a6, + ]), + pallas::Base::from_raw([ + 0x5d8e_9202_13b0_c07c, + 0x0c75_2080_6653_ef25, + 0x0a8a_c99c_9e71_ec48, + 0x0cea_69fc_1248_9c7a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6bf7_aa25_2e67_bfe4, + 0xf638_dd62_3264_efb2, + 0xc5d9_d3dd_7ab8_95eb, + 0x19cc_6f41_d5ea_400f, + ]), + pallas::Base::from_raw([ + 0xb5f9_31ac_c93d_a424, + 0x0669_28e6_f068_ab0b, + 0xaa97_d1f0_2516_8563, + 0x12c9_2e45_f41a_027e, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb5cd_03f6_546f_2d8c, + 0x008b_ed8d_5fb4_d8cc, + 0xfbcb_8613_de82_5e34, + 0x321d_9574_27a1_bf27, + ]), + pallas::Base::from_raw([ + 0xf46e_ef71_5d5a_f67e, + 0x0718_6aa5_d7df_a969, + 0xcdd8_6543_720a_6554, + 0x0109_0738_2d21_8149, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3b0a_d014_2f1d_6af2, + 0x99d7_56da_d234_fc14, + 0x45a8_b1c4_454c_5dfb, + 0x1ee7_4f77_ad27_f629, + ]), + pallas::Base::from_raw([ + 0xf38f_1264_df92_5a0f, + 0x198a_81c9_05ac_18d7, + 0xcc50_1caf_24f4_ed42, + 0x399e_531d_d4d1_3840, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb529_b39a_b8c8_6bbf, + 0x7d8f_ebda_e02d_678e, + 0x7fae_d795_ca8e_8302, + 0x31ed_a441_e002_ac2c, + ]), + pallas::Base::from_raw([ + 0x619d_1ef8_6855_6cbb, + 0xd88c_2fed_a381_abd6, + 0x6845_42a1_1e1f_ddd0, + 0x0771_38ca_a39c_2293, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8e3c_040a_f2be_9c63, + 0x0cfc_acc4_41b0_e1e2, + 0xf752_2875_1f06_363f, + 0x0032_71bd_2b81_e3cc, + ]), + pallas::Base::from_raw([ + 0xc3ad_aedb_26dc_6180, + 0x2ad6_7b13_2e77_7986, + 0x2dc6_e063_027b_de54, + 0x0af4_dae5_6e28_04a4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5075_53c9_94d0_dc2e, + 0x940f_4012_649a_26a7, + 0x9162_6f3d_cb07_7bdb, + 0x3c6c_9061_ba99_8e97, + ]), + pallas::Base::from_raw([ + 0x2178_ff4b_0732_3f45, + 0xc7bd_6c17_b4a7_3748, + 0x567e_30af_d336_1f3f, + 0x0fe0_0d2e_dc98_92d2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x923e_5ef9_fc71_838b, + 0x7d0f_0335_d240_a72f, + 0x1995_4895_1bc7_bb3e, + 0x21ab_30c3_8b38_7741, + ]), + pallas::Base::from_raw([ + 0x28a5_52b8_4d1c_2cb2, + 0x7879_b66b_db7d_c6f9, + 0x66cb_e4a2_b207_c3c3, + 0x1c5e_324e_ce7e_1fc2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0bea_8b37_7a6e_5de3, + 0xe8ad_fcbe_7b8f_c888, + 0x6443_7dff_eb54_1544, + 0x17e3_7101_e098_d708, + ]), + pallas::Base::from_raw([ + 0x5e3c_16ee_ea18_7549, + 0xcebc_d07b_5dda_4fdc, + 0xcaef_d061_c365_1317, + 0x08ba_afac_c8b0_f579, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0c45_64d3_dfdc_c360, + 0x3c34_d6db_627a_b743, + 0x19d6_66ef_aece_e57d, + 0x202c_137c_d1b6_7cb8, + ]), + pallas::Base::from_raw([ + 0x0dee_c67f_c432_1abc, + 0xc5cb_4568_bc4a_8783, + 0x86b1_1451_ecca_550c, + 0x0ff0_09f7_4820_2e77, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf299_ebe2_a905_3c8b, + 0x424d_1301_89df_a29d, + 0xea28_f364_490d_4a4b, + 0x2508_a2a0_337f_d881, + ]), + pallas::Base::from_raw([ + 0xd25c_bf8b_16a9_bc33, + 0x11a1_c1ed_a972_d5f0, + 0x9cef_8ada_9d7d_314e, + 0x00a8_b4df_bad1_2ed3, + ]), + ), + ( + pallas::Base::from_raw([ + 0x23be_9beb_9a9d_1973, + 0x08cb_a79c_d6be_891a, + 0xfe6b_6d74_0d9e_ca76, + 0x1cf9_8a3d_7cad_c772, + ]), + pallas::Base::from_raw([ + 0x37b5_48a9_8ca6_1367, + 0x7717_856a_a9b5_f7e9, + 0xbbc3_ed77_a685_e338, + 0x3721_1cad_a67a_824f, + ]), + ), + ( + pallas::Base::from_raw([ + 0xec8f_2eff_5163_90b6, + 0x502b_81eb_7035_b6aa, + 0xc6f2_0df9_5dfa_4d3e, + 0x0a19_842b_c197_5949, + ]), + pallas::Base::from_raw([ + 0x760b_a317_8fbe_610d, + 0x0370_d562_6abd_b305, + 0xb005_aebf_6e18_2d4c, + 0x0bfb_19da_eb56_6427, + ]), + ), + ( + pallas::Base::from_raw([ + 0x577e_5c79_60fc_0750, + 0xef55_42ac_ca0d_7327, + 0x1f01_e126_ce1b_85b2, + 0x2a4a_42d0_4ff1_a83e, + ]), + pallas::Base::from_raw([ + 0x89fb_594d_05da_15b6, + 0x409f_a8a7_c292_6e67, + 0xf51d_1898_aad8_d7bc, + 0x11a2_2949_d51d_cca1, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa13a_ce54_6c54_ddfd, + 0x4f2f_05d0_3b6b_c624, + 0x24d8_342b_6f1b_bed6, + 0x10f6_dcfd_3058_49b0, + ]), + pallas::Base::from_raw([ + 0x45e4_776c_b11e_2c8b, + 0x21dd_7840_7331_9fb1, + 0x9b3d_9a29_2c94_f391, + 0x2476_66c1_6beb_0949, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6942_a75b_0f86_97d1, + 0x1e65_3555_73de_ffb9, + 0xcaa7_5277_14cc_174f, + 0x0bd5_3c52_fdd8_9e3f, + ]), + pallas::Base::from_raw([ + 0xefbd_85ab_0779_2a56, + 0x94ee_6dd3_5be0_b609, + 0xe2e2_ebf4_4c60_80ad, + 0x37e7_f6b5_6da4_f564, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa785_938c_daf0_a82c, + 0x0ff1_052c_6b0f_e01a, + 0xa3a3_4892_9645_d506, + 0x35fc_4d9f_25e4_ee0c, + ]), + pallas::Base::from_raw([ + 0x1bd8_1387_35d5_eed0, + 0x2031_6e2b_4402_2210, + 0x1de4_87e5_3382_18c8, + 0x2639_15ef_7bb7_ac86, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7270_5394_9d68_af25, + 0x31ec_3a68_bc55_8512, + 0x6224_b2b9_4190_4745, + 0x2bae_d2fb_bfa0_7f92, + ]), + pallas::Base::from_raw([ + 0x1585_ad6e_c39d_17e7, + 0xb3f4_5d31_7909_30e0, + 0x10f2_cf93_6dce_27ab, + 0x3ac7_25dc_a3cc_5a5b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x16d5_99fd_795e_e6d3, + 0xa832_7e0c_535a_268d, + 0x66f3_59f1_a2d0_c9a0, + 0x0b43_b8cf_c755_ef13, + ]), + pallas::Base::from_raw([ + 0x771e_bb8c_9993_734f, + 0xe22b_9efa_7f0e_8978, + 0xf847_286e_a0aa_95f5, + 0x3cab_00dc_f0cb_4e65, + ]), + ), + ( + pallas::Base::from_raw([ + 0x940e_5b99_9381_5ed6, + 0xe458_f932_4ac9_8472, + 0x7277_a9b2_a4c6_6b96, + 0x2a9b_1dbb_3571_6aff, + ]), + pallas::Base::from_raw([ + 0x1d1e_8561_7c5f_74fa, + 0x04e0_d957_2d5e_bdec, + 0x697f_9f0f_730e_36dd, + 0x3196_6863_7d6b_29ae, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf4ec_666a_be5d_ceba, + 0x4e55_7554_6220_3670, + 0xc2c0_90b4_a237_cb56, + 0x32ae_228d_d97a_e433, + ]), + pallas::Base::from_raw([ + 0xad96_69b3_e76e_a998, + 0x91e7_879f_ed57_7b7c, + 0x1e41_9d83_9e6c_94d7, + 0x048d_94ba_a0e3_0800, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6c90_7b9f_576a_7619, + 0x7bb1_599f_1d1c_9120, + 0x3b2f_e84d_d429_e90c, + 0x33d9_fbea_8acc_5ae9, + ]), + pallas::Base::from_raw([ + 0x549a_ec50_44ca_17ec, + 0x645d_16ee_dbae_2cd2, + 0x0ab1_6a27_9c8b_feef, + 0x3ce8_42b9_6fec_08f2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x66fe_abea_b49c_bc29, + 0x831b_43fd_4f80_1ad0, + 0x8963_b81f_a95b_9f77, + 0x3fff_5b9f_33a9_c8da, + ]), + pallas::Base::from_raw([ + 0xb846_fdb3_3ece_c0f6, + 0x028f_eede_2d86_2dc1, + 0xb6fe_b8a0_d783_8453, + 0x1ebf_fb3e_ea47_f184, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdbd3_28a6_187e_d102, + 0x4d92_bae1_fab1_c408, + 0x5036_8ca9_4dc4_3f5c, + 0x03e9_6590_9135_7f5e, + ]), + pallas::Base::from_raw([ + 0x6929_1025_5164_3cbe, + 0xa488_9bf3_bc56_9b53, + 0xf020_bfe6_2ccd_f413, + 0x0128_5e40_0aa2_8746, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8a24_35af_b4a2_12b6, + 0x5132_1588_3ffa_9384, + 0xba59_b535_c741_b302, + 0x024f_79e6_c546_5014, + ]), + pallas::Base::from_raw([ + 0x708d_0744_747d_b6bd, + 0x4ba0_0f9c_9956_4ab8, + 0xa6a1_8626_802b_0d85, + 0x1180_93b3_83be_9f5f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4e81_e880_ac86_3341, + 0xb199_dda5_ee5c_d052, + 0x4bfb_9edb_2b9e_40bf, + 0x31f3_ccf9_bc82_4e71, + ]), + pallas::Base::from_raw([ + 0xc77e_ac27_414c_a39a, + 0x75e7_9a0b_3e2d_8063, + 0x0622_1629_392c_7383, + 0x003f_f9a6_01db_549a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x19a3_fe6e_8919_9977, + 0xcaae_dcab_3a2a_1fdc, + 0x5d08_6ffb_b871_b05d, + 0x1009_d4da_10a2_540b, + ]), + pallas::Base::from_raw([ + 0x3e52_b60b_e56e_d8d2, + 0xa1d1_62c0_5220_5502, + 0xdf1e_4b5f_9eff_40e2, + 0x3983_0483_a475_7d5e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8a0e_979e_fd3e_fc28, + 0x4949_282d_94e0_8968, + 0xf2b2_9f4b_9c13_6beb, + 0x10ff_7bf3_f711_ca78, + ]), + pallas::Base::from_raw([ + 0xf3ac_a739_fe6f_9a2b, + 0x24a4_32dd_3b09_89e1, + 0x2f22_37ec_e69c_8858, + 0x2d7e_82a5_6448_019f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0dd9_25b4_b2b2_2f7c, + 0x5f56_b22a_a5b3_3623, + 0x57d5_9fe0_f291_a84c, + 0x0d09_972b_30f1_be14, + ]), + pallas::Base::from_raw([ + 0x3374_d4c2_52f8_d6b4, + 0xc076_3cb4_bdd1_d03b, + 0x0075_cad7_fa9b_c762, + 0x3231_c543_5a25_3b5f, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd27e_395d_361b_5ec6, + 0xbf65_b40b_5fa3_aa57, + 0x6ebe_d47a_734c_d047, + 0x1c72_49cb_9959_520b, + ]), + pallas::Base::from_raw([ + 0xdaa7_b8e5_921f_ef38, + 0x96ef_2a6d_ad05_ee3f, + 0xc6f1_76e2_fc0b_18a4, + 0x389f_6781_7075_8a4d, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb719_cae7_9e3e_eee7, + 0x7195_0578_1db1_15a0, + 0x4bf3_df1d_89d4_101a, + 0x1180_028a_0688_b2fe, + ]), + pallas::Base::from_raw([ + 0xead4_6a5b_c181_84fa, + 0x752e_4ef1_9ed1_d259, + 0x6987_464e_4442_6e0f, + 0x0e76_4f02_842d_9e51, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4fc8_561b_e0e3_7afb, + 0x4e22_7ecd_b7af_8143, + 0x18e5_adcc_3d30_4460, + 0x1c1d_1e61_5c90_5b8a, + ]), + pallas::Base::from_raw([ + 0xe84b_9fc9_fa1a_3202, + 0x64d2_8a73_dea5_979f, + 0xa1b7_2a78_52c4_ace3, + 0x3176_462e_9090_e088, + ]), + ), + ( + pallas::Base::from_raw([ + 0x073d_58c4_427d_406e, + 0xeaae_a64c_9da4_b973, + 0xdb2b_606a_ce9c_98fd, + 0x320d_ab61_0606_cff0, + ]), + pallas::Base::from_raw([ + 0xf6f5_6017_e567_b738, + 0xad8b_2fa4_c80d_6dcf, + 0xef76_6787_acc5_eec6, + 0x2046_af02_0cc2_8cfd, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdfe0_be8c_ea45_93e2, + 0xbf25_71e6_2c01_a757, + 0x5646_bd2a_721f_cde4, + 0x2e56_8465_0c5e_df5a, + ]), + pallas::Base::from_raw([ + 0xc367_119b_e723_3de0, + 0xa859_64d3_8319_d7e2, + 0xbf56_4106_7772_44b3, + 0x05a7_a0a3_f91c_3fe3, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa157_1fb9_7ebd_b1cf, + 0xad70_58d4_9484_4afb, + 0x22ff_383e_fdf9_34da, + 0x03a8_f536_628d_a555, + ]), + pallas::Base::from_raw([ + 0x89a6_b560_8b35_83df, + 0x93d8_765b_4bb6_3abe, + 0x6607_c289_c75e_39ce, + 0x0846_7ac7_d613_3c93, + ]), + ), + ( + pallas::Base::from_raw([ + 0x09bb_0aa5_c0f7_596d, + 0xc0b7_9d5e_0a31_3f09, + 0x25c7_ae48_63c8_df2d, + 0x340f_7178_57c8_c4f5, + ]), + pallas::Base::from_raw([ + 0x58d6_0999_e9bd_175c, + 0x413c_51e3_6936_3dcc, + 0x44bf_08fb_a5bd_cc5f, + 0x09d9_43ab_4210_7233, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7660_e2b2_190b_69a3, + 0xa675_e6bd_1268_5aba, + 0x70c3_464c_345f_61a3, + 0x0c06_a269_203a_835c, + ]), + pallas::Base::from_raw([ + 0x5ea9_81b8_2f7f_4ab8, + 0x270c_896c_5e02_6b95, + 0x410a_6beb_1116_7954, + 0x25cc_f45d_51f9_413f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x83b6_8e9a_1a6a_c28f, + 0x252f_9079_e85e_8c2b, + 0x3dd5_a334_3f27_7d23, + 0x1f00_6677_bdfe_d386, + ]), + pallas::Base::from_raw([ + 0x21c2_597c_a01e_4b39, + 0x1195_b279_cab6_3aa8, + 0x5947_c45d_89d5_371c, + 0x0bfd_bc05_76f2_f69a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x56aa_cca5_f6d6_aeae, + 0x00de_b6ed_5b56_175d, + 0xe99e_0920_f6e3_ac48, + 0x1970_6df9_fa50_7068, + ]), + pallas::Base::from_raw([ + 0x68d1_0152_dcd8_93fb, + 0x05ce_c81d_e5b3_7f47, + 0x7f83_e062_2c1f_9c14, + 0x31cb_805d_ed05_7b35, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf740_8933_84f5_3c01, + 0xa65d_85ad_4b70_3a91, + 0xe988_f151_9c16_9eb4, + 0x39b7_6368_9c50_4537, + ]), + pallas::Base::from_raw([ + 0x8fcc_2881_8f89_9d3c, + 0x9a09_7667_344e_b7ed, + 0xc77a_ff9a_1099_b9ac, + 0x3c20_f228_e53f_9a40, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3e5f_02b6_0499_512f, + 0xc6aa_35d0_b5fc_7e3e, + 0x310d_1554_993b_9077, + 0x3b2e_af1c_97a7_e598, + ]), + pallas::Base::from_raw([ + 0xc7eb_cb64_9b4c_42bf, + 0x2bf3_2b91_10f2_ce19, + 0xd0eb_b868_6884_a28e, + 0x025e_43d6_95a4_e968, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe521_3aed_05e9_77a3, + 0xfe91_23c6_366e_6089, + 0xff9d_b1bf_4bbd_55c7, + 0x2806_f809_7feb_4387, + ]), + pallas::Base::from_raw([ + 0xf2cd_f9d7_d0fc_3fa3, + 0x277e_e79b_e9e8_6ab7, + 0x37c9_96f8_8238_9c4e, + 0x37d0_9ecf_faad_6231, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6d97_b12d_3d3a_a165, + 0x3ca0_bd33_97db_0743, + 0xc6eb_cdf5_27ec_2a4c, + 0x377f_7772_587b_c8e2, + ]), + pallas::Base::from_raw([ + 0x2563_f930_3ffd_2e65, + 0x54e7_472c_ef1a_9b21, + 0xfdf1_b209_a4ba_4952, + 0x285e_bf94_09ad_7630, + ]), + ), + ( + pallas::Base::from_raw([ + 0x300d_9e13_f84f_0abb, + 0xd16d_7740_9c8d_c8d6, + 0x88b4_6edb_02c4_9705, + 0x05c4_e9fb_bada_d36d, + ]), + pallas::Base::from_raw([ + 0xf151_5ce8_bead_7602, + 0x3928_bb28_9b22_02ae, + 0x11dd_8076_58b5_38d9, + 0x0bef_8f47_2c87_7974, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9182_5076_acdb_4b23, + 0x5e1d_aab6_2b52_5e01, + 0xd348_f694_cf00_7eab, + 0x0fcc_10bf_2527_b056, + ]), + pallas::Base::from_raw([ + 0x80f6_acc9_9258_cb51, + 0xc15d_6f31_1e48_7046, + 0x3042_b48e_8bab_078a, + 0x3cf4_7fcf_48ab_5d1b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6c13_9e12_49ce_58fd, + 0x04b0_81f9_23c7_d9e8, + 0x8f7a_9239_88e5_0d43, + 0x1fca_3b8d_b325_a3be, + ]), + pallas::Base::from_raw([ + 0x692f_f292_5bc7_efe1, + 0x375d_358d_b102_d895, + 0x7377_f146_2112_ee7e, + 0x22b9_ca2a_74d3_accf, + ]), + ), + ( + pallas::Base::from_raw([ + 0xda8d_d358_9491_7794, + 0xe067_e06d_8977_054b, + 0x68e7_65cf_e835_3e99, + 0x2c7b_ae76_7c60_05c6, + ]), + pallas::Base::from_raw([ + 0xbf71_5d06_4e5e_e34f, + 0x9c99_a7e1_8746_abeb, + 0x7615_e9eb_5e16_ccc4, + 0x328e_84b7_73ab_f7fa, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfe57_fcce_7fa3_b8ee, + 0xc9c2_448c_f7c2_687a, + 0x6fd2_be38_efd3_c2d4, + 0x0288_6502_ad64_3ca2, + ]), + pallas::Base::from_raw([ + 0x4bc1_9841_b03f_4d5c, + 0x460b_ea82_dcb0_7401, + 0x95b2_4a5e_6c57_84b4, + 0x2bfb_f8ee_7da6_9c92, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd39f_ac30_c415_232c, + 0x76dd_a79e_3bac_418d, + 0x146a_5061_cc03_a8bd, + 0x33d1_e61b_23b0_45c8, + ]), + pallas::Base::from_raw([ + 0x0a86_1d38_a0c1_6167, + 0x3403_d750_3625_fd76, + 0x5c14_43d8_4dfc_5111, + 0x11c4_166d_43ca_34b8, + ]), + ), + ( + pallas::Base::from_raw([ + 0x59d0_4901_73f3_dc32, + 0xba05_8d5b_fec4_3964, + 0xe934_dd16_6639_b95a, + 0x2bbb_c6ea_bb03_cde0, + ]), + pallas::Base::from_raw([ + 0xee85_cf18_4523_3d07, + 0xee68_4cb6_e0a9_3cf3, + 0x256b_3bba_79ee_21d0, + 0x1922_24ec_0f7f_7bb9, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5e0d_4224_2670_e7ae, + 0x1625_3e15_14a1_7a12, + 0xdc19_7727_5d06_feee, + 0x1945_e2c3_3e05_75dd, + ]), + pallas::Base::from_raw([ + 0xd13a_150f_7299_7634, + 0xfb54_5fc0_aeb3_d878, + 0x30f3_976d_4f5b_fab9, + 0x39e2_8dd6_964e_57c2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6338_94e3_1def_1331, + 0x178a_dcd2_9172_17f0, + 0x661d_3878_6be1_3883, + 0x3408_a710_6fc8_2ce2, + ]), + pallas::Base::from_raw([ + 0x342c_2e44_c71b_ed3f, + 0x96de_42e1_09ee_70d6, + 0x8589_6891_dd66_820f, + 0x0741_8793_5d70_7bcf, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc10b_7e08_f2dc_891d, + 0x5d68_913c_d9e6_d01e, + 0xdd5d_234c_090b_3903, + 0x15f0_5b8d_a216_b34d, + ]), + pallas::Base::from_raw([ + 0x7962_faa0_cf7a_f257, + 0xaeb0_181c_1252_a47d, + 0x1261_6f4b_f1ca_e2a5, + 0x2322_da04_1fbd_9122, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6a1f_d4d7_4910_b554, + 0xe38f_1107_a34e_2dfb, + 0x68c6_f5cb_806b_3ea4, + 0x2b4a_4711_8c56_07da, + ]), + pallas::Base::from_raw([ + 0xc783_e3cb_01aa_1026, + 0x5c61_2654_288f_5199, + 0x6308_5aa7_4c7a_1afd, + 0x2be0_c52c_6093_c62c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xcfd8_887f_b984_673a, + 0x7d45_4af5_572d_847a, + 0xb8cc_5e6b_e457_cafa, + 0x05f0_5b4a_1514_6b31, + ]), + pallas::Base::from_raw([ + 0x9b83_b107_3366_b9df, + 0x7e64_4e6e_6208_ad83, + 0xe7f1_ff6b_4f7f_ecbe, + 0x315c_4707_e207_5aa1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x937f_a439_75f4_9b60, + 0x302b_b808_40e5_8700, + 0xb139_c191_3f5d_2668, + 0x37d0_f747_3f38_8e55, + ]), + pallas::Base::from_raw([ + 0x85c8_de85_e1bb_51af, + 0x248e_5ca6_6754_a3fe, + 0xfcd6_2e4a_d4f9_c873, + 0x33ff_0368_6ba7_d5c7, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5e5b_adbc_d5fb_0443, + 0x78c4_d68d_9535_6613, + 0xfc3a_f91d_e825_cef9, + 0x0da7_ce87_e12d_4f1c, + ]), + pallas::Base::from_raw([ + 0x7ef0_5e88_6074_d377, + 0x4a7f_af08_52b5_609c, + 0x0b09_33a2_2f81_8986, + 0x0d6f_bbc5_de55_8690, + ]), + ), + ( + pallas::Base::from_raw([ + 0xaa91_03bf_b5ec_e132, + 0x1104_aaeb_126c_9c14, + 0x03d5_39c6_576f_6608, + 0x2297_a2d2_c5b4_e323, + ]), + pallas::Base::from_raw([ + 0x60d6_c848_219c_e7be, + 0xb05a_10e1_00cd_63b0, + 0x429b_ea17_7b3f_4b88, + 0x0c1d_c916_f323_e7ba, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7e91_af55_41b0_8bf4, + 0x7eb7_87f3_ce5f_d6b1, + 0x716d_a388_45f1_2b56, + 0x0b63_6e98_ac64_6f4d, + ]), + pallas::Base::from_raw([ + 0x4ab7_12d1_a43e_13db, + 0xe60a_52eb_e24b_27bf, + 0x056c_fba5_2b55_7f09, + 0x1fac_62f4_32cd_f236, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2dfa_15b7_ebbd_e631, + 0xb320_f94d_a9c0_63af, + 0x1b77_601c_50e8_6a37, + 0x3d4c_2ff5_d00a_3876, + ]), + pallas::Base::from_raw([ + 0x272a_3417_43de_26bc, + 0xaea5_4b59_4992_3c71, + 0x0c4d_e807_c535_b183, + 0x2765_c806_aec7_71a7, + ]), + ), + ( + pallas::Base::from_raw([ + 0x55ec_8f25_7b63_e13a, + 0xd5fc_34d5_66ec_9ee8, + 0x56c2_e320_91d0_8278, + 0x1691_2185_0e3d_dc23, + ]), + pallas::Base::from_raw([ + 0xa148_4983_bee5_638d, + 0x8a79_eb1b_c492_8b71, + 0xd6df_d4fc_d029_984b, + 0x2cbd_3698_162f_9ae9, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7a1b_05bc_e3c4_7320, + 0x32a7_6039_297e_8409, + 0x732f_44e5_5380_a997, + 0x202a_a34a_e870_a44e, + ]), + pallas::Base::from_raw([ + 0x4868_6f6e_4f31_74e7, + 0x36ff_575c_ae12_a1e3, + 0xe09a_f42d_581b_50a7, + 0x2114_4937_0a1c_f825, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdb42_2a77_b183_1102, + 0x7bf8_c21d_1049_98b1, + 0xce43_b79c_9ad6_75f9, + 0x349d_78c6_a718_8921, + ]), + pallas::Base::from_raw([ + 0x3070_ba0f_699f_080f, + 0xd134_f6e0_36b8_c18a, + 0xda34_d0a7_2cd2_aa3f, + 0x2945_7a3e_37c3_b020, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6fa1_6666_d21b_774e, + 0xb91f_c4b1_0e6f_767c, + 0x12af_8401_416c_df0b, + 0x220f_43a3_4cc1_d1a0, + ]), + pallas::Base::from_raw([ + 0xab2b_060c_b26e_f892, + 0xe580_8ab1_a538_10e2, + 0xf36f_e472_3313_570c, + 0x36f1_3b73_3934_59b1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4e49_ebb2_947d_b488, + 0x242c_04d3_d97d_c253, + 0x9f41_d8d8_d7fd_b017, + 0x013c_52dc_adff_81e0, + ]), + pallas::Base::from_raw([ + 0x0a29_7aca_54ca_ebd9, + 0x63b0_abde_e434_9e09, + 0x51d0_948c_c749_f20b, + 0x069a_8db0_9a55_b0f4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x406f_b28a_71f7_8032, + 0x969e_b429_82fe_4929, + 0xb1af_24a1_53c8_2095, + 0x052a_3255_a165_23a7, + ]), + pallas::Base::from_raw([ + 0x98f1_82b9_5ac3_dcd2, + 0xb088_ed0c_bf43_f3a8, + 0xf950_fe2d_c059_db22, + 0x1440_06da_cd6f_d539, + ]), + ), + ( + pallas::Base::from_raw([ + 0xda59_fedf_ac9d_3f1e, + 0x226a_cb4a_3e98_29b1, + 0x3a73_3388_a504_88dc, + 0x3891_f70a_133b_a457, + ]), + pallas::Base::from_raw([ + 0x25c4_6ed8_4bf2_d71f, + 0xbff1_2fe2_1b00_e737, + 0xaedc_6377_1e3f_8787, + 0x0ed1_b27c_c7ef_d5ac, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc0d0_516d_40d3_cc81, + 0xa649_0816_1677_43c9, + 0xca42_f4c4_4243_c1d5, + 0x23b3_8f5b_b23d_8b5e, + ]), + pallas::Base::from_raw([ + 0xed2a_a32c_dcc3_fa23, + 0x8cda_3463_fef1_02c3, + 0x6b5a_f006_e433_ce3c, + 0x1225_df05_7f84_56b2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8291_0d73_56a0_e04c, + 0x64b6_73ac_c38f_66f0, + 0xdd23_f657_9d03_0932, + 0x2e91_cf8d_0328_73cd, + ]), + pallas::Base::from_raw([ + 0x6c94_8550_64c0_d6b3, + 0x7a29_80d2_5e0a_beb0, + 0xb651_e0fb_26b8_1656, + 0x3b5a_8440_9640_1532, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbadb_8627_9571_bbfb, + 0x124b_4809_a75e_8835, + 0xc9e7_00a7_4761_1c17, + 0x0920_b057_f207_5bcd, + ]), + pallas::Base::from_raw([ + 0xedf4_28d2_7f57_6e69, + 0x81db_b861_29e9_b438, + 0xfa8e_eed3_1e15_ec93, + 0x1126_9fbe_894e_0bed, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb1f4_fe1d_7c44_7f1f, + 0x83c4_259d_9758_51fc, + 0x117c_2cb5_bf1e_601b, + 0x22d9_4c09_d954_00dd, + ]), + pallas::Base::from_raw([ + 0xd683_7d9f_b5d2_4924, + 0x313b_2636_f504_0619, + 0x2b63_fc49_08e0_48d6, + 0x1144_ded2_0553_ac9c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x387c_b408_a242_5394, + 0xd079_369b_b24c_53d9, + 0x8f15_d008_d9e9_d4c3, + 0x3150_c6bf_e2c6_f49d, + ]), + pallas::Base::from_raw([ + 0x0404_728f_c1f7_729d, + 0x7867_a4bd_2454_fc8c, + 0x41ce_532d_3e8a_12e2, + 0x1ba3_02a9_468e_f35f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3db3_09bf_c9b3_4ec5, + 0x5033_0f96_20ca_8bcc, + 0xa8ba_c058_76aa_2be0, + 0x1448_b16c_5253_7aeb, + ]), + pallas::Base::from_raw([ + 0xc139_ae90_00d8_2a6f, + 0xe882_76a0_e243_47b5, + 0x3ddd_9c6e_1e82_af5b, + 0x36f6_2912_c020_70b8, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1402_2b05_d861_fd8f, + 0x41d7_89f5_50b6_a9e6, + 0x87d2_6c5d_6fc8_2332, + 0x02f8_35e2_95b6_562e, + ]), + pallas::Base::from_raw([ + 0xbeb9_3c06_52f7_4ad8, + 0xe619_72c1_05e4_cd9d, + 0x884a_8d7f_678f_75dd, + 0x2874_6006_1cc8_886f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x69f1_75ca_c3b9_58f2, + 0x6747_f1ef_abb5_3998, + 0x46ec_4c53_0949_f44c, + 0x07ba_e7f2_46e1_1e0e, + ]), + pallas::Base::from_raw([ + 0x1021_d25b_0c19_dc41, + 0xea7a_36bd_9497_28f4, + 0xa859_e7cd_2cea_c8a7, + 0x0efc_9a8c_f825_3990, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5dea_1fb9_0da5_a77b, + 0xc0e1_3bb8_134a_bcc6, + 0x9938_d35c_c9ea_b2d4, + 0x2283_5512_a16b_cd1f, + ]), + pallas::Base::from_raw([ + 0x9be9_eaf3_1b0f_ad7e, + 0x3491_2b52_0ca1_71f9, + 0x0721_8f00_31b9_ac7e, + 0x0e74_f8e8_90c4_692f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6b4a_0ce4_26ac_5fb9, + 0x13cf_2331_e171_707e, + 0x8539_192e_4fcd_6ea1, + 0x128c_aa53_6e03_01ab, + ]), + pallas::Base::from_raw([ + 0x6e6b_8cb3_43a7_fcba, + 0xb4b2_353a_6472_323d, + 0x4867_0871_a8fd_61a2, + 0x11c8_bcf8_bdca_1c4d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8f8f_bb6f_6f0c_7693, + 0xa8cd_2c9f_9f6f_9eda, + 0xf8f7_1e1d_01ab_dafa, + 0x301a_41e0_0512_a1dc, + ]), + pallas::Base::from_raw([ + 0xcb70_6c7b_9d80_868f, + 0xf2a6_d279_5c03_64f4, + 0xbf2a_e148_ec6b_7343, + 0x303f_3b4b_74c4_59b8, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4c67_a5c8_e007_8e3b, + 0x5a11_f487_f259_6bcb, + 0x52d4_26c8_e498_b8aa, + 0x1bf9_81ee_4546_e1d6, + ]), + pallas::Base::from_raw([ + 0xb25b_9eda_f3c4_0ace, + 0x4cd8_57e5_5ff9_a68e, + 0x4c57_4295_a50b_e5aa, + 0x1457_bcd8_9f13_ef72, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6e49_8196_9af9_5023, + 0xa9f4_ad41_da85_fd4d, + 0xf643_d9f0_fcdb_da79, + 0x1077_61df_2dd6_6275, + ]), + pallas::Base::from_raw([ + 0xfa32_bd06_b1af_c76e, + 0xe61c_2fa4_1da2_cb0e, + 0x4c70_289c_043c_e9ed, + 0x2056_dd5d_ddfb_d391, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe6f7_b311_a5eb_8af4, + 0xbbcb_dea1_4f28_c13d, + 0x832a_fc2a_c06b_48c0, + 0x1884_c484_9a23_39d1, + ]), + pallas::Base::from_raw([ + 0xae85_ea27_cf05_147e, + 0xa661_d654_5ad7_8770, + 0x8cff_b5bd_23cd_1e02, + 0x0c4a_a17a_3d59_87b0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0903_2b71_20d2_c6d6, + 0x631f_355d_d06d_ccc6, + 0xf87c_c78c_ef4a_6b96, + 0x3ef8_d7d1_dc6f_9f9d, + ]), + pallas::Base::from_raw([ + 0x43ce_ad49_48dd_c9d2, + 0x2dc3_7407_7cca_9ffc, + 0xac61_ddbd_67db_3e8d, + 0x0297_f7b8_d03f_dedc, + ]), + ), + ( + pallas::Base::from_raw([ + 0x361c_0eb5_afba_a00d, + 0x8051_789c_48c8_8225, + 0x9887_1616_2649_2d22, + 0x3d51_6690_501f_18a2, + ]), + pallas::Base::from_raw([ + 0x3a63_1bfb_843d_d126, + 0x4a8c_64d0_c4eb_0522, + 0x9124_952e_3015_76f3, + 0x1a00_2583_79af_10c8, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd1b3_d060_1eee_6e43, + 0xc4c5_f777_afcb_3d7c, + 0x5890_cf65_cdee_4f41, + 0x3bac_6dcd_6ca7_0869, + ]), + pallas::Base::from_raw([ + 0xddf9_20c5_9c7d_0fc6, + 0x0586_3a0c_6afe_2d3d, + 0x14d2_e276_9317_f416, + 0x1b0a_6eba_dac7_b097, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1396_4ab6_46aa_aa39, + 0x63ab_087a_ae54_f31b, + 0x6d98_908d_b903_5320, + 0x38d0_a593_6ed6_9ee0, + ]), + pallas::Base::from_raw([ + 0x63ad_df45_aee3_572a, + 0xce8b_2242_3aa8_96fe, + 0xddc1_4261_c724_5d07, + 0x3535_1cd4_d4f9_1ba2, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb5ca_a06f_a744_e9dd, + 0x38e3_7f58_5670_d457, + 0xb4df_a467_546f_0bbb, + 0x01c3_1821_87e9_c394, + ]), + pallas::Base::from_raw([ + 0x735c_1df3_1c86_a46f, + 0xc35a_47e7_9530_8784, + 0x0b2d_cd84_17ad_d715, + 0x106c_dfe8_b63a_2baa, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1a6e_026d_0c07_0abb, + 0xaca9_b562_15b5_32b2, + 0xd41e_5c95_365c_f30a, + 0x288d_ca02_8dc0_36dc, + ]), + pallas::Base::from_raw([ + 0x662c_4ded_8523_98ed, + 0xb2d7_be0a_1d12_423d, + 0x3ec6_e3b5_de20_815c, + 0x291f_f461_d024_af8f, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb09f_fa66_d16c_27ec, + 0xa1da_22b0_8593_d72a, + 0xe632_9240_dc54_eb20, + 0x1a76_7954_e044_678d, + ]), + pallas::Base::from_raw([ + 0xe6fa_cd09_85a5_0427, + 0xd348_8598_eddd_f7b2, + 0xd7a2_306f_08c8_bcc4, + 0x14f0_fcd5_0316_e459, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8f32_c8f3_5b64_6f4e, + 0x21aa_08fc_88eb_4def, + 0x2801_597f_de58_9a34, + 0x3a30_a417_2ad6_0466, + ]), + pallas::Base::from_raw([ + 0x7d06_9540_e011_19ee, + 0xc967_557c_731b_7356, + 0xda5a_4c02_83a4_2f1d, + 0x3d3a_57fb_c972_c1e0, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd3c1_f6cc_c3e0_2a53, + 0x0547_5fdc_7ec7_1268, + 0xf000_9637_f19f_5b9d, + 0x30ff_defd_ca48_bd81, + ]), + pallas::Base::from_raw([ + 0x91ae_9409_8215_c29a, + 0x6132_5ed0_20eb_03f6, + 0x7902_c795_04be_18dc, + 0x2fd2_0d09_cb6b_ffe2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1a31_a60b_d542_73dc, + 0xdb7f_1cc7_04b6_b150, + 0x1861_c054_0121_d013, + 0x206c_c173_9153_a5a3, + ]), + pallas::Base::from_raw([ + 0xe0e9_cb82_5775_fbd5, + 0x4376_49cc_7524_f7df, + 0x7bd2_080e_2134_cd15, + 0x31f0_de88_e2da_7e71, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfc72_8173_780b_d7a5, + 0x7aef_d664_bcbf_065b, + 0xb00b_a3b7_0b2c_e355, + 0x3699_84a7_28c9_f5db, + ]), + pallas::Base::from_raw([ + 0xce09_1005_839f_4217, + 0x3295_dcc5_fe7c_58a3, + 0x5aa3_0609_bda0_b60a, + 0x0241_cf89_cdbb_b0f2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1b00_1931_9eba_29b1, + 0x7216_7d55_26f9_8da3, + 0xe7cf_f705_a799_754f, + 0x2990_fd50_70f8_dc2b, + ]), + pallas::Base::from_raw([ + 0x1eed_c7c4_49c6_3e99, + 0xbbf4_2638_ef85_5544, + 0xf755_cb96_23f0_a42e, + 0x01ee_b0e0_5fe3_1d97, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe37e_1f9e_40b1_54a3, + 0x652d_23f0_cc13_026d, + 0x8e86_4190_1d49_7ee8, + 0x0664_9f66_0725_b3b9, + ]), + pallas::Base::from_raw([ + 0xf17e_dcbc_83c0_56f4, + 0xbb63_cec3_3dc1_383f, + 0x8e3f_6121_894f_a536, + 0x3c11_2d8a_1c60_b07c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1a3e_7d79_56bb_fe12, + 0xa7a9_ca39_4c1d_37be, + 0x9740_4aaf_c9c4_a82e, + 0x38aa_35a0_3d7c_378a, + ]), + pallas::Base::from_raw([ + 0xb631_7d5e_8de0_bb5d, + 0xdc67_9917_adbc_69d0, + 0x68b9_71fe_c7f3_8566, + 0x1989_cd89_9970_7a95, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb220_5013_ba92_96bb, + 0x2058_da75_36fa_a776, + 0x0436_5be3_f84a_c088, + 0x1af4_7a2f_3b0f_a032, + ]), + pallas::Base::from_raw([ + 0x2abf_4796_3d43_d58f, + 0xb235_0d75_403d_f0f1, + 0xa021_f706_77fe_3e5a, + 0x322f_cdad_4c07_7b90, + ]), + ), + ( + pallas::Base::from_raw([ + 0x06a9_ae6c_cbe5_d5d4, + 0xaa6b_45d7_03a5_e6f6, + 0x4614_a78b_bebe_0932, + 0x18cc_6bd2_2c90_ec21, + ]), + pallas::Base::from_raw([ + 0x967c_718d_fed8_a0b8, + 0x95a7_bc7f_31b3_4247, + 0x5a19_1faf_19e0_da2c, + 0x115b_c841_1338_488f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1d51_ca19_915e_a485, + 0x0b43_77f8_251d_b7af, + 0x0825_fb37_e0ea_5a48, + 0x1548_072e_3b8c_9cbf, + ]), + pallas::Base::from_raw([ + 0x48eb_6bb8_ab54_4b29, + 0xae66_f29c_ec75_ea3a, + 0x3195_4ba2_167a_b49c, + 0x3d57_aaaa_db93_5f7b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0c46_3390_d414_4397, + 0xb683_52dc_4bf0_80f6, + 0xa6a1_5861_0737_0e45, + 0x2400_81aa_ffbc_d341, + ]), + pallas::Base::from_raw([ + 0xbbef_dfca_46b3_a803, + 0xc3dd_0e85_0c04_6877, + 0x71e3_e8e2_2feb_ec64, + 0x08a9_eaf7_b5a5_39fc, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5e06_c2b1_b31b_93b4, + 0xc313_a37c_4320_dc75, + 0x7efc_a295_2612_6f7b, + 0x0aff_f83d_40bf_1741, + ]), + pallas::Base::from_raw([ + 0xc7cb_8f4c_fdc8_1d4b, + 0x5a50_fd94_f40c_8457, + 0x71d6_4791_1e74_f12b, + 0x1541_6661_ee80_7c70, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2802_c3f2_89e9_5d61, + 0xf33c_77fb_6c07_ea84, + 0xbbc9_320f_0df4_143a, + 0x356c_a3df_9cc0_4f80, + ]), + pallas::Base::from_raw([ + 0x4f45_fa2b_9fd8_1daa, + 0x7f47_5e59_7199_616e, + 0x7d3b_df29_ce4e_dfb5, + 0x1dae_8d6b_f927_1f87, + ]), + ), + ( + pallas::Base::from_raw([ + 0x473f_87de_7e94_192f, + 0xd393_9d88_1211_44b0, + 0x963b_e798_1010_c5ea, + 0x17ef_3597_df23_de2e, + ]), + pallas::Base::from_raw([ + 0x1e95_8dac_55e3_db76, + 0x12d2_c681_d82e_ed9c, + 0xd3d5_1684_1d5e_e0cf, + 0x0ac9_26dd_9cea_3447, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6858_89c4_1505_8b08, + 0x6db7_b0e4_db13_97af, + 0x5b80_1548_f65c_a777, + 0x0bc7_e844_145e_084b, + ]), + pallas::Base::from_raw([ + 0xe538_4d8c_3f79_208a, + 0x151f_22e1_29fd_3354, + 0x7a90_a95c_27ac_21db, + 0x19ed_e445_2202_a655, + ]), + ), + ( + pallas::Base::from_raw([ + 0xded8_57fc_ee32_3789, + 0x0bfe_aa0f_301d_6f33, + 0xf39c_96d3_5d9b_bf6d, + 0x2a9d_8309_fabf_976f, + ]), + pallas::Base::from_raw([ + 0x528c_0f45_53ee_3161, + 0x6dca_d2cc_ef93_aa93, + 0xe704_f08a_2ec9_996c, + 0x1bfd_16da_cc0d_2d3f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x188b_8471_e40c_3640, + 0xa201_8659_0697_76d0, + 0xd08d_57e0_209d_0571, + 0x1a90_63bb_e154_64ae, + ]), + pallas::Base::from_raw([ + 0x568d_9049_e597_c096, + 0xdd50_78d0_87aa_87c5, + 0x3c15_73d9_7187_5ef2, + 0x388e_691e_ea9e_23f4, + ]), + ), + ( + pallas::Base::from_raw([ + 0x40ab_3fc1_9370_1560, + 0x29ce_a290_2ec8_40f5, + 0xd75d_0b95_a9f7_f97b, + 0x0af0_7979_abd8_c613, + ]), + pallas::Base::from_raw([ + 0x5c15_cad2_0553_9ced, + 0xc602_0943_036b_091b, + 0x4c2c_78ce_e591_7f5e, + 0x1959_acff_f5a3_4bf9, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfa8d_876d_7cc0_0fb8, + 0x5a9c_4a97_786d_19a1, + 0x12be_8115_3b9b_aee9, + 0x1a0f_2978_8594_d43f, + ]), + pallas::Base::from_raw([ + 0xed97_14bc_d4e8_8df7, + 0xe734_c0d3_0dd8_9ea3, + 0xec5d_2259_c86d_e1f5, + 0x1322_03f3_5788_8faf, + ]), + ), + ( + pallas::Base::from_raw([ + 0x93b5_3b69_0484_8f93, + 0x1339_8c0d_df8c_cf22, + 0xf46d_112e_a566_4f42, + 0x0041_e2f5_f2d6_8640, + ]), + pallas::Base::from_raw([ + 0xb605_0c79_bc10_7760, + 0xee50_15ea_4b49_adac, + 0x65e3_2182_22f4_2268, + 0x31cc_6994_41a9_c0ad, + ]), + ), + ( + pallas::Base::from_raw([ + 0x94ae_ab7c_eb62_81ab, + 0x5228_ca94_d8f1_56ab, + 0x475a_af93_16b9_884e, + 0x1986_0f4a_6c3e_b16d, + ]), + pallas::Base::from_raw([ + 0xebdc_e98f_ed8c_6d40, + 0x0540_8297_454e_307f, + 0xd024_3225_3f4a_6703, + 0x1af1_a0e4_f57d_5be3, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfa36_a92e_b85b_e634, + 0xb44a_ed3d_eab7_8850, + 0xbe32_58d6_6770_c14b, + 0x35ff_5141_816d_634f, + ]), + pallas::Base::from_raw([ + 0x500c_0c1b_ceaa_bd2c, + 0xf0d6_669b_f68a_ef2e, + 0xbd8d_f904_9439_9c72, + 0x2db1_ef2c_35ad_69d3, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc0b4_8b5d_58ff_c10c, + 0xe5ca_70be_a28e_8cd2, + 0xf439_95af_87ab_fbbd, + 0x0730_82f6_d583_d377, + ]), + pallas::Base::from_raw([ + 0x1062_30a0_d505_0d13, + 0x4b6a_3c80_f34b_c350, + 0xc6fe_8327_3096_a319, + 0x2230_637f_0cc2_89f0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x25e8_9191_c18b_b009, + 0x82f9_29c5_b2a1_8aad, + 0x3f85_1520_6a2a_9706, + 0x25fe_4261_1ed7_737c, + ]), + pallas::Base::from_raw([ + 0x8337_c552_d63e_02f6, + 0x73cc_77a9_0b10_5fb4, + 0x94db_e561_3bad_7a8c, + 0x26e7_36b7_a39f_c440, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0946_b650_b1c0_8743, + 0x9db9_c020_11b8_8c65, + 0xfa3c_4dca_cbf8_ff13, + 0x06e5_c844_eb13_24bc, + ]), + pallas::Base::from_raw([ + 0x3c8f_57f9_a8a3_70a1, + 0x37c5_8b38_e0fc_1baf, + 0x6b33_3515_afc8_813b, + 0x1a82_f5f2_799f_573a, + ]), + ), + ( + pallas::Base::from_raw([ + 0xbea1_5e63_420a_279b, + 0x8e07_282b_8912_351d, + 0x408f_f799_d86a_fd7d, + 0x078f_7b8e_ce6e_22e4, + ]), + pallas::Base::from_raw([ + 0x88bc_d999_cef1_ecdf, + 0x333d_9c44_d4e7_0bec, + 0x8035_febd_691a_b18e, + 0x1407_a098_70a5_1148, + ]), + ), + ( + pallas::Base::from_raw([ + 0x454c_97de_2584_aa5f, + 0xeec7_9406_7a27_a899, + 0x0955_d427_80d3_4702, + 0x1d35_9759_c143_d6ff, + ]), + pallas::Base::from_raw([ + 0xa816_af30_15f6_605c, + 0x0710_71d6_689b_a943, + 0xdb02_148a_c73c_a8e1, + 0x05dd_3dec_b765_a777, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdb80_38eb_d76a_fb2f, + 0x0dc9_0a9f_1c88_325d, + 0xcf8c_c81e_1622_6cf5, + 0x2a99_7c7b_a771_a172, + ]), + pallas::Base::from_raw([ + 0xe8c2_d9bd_2155_e22f, + 0x8a70_25a0_bd8f_e9a9, + 0x5be9_c192_1d28_e841, + 0x12ea_8763_5c8a_9c3b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4e5c_2298_84b8_933c, + 0x4677_86a0_b2a8_994c, + 0x1325_900b_c323_5b30, + 0x2a1e_4623_a4ae_cc18, + ]), + pallas::Base::from_raw([ + 0x566c_512b_6e4b_d49b, + 0xa74e_0f1c_1e2f_0b5d, + 0xcb43_553c_5583_6e1d, + 0x13a4_a175_6930_a346, + ]), + ), + ( + pallas::Base::from_raw([ + 0x503c_48f0_8921_2210, + 0xed91_7232_785d_86d0, + 0xc0c2_ad95_b9fd_35fa, + 0x0b03_ca6a_1e38_17f6, + ]), + pallas::Base::from_raw([ + 0x4c85_4c2e_f43b_beb6, + 0x3325_8fe3_0797_c712, + 0x7b2b_65b2_71d7_d17f, + 0x2608_45d2_59cb_fed7, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5143_9665_16ca_1642, + 0xfaa0_77c5_094f_ad4d, + 0xcbc1_d869_aa7d_14eb, + 0x2cf2_0f0a_34c2_1025, + ]), + pallas::Base::from_raw([ + 0xad36_eea9_9921_cdc6, + 0x2e1d_370d_297f_0a41, + 0xc957_086f_0915_9341, + 0x22cc_cf10_e21e_1daf, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfb44_e614_c314_ad4c, + 0x8b24_db0b_dfd8_617d, + 0xd17d_196f_3f6a_9e26, + 0x27c8_ffd6_32df_7764, + ]), + pallas::Base::from_raw([ + 0xa5ca_43b4_390c_42dc, + 0xd7a2_791f_6a1b_568b, + 0x98a7_1f22_9c63_b251, + 0x31a3_0d03_e7a9_779d, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf4b4_4ce9_8722_550d, + 0xdd93_8b22_0139_0ac8, + 0xfe86_f423_5227_eeaa, + 0x31d2_8dfe_adb7_adcb, + ]), + pallas::Base::from_raw([ + 0x5adb_7f33_74bc_7bdc, + 0xc10f_d1b1_2bb8_8630, + 0xae28_7ce5_935b_f41c, + 0x2a20_9773_8054_a39f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3be7_413e_3d67_146b, + 0xad57_cda9_2e17_75bd, + 0x2c5f_f9c4_e371_aee9, + 0x1b81_7a0e_2f33_4e49, + ]), + pallas::Base::from_raw([ + 0xa15d_8dfb_cd6d_2737, + 0x2a49_ce19_5f43_8fcb, + 0x4d38_1b52_1b1a_cfd1, + 0x0e93_ce94_5828_a23c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1f51_ce5d_d476_cea2, + 0xdd2f_92cf_2184_73d4, + 0x666a_9231_c766_0b6b, + 0x2b1a_a9ae_9756_b3fb, + ]), + pallas::Base::from_raw([ + 0x46a9_9cce_8c15_cca3, + 0xbf85_bbce_c460_d9aa, + 0x4e8a_a7b8_4aa4_1021, + 0x1780_a81a_6a2a_e54c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe17a_6b50_9568_c08a, + 0x259f_ae60_9e4e_fd82, + 0xaf7f_d128_3ec4_4bdc, + 0x1c09_ded3_74a6_91ad, + ]), + pallas::Base::from_raw([ + 0x70df_b9a3_3551_a4d5, + 0xfe2e_eecc_1187_c582, + 0x8586_f426_db21_1456, + 0x26a6_2ba8_9350_0fca, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5ec3_cd23_5624_6658, + 0x0bab_696b_14fe_b07f, + 0xf70e_9a72_5b80_1b25, + 0x22ff_987b_a1c6_cd80, + ]), + pallas::Base::from_raw([ + 0x7808_0fb5_8b7c_ee41, + 0xce4d_d83d_b956_929b, + 0xeac8_39ce_a74d_ddc4, + 0x2317_33ea_2b06_a86d, + ]), + ), + ( + pallas::Base::from_raw([ + 0xaec8_6746_685d_fe1f, + 0xc218_0c0a_b71e_5168, + 0x8d76_6976_1a70_70fa, + 0x0008_c8a2_7bfc_9cdb, + ]), + pallas::Base::from_raw([ + 0xaa4f_3a36_9871_ca77, + 0x7b4c_0ea1_c1ad_696f, + 0x4ed8_5210_fab0_f980, + 0x3e6c_bd12_b49c_1bdb, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8f6a_0cea_47e1_7bc8, + 0x2864_1feb_0791_af26, + 0x60a3_c179_c72a_2b89, + 0x10ee_4430_7ce0_cbf3, + ]), + pallas::Base::from_raw([ + 0x66aa_db97_f4c0_a463, + 0xe302_d7db_78d5_576d, + 0x482d_9194_87cb_1e2f, + 0x0e9a_4a4a_36ad_1881, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4c34_5c29_f898_6470, + 0xdff4_47e0_e1f7_6080, + 0xf9db_c0ba_062c_1970, + 0x06cd_6193_ac7c_e277, + ]), + pallas::Base::from_raw([ + 0x6104_a761_347d_3597, + 0x8280_faa1_48a1_e104, + 0xa011_5d0a_2f37_3747, + 0x0c73_50b8_829e_b73b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7a98_41a0_1d18_03f0, + 0x9dcd_a639_ea9d_d030, + 0xd91c_8c1f_f2fc_d414, + 0x22b1_7e59_75fc_4d7f, + ]), + pallas::Base::from_raw([ + 0xcaea_f461_5474_dd21, + 0x973b_49e9_4e35_150e, + 0x1a20_3ee4_03cb_bc49, + 0x2012_40e4_943d_2c2c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x75f1_33d9_1456_5351, + 0x1896_214f_0087_2854, + 0x0cd4_7ce7_26fe_ea9d, + 0x2663_26f8_7005_286e, + ]), + pallas::Base::from_raw([ + 0x1737_3826_4271_f35f, + 0xc7d8_f940_6e0a_7c1f, + 0x5924_d283_767f_9d5c, + 0x0eb5_0f0e_3127_577d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x68e2_0f5e_bd39_c14c, + 0x477b_6956_a99f_77cb, + 0x313a_9885_a170_99ce, + 0x1a86_5d1d_04a2_df73, + ]), + pallas::Base::from_raw([ + 0x0623_0fc4_c152_65bb, + 0x52dd_72d4_2781_e376, + 0xa418_49f5_864e_6635, + 0x10fa_c29d_eb03_270d, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa57d_3895_4a64_fb15, + 0xca09_c64e_7421_cae2, + 0xf017_5c2b_ec60_fe7f, + 0x0195_07a1_b05a_ac7e, + ]), + pallas::Base::from_raw([ + 0x6075_dd27_63ab_77ac, + 0x1aa0_6090_7bb7_8a07, + 0xef6a_bddb_8c68_635f, + 0x1b09_b5e0_1b7a_f882, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7849_72ec_1ae0_37c3, + 0x5a42_69be_4726_d8bd, + 0x56a7_19a2_e165_6dc7, + 0x1418_9c78_50da_d383, + ]), + pallas::Base::from_raw([ + 0x815b_cd2b_ca53_eb95, + 0x556c_78b4_7751_dbf6, + 0xf053_fd13_1a20_08c1, + 0x2881_c78c_feab_68d2, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf306_1913_4ae0_7a9e, + 0xd306_6f26_bd6b_4a10, + 0xd230_d8ea_c29a_51fc, + 0x0e6c_d0f8_d1ea_c0f0, + ]), + pallas::Base::from_raw([ + 0x9fa4_27be_acec_9bb8, + 0x2a2b_c250_80aa_697f, + 0xdbf7_9c2f_db4d_bd95, + 0x0028_bb6a_8622_43f5, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf013_8449_cc96_1899, + 0x379d_ac9d_9ebf_876a, + 0xeb7f_99d0_31cd_e93d, + 0x130b_5b85_19be_08fd, + ]), + pallas::Base::from_raw([ + 0xa4ed_7e43_422c_baeb, + 0x15bd_a565_cfd0_99f1, + 0x75e7_3fae_ef12_8208, + 0x1196_b027_3cf1_1fbc, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc311_f99e_5530_bae5, + 0x5e96_2191_2f98_67c0, + 0x0ec4_6179_0832_791f, + 0x3ab8_8c62_791f_cabc, + ]), + pallas::Base::from_raw([ + 0xe30c_dbb2_d1a3_46f3, + 0x300d_881b_2157_d573, + 0xe362_292b_af62_b032, + 0x02c4_23ea_26f4_a3a1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7244_fb82_f14a_5b15, + 0x1716_dbf8_c880_c5a0, + 0x134b_bb0d_fa04_d130, + 0x2c80_0943_ac16_971f, + ]), + pallas::Base::from_raw([ + 0x978a_d2ca_089a_5c31, + 0xe1e6_1bbd_0163_3f49, + 0x3330_aaf3_b642_cbd0, + 0x09a1_c6d2_ba6b_3d2c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x28a5_4ff3_bc33_d5be, + 0x3bf6_b12e_cf48_395c, + 0x222d_c71b_557c_9043, + 0x2440_8b65_1f74_a85b, + ]), + pallas::Base::from_raw([ + 0x9066_0895_04af_52c0, + 0x35e4_e8f7_66c0_15d1, + 0xa386_9229_659f_2484, + 0x207d_3198_11df_6bc7, + ]), + ), + ( + pallas::Base::from_raw([ + 0x15d9_722b_d97c_c054, + 0xc0a4_0440_6239_45f9, + 0xb46b_37e5_73fd_989c, + 0x2b58_6ce7_ac3a_9166, + ]), + pallas::Base::from_raw([ + 0xb9d6_2d9d_3f31_dbef, + 0x7275_cd16_b7e6_a7a5, + 0x8d85_c044_6dd5_7a4d, + 0x0d86_a9fc_da53_0505, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6cc3_c1bc_2016_e727, + 0x617b_0dd8_180a_164b, + 0x4fd6_39de_9f61_971e, + 0x3d4a_6fd8_19ba_27f0, + ]), + pallas::Base::from_raw([ + 0x3413_e80e_aa57_8bd4, + 0x7b16_485e_63bb_769f, + 0x62bb_5921_8c4c_4aa2, + 0x1098_9857_f8af_586f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4d33_b7d2_2e3a_e53d, + 0xe888_af7e_b724_5aa1, + 0x78e6_f0f3_0b76_b896, + 0x15f9_f0ca_f850_8e61, + ]), + pallas::Base::from_raw([ + 0xbece_4f0d_e233_cb6a, + 0xeb3f_a9df_d0c3_9b89, + 0xb351_ff58_43cb_cac6, + 0x372a_a61b_e920_3f30, + ]), + ), + ( + pallas::Base::from_raw([ + 0x74f7_4a0c_a5d5_f226, + 0x3751_b852_e703_7cb6, + 0x9ad1_c8fa_759a_1891, + 0x10fd_b6b7_2434_8442, + ]), + pallas::Base::from_raw([ + 0xfcd7_aa2c_6716_e475, + 0x4f9c_2246_cede_c392, + 0x06aa_a436_01b9_97e3, + 0x0a26_804b_d57e_c010, + ]), + ), + ( + pallas::Base::from_raw([ + 0x539f_f781_f164_4836, + 0xd1dc_dc87_3d99_a0c6, + 0x2de4_7396_8ef1_0b89, + 0x0ff1_67a9_82ba_334c, + ]), + pallas::Base::from_raw([ + 0xb50d_ab91_5188_8e47, + 0xc1e0_55fa_97df_88cb, + 0x3547_e3a1_e0dd_755d, + 0x2d28_735e_9afb_ed0a, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa278_8a09_c089_d696, + 0xf529_cbd9_0600_711e, + 0xcb66_bb7a_1ef8_523e, + 0x29b9_17aa_34eb_2bac, + ]), + pallas::Base::from_raw([ + 0xed7f_a639_85a4_bf96, + 0xbc46_b5a4_cea4_8664, + 0x2c0b_8691_1652_3fde, + 0x3d30_43c2_7ddc_7417, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3c6e_e712_6545_c6fc, + 0x361e_60a3_9a64_72b5, + 0xa4fa_2bc5_6f5a_3772, + 0x206b_0968_ab5d_844a, + ]), + pallas::Base::from_raw([ + 0x7cc3_6e87_ba43_ad08, + 0xf915_23eb_eca6_b1dc, + 0x2fa9_d1da_381f_945a, + 0x25ff_57c6_8a6c_aa43, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7726_42a3_c1c2_2be0, + 0x5061_8408_7ab7_daae, + 0x67ee_d982_89e3_c3e4, + 0x1c40_7832_12e1_7379, + ]), + pallas::Base::from_raw([ + 0xaa43_becb_fcb9_ce01, + 0xe538_4afd_eab9_4a52, + 0x3e9c_4b32_8d24_33ff, + 0x0057_5aff_a2e5_24a2, + ]), + ), + ( + pallas::Base::from_raw([ + 0xea0e_ce17_54e1_854f, + 0x4459_6094_9fc0_86c4, + 0x835e_6759_c23d_e766, + 0x0be3_ab41_eeec_484d, + ]), + pallas::Base::from_raw([ + 0x48f9_a609_6201_6be1, + 0x1909_3863_efb7_d549, + 0x2b59_8ff2_accf_39ce, + 0x389d_847e_e5a1_b1e9, + ]), + ), + ( + pallas::Base::from_raw([ + 0x936a_c037_696b_6537, + 0x3560_e5b4_a313_28a8, + 0x1f47_fea4_cfcb_2890, + 0x0c6b_bc4d_4957_3c28, + ]), + pallas::Base::from_raw([ + 0x2ac6_7e2f_fba6_8746, + 0x80e1_d8ee_95b1_e538, + 0x0ff9_37f0_4793_a319, + 0x1094_a38b_a671_2718, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8ec7_df2c_b2b8_fae3, + 0xeb40_cbe1_652c_ef89, + 0xa02d_a353_aadf_1885, + 0x2c64_885e_84cb_e485, + ]), + pallas::Base::from_raw([ + 0xccf9_06a7_d926_e277, + 0x5d7e_8f08_57f5_5c3e, + 0xdf71_1b47_1772_d5f2, + 0x0ca8_260c_f185_b333, + ]), + ), + ( + pallas::Base::from_raw([ + 0xefba_501d_d999_2307, + 0xcbce_e2bf_bc0a_86da, + 0x7ba9_c366_8cca_0e58, + 0x10e0_7bda_6733_fc28, + ]), + pallas::Base::from_raw([ + 0x0a72_e66f_df7d_ebc3, + 0xf16a_67d6_2aa2_75b9, + 0x03d2_5e00_aa84_2a6f, + 0x26c7_55bc_dfdb_ce70, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1853_7664_9aef_2dff, + 0x7f94_4c4f_10a4_9717, + 0xe235_80ff_b9f5_ac99, + 0x1d38_7fb6_5f7f_4a52, + ]), + pallas::Base::from_raw([ + 0xb59f_6906_b41f_c19e, + 0x6ae3_f226_ba4b_d26b, + 0x5171_2aca_d3e3_cb0c, + 0x3019_b3ab_9250_0e25, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7405_db8c_8d13_97ec, + 0x3a1f_5c63_44b8_b4b2, + 0x3b16_175b_df99_eacb, + 0x0e5c_3936_5a61_4966, + ]), + pallas::Base::from_raw([ + 0x3c2a_824f_2a3b_26ee, + 0x0cb5_c814_2e77_4dc9, + 0xe648_7df6_e45f_90f6, + 0x3a37_71c5_e20a_e479, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0f51_40a8_9629_08a9, + 0xab28_d099_4637_c6c5, + 0x8d40_0c2d_dfbb_597d, + 0x359a_fe63_b4f2_9823, + ]), + pallas::Base::from_raw([ + 0x1fc4_f014_db42_75cc, + 0x136d_5fd6_5404_503c, + 0x0844_9c43_ce86_523a, + 0x0677_006e_034e_098f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7cb5_4bb7_ecfc_0a93, + 0x64fc_3b54_53ba_a1af, + 0xc453_e619_2e6a_86d5, + 0x2838_035c_ac98_9917, + ]), + pallas::Base::from_raw([ + 0x0f6e_5839_dff8_8640, + 0x1290_4182_7ebc_73df, + 0x4735_3b6f_4a1f_7e8a, + 0x31aa_a1ad_ca93_7f4c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9f43_37c3_6d2b_534d, + 0x3bc4_bb39_ede7_0bc3, + 0x2f38_79ae_104b_8a55, + 0x13b0_c171_d97a_4fc8, + ]), + pallas::Base::from_raw([ + 0x0d46_d1fa_370c_5421, + 0x8397_7f28_65be_b753, + 0x63f4_7e7d_e49e_dfd8, + 0x118c_46bf_17f3_a19f, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb4eb_a0ce_8969_48bf, + 0xad2a_98d9_656d_0cf3, + 0xc651_741d_4d5c_668a, + 0x039e_bf84_e942_4d6f, + ]), + pallas::Base::from_raw([ + 0xe095_c878_476c_81a2, + 0xedc7_bc68_679d_680e, + 0xf763_852b_4eed_2af1, + 0x15e9_98b1_e98e_6e1b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8566_9a18_2b28_8d21, + 0x9344_357f_0a1f_da13, + 0x3772_d744_a179_b8ed, + 0x1b03_8534_4ec0_b55a, + ]), + pallas::Base::from_raw([ + 0x4661_8dac_39b6_0421, + 0x4ef5_5d88_b13c_01b6, + 0x5315_7c47_50c6_9734, + 0x2a3a_6e07_2174_0395, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1e17_b775_4399_3b91, + 0x2837_ecac_c403_6f3d, + 0x56b4_0a0a_664d_8fb4, + 0x2f04_0f71_e7e4_d554, + ]), + pallas::Base::from_raw([ + 0xcbf0_51de_b98f_c269, + 0x5c6a_a704_7588_9496, + 0x42fa_c5f8_299f_bcfe, + 0x1247_eccd_7849_645b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x163e_3aad_4d5f_7140, + 0xb692_8158_7410_67a5, + 0x26b5_6868_509f_8ba2, + 0x05b1_5c3d_6a3f_a4ff, + ]), + pallas::Base::from_raw([ + 0x7cab_64a8_a8d0_1dc7, + 0x5b3b_f269_2d68_3607, + 0x2a15_8448_6787_3934, + 0x259a_0f62_1c04_39a1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5bda_eacf_bdfb_6df5, + 0xff6e_56d7_32b9_a43d, + 0x4557_5d7b_eff9_afc9, + 0x115f_3e25_b370_bb60, + ]), + pallas::Base::from_raw([ + 0xfa75_dfbd_dac8_8daa, + 0x2f30_93f7_6e39_fdb3, + 0xc8c1_e227_7dc5_95ff, + 0x3ea1_7ec5_148c_679d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2447_5f8c_9993_6ad1, + 0x51ee_6362_1f78_87c0, + 0x475e_d1d0_ab29_f628, + 0x0303_ffaa_2430_a0b7, + ]), + pallas::Base::from_raw([ + 0x1dd2_a952_d5a5_189d, + 0x22cc_a0bf_5041_4271, + 0x49b2_3458_622c_c627, + 0x0130_c236_40f8_7f89, + ]), + ), + ( + pallas::Base::from_raw([ + 0xaaf1_9c5a_cad7_1c28, + 0x8bb1_2918_8b7a_7858, + 0x23e7_ce2d_92dc_d66a, + 0x1942_3751_8e3a_2f23, + ]), + pallas::Base::from_raw([ + 0xa32c_eb66_4b3e_d4d9, + 0xeb2b_683e_f2b0_6773, + 0xa6f3_5519_396e_3a07, + 0x1d2b_b94d_b61e_c34c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4d21_de83_ed0b_0ed9, + 0xbddf_daab_a5f2_5e3b, + 0x0bbc_a486_495e_59d5, + 0x1685_4768_9da6_e3ad, + ]), + pallas::Base::from_raw([ + 0x8704_6afb_e059_6e25, + 0xbf4f_ef21_6f4a_5988, + 0x336a_e9d0_5d21_7097, + 0x0a21_13b0_758e_02e5, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1b2a_4f19_90f6_4ec2, + 0x56ab_849f_5d02_0569, + 0x2814_07aa_f7bf_a9ed, + 0x0601_c91a_2106_8b41, + ]), + pallas::Base::from_raw([ + 0xcb15_a0dd_808e_bc3d, + 0x07de_f385_c064_d556, + 0x2d6e_5a28_1e93_64d8, + 0x1039_dc0a_cb9e_a154, + ]), + ), + ( + pallas::Base::from_raw([ + 0xed0a_e905_2f8f_ec82, + 0xc113_bd2c_e530_54b2, + 0x0c7a_a4fc_f303_779b, + 0x0874_2829_cd8c_a708, + ]), + pallas::Base::from_raw([ + 0x6882_2f87_7899_c3f0, + 0x1896_a6ab_5b9e_67b3, + 0xcd49_5299_356c_6330, + 0x28a6_ee29_3adc_b613, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4b5e_4d5b_3583_5cb1, + 0x7c02_2a85_aaa9_48e4, + 0x8e1f_4a9b_8d4b_d320, + 0x3a17_94db_a7a9_3fc6, + ]), + pallas::Base::from_raw([ + 0xdd55_72e9_e0fe_59d3, + 0x7b10_d3c9_524a_5acb, + 0xfdfd_2d82_7b63_647c, + 0x0f9a_11b5_efe5_01ac, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8a46_db66_f88a_dfda, + 0x231a_f130_d914_9161, + 0x9afc_b0b8_a037_044d, + 0x2f80_ba78_43d6_0676, + ]), + pallas::Base::from_raw([ + 0x7b8b_4486_b3b0_f9ac, + 0xe9b8_0152_03ff_232a, + 0x3152_b63f_bf6b_2e27, + 0x2c41_0add_3b9a_25cd, + ]), + ), + ( + pallas::Base::from_raw([ + 0x346c_dbf0_be76_95e5, + 0x67e3_f66a_ed16_d76a, + 0x0461_f1a2_8763_60a3, + 0x0a18_29ce_9f91_a1d5, + ]), + pallas::Base::from_raw([ + 0xf17b_9a2b_6fc8_8392, + 0x2c4e_a7a2_bc5d_fd52, + 0xf512_092f_5365_a2ac, + 0x2c5f_2e6d_efce_5696, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa5c1_e1f8_460d_9bd0, + 0xb139_cee1_fbb6_2242, + 0xf5e6_14a0_2785_2538, + 0x133b_ce23_0971_df91, + ]), + pallas::Base::from_raw([ + 0x189c_cd97_00f7_da52, + 0x0ebf_79f0_cb47_65ce, + 0x749c_44ef_1e7d_1d11, + 0x312f_ad99_bbe7_c204, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe752_8bbc_917a_6be2, + 0x0bc2_ab1e_2504_cf52, + 0x3a48_1726_09ca_4031, + 0x2ce5_37e1_0a7c_529a, + ]), + pallas::Base::from_raw([ + 0x64f7_9aef_f4c4_2d79, + 0x9fd8_db65_8592_ed64, + 0xf842_04da_8886_9faf, + 0x377a_985c_3a1b_860c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5d3e_bfc6_25d2_05fa, + 0x10fa_dab3_20e6_ff3f, + 0x1784_f663_e29a_30ca, + 0x2a69_96bb_3ac5_9673, + ]), + pallas::Base::from_raw([ + 0x3fb6_a9c2_e6d6_0488, + 0x90b5_9221_a401_79bf, + 0xdc88_a94b_7528_facb, + 0x2c29_eed6_7fbd_9a89, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfea6_2c82_74a6_af4c, + 0x388c_7bcf_228c_73fe, + 0xc0c7_ea19_e955_ddd7, + 0x1e46_4602_7b2b_c1ca, + ]), + pallas::Base::from_raw([ + 0xca09_e0e1_e39c_3c01, + 0x701a_20ec_477e_affc, + 0x0fa5_db8e_79e9_4367, + 0x3cfc_3c48_e73a_04d1, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd84c_ccf5_eb33_4179, + 0xce23_1898_15de_f8c7, + 0xe76c_8dea_7db5_8e1f, + 0x3aca_479f_f6b4_9d8a, + ]), + pallas::Base::from_raw([ + 0x17f1_246d_f123_7a60, + 0x2e17_9d8f_8d2f_2fa6, + 0x32d0_e613_423e_f64f, + 0x067c_293d_8b6e_c1ae, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8ecd_0c0a_17a7_8a19, + 0x7136_7ba6_7f60_b188, + 0xff5a_bf2c_d1aa_d63a, + 0x3656_099b_d15e_edb5, + ]), + pallas::Base::from_raw([ + 0xc0df_1063_d1e2_9ebc, + 0x9aec_45eb_3df3_5ab5, + 0x6e71_3f49_7b86_cba1, + 0x2e44_9f1d_b523_59ca, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa56e_440e_afba_0e69, + 0x10fb_4ac0_433e_be1a, + 0xd95f_392c_8d71_ee6d, + 0x34ef_089e_8af4_75db, + ]), + pallas::Base::from_raw([ + 0x9cde_8968_4faf_ee29, + 0xd390_7c2b_4645_4693, + 0xf858_c045_9eb2_6def, + 0x2a59_6136_fc96_199e, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd341_83b8_68a0_094b, + 0x46c5_6330_1224_b24f, + 0x939f_ac11_dc0b_394b, + 0x1198_5ddc_b45f_ed85, + ]), + pallas::Base::from_raw([ + 0x4503_5252_8c23_bdac, + 0x818c_e278_0479_31a8, + 0x4d8b_05af_33be_0a68, + 0x2854_b240_01fe_2947, + ]), + ), + ( + pallas::Base::from_raw([ + 0x21ea_923b_9dba_2757, + 0x102d_32ee_7267_baa4, + 0xe07e_8d17_ff5b_d96d, + 0x0539_fe92_593b_cb44, + ]), + pallas::Base::from_raw([ + 0x89a6_e78c_a798_2cda, + 0x776a_98ae_c6c1_fc45, + 0xa4c1_cc4e_5efb_20c5, + 0x2f15_1e00_8953_fe2c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1023_5041_83b9_1d97, + 0x81fe_bc2a_8877_ca60, + 0x1924_5c49_fe3f_bb4c, + 0x0d38_a13f_e3f3_74f3, + ]), + pallas::Base::from_raw([ + 0xd643_1731_44a1_45de, + 0xcd95_d78b_7357_5f87, + 0x6ed2_6c3f_d329_09e3, + 0x0960_8380_e85f_342a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4291_8cdb_be2f_c05e, + 0x9acf_4a57_b166_d495, + 0xca99_c923_f45e_49ab, + 0x3fa1_33ab_04b7_a1e9, + ]), + pallas::Base::from_raw([ + 0xd731_a982_524d_4da0, + 0x55ec_878d_0321_1172, + 0x48d5_eaed_e324_92cd, + 0x0cff_b957_d167_7198, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf528_ad8a_c429_a4c2, + 0xd99b_85cd_8bcb_927e, + 0x853a_6c3f_3ecd_f11d, + 0x3fb4_d9e9_0d91_ecb0, + ]), + pallas::Base::from_raw([ + 0x9137_5cbd_7088_a770, + 0xbc73_46bd_4063_fc5d, + 0x52ee_b5a0_1934_a31c, + 0x3597_389b_0dc1_1334, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdb04_e2bd_c6a3_3802, + 0x4896_61c3_cebe_0978, + 0x21f9_61cc_a49e_f597, + 0x0fc8_c0b7_b394_2242, + ]), + pallas::Base::from_raw([ + 0x8f85_9b56_948a_bed6, + 0x72b1_52a1_bb7c_2347, + 0xfcda_82d5_34b4_88d3, + 0x3e4d_040b_c1bf_dcf7, + ]), + ), + ( + pallas::Base::from_raw([ + 0x24a9_d7d9_f5f8_fb4c, + 0xa84b_e92f_da7a_6564, + 0x7c10_a2c0_1bac_9ff6, + 0x343d_ecbb_2e70_9ef9, + ]), + pallas::Base::from_raw([ + 0xf538_642e_d7fd_e654, + 0x6d30_faa6_432e_584b, + 0x18f2_02ad_2856_2bcc, + 0x1a0c_7166_2352_d21d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x08e8_9011_14d3_3f6a, + 0x0d82_62ea_f48c_28a2, + 0x89fe_087b_2750_6546, + 0x0895_2f7a_efaa_e8e6, + ]), + pallas::Base::from_raw([ + 0xca63_280f_9555_84ad, + 0xc7be_a103_e8a5_d522, + 0xb795_d913_5441_e491, + 0x3aa2_13ad_62ab_a032, + ]), + ), + ( + pallas::Base::from_raw([ + 0x154d_e3e2_a46d_afa1, + 0xef1b_cb7e_af7c_9641, + 0xc4cc_1e3f_0b9c_48b7, + 0x227b_14a3_1a99_2a2e, + ]), + pallas::Base::from_raw([ + 0xe7e2_e290_71fb_4a61, + 0x28f4_a7fe_9e2c_3b73, + 0x2fc8_8036_1925_c86a, + 0x2cde_373d_3d92_f96a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6c19_1842_a7d6_f4c8, + 0xa31d_ba99_3039_3d70, + 0x3514_6c26_3e06_7076, + 0x3803_9a47_4646_486b, + ]), + pallas::Base::from_raw([ + 0x27af_d249_5982_96c5, + 0xfcf6_b6aa_f5b2_d252, + 0x5c22_b91e_7495_dfec, + 0x2d57_9b70_dfa4_6507, + ]), + ), + ( + pallas::Base::from_raw([ + 0x50af_b111_4376_64df, + 0x1a85_72f4_fe6f_5325, + 0x1388_bf8b_eacf_630e, + 0x386b_3799_e109_7613, + ]), + pallas::Base::from_raw([ + 0x1d70_2f5b_9143_e12a, + 0xaac6_af7e_c5f3_428d, + 0x17dc_6b11_551f_52f2, + 0x0f7c_38b0_e835_e793, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc6e1_bd75_9bd4_3e46, + 0xb4a0_cc52_41e1_0baf, + 0x9b77_ed75_c46e_f6ab, + 0x0c02_f929_5248_ff03, + ]), + pallas::Base::from_raw([ + 0xc445_5024_0956_6825, + 0x0996_b65c_b2e1_7918, + 0xec22_9f0f_1eb1_49a6, + 0x0c64_8828_4462_5887, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc412_8bbd_2797_ba18, + 0xbd47_1f04_69d3_5b08, + 0x021a_0ae6_846c_2364, + 0x1e0d_3b1e_f4ea_6379, + ]), + pallas::Base::from_raw([ + 0xd0bd_8eb2_1cab_9f78, + 0x1bfb_e611_fe72_74e3, + 0xf844_8815_10e3_d7de, + 0x150b_00bb_a5ac_9396, + ]), + ), + ( + pallas::Base::from_raw([ + 0x752e_2b2b_9169_c0de, + 0xf2e4_7feb_5a2b_1656, + 0x61f7_7831_314b_7912, + 0x29d5_36bf_1530_146c, + ]), + pallas::Base::from_raw([ + 0xc890_bb54_9aff_0c72, + 0x197d_a4e0_76db_b42a, + 0x746f_0142_3821_5206, + 0x2512_c57e_4c5a_951e, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe7e6_6a45_c1aa_686c, + 0x8c98_b474_bd46_1267, + 0x8ae1_d414_4cc1_4cdf, + 0x1b9f_33fa_945c_8c67, + ]), + pallas::Base::from_raw([ + 0x9802_7495_ad6d_6b21, + 0xccc8_ed29_cecc_1b1b, + 0x74c8_0f51_6930_7044, + 0x334b_d2ec_3b06_2238, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe2e3_e34a_e48a_4542, + 0x77a7_9d9d_ee72_233a, + 0x9290_2fcd_5d97_3f99, + 0x0e28_df80_537b_d86b, + ]), + pallas::Base::from_raw([ + 0xb257_f5b7_a198_5b9a, + 0xaddd_7e70_cbc2_8f19, + 0xc35b_17df_11d3_bb77, + 0x28f4_8a76_e85b_984c, + ]), + ), + ( + pallas::Base::from_raw([ + 0xc1d2_64b9_7aa5_7f83, + 0xc8a7_697d_1031_d3ba, + 0x1e54_33c8_d6c1_c1ba, + 0x1a28_4362_7c85_7a95, + ]), + pallas::Base::from_raw([ + 0x5c00_7005_f638_803b, + 0x153c_6810_33d1_3436, + 0x39ca_84fa_65ec_c4cf, + 0x36a5_7369_ebe3_2da1, + ]), + ), + ( + pallas::Base::from_raw([ + 0xcb3b_fab7_1ed6_88d1, + 0x959d_75b1_c3da_4470, + 0xff6b_cf88_212e_3d2b, + 0x1ab4_0ee5_990b_6b99, + ]), + pallas::Base::from_raw([ + 0x18fc_cc71_d6e5_8e43, + 0x52fa_e1cf_1c00_0d21, + 0xfe55_97c1_42a4_a225, + 0x0a8a_53ab_72a3_13ba, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf4ce_3a37_5416_8e4b, + 0x61c7_546a_ec52_434a, + 0x7d70_ac66_6893_57e8, + 0x1c12_4475_9a7d_efc8, + ]), + pallas::Base::from_raw([ + 0x3b5f_2cb6_a96e_db5a, + 0xef71_e886_2359_5d4f, + 0xe5a2_7436_5afd_c9e0, + 0x110a_f91f_2774_3efe, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6895_e22a_e677_de95, + 0x9fbb_9b3d_937b_3dc3, + 0x0bc1_f4d9_d4d5_bd66, + 0x27f2_b67f_1fec_e78a, + ]), + pallas::Base::from_raw([ + 0x7234_9411_50bc_efc4, + 0xac8e_5753_a8f5_0362, + 0xf7e3_8c41_e428_466c, + 0x315e_645b_5d4d_cf13, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5494_bb94_e808_6993, + 0x909a_795b_c421_efe8, + 0x038b_cb01_6b45_f433, + 0x2fc4_4636_cbb6_d6dd, + ]), + pallas::Base::from_raw([ + 0xa37f_6fe0_0cbd_bb87, + 0x0aae_cb06_be08_0506, + 0xdd80_a12f_e84d_f36f, + 0x1dc8_84e0_7af9_e250, + ]), + ), + ( + pallas::Base::from_raw([ + 0xdaa4_1d05_a56d_0e0b, + 0x6fed_5c75_13eb_443c, + 0x7a8b_0e4c_65b1_cd50, + 0x214f_7c8e_ed8b_b0ef, + ]), + pallas::Base::from_raw([ + 0xfe49_d9d5_1881_ea16, + 0x2353_dd64_ed34_1337, + 0x56b2_0c8e_d9f1_26d9, + 0x2c22_92b4_4c2b_0de8, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa74d_964e_9b7c_04bb, + 0x335c_bb5b_4ec0_2dec, + 0x8a84_7bec_6994_329f, + 0x1aa7_1c22_0de7_c5d4, + ]), + pallas::Base::from_raw([ + 0xc3b8_82a6_d6f9_f593, + 0x9ba7_9e1b_259f_8ae6, + 0x8ab6_b763_bd79_cc26, + 0x2183_61af_34fa_b740, + ]), + ), + ( + pallas::Base::from_raw([ + 0x97b6_5e79_5bd5_3661, + 0x560b_b24b_f4dc_8b38, + 0x16ce_dcc1_c055_12da, + 0x1571_6cae_b4f2_105b, + ]), + pallas::Base::from_raw([ + 0x3f1a_b4a4_ab91_7f25, + 0x4597_f16e_4e48_2ec7, + 0x43c4_b3e2_9f78_544b, + 0x0abf_eec2_26bf_1c59, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9beb_a739_41b6_91d7, + 0xcf15_e49b_9d08_eba3, + 0xd443_2465_4f9b_bb6e, + 0x2542_f10c_5975_b4f6, + ]), + pallas::Base::from_raw([ + 0x410d_c2a6_f3c7_221c, + 0x9e64_ef82_f138_821d, + 0x3607_68e2_df2a_cb50, + 0x2af0_280c_a2a1_a946, + ]), + ), + ( + pallas::Base::from_raw([ + 0x40aa_4da4_2525_32cc, + 0xa047_25f5_6850_6918, + 0xd096_8644_fefc_e14a, + 0x0591_ae22_96a9_a185, + ]), + pallas::Base::from_raw([ + 0xc59d_ea56_2de9_c223, + 0x1548_1b85_8a62_94f1, + 0x0aac_a08d_c1b6_25a2, + 0x0f05_cf9a_21ce_dda1, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd14b_b7d1_5076_72cc, + 0x6330_7f6f_52ce_3ad5, + 0x7ce5_1e25_ad24_3072, + 0x07dc_2bac_dd7c_6ea0, + ]), + pallas::Base::from_raw([ + 0x5836_c3d5_7c40_ffae, + 0x46f5_819e_44b7_ae09, + 0xb796_fb55_e09c_9785, + 0x2d95_737b_55d5_5dd1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x86dd_a9bf_0115_82af, + 0x40d9_e41e_3690_6b44, + 0x5787_20aa_8983_bfa2, + 0x2f1a_1706_18f1_8afb, + ]), + pallas::Base::from_raw([ + 0x09f8_9309_75e4_a8c6, + 0xef85_967d_be1c_9f79, + 0x4a38_18ba_32b7_bcd4, + 0x0a8f_1295_63b5_5cb7, + ]), + ), + ( + pallas::Base::from_raw([ + 0x64e5_850f_a84e_2a38, + 0x67a1_f9d2_a1cb_2892, + 0x34b6_e8a9_6a3d_1adc, + 0x2e6f_30e3_86d6_a0c0, + ]), + pallas::Base::from_raw([ + 0xb9d1_3f17_8e65_ff6a, + 0x4ffb_0b20_e25a_7848, + 0x6c2f_dcd2_e668_e2d3, + 0x1e93_9c54_a6fa_239c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3316_8467_79ce_e7f6, + 0xfb6e_d07b_5560_c360, + 0x4a8f_2f59_55ff_6357, + 0x28fb_77fc_9375_6d8d, + ]), + pallas::Base::from_raw([ + 0xe3e2_1b78_b2cf_5612, + 0xc8d4_70e2_cb22_7688, + 0x731b_0c6e_e1d4_f24b, + 0x30cd_02ff_4619_55c2, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8e9c_4a02_e5ea_9126, + 0x6fb9_f18e_4e5e_96dd, + 0x494c_3274_6849_1131, + 0x18b5_fe24_35aa_0912, + ]), + pallas::Base::from_raw([ + 0x4956_0a92_8dfc_8d75, + 0xc948_7b69_3b11_267e, + 0x315e_da1d_1b42_789f, + 0x35d4_3503_9f73_bf9a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0010_6821_9f96_2f62, + 0x3931_2d4d_6b30_e2e7, + 0x94be_b29a_ad36_c2c6, + 0x36f4_5328_c007_73d2, + ]), + pallas::Base::from_raw([ + 0xe4f3_0b41_1e5d_d8b5, + 0x65e5_1f6f_4823_cbd8, + 0xf47f_3fd3_d0b6_f299, + 0x1427_8ff8_e951_56be, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe14f_d0ad_d91e_2015, + 0xa2bc_ccd9_60a9_fdab, + 0x715f_aab5_f71c_b572, + 0x1960_ed3b_e23f_8c8c, + ]), + pallas::Base::from_raw([ + 0x6d4d_e792_32d9_75b0, + 0xa0e0_d86f_f79c_4490, + 0x947e_ec8e_5f3c_08d9, + 0x162a_f9d0_f176_eeb6, + ]), + ), + ( + pallas::Base::from_raw([ + 0x77be_2a89_644f_4e4f, + 0xdac2_fd2f_f979_19f8, + 0x482d_415e_0aa4_336e, + 0x12b4_8f67_c25d_ddea, + ]), + pallas::Base::from_raw([ + 0x1447_b356_5495_fdc2, + 0xfa86_152d_2046_a6bd, + 0x0e48_3cee_96b6_3430, + 0x310a_f969_9f04_356b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x49d9_568f_4b49_90b2, + 0xff1d_52e9_6c43_0eed, + 0x9dab_c67e_0ab6_7fbc, + 0x15b8_acf0_dafc_6bd0, + ]), + pallas::Base::from_raw([ + 0x04db_2abc_c8af_ebe1, + 0x858a_4302_b8ed_aede, + 0x857e_f9df_9683_371a, + 0x0ed5_6792_c5bc_56a9, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8fc8_c4bb_97be_29b8, + 0x723f_ec3a_c6fd_9388, + 0xcc18_5b44_14ce_3800, + 0x1903_31a9_ed7c_af8c, + ]), + pallas::Base::from_raw([ + 0x6e48_8eb0_820c_1768, + 0x7fcb_5d2c_98f1_ca49, + 0x32c6_8966_238e_4125, + 0x06ca_d05e_b165_eacc, + ]), + ), + ( + pallas::Base::from_raw([ + 0x05d6_73c0_9942_4c38, + 0x9b1c_e944_65d8_d36c, + 0x4192_c0f4_f365_0b99, + 0x3383_b385_084d_b871, + ]), + pallas::Base::from_raw([ + 0x41a3_b162_abb4_42dc, + 0x017f_86b0_2583_46ed, + 0x458e_9783_e3ed_a2b5, + 0x083e_e1cb_29d3_6b81, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe476_9543_7977_767d, + 0xed9c_7b2a_f66c_175f, + 0xb833_a588_5741_cd5e, + 0x363d_b7fb_cd58_d72c, + ]), + pallas::Base::from_raw([ + 0xf313_93cb_d9f8_02e3, + 0xe6eb_366c_2b43_078b, + 0xbca3_b4dc_3212_594e, + 0x08b1_1978_df11_1b34, + ]), + ), + ( + pallas::Base::from_raw([ + 0x77ce_a705_9e6f_006c, + 0xa35b_db7d_dc08_66ab, + 0x715a_f92e_56f1_97ea, + 0x3664_9345_05c4_d86e, + ]), + pallas::Base::from_raw([ + 0x9c53_e468_a11d_cffc, + 0x1c3e_0f67_68dd_602c, + 0x1ba5_77b8_d65c_4577, + 0x1d42_09b8_67f6_9ae6, + ]), + ), + ( + pallas::Base::from_raw([ + 0x260a_5380_a10f_5880, + 0x7a1b_b8cc_b65a_04e5, + 0x27a8_f9ff_d28b_2308, + 0x3a32_fb37_acbe_f3c3, + ]), + pallas::Base::from_raw([ + 0x914a_9b46_a374_9954, + 0xfed9_b9fc_0c61_8135, + 0xfe45_3ec9_a172_f21e, + 0x1c9f_90fd_5778_ec0f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9910_a461_c209_3108, + 0x2c2e_d023_b39e_9004, + 0x2c03_9b33_a27f_913b, + 0x0a0e_6954_9577_4dec, + ]), + pallas::Base::from_raw([ + 0xe6b9_b73d_4549_90d8, + 0x47ba_dd67_2352_1eed, + 0x40be_e046_81a5_cc00, + 0x36b5_ea1f_1eff_932d, + ]), + ), + ( + pallas::Base::from_raw([ + 0xd2e0_0398_4661_f7a3, + 0xca79_c17c_09e6_51e1, + 0x7836_47d3_410b_a538, + 0x217c_55a4_8acb_9a5d, + ]), + pallas::Base::from_raw([ + 0x1bd2_0b6c_04fd_e263, + 0x02c7_b694_dc6d_a359, + 0x3a71_9cfc_8a1b_9a1c, + 0x3fff_5323_5b48_9bbe, + ]), + ), + ( + pallas::Base::from_raw([ + 0xe25b_d2f6_66fc_16d2, + 0xf999_b1a1_db9c_75c2, + 0xd0f6_f6cf_794f_162d, + 0x3366_32de_5352_5f98, + ]), + pallas::Base::from_raw([ + 0x1c68_3a79_9230_e098, + 0x807c_7b25_a72c_04ed, + 0x534e_0c05_0c08_e762, + 0x2bc6_1d1f_88b2_bab0, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6ee1_ad02_6d11_3806, + 0xc402_cf86_0c69_c2f8, + 0x1b43_0a98_7ad4_7948, + 0x3123_cab0_04e7_4073, + ]), + pallas::Base::from_raw([ + 0x28ac_c01e_af74_1193, + 0x3d35_2c06_3e65_b2b5, + 0x5da6_8188_3d2a_52ab, + 0x38f2_4ab3_5569_517a, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6baf_341c_2702_9a80, + 0x31d7_cf3d_2326_22c1, + 0xb56b_e361_456f_7da3, + 0x22d9_07c2_5b47_0494, + ]), + pallas::Base::from_raw([ + 0x8110_08ea_9321_b2e1, + 0x9e15_c19c_8b04_0ec1, + 0xc7b4_860c_a4fd_0f1a, + 0x3fb3_5b5c_9ff2_ce92, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3eb4_cbe9_57c3_215d, + 0xe3bb_1c30_aa5a_2018, + 0x6829_f364_04df_ce6a, + 0x1e4c_9bb8_5e77_3502, + ]), + pallas::Base::from_raw([ + 0xf927_5406_c35a_b692, + 0xedb3_cacf_43f8_d2eb, + 0x5b04_6f41_e82f_fa8d, + 0x16c2_b490_1be1_772e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x91f2_5bb0_0df3_b325, + 0x9fa2_042e_bf14_310e, + 0x440d_8c06_8bb1_5a05, + 0x000a_e05a_1117_51b6, + ]), + pallas::Base::from_raw([ + 0x71a9_f252_e8dc_96fc, + 0x93e9_0e94_a948_47fc, + 0x35bd_756d_82e4_6522, + 0x0dd4_182d_0922_90ad, + ]), + ), + ( + pallas::Base::from_raw([ + 0xac7d_898a_152e_395a, + 0x893e_8851_1ab3_33da, + 0xf069_82b1_791e_1812, + 0x1fb6_f3c4_cf65_d002, + ]), + pallas::Base::from_raw([ + 0x65c5_e104_bca2_bc38, + 0xcd1b_883c_113f_a262, + 0x96bc_af49_58ae_d45e, + 0x3c4c_e7d2_6eb9_a372, + ]), + ), + ( + pallas::Base::from_raw([ + 0x867f_b40f_ed90_02ea, + 0x40a5_041b_7cba_a776, + 0xc206_37a4_c7c4_ac98, + 0x2570_9da2_0e46_8fbe, + ]), + pallas::Base::from_raw([ + 0x651a_689f_4ec0_fd53, + 0x4a1e_3008_41ea_e098, + 0x9e4d_ec05_ca83_49d2, + 0x2fcb_ed31_57c6_0795, + ]), + ), + ( + pallas::Base::from_raw([ + 0xa5b5_1c60_e817_6fd1, + 0x2e8f_21e3_bfb9_750f, + 0xf45c_e53c_8b74_a941, + 0x04fd_1a36_e04a_7b99, + ]), + pallas::Base::from_raw([ + 0x5ca7_72c9_9b5a_d4f9, + 0xea3c_aa3d_8393_caee, + 0x3508_ad20_2818_0ba6, + 0x1b7d_cd69_c52c_070f, + ]), + ), + ( + pallas::Base::from_raw([ + 0xf076_4e26_b3f1_d20e, + 0x18c7_142e_8625_e9c8, + 0x51f7_7d4f_7dde_dce2, + 0x2373_bf50_232b_3f22, + ]), + pallas::Base::from_raw([ + 0x47b8_d63d_328c_c660, + 0xdafe_f5c4_0e05_ae5d, + 0x98b5_a965_47c9_1289, + 0x01ad_0491_0905_0a6c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1144_a189_947f_94df, + 0xe428_df16_cd10_22ba, + 0x7565_d9ea_3476_c4a8, + 0x2070_a6e3_1265_c3dc, + ]), + pallas::Base::from_raw([ + 0x91df_7f7d_dec9_e146, + 0x9135_871c_349e_dbe7, + 0x7edb_dcb4_d0e8_bb0a, + 0x0c90_c909_0575_8d15, + ]), + ), + ( + pallas::Base::from_raw([ + 0x497e_9d18_b31f_1ea6, + 0x087a_5e39_1bec_b0e3, + 0xe8de_4d9c_6a44_8c64, + 0x141d_4aad_df85_22b8, + ]), + pallas::Base::from_raw([ + 0xff15_6277_b7ae_3c64, + 0x3455_b096_0f5b_a73c, + 0x2051_cfe3_04f9_e861, + 0x19f8_6f8b_52e2_d6d7, + ]), + ), + ( + pallas::Base::from_raw([ + 0x9eb2_3cac_ee0c_46a4, + 0xe61a_c293_cba7_7cd6, + 0xc50e_6571_9c07_30af, + 0x2225_2c4f_0dc9_d926, + ]), + pallas::Base::from_raw([ + 0x99ee_8f10_2d20_4335, + 0x09d9_c154_3bf6_02d3, + 0x61d7_e3d0_9e64_c519, + 0x1b3f_b8bc_c227_b8f3, + ]), + ), + ( + pallas::Base::from_raw([ + 0x6b9e_a4ea_df65_cb3f, + 0xc1fe_f822_6717_eb27, + 0xfb69_b82e_a0f3_4f2c, + 0x2594_f3af_922c_6c4c, + ]), + pallas::Base::from_raw([ + 0xc431_a2b7_6a21_a9ae, + 0xba4f_fa4e_a749_4caa, + 0x354d_e308_ab36_c4f9, + 0x244b_96c7_5d78_711b, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4768_edc3_6542_5192, + 0x6635_8e8a_a347_fefe, + 0xb5f4_dfd5_e5b9_73ce, + 0x1a59_51c5_25bf_f454, + ]), + pallas::Base::from_raw([ + 0x7e01_049a_8df5_fd8a, + 0x6b01_2890_41d2_19dd, + 0x3844_3e89_e500_e012, + 0x2be0_f234_4af5_6d77, + ]), + ), + ( + pallas::Base::from_raw([ + 0xae1e_74fc_1860_213a, + 0x3fe4_3c3a_2886_f22c, + 0xdefa_9b1e_edbe_c1e1, + 0x31bc_5ab6_4e9f_5faf, + ]), + pallas::Base::from_raw([ + 0xbd19_b196_572a_8513, + 0x4272_e09b_f426_f2c9, + 0x9925_f875_ae11_ddf7, + 0x1fb9_5b9e_53fb_0d63, + ]), + ), + ( + pallas::Base::from_raw([ + 0xb236_a763_08e8_7326, + 0x8974_b02e_45e8_dca3, + 0x0575_b904_ff33_10d8, + 0x0347_ed41_4f04_efca, + ]), + pallas::Base::from_raw([ + 0x18eb_fd89_32b5_0ad8, + 0x82a2_5574_4ed0_515d, + 0xb135_d6bf_0377_152a, + 0x3419_a1db_bc9a_9880, + ]), + ), + ( + pallas::Base::from_raw([ + 0x8c84_0714_8c42_e400, + 0xee8a_bd72_6199_8367, + 0xfe4e_c3e9_5c51_4cd9, + 0x2b7d_7f9a_0bc8_cf07, + ]), + pallas::Base::from_raw([ + 0xe58f_7f61_3068_dfd4, + 0x31c9_d482_7192_7dd4, + 0xa09f_1e90_db5e_fe74, + 0x2e12_f957_7e54_b546, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3d25_c1f3_eb56_14c1, + 0x78b0_2ff0_db91_5adc, + 0x52e4_595a_4745_1abb, + 0x20c0_812b_5d28_2662, + ]), + pallas::Base::from_raw([ + 0xc166_e032_a3ff_9774, + 0xbf21_613f_887f_5cc1, + 0x6666_23cc_e45a_acb3, + 0x1f8a_670a_47ae_5415, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0e32_ea98_b294_1bb3, + 0x0040_f5f6_d5d0_174b, + 0x137c_87e7_3271_65a4, + 0x0614_8c65_f99b_da0a, + ]), + pallas::Base::from_raw([ + 0x6348_5e06_f44d_7edc, + 0x3b2b_37a4_b180_07ab, + 0xfe96_302e_4023_3b6d, + 0x0c86_9dda_efca_80d1, + ]), + ), + ( + pallas::Base::from_raw([ + 0x74e3_303e_17d4_1cf1, + 0xb9b5_5056_8945_d1c1, + 0xf5ca_f3d6_bab0_c8ee, + 0x20aa_c0ce_dcdc_1816, + ]), + pallas::Base::from_raw([ + 0x465b_2a4b_f80c_a876, + 0x7f65_deed_eb30_4907, + 0xcf39_c880_9f38_77e2, + 0x0286_a346_71e5_743b, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfb50_a168_127f_b04b, + 0x40f8_01e3_04f5_df03, + 0xb4e1_c034_c959_50eb, + 0x1126_9317_caf7_5d07, + ]), + pallas::Base::from_raw([ + 0x42c5_97bd_71be_93ad, + 0xe220_1b27_f87d_2e4b, + 0x11f2_a2f5_a81c_6934, + 0x25a7_0d53_1d5b_addf, + ]), + ), + ( + pallas::Base::from_raw([ + 0x86c3_b612_1d8d_3fa4, + 0x4914_c783_0ca4_f15f, + 0x04ea_4fe6_f986_a7ec, + 0x2026_f468_4a50_7b0c, + ]), + pallas::Base::from_raw([ + 0x435e_50f3_7974_66eb, + 0x8b5c_73b6_b70e_0033, + 0x7ed9_4ab6_144a_18bf, + 0x2b10_91f0_ffa9_d775, + ]), + ), + ( + pallas::Base::from_raw([ + 0x7335_a5d6_15f2_85a5, + 0x65e3_ce55_379a_4653, + 0xc59c_ed4a_cc76_2af0, + 0x0429_7a2c_6046_7f76, + ]), + pallas::Base::from_raw([ + 0x38f3_ec3f_0286_6257, + 0x1e13_f010_a49e_a5c0, + 0xdaf1_e8bb_06ca_2eec, + 0x329d_3ddb_9b5a_922d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x400a_e7f0_113b_1b88, + 0x6505_dd5a_729f_0ed9, + 0xf46d_81a6_8261_b4c8, + 0x2450_8490_e887_3820, + ]), + pallas::Base::from_raw([ + 0x35b8_1e5b_7fea_8621, + 0x9ab1_93bd_5e06_6a86, + 0x8692_12f1_d266_8fc3, + 0x15ea_d070_2b54_0d1e, + ]), + ), + ( + pallas::Base::from_raw([ + 0x958c_8b6a_f42e_66da, + 0xdfcd_951f_1c14_d6f5, + 0xb5bc_07dc_6f54_f30a, + 0x1d82_d649_7016_56da, + ]), + pallas::Base::from_raw([ + 0x05be_a7e3_a36e_01f9, + 0x77ab_d96c_4618_47be, + 0xa7b5_16d3_9767_5ff3, + 0x1785_5e76_cc3c_e4be, + ]), + ), + ( + pallas::Base::from_raw([ + 0xfd0a_310a_b4fb_5f5a, + 0xd05f_e1c2_4618_bb6f, + 0x5741_e067_013d_e384, + 0x2373_0ed6_879c_4bf1, + ]), + pallas::Base::from_raw([ + 0x7e9f_f7bc_b94c_5d16, + 0xd75e_56b3_9f76_fd96, + 0x5562_35b1_256d_ea0f, + 0x3ecd_11be_4f08_056d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x3d04_4438_d2b7_8ca6, + 0xa679_db14_51f3_dcb0, + 0x922b_166c_a2b2_bb15, + 0x1107_3835_6e3d_778a, + ]), + pallas::Base::from_raw([ + 0x95ce_a45d_d63e_b7e2, + 0x2af0_c617_e626_d526, + 0x9d55_3537_9131_d95d, + 0x0d84_161a_6b99_3a77, + ]), + ), + ( + pallas::Base::from_raw([ + 0x4df8_74c2_f161_5191, + 0xd297_0d9a_0843_1ff4, + 0x66c1_4b9b_525d_f889, + 0x3b83_8a77_8a22_ed9a, + ]), + pallas::Base::from_raw([ + 0x6786_9621_06cb_caae, + 0x83f2_0b98_433f_f62b, + 0xff05_c2a9_4d11_970e, + 0x32db_3f0c_596c_894c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x18d2_6b17_c5b7_95b4, + 0x5097_014c_31df_3d6b, + 0xc1bc_54b7_a9a3_6f47, + 0x003f_0149_4252_2ca4, + ]), + pallas::Base::from_raw([ + 0xe704_1773_6871_2b5d, + 0x012a_3a17_1425_6ed0, + 0x750f_6ae1_dac8_4dba, + 0x39fa_e6ac_0da9_dcec, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0022_fc4f_c196_3017, + 0x6e6d_a891_b75a_a6f2, + 0x8e65_aa60_d9c7_8441, + 0x2ff0_f181_181a_9fa8, + ]), + pallas::Base::from_raw([ + 0x08ea_646a_eaa0_74f3, + 0xe893_09ca_01bb_be00, + 0x0021_c936_55f7_2464, + 0x2e6f_5fdc_b38c_af4f, + ]), + ), + ( + pallas::Base::from_raw([ + 0x99bf_57d6_5784_c501, + 0x7017_bd72_7c8e_b52c, + 0xe919_d31c_2d32_8f5d, + 0x0bcd_43af_aaf5_2fb7, + ]), + pallas::Base::from_raw([ + 0x60d0_5ba3_c3d4_e2a8, + 0x03a1_39f0_df52_02d2, + 0xe5c8_8a6e_21e1_9481, + 0x384a_d0a4_1d52_9ba5, + ]), + ), + ( + pallas::Base::from_raw([ + 0xac1f_1153_693b_c3fb, + 0x95cb_3d54_26f8_0864, + 0xacc9_531f_91c2_17a7, + 0x37a0_65ab_2b62_bad6, + ]), + pallas::Base::from_raw([ + 0x2556_2833_d728_cc98, + 0x157a_5fe3_6bdc_7a86, + 0x45a9_fabe_1703_6e96, + 0x15a6_2e9a_3e84_6efb, + ]), + ), + ( + pallas::Base::from_raw([ + 0x84eb_0966_60b5_7d7e, + 0xbcb3_c183_689f_69b6, + 0xc179_6b0c_1aa2_7a8d, + 0x1f47_12b0_6b77_73e3, + ]), + pallas::Base::from_raw([ + 0x2e00_a10a_f3c4_6f66, + 0xd4d2_d9a4_4958_e31e, + 0xaae5_c9f9_9f8c_6e6a, + 0x0ffb_5c9c_6c4a_c3aa, + ]), + ), + ( + pallas::Base::from_raw([ + 0xebdf_427c_e6aa_6032, + 0x2284_d8b0_888f_cc3a, + 0x3f9c_8166_83f2_fe8d, + 0x3be0_c3d2_1862_d2b0, + ]), + pallas::Base::from_raw([ + 0xd068_4fe3_7825_0382, + 0x29a3_85cf_563b_538f, + 0xb401_2ac9_07b0_9161, + 0x3fe4_fabe_5f26_0616, + ]), + ), + ( + pallas::Base::from_raw([ + 0x0bc2_86a7_8d4d_c29a, + 0xa808_bac0_57cb_7c72, + 0x1e71_e213_e7ad_d6cd, + 0x3d6b_987c_9596_0b11, + ]), + pallas::Base::from_raw([ + 0xe4ac_cdd3_7e1d_475a, + 0xe6b1_afb2_8e9a_e97c, + 0xb43c_7942_dc38_771b, + 0x0a9f_622c_5b76_581d, + ]), + ), + ( + pallas::Base::from_raw([ + 0x346d_1c37_d315_d20d, + 0x2f90_33b6_9759_2232, + 0x19fa_9995_c446_fd03, + 0x0e97_a5fa_b367_fc42, + ]), + pallas::Base::from_raw([ + 0xa4f9_7858_7b47_3a80, + 0x5c3a_72cf_5848_9bcc, + 0x812b_72e4_2421_a299, + 0x2b11_99ab_416d_bef7, + ]), + ), + ( + pallas::Base::from_raw([ + 0x5e07_f1b3_b73a_4e48, + 0x19b0_925f_6f8f_6b83, + 0xcf98_c5cc_7f9a_9cb2, + 0x0f1a_0d41_9b39_1617, + ]), + pallas::Base::from_raw([ + 0x632f_c83e_f090_fdec, + 0x2f3c_0e5b_a18e_d660, + 0xce90_d880_46e7_cabf, + 0x0cad_fd0b_a42e_ea59, + ]), + ), + ( + pallas::Base::from_raw([ + 0x238c_cf74_76ab_5023, + 0x074a_888a_fb1b_0035, + 0x3763_56e8_efeb_ecee, + 0x203e_1e97_4451_db54, + ]), + pallas::Base::from_raw([ + 0x205f_a8cc_d857_d1b7, + 0xa271_379b_274f_7284, + 0x3d47_8f66_532f_d8fc, + 0x1366_88be_56b9_e771, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2b08_b53d_bb1c_746f, + 0xded6_6332_130e_59f9, + 0xec3e_7d70_890f_11b3, + 0x070b_8394_0557_a571, + ]), + pallas::Base::from_raw([ + 0x9320_2c85_006c_febc, + 0x679f_3693_3fc6_5b4d, + 0xea12_f030_0d2a_06e1, + 0x2e11_4723_802f_792c, + ]), + ), + ( + pallas::Base::from_raw([ + 0x356f_e85c_2e67_e247, + 0x1d0d_775e_a300_5f6d, + 0x80f8_64c6_bbd0_c16c, + 0x317a_eda0_114e_e9f0, + ]), + pallas::Base::from_raw([ + 0xcda6_4589_4bfc_0455, + 0xf9fc_3a76_ac88_3c1f, + 0x61f6_a208_798d_28ec, + 0x2c02_1be9_a8c2_9b49, + ]), + ), + ( + pallas::Base::from_raw([ + 0x717a_a137_f5c5_be89, + 0x9028_5359_3102_985b, + 0x4ffc_0b33_2c5a_0750, + 0x2494_2c9c_ed84_3a53, + ]), + pallas::Base::from_raw([ + 0xfbdb_198d_0cae_4fa7, + 0x301e_d666_339f_ae29, + 0xe8db_271b_351f_54c1, + 0x1ec6_b81d_e40c_48da, + ]), + ), + ( + pallas::Base::from_raw([ + 0x1fad_ffb9_7149_61e3, + 0xa176_3927_04e4_6feb, + 0x335d_89f8_2091_038d, + 0x0556_0da5_b68a_034a, + ]), + pallas::Base::from_raw([ + 0x7cbc_a061_15ef_3129, + 0x8e19_e08d_db9c_008d, + 0xa745_abb6_b7a1_36fb, + 0x2ccc_b707_6bde_5545, + ]), + ), + ( + pallas::Base::from_raw([ + 0x66ba_c59c_6f88_1b40, + 0xae9b_1f86_51c5_f96c, + 0x688e_7e00_9bdc_52e1, + 0x27e4_380e_842e_d733, + ]), + pallas::Base::from_raw([ + 0x1be8_47f3_618d_78db, + 0xcbf1_c63d_6c8b_f57f, + 0x62cb_3b48_a39f_91dc, + 0x0ced_7d2b_0b84_8552, + ]), + ), + ( + pallas::Base::from_raw([ + 0x2bc3_ed47_d3b1_9dae, + 0x7929_235c_2bdf_6880, + 0x4ec8_7166_4d23_deae, + 0x026a_bf29_d792_9647, + ]), + pallas::Base::from_raw([ + 0x8951_204b_5262_6b96, + 0x15ba_29c7_c672_fad2, + 0x0d49_9ba7_a480_134c, + 0x397c_dfb1_4d54_65ce, + ]), + ), +]; diff --git a/halo2_gadgets_optimized/src/utilities.rs b/halo2_gadgets_optimized/src/utilities.rs new file mode 100644 index 0000000000..739f4411de --- /dev/null +++ b/halo2_gadgets_optimized/src/utilities.rs @@ -0,0 +1,496 @@ +//! Utility gadgets. + +use ff::{Field, PrimeField, PrimeFieldBits}; +use halo2_proofs::{ + circuit::{AssignedCell, Cell, Layouter, Value}, + plonk::{Advice, Column, Error, Expression}, +}; + +use std::marker::PhantomData; +use std::ops::Range; + +pub mod cond_swap; +pub mod decompose_running_sum; +pub mod lookup_range_check; + +/// A type that has a value at either keygen or proving time. +pub trait FieldValue { + /// Returns the value of this type. + fn value(&self) -> Value<&F>; +} + +impl FieldValue for Value { + fn value(&self) -> Value<&F> { + self.as_ref() + } +} + +impl FieldValue for AssignedCell { + fn value(&self) -> Value<&F> { + self.value() + } +} + +/// Trait for a variable in the circuit. +pub trait Var: Clone + std::fmt::Debug + From> { + /// The cell at which this variable was allocated. + fn cell(&self) -> Cell; + + /// The value allocated to this variable. + fn value(&self) -> Value; +} + +impl Var for AssignedCell { + fn cell(&self) -> Cell { + self.cell() + } + + fn value(&self) -> Value { + self.value().cloned() + } +} + +/// Trait for utilities used across circuits. +pub trait UtilitiesInstructions { + /// Variable in the circuit. + type Var: Var; + + /// Load a variable. + fn load_private( + &self, + mut layouter: impl Layouter, + column: Column, + value: Value, + ) -> Result { + layouter.assign_region( + || "load private", + |mut region| { + region + .assign_advice(|| "load private", column, 0, || value) + .map(Self::Var::from) + }, + ) + } +} + +/// A type representing a range-constrained field element. +#[derive(Clone, Copy, Debug)] +pub struct RangeConstrained> { + inner: T, + num_bits: usize, + _phantom: PhantomData, +} + +impl> RangeConstrained { + /// Returns the range-constrained inner type. + pub fn inner(&self) -> &T { + &self.inner + } + + /// Returns the number of bits to which this cell is constrained. + pub fn num_bits(&self) -> usize { + self.num_bits + } +} + +impl RangeConstrained> { + /// Constructs a `RangeConstrained>` as a bitrange of the given value. + pub fn bitrange_of(value: Value<&F>, bitrange: Range) -> Self { + let num_bits = bitrange.len(); + Self { + inner: value.map(|value| bitrange_subset(value, bitrange)), + num_bits, + _phantom: PhantomData::default(), + } + } +} + +impl RangeConstrained> { + /// Constructs a `RangeConstrained>` without verifying that the + /// cell is correctly range constrained. + /// + /// This API only exists to ease with integrating this type into existing circuits, + /// and will likely be removed in future. + pub fn unsound_unchecked(cell: AssignedCell, num_bits: usize) -> Self { + Self { + inner: cell, + num_bits, + _phantom: PhantomData::default(), + } + } + + /// Extracts the range-constrained value from this range-constrained cell. + pub fn value(&self) -> RangeConstrained> { + RangeConstrained { + inner: self.inner.value().copied(), + num_bits: self.num_bits, + _phantom: PhantomData::default(), + } + } +} + +/// Checks that an expression is either 1 or 0. +pub fn bool_check(value: Expression) -> Expression { + range_check(value, 2) +} + +/// If `a` then `b`, else `c`. Returns (a * b) + (1 - a) * c. +/// +/// `a` must be a boolean-constrained expression. +pub fn ternary(a: Expression, b: Expression, c: Expression) -> Expression { + let one_minus_a = Expression::Constant(F::ONE) - a.clone(); + a * b + one_minus_a * c +} + +/// Takes a specified subsequence of the little-endian bit representation of a field element. +/// The bits are numbered from 0 for the LSB. +pub fn bitrange_subset(field_elem: &F, bitrange: Range) -> F { + // We can allow a subsequence of length NUM_BITS, because + // field_elem.to_le_bits() returns canonical bitstrings. + assert!(bitrange.end <= F::NUM_BITS as usize); + + field_elem + .to_le_bits() + .iter() + .by_vals() + .skip(bitrange.start) + .take(bitrange.end - bitrange.start) + .rev() + .fold(F::ZERO, |acc, bit| { + if bit { + acc.double() + F::ONE + } else { + acc.double() + } + }) +} + +/// Check that an expression is in the small range [0..range), +/// i.e. 0 ≤ word < range. +pub fn range_check(word: Expression, range: usize) -> Expression { + (1..range).fold(word.clone(), |acc, i| { + acc * (Expression::Constant(F::from(i as u64)) - word.clone()) + }) +} + +/// Decompose a word `alpha` into `window_num_bits` bits (little-endian) +/// For a window size of `w`, this returns [k_0, ..., k_n] where each `k_i` +/// is a `w`-bit value, and `scalar = k_0 + k_1 * w + k_n * w^n`. +/// +/// # Panics +/// +/// We are returning a `Vec` which means the window size is limited to +/// <= 8 bits. +pub fn decompose_word( + word: &F, + word_num_bits: usize, + window_num_bits: usize, +) -> Vec { + assert!(window_num_bits <= 8); + + // Pad bits to multiple of window_num_bits + let padding = (window_num_bits - (word_num_bits % window_num_bits)) % window_num_bits; + let bits: Vec = word + .to_le_bits() + .into_iter() + .take(word_num_bits) + .chain(std::iter::repeat(false).take(padding)) + .collect(); + assert_eq!(bits.len(), word_num_bits + padding); + + bits.chunks_exact(window_num_bits) + .map(|chunk| chunk.iter().rev().fold(0, |acc, b| (acc << 1) + (*b as u8))) + .collect() +} + +/// The u64 integer represented by an L-bit little-endian bitstring. +/// +/// # Panics +/// +/// Panics if the bitstring is longer than 64 bits. +pub fn lebs2ip(bits: &[bool; L]) -> u64 { + assert!(L <= 64); + bits.iter() + .enumerate() + .fold(0u64, |acc, (i, b)| acc + if *b { 1 << i } else { 0 }) +} + +/// The sequence of bits representing a u64 in little-endian order. +/// +/// # Panics +/// +/// Panics if the expected length of the sequence `NUM_BITS` exceeds +/// 64. +pub fn i2lebsp(int: u64) -> [bool; NUM_BITS] { + /// Takes in an FnMut closure and returns a constant-length array with elements of + /// type `Output`. + fn gen_const_array( + closure: impl FnMut(usize) -> Output, + ) -> [Output; LEN] { + let mut ret: [Output; LEN] = [Default::default(); LEN]; + for (bit, val) in ret.iter_mut().zip((0..LEN).map(closure)) { + *bit = val; + } + ret + } + assert!(NUM_BITS <= 64); + gen_const_array(|mask: usize| (int & (1 << mask)) != 0) +} + +#[cfg(test)] +mod tests { + use super::*; + use group::ff::{Field, FromUniformBytes, PrimeField}; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + dev::{FailureLocation, MockProver, VerifyFailure}, + plonk::{Any, Circuit, ConstraintSystem, Constraints, Error, Selector}, + poly::Rotation, + }; + use pasta_curves::pallas; + use proptest::prelude::*; + use rand::rngs::OsRng; + use std::convert::TryInto; + use std::iter; + use uint::construct_uint; + + #[test] + fn test_range_check() { + struct MyCircuit(u8); + + impl UtilitiesInstructions for MyCircuit { + type Var = AssignedCell; + } + + #[derive(Clone)] + struct Config { + selector: Selector, + advice: Column, + } + + impl Circuit for MyCircuit { + type Config = Config; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + MyCircuit(self.0) + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let selector = meta.selector(); + let advice = meta.advice_column(); + + meta.create_gate("range check", |meta| { + let selector = meta.query_selector(selector); + let advice = meta.query_advice(advice, Rotation::cur()); + + Constraints::with_selector(selector, Some(range_check(advice, RANGE))) + }); + + Config { selector, advice } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "range constrain", + |mut region| { + config.selector.enable(&mut region, 0)?; + region.assign_advice( + || format!("witness {}", self.0), + config.advice, + 0, + || Value::known(pallas::Base::from(self.0 as u64)), + )?; + + Ok(()) + }, + ) + } + } + + for i in 0..8 { + let circuit: MyCircuit<8> = MyCircuit(i); + let prover = MockProver::::run(3, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + } + + { + let circuit: MyCircuit<8> = MyCircuit(8); + let prover = MockProver::::run(3, &circuit, vec![]).unwrap(); + assert_eq!( + prover.verify(), + Err(vec![VerifyFailure::ConstraintNotSatisfied { + constraint: ((0, "range check").into(), 0, "").into(), + location: FailureLocation::InRegion { + region: (0, "range constrain").into(), + offset: 0, + }, + cell_values: vec![(((Any::Advice, 0).into(), 0).into(), "0x8".to_string())], + }]) + ); + } + } + + #[allow(clippy::assign_op_pattern)] + #[allow(clippy::ptr_offset_with_cast)] + #[test] + fn test_bitrange_subset() { + let rng = OsRng; + + construct_uint! { + struct U256(4); + } + + // Subset full range. + { + let field_elem = pallas::Base::random(rng); + let bitrange = 0..(pallas::Base::NUM_BITS as usize); + let subset = bitrange_subset(&field_elem, bitrange); + assert_eq!(field_elem, subset); + } + + // Subset zero bits + { + let field_elem = pallas::Base::random(rng); + let bitrange = 0..0; + let subset = bitrange_subset(&field_elem, bitrange); + assert_eq!(pallas::Base::ZERO, subset); + } + + // Closure to decompose field element into pieces using consecutive ranges, + // and check that we recover the original. + let decompose = |field_elem: pallas::Base, ranges: &[Range]| { + assert_eq!( + ranges.iter().map(|range| range.len()).sum::(), + pallas::Base::NUM_BITS as usize + ); + assert_eq!(ranges[0].start, 0); + assert_eq!(ranges.last().unwrap().end, pallas::Base::NUM_BITS as usize); + + // Check ranges are contiguous + #[allow(unused_assignments)] + { + let mut ranges = ranges.iter(); + let mut range = ranges.next().unwrap(); + if let Some(next_range) = ranges.next() { + assert_eq!(range.end, next_range.start); + range = next_range; + } + } + + let subsets = ranges + .iter() + .map(|range| bitrange_subset(&field_elem, range.clone())) + .collect::>(); + + let mut sum = subsets[0]; + let mut num_bits = 0; + for (idx, subset) in subsets.iter().skip(1).enumerate() { + // 2^num_bits + let range_shift: [u8; 32] = { + num_bits += ranges[idx].len(); + let mut range_shift = [0u8; 32]; + U256([2, 0, 0, 0]) + .pow(U256([num_bits as u64, 0, 0, 0])) + .to_little_endian(&mut range_shift); + range_shift + }; + sum += subset * pallas::Base::from_repr(range_shift).unwrap(); + } + assert_eq!(field_elem, sum); + }; + + decompose(pallas::Base::random(rng), &[0..255]); + decompose(pallas::Base::random(rng), &[0..1, 1..255]); + decompose(pallas::Base::random(rng), &[0..254, 254..255]); + decompose(pallas::Base::random(rng), &[0..127, 127..255]); + decompose(pallas::Base::random(rng), &[0..128, 128..255]); + decompose( + pallas::Base::random(rng), + &[0..50, 50..100, 100..150, 150..200, 200..255], + ); + } + + prop_compose! { + fn arb_scalar()(bytes in prop::array::uniform32(0u8..)) -> pallas::Scalar { + // Instead of rejecting out-of-range bytes, let's reduce them. + let mut buf = [0; 64]; + buf[..32].copy_from_slice(&bytes); + pallas::Scalar::from_uniform_bytes(&buf) + } + } + + proptest! { + #[test] + fn test_decompose_word( + scalar in arb_scalar(), + window_num_bits in 1u8..9 + ) { + // Get decomposition into `window_num_bits` bits + let decomposed = decompose_word(&scalar, pallas::Scalar::NUM_BITS as usize, window_num_bits as usize); + + // Flatten bits + let bits = decomposed + .iter() + .flat_map(|window| (0..window_num_bits).map(move |mask| (window & (1 << mask)) != 0)); + + // Ensure this decomposition contains 256 or fewer set bits. + assert!(!bits.clone().skip(32*8).any(|b| b)); + + // Pad or truncate bits to 32 bytes + let bits: Vec = bits.chain(iter::repeat(false)).take(32*8).collect(); + + let bytes: Vec = bits.chunks_exact(8).map(|chunk| chunk.iter().rev().fold(0, |acc, b| (acc << 1) + (*b as u8))).collect(); + + // Check that original scalar is recovered from decomposition + assert_eq!(scalar, pallas::Scalar::from_repr(bytes.try_into().unwrap()).unwrap()); + } + } + + #[test] + fn lebs2ip_round_trip() { + use rand::rngs::OsRng; + + let mut rng = OsRng; + { + let int = rng.next_u64(); + assert_eq!(lebs2ip::<64>(&i2lebsp(int)), int); + } + + assert_eq!(lebs2ip::<64>(&i2lebsp(0)), 0); + assert_eq!( + lebs2ip::<64>(&i2lebsp(0xFFFFFFFFFFFFFFFF)), + 0xFFFFFFFFFFFFFFFF + ); + } + + #[test] + fn i2lebsp_round_trip() { + { + let bitstring = (0..64).map(|_| rand::random()).collect::>(); + assert_eq!( + i2lebsp::<64>(lebs2ip::<64>(&bitstring.clone().try_into().unwrap())).to_vec(), + bitstring + ); + } + + { + let bitstring = [false; 64]; + assert_eq!(i2lebsp(lebs2ip(&bitstring)), bitstring); + } + + { + let bitstring = [true; 64]; + assert_eq!(i2lebsp(lebs2ip(&bitstring)), bitstring); + } + + { + let bitstring = []; + assert_eq!(i2lebsp(lebs2ip(&bitstring)), bitstring); + } + } +} diff --git a/halo2_gadgets_optimized/src/utilities/cond_swap.rs b/halo2_gadgets_optimized/src/utilities/cond_swap.rs new file mode 100644 index 0000000000..6222fcab63 --- /dev/null +++ b/halo2_gadgets_optimized/src/utilities/cond_swap.rs @@ -0,0 +1,624 @@ +//! Gadget and chip for a conditional swap utility. + +use super::{bool_check, ternary, UtilitiesInstructions}; + +use crate::ecc::chip::{EccPoint, NonIdentityEccPoint}; +use group::ff::{Field, PrimeField}; +use halo2_proofs::{ + circuit::{AssignedCell, Chip, Layouter, Value}, + plonk::{self, Advice, Column, ConstraintSystem, Constraints, Error, Selector}, + poly::Rotation, +}; +use pasta_curves::pallas; +use std::marker::PhantomData; + +/// Instructions for a conditional swap gadget. +pub trait CondSwapInstructions: UtilitiesInstructions { + #[allow(clippy::type_complexity)] + /// Given an input pair (a,b) and a `swap` boolean flag, returns + /// (b,a) if `swap` is set, else (a,b) if `swap` is not set. + /// + /// The second element of the pair is required to be a witnessed + /// value, not a variable that already exists in the circuit. + fn swap( + &self, + layouter: impl Layouter, + pair: (Self::Var, Value), + swap: Value, + ) -> Result<(Self::Var, Self::Var), Error>; + + /// Given an input `(choice, left, right)` where `choice` is a boolean flag, + /// returns `left` if `choice` is not set and `right` if `choice` is set. + fn mux( + &self, + layouter: &mut impl Layouter, + choice: Self::Var, + left: Self::Var, + right: Self::Var, + ) -> Result; +} + +/// A chip implementing a conditional swap. +#[derive(Clone, Debug)] +pub struct CondSwapChip { + config: CondSwapConfig, + _marker: PhantomData, +} + +impl Chip for CondSwapChip { + type Config = CondSwapConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} + +/// Configuration for the [`CondSwapChip`]. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CondSwapConfig { + q_swap: Selector, + a: Column, + b: Column, + a_swapped: Column, + b_swapped: Column, + swap: Column, +} + +#[cfg(test)] +impl CondSwapConfig { + pub(crate) fn a(&self) -> Column { + self.a + } +} + +impl UtilitiesInstructions for CondSwapChip { + type Var = AssignedCell; +} + +impl CondSwapInstructions for CondSwapChip { + #[allow(clippy::type_complexity)] + fn swap( + &self, + mut layouter: impl Layouter, + pair: (Self::Var, Value), + swap: Value, + ) -> Result<(Self::Var, Self::Var), Error> { + let config = self.config(); + + layouter.assign_region( + || "swap", + |mut region| { + // Enable `q_swap` selector + config.q_swap.enable(&mut region, 0)?; + + // Copy in `a` value + let a = pair.0.copy_advice(|| "copy a", &mut region, config.a, 0)?; + + // Witness `b` value + let b = region.assign_advice(|| "witness b", config.b, 0, || pair.1)?; + + // Witness `swap` value + let swap_val = swap.map(|swap| F::from(swap as u64)); + region.assign_advice(|| "swap", config.swap, 0, || swap_val)?; + + // Conditionally swap a + let a_swapped = { + let a_swapped = a + .value() + .zip(b.value()) + .zip(swap) + .map(|((a, b), swap)| if swap { b } else { a }) + .cloned(); + region.assign_advice(|| "a_swapped", config.a_swapped, 0, || a_swapped)? + }; + + // Conditionally swap b + let b_swapped = { + let b_swapped = a + .value() + .zip(b.value()) + .zip(swap) + .map(|((a, b), swap)| if swap { a } else { b }) + .cloned(); + region.assign_advice(|| "b_swapped", config.b_swapped, 0, || b_swapped)? + }; + + // Return swapped pair + Ok((a_swapped, b_swapped)) + }, + ) + } + + fn mux( + &self, + layouter: &mut impl Layouter, + choice: Self::Var, + left: Self::Var, + right: Self::Var, + ) -> Result { + let config = self.config(); + + layouter.assign_region( + || "mux", + |mut region| { + // Enable `q_swap` selector + config.q_swap.enable(&mut region, 0)?; + + // Copy in `a` value + let left = left.copy_advice(|| "copy left", &mut region, config.a, 0)?; + + // Copy in `b` value + let right = right.copy_advice(|| "copy right", &mut region, config.b, 0)?; + + // Copy `choice` value + let choice = choice.copy_advice(|| "copy choice", &mut region, config.swap, 0)?; + + let a_swapped = left + .value() + .zip(right.value()) + .zip(choice.value()) + .map(|((left, right), choice)| { + if *choice == F::from(0_u64) { + left + } else { + right + } + }) + .cloned(); + let b_swapped = left + .value() + .zip(right.value()) + .zip(choice.value()) + .map(|((left, right), choice)| { + if *choice == F::from(0_u64) { + right + } else { + left + } + }) + .cloned(); + + region.assign_advice(|| "out b_swap", self.config.b_swapped, 0, || b_swapped)?; + region.assign_advice(|| "out a_swap", self.config.a_swapped, 0, || a_swapped) + }, + ) + } +} + +impl CondSwapChip { + /// Given an input `(choice, left, right)` where `choice` is a boolean flag and `left` and `right` are `EccPoint`, + /// returns `left` if `choice` is not set and `right` if `choice` is set. + pub fn mux_on_points( + &self, + mut layouter: impl Layouter, + choice: &AssignedCell, + left: &EccPoint, + right: &EccPoint, + ) -> Result { + let x_cell = self.mux(&mut layouter, choice.clone(), left.x(), right.x())?; + let y_cell = self.mux(&mut layouter, choice.clone(), left.y(), right.y())?; + Ok(EccPoint::from_coordinates_unchecked( + x_cell.into(), + y_cell.into(), + )) + } + + /// Given an input `(choice, left, right)` where `choice` is a boolean flag and `left` and `right` are + /// `NonIdentityEccPoint`, returns `left` if `choice` is not set and `right` if `choice` is set. + pub fn mux_on_non_identity_points( + &self, + mut layouter: impl Layouter, + choice: &AssignedCell, + left: &NonIdentityEccPoint, + right: &NonIdentityEccPoint, + ) -> Result { + let x_cell = self.mux(&mut layouter, choice.clone(), left.x(), right.x())?; + let y_cell = self.mux(&mut layouter, choice.clone(), left.y(), right.y())?; + Ok(NonIdentityEccPoint::from_coordinates_unchecked( + x_cell.into(), + y_cell.into(), + )) + } +} + +impl CondSwapChip { + /// Configures this chip for use in a circuit. + /// + /// # Side-effects + /// + /// `advices[0]` will be equality-enabled. + pub fn configure( + meta: &mut ConstraintSystem, + advices: [Column; 5], + ) -> CondSwapConfig { + let a = advices[0]; + // Only column a is used in an equality constraint directly by this chip. + meta.enable_equality(a); + + let q_swap = meta.selector(); + + let config = CondSwapConfig { + q_swap, + a, + b: advices[1], + a_swapped: advices[2], + b_swapped: advices[3], + swap: advices[4], + }; + + // TODO: optimise shape of gate for Merkle path validation + + meta.create_gate("a' = b ⋅ swap + a ⋅ (1-swap)", |meta| { + let q_swap = meta.query_selector(q_swap); + + let a = meta.query_advice(config.a, Rotation::cur()); + let b = meta.query_advice(config.b, Rotation::cur()); + let a_swapped = meta.query_advice(config.a_swapped, Rotation::cur()); + let b_swapped = meta.query_advice(config.b_swapped, Rotation::cur()); + let swap = meta.query_advice(config.swap, Rotation::cur()); + + // This checks that `a_swapped` is equal to `b` when `swap` is set, + // but remains as `a` when `swap` is not set. + let a_check = a_swapped - ternary(swap.clone(), b.clone(), a.clone()); + + // This checks that `b_swapped` is equal to `a` when `swap` is set, + // but remains as `b` when `swap` is not set. + let b_check = b_swapped - ternary(swap.clone(), a, b); + + // Check `swap` is boolean. + let bool_check = bool_check(swap); + + Constraints::with_selector( + q_swap, + [ + ("a check", a_check), + ("b check", b_check), + ("swap is bool", bool_check), + ], + ) + }); + + config + } + + /// Constructs a [`CondSwapChip`] given a [`CondSwapConfig`]. + pub fn construct(config: CondSwapConfig) -> Self { + CondSwapChip { + config, + _marker: PhantomData, + } + } +} + +#[cfg(test)] +mod tests { + use super::super::UtilitiesInstructions; + use super::{CondSwapChip, CondSwapConfig, CondSwapInstructions}; + use group::ff::{Field, PrimeField}; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + dev::MockProver, + plonk::{Circuit, ConstraintSystem, Error}, + }; + use pasta_curves::pallas::Base; + use rand::rngs::OsRng; + + #[test] + fn cond_swap() { + #[derive(Default)] + struct MyCircuit { + a: Value, + b: Value, + swap: Value, + } + + impl Circuit for MyCircuit { + type Config = CondSwapConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let advices = [ + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + ]; + + CondSwapChip::::configure(meta, advices) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chip = CondSwapChip::::construct(config.clone()); + + // Load the pair and the swap flag into the circuit. + let a = chip.load_private(layouter.namespace(|| "a"), config.a, self.a)?; + // Return the swapped pair. + let swapped_pair = chip.swap( + layouter.namespace(|| "swap"), + (a.clone(), self.b), + self.swap, + )?; + + self.swap + .zip(a.value().zip(self.b.as_ref())) + .zip(swapped_pair.0.value().zip(swapped_pair.1.value())) + .assert_if_known(|((swap, (a, b)), (a_swapped, b_swapped))| { + if *swap { + // Check that `a` and `b` have been swapped + (a_swapped == b) && (b_swapped == a) + } else { + // Check that `a` and `b` have not been swapped + (a_swapped == a) && (b_swapped == b) + } + }); + + Ok(()) + } + } + + let rng = OsRng; + + // Test swap case + { + let circuit: MyCircuit = MyCircuit { + a: Value::known(Base::random(rng)), + b: Value::known(Base::random(rng)), + swap: Value::known(true), + }; + let prover = MockProver::::run(3, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + } + + // Test non-swap case + { + let circuit: MyCircuit = MyCircuit { + a: Value::known(Base::random(rng)), + b: Value::known(Base::random(rng)), + swap: Value::known(false), + }; + let prover = MockProver::::run(3, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + } + } + + #[test] + fn test_mux() { + use crate::{ + ecc::{ + chip::{EccChip, EccConfig}, + tests::TestFixedBases, + NonIdentityPoint, Point, + }, + utilities::lookup_range_check::LookupRangeCheckConfig, + }; + + use group::{cofactor::CofactorCurveAffine, Curve, Group}; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + dev::MockProver, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance}, + }; + use pasta_curves::arithmetic::CurveAffine; + use pasta_curves::{pallas, EpAffine}; + + use rand::rngs::OsRng; + + #[derive(Clone, Debug)] + pub struct MyConfig { + primary: Column, + advice: Column, + cond_swap_config: CondSwapConfig, + ecc_config: EccConfig, + } + + #[derive(Default)] + struct MyCircuit { + left_point: Value, + right_point: Value, + choice: Value, + } + + impl Circuit for MyCircuit { + type Config = MyConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let advices = [ + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + ]; + + for advice in advices.iter() { + meta.enable_equality(*advice); + } + + // Instance column used for public inputs + let primary = meta.instance_column(); + meta.enable_equality(primary); + + let cond_swap_config = + CondSwapChip::configure(meta, advices[0..5].try_into().unwrap()); + + let table_idx = meta.lookup_table_column(); + let table_range_check_tag = meta.lookup_table_column(); + + let lagrange_coeffs = [ + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + ]; + meta.enable_constant(lagrange_coeffs[0]); + + let range_check = LookupRangeCheckConfig::configure( + meta, + advices[9], + table_idx, + table_range_check_tag, + ); + + let ecc_config = EccChip::::configure( + meta, + advices, + lagrange_coeffs, + range_check, + ); + + MyConfig { + primary, + advice: advices[0], + cond_swap_config, + ecc_config, + } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // Construct a CondSwap chip + let cond_swap_chip = CondSwapChip::construct(config.cond_swap_config); + + // Construct an ECC chip + let ecc_chip = EccChip::construct(config.ecc_config); + + // Assign choice + let choice = layouter.assign_region( + || "load private", + |mut region| { + region.assign_advice(|| "load private", config.advice, 0, || self.choice) + }, + )?; + + // Test mux on non identity points + // Assign left point + let left_non_identity_point = NonIdentityPoint::new( + ecc_chip.clone(), + layouter.namespace(|| "left point"), + self.left_point.map(|left_point| left_point), + )?; + + // Assign right point + let right_non_identity_point = NonIdentityPoint::new( + ecc_chip.clone(), + layouter.namespace(|| "right point"), + self.right_point.map(|right_point| right_point), + )?; + + // Apply mux + let result_non_identity_point = cond_swap_chip.mux_on_non_identity_points( + layouter.namespace(|| "MUX"), + &choice, + left_non_identity_point.inner(), + right_non_identity_point.inner(), + )?; + + // Check equality with instance + layouter.constrain_instance( + result_non_identity_point.x().cell(), + config.primary, + 0, + )?; + layouter.constrain_instance( + result_non_identity_point.y().cell(), + config.primary, + 1, + )?; + + // Test mux on points + // Assign left point + let left_point = Point::new( + ecc_chip.clone(), + layouter.namespace(|| "left point"), + self.left_point.map(|left_point| left_point), + )?; + + // Assign right point + let right_point = Point::new( + ecc_chip, + layouter.namespace(|| "right point"), + self.right_point.map(|right_point| right_point), + )?; + + // Apply mux + let result = cond_swap_chip.mux_on_points( + layouter.namespace(|| "MUX"), + &choice, + left_point.inner(), + right_point.inner(), + )?; + + // Check equality with instance + layouter.constrain_instance(result.x().cell(), config.primary, 0)?; + layouter.constrain_instance(result.y().cell(), config.primary, 1) + } + } + + // Test different circuits + let mut circuits = vec![]; + let mut instances = vec![]; + for choice in [false, true] { + let choice_value = if choice { + pallas::Base::one() + } else { + pallas::Base::zero() + }; + let left_point = pallas::Point::random(OsRng).to_affine(); + let right_point = pallas::Point::random(OsRng).to_affine(); + circuits.push(MyCircuit { + left_point: Value::known(left_point), + right_point: Value::known(right_point), + choice: Value::known(choice_value), + }); + let expected_output = if choice { right_point } else { left_point }; + let (expected_x, expected_y) = if bool::from(expected_output.is_identity()) { + (pallas::Base::zero(), pallas::Base::zero()) + } else { + let coords = expected_output.coordinates().unwrap(); + (*coords.x(), *coords.y()) + }; + instances.push([[expected_x, expected_y]]); + } + + for (circuit, instance) in circuits.iter().zip(instances.iter()) { + let prover = MockProver::::run( + 5, + circuit, + instance.iter().map(|p| p.to_vec()).collect(), + ) + .unwrap(); + assert_eq!(prover.verify(), Ok(())); + } + } +} \ No newline at end of file diff --git a/halo2_gadgets_optimized/src/utilities/decompose_running_sum.rs b/halo2_gadgets_optimized/src/utilities/decompose_running_sum.rs new file mode 100644 index 0000000000..5a83e4175f --- /dev/null +++ b/halo2_gadgets_optimized/src/utilities/decompose_running_sum.rs @@ -0,0 +1,389 @@ +//! Decomposes an $n$-bit field element $\alpha$ into $W$ windows, each window +//! being a $K$-bit word, using a running sum $z$. +//! We constrain $K \leq 3$ for this helper. +//! $$\alpha = k_0 + (2^K) k_1 + (2^{2K}) k_2 + ... + (2^{(W-1)K}) k_{W-1}$$ +//! +//! $z_0$ is initialized as $\alpha$. Each successive $z_{i+1}$ is computed as +//! $$z_{i+1} = (z_{i} - k_i) / (2^K).$$ +//! $z_W$ is constrained to be zero. +//! The difference between each interstitial running sum output is constrained +//! to be $K$ bits, i.e. +//! `range_check`($k_i$, $2^K$), +//! where +//! ```text +//! range_check(word, range) +//! = word * (1 - word) * (2 - word) * ... * ((range - 1) - word) +//! ``` +//! +//! Given that the `range_check` constraint will be toggled by a selector, in +//! practice we will have a `selector * range_check(word, range)` expression +//! of degree `range + 1`. +//! +//! This means that $2^K$ has to be at most `degree_bound - 1` in order for +//! the range check constraint to stay within the degree bound. + +use ff::PrimeFieldBits; +use halo2_proofs::{ + circuit::{AssignedCell, Region, Value}, + plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, + poly::Rotation, +}; + +use super::range_check; + +use std::marker::PhantomData; + +/// The running sum $[z_0, ..., z_W]$. If created in strict mode, $z_W = 0$. +#[derive(Debug)] +pub struct RunningSum(Vec>); +impl std::ops::Deref for RunningSum { + type Target = Vec>; + + fn deref(&self) -> &Vec> { + &self.0 + } +} + +/// Configuration that provides methods for running sum decomposition. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct RunningSumConfig { + q_range_check: Selector, + z: Column, + _marker: PhantomData, +} + +impl RunningSumConfig { + /// Returns the q_range_check selector of this [`RunningSumConfig`]. + pub(crate) fn q_range_check(&self) -> Selector { + self.q_range_check + } + + /// `perm` MUST include the advice column `z`. + /// + /// # Panics + /// + /// Panics if WINDOW_NUM_BITS > 3. + /// + /// # Side-effects + /// + /// `z` will be equality-enabled. + pub fn configure( + meta: &mut ConstraintSystem, + q_range_check: Selector, + z: Column, + ) -> Self { + assert!(WINDOW_NUM_BITS <= 3); + + meta.enable_equality(z); + + let config = Self { + q_range_check, + z, + _marker: PhantomData, + }; + + // https://p.z.cash/halo2-0.1:decompose-short-range + meta.create_gate("range check", |meta| { + let q_range_check = meta.query_selector(config.q_range_check); + let z_cur = meta.query_advice(config.z, Rotation::cur()); + let z_next = meta.query_advice(config.z, Rotation::next()); + // z_i = 2^{K}⋅z_{i + 1} + k_i + // => k_i = z_i - 2^{K}⋅z_{i + 1} + let word = z_cur - z_next * F::from(1 << WINDOW_NUM_BITS); + + Constraints::with_selector(q_range_check, Some(range_check(word, 1 << WINDOW_NUM_BITS))) + }); + + config + } + + /// Decompose a field element alpha that is witnessed in this helper. + /// + /// `strict` = true constrains the final running sum to be zero, i.e. + /// constrains alpha to be within WINDOW_NUM_BITS * num_windows bits. + pub fn witness_decompose( + &self, + region: &mut Region<'_, F>, + offset: usize, + alpha: Value, + strict: bool, + word_num_bits: usize, + num_windows: usize, + ) -> Result, Error> { + let z_0 = region.assign_advice(|| "z_0 = alpha", self.z, offset, || alpha)?; + self.decompose(region, offset, z_0, strict, word_num_bits, num_windows) + } + + /// Decompose an existing variable alpha that is copied into this helper. + /// + /// `strict` = true constrains the final running sum to be zero, i.e. + /// constrains alpha to be within WINDOW_NUM_BITS * num_windows bits. + pub fn copy_decompose( + &self, + region: &mut Region<'_, F>, + offset: usize, + alpha: AssignedCell, + strict: bool, + word_num_bits: usize, + num_windows: usize, + ) -> Result, Error> { + let z_0 = alpha.copy_advice(|| "copy z_0 = alpha", region, self.z, offset)?; + self.decompose(region, offset, z_0, strict, word_num_bits, num_windows) + } + + /// `z_0` must be the cell at `(self.z, offset)` in `region`. + /// + /// # Panics + /// + /// Panics if there are too many windows for the given word size. + fn decompose( + &self, + region: &mut Region<'_, F>, + offset: usize, + z_0: AssignedCell, + strict: bool, + word_num_bits: usize, + num_windows: usize, + ) -> Result, Error> { + // Make sure that we do not have more windows than required for the number + // of bits in the word. In other words, every window must contain at least + // one bit of the word (no empty windows). + // + // For example, let: + // - word_num_bits = 64 + // - WINDOW_NUM_BITS = 3 + // In this case, the maximum allowed num_windows is 22: + // 3 * 22 < 64 + 3 + // + assert!(WINDOW_NUM_BITS * num_windows < word_num_bits + WINDOW_NUM_BITS); + + // Enable selectors + for idx in 0..num_windows { + self.q_range_check.enable(region, offset + idx)?; + } + + // Decompose base field element into K-bit words. + let words = z_0 + .value() + .map(|word| super::decompose_word::(word, word_num_bits, WINDOW_NUM_BITS)) + .transpose_vec(num_windows); + + // Initialize empty vector to store running sum values [z_0, ..., z_W]. + let mut zs: Vec> = vec![z_0.clone()]; + let mut z = z_0; + + // Assign running sum `z_{i+1}` = (z_i - k_i) / (2^K) for i = 0..=n-1. + // Outside of this helper, z_0 = alpha must have already been loaded into the + // `z` column at `offset`. + let two_pow_k_inv = Value::known(F::from(1 << WINDOW_NUM_BITS as u64).invert().unwrap()); + for (i, word) in words.iter().enumerate() { + // z_next = (z_cur - word) / (2^K) + let z_next = { + let z_cur_val = z.value().copied(); + let word = word.map(|word| F::from(word as u64)); + let z_next_val = (z_cur_val - word) * two_pow_k_inv; + region.assign_advice( + || format!("z_{:?}", i + 1), + self.z, + offset + i + 1, + || z_next_val, + )? + }; + + // Update `z`. + z = z_next; + zs.push(z.clone()); + } + assert_eq!(zs.len(), num_windows + 1); + + if strict { + // Constrain the final running sum output to be zero. + region.constrain_constant(zs.last().unwrap().cell(), F::ZERO)?; + } + + Ok(RunningSum(zs)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use group::ff::{Field, PrimeField}; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + dev::{FailureLocation, MockProver, VerifyFailure}, + plonk::{Any, Circuit, ConstraintSystem, Error}, + }; + use pasta_curves::pallas; + use rand::rngs::OsRng; + + use crate::ecc::chip::{ + FIXED_BASE_WINDOW_SIZE, L_SCALAR_SHORT as L_SHORT, NUM_WINDOWS, NUM_WINDOWS_SHORT, + }; + + const L_BASE: usize = pallas::Base::NUM_BITS as usize; + + #[test] + fn test_running_sum() { + struct MyCircuit< + F: PrimeFieldBits, + const WORD_NUM_BITS: usize, + const WINDOW_NUM_BITS: usize, + const NUM_WINDOWS: usize, + > { + alpha: Value, + strict: bool, + } + + impl< + F: PrimeFieldBits, + const WORD_NUM_BITS: usize, + const WINDOW_NUM_BITS: usize, + const NUM_WINDOWS: usize, + > Circuit for MyCircuit + { + type Config = RunningSumConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self { + alpha: Value::unknown(), + strict: self.strict, + } + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let z = meta.advice_column(); + let q_range_check = meta.selector(); + let constants = meta.fixed_column(); + meta.enable_constant(constants); + + RunningSumConfig::::configure(meta, q_range_check, z) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "decompose", + |mut region| { + let offset = 0; + let zs = config.witness_decompose( + &mut region, + offset, + self.alpha, + self.strict, + WORD_NUM_BITS, + NUM_WINDOWS, + )?; + let alpha = zs[0].clone(); + + let offset = offset + NUM_WINDOWS + 1; + + config.copy_decompose( + &mut region, + offset, + alpha, + self.strict, + WORD_NUM_BITS, + NUM_WINDOWS, + )?; + + Ok(()) + }, + ) + } + } + + // Random base field element + { + let alpha = pallas::Base::random(OsRng); + + // Strict full decomposition should pass. + let circuit: MyCircuit = + MyCircuit { + alpha: Value::known(alpha), + strict: true, + }; + let prover = MockProver::::run(8, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + } + + // Random 64-bit word + { + let alpha = pallas::Base::from(rand::random::()); + + // Strict full decomposition should pass. + let circuit: MyCircuit< + pallas::Base, + L_SHORT, + FIXED_BASE_WINDOW_SIZE, + { NUM_WINDOWS_SHORT }, + > = MyCircuit { + alpha: Value::known(alpha), + strict: true, + }; + let prover = MockProver::::run(8, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + } + + // 2^66 + { + let alpha = pallas::Base::from_u128(1 << 66); + + // Strict partial decomposition should fail. + let circuit: MyCircuit< + pallas::Base, + L_SHORT, + FIXED_BASE_WINDOW_SIZE, + { NUM_WINDOWS_SHORT }, + > = MyCircuit { + alpha: Value::known(alpha), + strict: true, + }; + let prover = MockProver::::run(8, &circuit, vec![]).unwrap(); + assert_eq!( + prover.verify(), + Err(vec![ + VerifyFailure::Permutation { + column: (Any::Fixed, 0).into(), + location: FailureLocation::OutsideRegion { row: 0 }, + }, + VerifyFailure::Permutation { + column: (Any::Fixed, 0).into(), + location: FailureLocation::OutsideRegion { row: 1 }, + }, + VerifyFailure::Permutation { + column: (Any::Advice, 0).into(), + location: FailureLocation::InRegion { + region: (0, "decompose").into(), + offset: 22, + }, + }, + VerifyFailure::Permutation { + column: (Any::Advice, 0).into(), + location: FailureLocation::InRegion { + region: (0, "decompose").into(), + offset: 45, + }, + }, + ]) + ); + + // Non-strict partial decomposition should pass. + let circuit: MyCircuit< + pallas::Base, + { L_SHORT }, + FIXED_BASE_WINDOW_SIZE, + { NUM_WINDOWS_SHORT }, + > = MyCircuit { + alpha: Value::known(alpha), + strict: false, + }; + let prover = MockProver::::run(8, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + } + } +} diff --git a/halo2_gadgets_optimized/src/utilities/lookup_range_check.rs b/halo2_gadgets_optimized/src/utilities/lookup_range_check.rs new file mode 100644 index 0000000000..a2dddea4aa --- /dev/null +++ b/halo2_gadgets_optimized/src/utilities/lookup_range_check.rs @@ -0,0 +1,772 @@ +//! Make use of a K-bit lookup table to decompose a field element into K-bit +//! words. + +use halo2_proofs::{ + circuit::{AssignedCell, Layouter, Region}, + plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector, TableColumn}, + poly::Rotation, +}; +use std::{convert::TryInto, marker::PhantomData}; + +use ff::PrimeFieldBits; + +use super::*; + +/// The running sum $[z_0, ..., z_W]$. If created in strict mode, $z_W = 0$. +#[derive(Debug)] +pub struct RunningSum(Vec>); +impl std::ops::Deref for RunningSum { + type Target = Vec>; + + fn deref(&self) -> &Vec> { + &self.0 + } +} + +impl RangeConstrained> { + /// Witnesses a subset of the bits in `value` and constrains them to be the correct + /// number of bits. + /// + /// # Panics + /// + /// Panics if `bitrange.len() >= K`. + pub fn witness_short( + lookup_config: &LookupRangeCheckConfig, + layouter: impl Layouter, + value: Value<&F>, + bitrange: Range, + ) -> Result { + let num_bits = bitrange.len(); + assert!(num_bits < K); + + // Witness the subset and constrain it to be the correct number of bits. + lookup_config + .witness_short_check( + layouter, + value.map(|value| bitrange_subset(value, bitrange)), + num_bits, + ) + .map(|inner| Self { + inner, + num_bits, + _phantom: PhantomData::default(), + }) + } +} + +/// Configuration that provides methods for a lookup range check. +#[derive(Eq, PartialEq, Debug, Clone, Copy)] +pub struct LookupRangeCheckConfig { + q_lookup: Selector, + q_running: Selector, + q_bitshift: Selector, + q_range_check_4: Selector, + q_range_check_5: Selector, + running_sum: Column, + table_idx: TableColumn, + table_range_check_tag: TableColumn, + _marker: PhantomData, +} + +impl LookupRangeCheckConfig { + /// The `running_sum` advice column breaks the field element into `K`-bit + /// words. It is used to construct the input expression to the lookup + /// argument. + /// + /// The `table_idx` fixed column contains values from [0..2^K). Looking up + /// a value in `table_idx` constrains it to be within this range. The table + /// can be loaded outside this helper. + /// + /// # Side-effects + /// + /// Both the `running_sum` and `constants` columns will be equality-enabled. + pub fn configure( + meta: &mut ConstraintSystem, + running_sum: Column, + table_idx: TableColumn, + table_range_check_tag: TableColumn, + ) -> Self { + meta.enable_equality(running_sum); + + let q_lookup = meta.complex_selector(); + let q_running = meta.complex_selector(); + let q_bitshift = meta.selector(); + let q_range_check_4 = meta.complex_selector(); + let q_range_check_5 = meta.complex_selector(); + let config = LookupRangeCheckConfig { + q_lookup, + q_running, + q_bitshift, + q_range_check_4, + q_range_check_5, + running_sum, + table_idx, + table_range_check_tag, + _marker: PhantomData, + }; + + // https://p.z.cash/halo2-0.1:decompose-combined-lookup + meta.lookup(|meta| { + let q_lookup = meta.query_selector(config.q_lookup); + let q_running = meta.query_selector(config.q_running); + let q_range_check_4 = meta.query_selector(config.q_range_check_4); + let q_range_check_5 = meta.query_selector(config.q_range_check_5); + let z_cur = meta.query_advice(config.running_sum, Rotation::cur()); + let one = Expression::Constant(F::ONE); + + // In the case of a running sum decomposition, we recover the word from + // the difference of the running sums: + // z_i = 2^{K}⋅z_{i + 1} + a_i + // => a_i = z_i - 2^{K}⋅z_{i + 1} + let running_sum_lookup = { + let running_sum_word = { + let z_next = meta.query_advice(config.running_sum, Rotation::next()); + z_cur.clone() - z_next * F::from(1 << K) + }; + + q_running.clone() * running_sum_word + }; + + // In the short range check, the word is directly witnessed. + let short_lookup = { + let short_word = z_cur.clone(); + let q_short = one.clone() - q_running; + + q_short * short_word + }; + + // q_range_check is equal to + // - 1 if q_range_check_4 = 1 or q_range_check_5 = 1 + // - 0 otherwise + let q_range_check = one.clone() + - (one.clone() - q_range_check_4.clone()) * (one.clone() - q_range_check_5.clone()); + + // num_bits is equal to + // - 5 if q_range_check_5 = 1 + // - 4 if q_range_check_4 = 1 and q_range_check_5 = 0 + // - 0 otherwise + let num_bits = q_range_check_5.clone() * Expression::Constant(F::from(5_u64)) + + (one.clone() - q_range_check_5) + * q_range_check_4 + * Expression::Constant(F::from(4_u64)); + + // Combine the running sum, short lookups and optimized range checks: + vec![ + ( + q_lookup.clone() + * ((one - q_range_check.clone()) * (running_sum_lookup + short_lookup) + + q_range_check.clone() * z_cur), + config.table_idx, + ), + ( + q_lookup * q_range_check * num_bits, + config.table_range_check_tag, + ), + ] + }); + + // For short lookups, check that the word has been shifted by the correct number of bits. + // https://p.z.cash/halo2-0.1:decompose-short-lookup + meta.create_gate("Short lookup bitshift", |meta| { + let q_bitshift = meta.query_selector(config.q_bitshift); + let word = meta.query_advice(config.running_sum, Rotation::prev()); + let shifted_word = meta.query_advice(config.running_sum, Rotation::cur()); + let inv_two_pow_s = meta.query_advice(config.running_sum, Rotation::next()); + + let two_pow_k = F::from(1 << K); + + // shifted_word = word * 2^{K-s} + // = word * 2^K * inv_two_pow_s + Constraints::with_selector( + q_bitshift, + Some(word * two_pow_k * inv_two_pow_s - shifted_word), + ) + }); + + config + } + + #[cfg(test)] + // Fill `table_idx` and `table_range_check_tag`. + // This is only used in testing for now, since the Sinsemilla chip provides a pre-loaded table + // in the Orchard context. + pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + layouter.assign_table( + || "table_idx", + |mut table| { + // We generate the row values lazily (we only need them during keygen). + for index in 0..(1 << K) { + table.assign_cell( + || "table_idx", + self.table_idx, + index, + || Value::known(F::from(index as u64)), + )?; + table.assign_cell( + || "table_range_check_tag", + self.table_range_check_tag, + index, + || Value::known(F::ZERO), + )?; + } + for index in 0..(1 << 4) { + let new_index = index + (1 << K); + table.assign_cell( + || "table_idx", + self.table_idx, + new_index, + || Value::known(F::from(index as u64)), + )?; + table.assign_cell( + || "table_range_check_tag", + self.table_range_check_tag, + new_index, + || Value::known(F::from(4_u64)), + )?; + } + for index in 0..(1 << 5) { + let new_index = index + (1 << K) + (1 << 4); + table.assign_cell( + || "table_idx", + self.table_idx, + new_index, + || Value::known(F::from(index as u64)), + )?; + table.assign_cell( + || "table_range_check_tag", + self.table_range_check_tag, + new_index, + || Value::known(F::from(5_u64)), + )?; + } + Ok(()) + }, + ) + } + + /// Range check on an existing cell that is copied into this helper. + /// + /// Returns an error if `element` is not in a column that was passed to + /// [`ConstraintSystem::enable_equality`] during circuit configuration. + pub fn copy_check( + &self, + mut layouter: impl Layouter, + element: AssignedCell, + num_words: usize, + strict: bool, + ) -> Result, Error> { + layouter.assign_region( + || format!("{:?} words range check", num_words), + |mut region| { + // Copy `element` and initialize running sum `z_0 = element` to decompose it. + let z_0 = element.copy_advice(|| "z_0", &mut region, self.running_sum, 0)?; + self.range_check(&mut region, z_0, num_words, strict) + }, + ) + } + + /// Range check on a value that is witnessed in this helper. + pub fn witness_check( + &self, + mut layouter: impl Layouter, + value: Value, + num_words: usize, + strict: bool, + ) -> Result, Error> { + layouter.assign_region( + || "Witness element", + |mut region| { + let z_0 = + region.assign_advice(|| "Witness element", self.running_sum, 0, || value)?; + self.range_check(&mut region, z_0, num_words, strict) + }, + ) + } + + /// If `strict` is set to "true", the field element must fit into + /// `num_words * K` bits. In other words, the the final cumulative sum `z_{num_words}` + /// must be zero. + /// + /// If `strict` is set to "false", the final `z_{num_words}` is not constrained. + /// + /// `element` must have been assigned to `self.running_sum` at offset 0. + fn range_check( + &self, + region: &mut Region<'_, F>, + element: AssignedCell, + num_words: usize, + strict: bool, + ) -> Result, Error> { + // `num_words` must fit into a single field element. + assert!(num_words * K <= F::CAPACITY as usize); + let num_bits = num_words * K; + + // Chunk the first num_bits bits into K-bit words. + let words = { + // Take first num_bits bits of `element`. + let bits = element.value().map(|element| { + element + .to_le_bits() + .into_iter() + .take(num_bits) + .collect::>() + }); + + bits.map(|bits| { + bits.chunks_exact(K) + .map(|word| F::from(lebs2ip::(&(word.try_into().unwrap())))) + .collect::>() + }) + .transpose_vec(num_words) + }; + + let mut zs = vec![element.clone()]; + + // Assign cumulative sum such that + // z_i = 2^{K}⋅z_{i + 1} + a_i + // => z_{i + 1} = (z_i - a_i) / (2^K) + // + // For `element` = a_0 + 2^10 a_1 + ... + 2^{120} a_{12}}, initialize z_0 = `element`. + // If `element` fits in 130 bits, we end up with z_{13} = 0. + let mut z = element; + let inv_two_pow_k = F::from(1u64 << K).invert().unwrap(); + for (idx, word) in words.iter().enumerate() { + // Enable q_lookup on this row + self.q_lookup.enable(region, idx)?; + // Enable q_running on this row + self.q_running.enable(region, idx)?; + + // z_next = (z_cur - m_cur) / 2^K + z = { + let z_val = z + .value() + .zip(*word) + .map(|(z, word)| (*z - word) * inv_two_pow_k); + + // Assign z_next + region.assign_advice( + || format!("z_{:?}", idx + 1), + self.running_sum, + idx + 1, + || z_val, + )? + }; + zs.push(z.clone()); + } + + if strict { + // Constrain the final `z` to be zero. + region.constrain_constant(zs.last().unwrap().cell(), F::ZERO)?; + } + + Ok(RunningSum(zs)) + } + + /// Short range check on an existing cell that is copied into this helper. + /// + /// # Panics + /// + /// Panics if NUM_BITS is equal to or larger than K. + pub fn copy_short_check( + &self, + mut layouter: impl Layouter, + element: AssignedCell, + num_bits: usize, + ) -> Result<(), Error> { + assert!(num_bits < K); + layouter.assign_region( + || format!("Range check {:?} bits", num_bits), + |mut region| { + // Copy `element` to use in the k-bit lookup. + let element = + element.copy_advice(|| "element", &mut region, self.running_sum, 0)?; + + self.short_range_check(&mut region, element, num_bits) + }, + ) + } + + /// Short range check on value that is witnessed in this helper. + /// + /// # Panics + /// + /// Panics if num_bits is larger than K. + pub fn witness_short_check( + &self, + mut layouter: impl Layouter, + element: Value, + num_bits: usize, + ) -> Result, Error> { + assert!(num_bits <= K); + layouter.assign_region( + || format!("Range check {:?} bits", num_bits), + |mut region| { + // Witness `element` to use in the k-bit lookup. + let element = + region.assign_advice(|| "Witness element", self.running_sum, 0, || element)?; + + self.short_range_check(&mut region, element.clone(), num_bits)?; + + Ok(element) + }, + ) + } + + /// Constrain `x` to be a NUM_BITS word. + /// + /// `element` must have been assigned to `self.running_sum` at offset 0. + fn short_range_check( + &self, + region: &mut Region<'_, F>, + element: AssignedCell, + num_bits: usize, + ) -> Result<(), Error> { + // Enable lookup for `element`. + self.q_lookup.enable(region, 0)?; + + match num_bits { + 4 => { + self.q_range_check_4.enable(region, 0)?; + } + 5 => { + self.q_range_check_5.enable(region, 0)?; + } + _ => { + // Enable lookup for shifted element, to constrain it to 10 bits. + self.q_lookup.enable(region, 1)?; + + // Check element has been shifted by the correct number of bits. + self.q_bitshift.enable(region, 1)?; + + // Assign shifted `element * 2^{K - num_bits}` + let shifted = element.value().into_field() * F::from(1 << (K - num_bits)); + + region.assign_advice( + || format!("element * 2^({}-{})", K, num_bits), + self.running_sum, + 1, + || shifted, + )?; + + // Assign 2^{-num_bits} from a fixed column. + let inv_two_pow_s = F::from(1 << num_bits).invert().unwrap(); + region.assign_advice_from_constant( + || format!("2^(-{})", num_bits), + self.running_sum, + 2, + inv_two_pow_s, + )?; + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::LookupRangeCheckConfig; + + use super::super::lebs2ip; + use crate::sinsemilla::primitives::K; + + use ff::{Field, PrimeFieldBits}; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + dev::{FailureLocation, MockProver, VerifyFailure}, + plonk::{Circuit, ConstraintSystem, Error}, + }; + use pasta_curves::pallas; + + use std::{convert::TryInto, marker::PhantomData}; + + #[test] + fn lookup_range_check() { + #[derive(Clone, Copy)] + struct MyCircuit { + num_words: usize, + _marker: PhantomData, + } + + impl Circuit for MyCircuit { + type Config = LookupRangeCheckConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + *self + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let running_sum = meta.advice_column(); + let table_idx = meta.lookup_table_column(); + let table_range_check_tag = meta.lookup_table_column(); + let constants = meta.fixed_column(); + meta.enable_constant(constants); + + LookupRangeCheckConfig::::configure( + meta, + running_sum, + table_idx, + table_range_check_tag, + ) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // Load table_idx + config.load(&mut layouter)?; + + // Lookup constraining element to be no longer than num_words * K bits. + let elements_and_expected_final_zs = [ + (F::from((1 << (self.num_words * K)) - 1), F::ZERO, true), // a word that is within self.num_words * K bits long + (F::from(1 << (self.num_words * K)), F::ONE, false), // a word that is just over self.num_words * K bits long + ]; + + fn expected_zs( + element: F, + num_words: usize, + ) -> Vec { + let chunks = { + element + .to_le_bits() + .iter() + .by_vals() + .take(num_words * K) + .collect::>() + .chunks_exact(K) + .map(|chunk| F::from(lebs2ip::(chunk.try_into().unwrap()))) + .collect::>() + }; + let expected_zs = { + let inv_two_pow_k = F::from(1 << K).invert().unwrap(); + chunks.iter().fold(vec![element], |mut zs, a_i| { + // z_{i + 1} = (z_i - a_i) / 2^{K} + let z = (zs[zs.len() - 1] - a_i) * inv_two_pow_k; + zs.push(z); + zs + }) + }; + expected_zs + } + + for (element, expected_final_z, strict) in elements_and_expected_final_zs.iter() { + let expected_zs = expected_zs::(*element, self.num_words); + + let zs = config.witness_check( + layouter.namespace(|| format!("Lookup {:?}", self.num_words)), + Value::known(*element), + self.num_words, + *strict, + )?; + + assert_eq!(*expected_zs.last().unwrap(), *expected_final_z); + + for (expected_z, z) in expected_zs.into_iter().zip(zs.iter()) { + z.value().assert_if_known(|z| &&expected_z == z); + } + } + Ok(()) + } + } + + { + let circuit: MyCircuit = MyCircuit { + num_words: 6, + _marker: PhantomData, + }; + + let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + } + } + + #[test] + fn short_range_check() { + struct MyCircuit { + element: Value, + num_bits: usize, + } + + impl Circuit for MyCircuit { + type Config = LookupRangeCheckConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + MyCircuit { + element: Value::unknown(), + num_bits: self.num_bits, + } + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let running_sum = meta.advice_column(); + let table_idx = meta.lookup_table_column(); + let table_range_check_tag = meta.lookup_table_column(); + let constants = meta.fixed_column(); + meta.enable_constant(constants); + + LookupRangeCheckConfig::::configure( + meta, + running_sum, + table_idx, + table_range_check_tag, + ) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // Load table_idx + config.load(&mut layouter)?; + + // Lookup constraining element to be no longer than num_bits. + config.witness_short_check( + layouter.namespace(|| format!("Lookup {:?} bits", self.num_bits)), + self.element, + self.num_bits, + )?; + + Ok(()) + } + } + + // Edge case: zero bits + { + let circuit: MyCircuit = MyCircuit { + element: Value::known(pallas::Base::ZERO), + num_bits: 0, + }; + let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + } + + // Edge case: K bits + { + let circuit: MyCircuit = MyCircuit { + element: Value::known(pallas::Base::from((1 << K) - 1)), + num_bits: K, + }; + let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + } + + // Element within `num_bits` + { + let circuit: MyCircuit = MyCircuit { + element: Value::known(pallas::Base::from((1 << 6) - 1)), + num_bits: 6, + }; + let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + } + + // Element larger than `num_bits` but within K bits + { + let circuit: MyCircuit = MyCircuit { + element: Value::known(pallas::Base::from(1 << 6)), + num_bits: 6, + }; + let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); + assert_eq!( + prover.verify(), + Err(vec![VerifyFailure::Lookup { + lookup_index: 0, + location: FailureLocation::InRegion { + region: (1, "Range check 6 bits").into(), + offset: 1, + }, + }]) + ); + } + + // Element larger than K bits + { + let circuit: MyCircuit = MyCircuit { + element: Value::known(pallas::Base::from(1 << K)), + num_bits: 6, + }; + let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); + assert_eq!( + prover.verify(), + Err(vec![ + VerifyFailure::Lookup { + lookup_index: 0, + location: FailureLocation::InRegion { + region: (1, "Range check 6 bits").into(), + offset: 0, + }, + }, + VerifyFailure::Lookup { + lookup_index: 0, + location: FailureLocation::InRegion { + region: (1, "Range check 6 bits").into(), + offset: 1, + }, + }, + ]) + ); + } + + // Element which is not within `num_bits`, but which has a shifted value within + // num_bits + { + let num_bits = 6; + let shifted = pallas::Base::from((1 << num_bits) - 1); + // Recall that shifted = element * 2^{K-s} + // => element = shifted * 2^{s-K} + let element = shifted + * pallas::Base::from(1 << (K as u64 - num_bits)) + .invert() + .unwrap(); + let circuit: MyCircuit = MyCircuit { + element: Value::known(element), + num_bits: num_bits as usize, + }; + let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); + assert_eq!( + prover.verify(), + Err(vec![VerifyFailure::Lookup { + lookup_index: 0, + location: FailureLocation::InRegion { + region: (1, "Range check 6 bits").into(), + offset: 0, + }, + }]) + ); + } + + // Element within 4 bits + { + let circuit: MyCircuit = MyCircuit { + element: Value::known(pallas::Base::from((1 << 4) - 1)), + num_bits: 4, + }; + let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + } + + // Element larger than 5 bits + { + let circuit: MyCircuit = MyCircuit { + element: Value::known(pallas::Base::from(1 << 5)), + num_bits: 5, + }; + let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); + assert_eq!( + prover.verify(), + Err(vec![VerifyFailure::Lookup { + lookup_index: 0, + location: FailureLocation::InRegion { + region: (1, "Range check 5 bits").into(), + offset: 0, + }, + }]) + ); + } + } +} \ No newline at end of file diff --git a/halo2_proofs/src/circuit.rs b/halo2_proofs/src/circuit.rs index 0822d8d8aa..9c3faf70cd 100644 --- a/halo2_proofs/src/circuit.rs +++ b/halo2_proofs/src/circuit.rs @@ -116,8 +116,8 @@ impl AssignedCell { } impl AssignedCell -where - for<'v> Assigned: From<&'v V>, + where + for<'v> Assigned: From<&'v V>, { /// Returns the field element value of the [`AssignedCell`]. pub fn value_field(&self) -> Value> { @@ -139,9 +139,19 @@ impl AssignedCell, F> { } } +impl From> for AssignedCell, F> { + fn from(ac: AssignedCell) -> Self { + AssignedCell { + value: ac.value.map(|a| a.into()), + cell: ac.cell, + _marker: Default::default(), + } + } +} + impl AssignedCell -where - for<'v> Assigned: From<&'v V>, + where + for<'v> Assigned: From<&'v V>, { /// Copies the value to a given advice cell and constrains them to be equal. /// @@ -154,9 +164,9 @@ where column: Column, offset: usize, ) -> Result - where - A: Fn() -> AR, - AR: Into, + where + A: Fn() -> AR, + AR: Into, { let assigned_cell = region.assign_advice(annotation, column, offset, || self.value.clone())?; @@ -196,9 +206,9 @@ impl<'r, F: Field> Region<'r, F> { selector: &Selector, offset: usize, ) -> Result<(), Error> - where - A: Fn() -> AR, - AR: Into, + where + A: Fn() -> AR, + AR: Into, { self.region .enable_selector(&|| annotation().into(), selector, offset) @@ -214,11 +224,11 @@ impl<'r, F: Field> Region<'r, F> { offset: usize, mut to: V, ) -> Result, Error> - where - V: FnMut() -> Value + 'v, - for<'vr> Assigned: From<&'vr VR>, - A: Fn() -> AR, - AR: Into, + where + V: FnMut() -> Value + 'v, + for<'vr> Assigned: From<&'vr VR>, + A: Fn() -> AR, + AR: Into, { let mut value = Value::unknown(); let cell = @@ -250,10 +260,10 @@ impl<'r, F: Field> Region<'r, F> { offset: usize, constant: VR, ) -> Result, Error> - where - for<'vr> Assigned: From<&'vr VR>, - A: Fn() -> AR, - AR: Into, + where + for<'vr> Assigned: From<&'vr VR>, + A: Fn() -> AR, + AR: Into, { let cell = self.region.assign_advice_from_constant( &|| annotation().into(), @@ -281,9 +291,9 @@ impl<'r, F: Field> Region<'r, F> { advice: Column, offset: usize, ) -> Result, Error> - where - A: Fn() -> AR, - AR: Into, + where + A: Fn() -> AR, + AR: Into, { let (cell, value) = self.region.assign_advice_from_instance( &|| annotation().into(), @@ -323,11 +333,11 @@ impl<'r, F: Field> Region<'r, F> { offset: usize, mut to: V, ) -> Result, Error> - where - V: FnMut() -> Value + 'v, - for<'vr> Assigned: From<&'vr VR>, - A: Fn() -> AR, - AR: Into, + where + V: FnMut() -> Value + 'v, + for<'vr> Assigned: From<&'vr VR>, + A: Fn() -> AR, + AR: Into, { let mut value = Value::unknown(); let cell = @@ -350,8 +360,8 @@ impl<'r, F: Field> Region<'r, F> { /// /// Returns an error if the cell is in a column where equality has not been enabled. pub fn constrain_constant(&mut self, cell: Cell, constant: VR) -> Result<(), Error> - where - VR: Into>, + where + VR: Into>, { self.region.constrain_constant(cell, constant.into()) } @@ -390,11 +400,11 @@ impl<'r, F: Field> Table<'r, F> { offset: usize, mut to: V, ) -> Result<(), Error> - where - V: FnMut() -> Value + 'v, - VR: Into>, - A: Fn() -> AR, - AR: Into, + where + V: FnMut() -> Value + 'v, + VR: Into>, + A: Fn() -> AR, + AR: Into, { self.table .assign_cell(&|| annotation().into(), column, offset, &mut || { @@ -426,10 +436,10 @@ pub trait Layouter { /// }); /// ``` fn assign_region(&mut self, name: N, assignment: A) -> Result - where - A: FnMut(Region<'_, F>) -> Result, - N: Fn() -> NR, - NR: Into; + where + A: FnMut(Region<'_, F>) -> Result, + N: Fn() -> NR, + NR: Into; /// Assign a table region to an absolute row number. /// @@ -440,10 +450,10 @@ pub trait Layouter { /// }); /// ``` fn assign_table(&mut self, name: N, assignment: A) -> Result<(), Error> - where - A: FnMut(Table<'_, F>) -> Result<(), Error>, - N: Fn() -> NR, - NR: Into; + where + A: FnMut(Table<'_, F>) -> Result<(), Error>, + N: Fn() -> NR, + NR: Into; /// Constrains a [`Cell`] to equal an instance column's row value at an /// absolute position. @@ -463,9 +473,9 @@ pub trait Layouter { /// /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. fn push_namespace(&mut self, name_fn: N) - where - NR: Into, - N: FnOnce() -> NR; + where + NR: Into, + N: FnOnce() -> NR; /// Exits out of the existing namespace. /// @@ -474,9 +484,9 @@ pub trait Layouter { /// Enters into a namespace. fn namespace(&mut self, name_fn: N) -> NamespacedLayouter<'_, F, Self::Root> - where - NR: Into, - N: FnOnce() -> NR, + where + NR: Into, + N: FnOnce() -> NR, { self.get_root().push_namespace(name_fn); @@ -493,19 +503,19 @@ impl<'a, F: Field, L: Layouter + 'a> Layouter for NamespacedLayouter<'a, F type Root = L::Root; fn assign_region(&mut self, name: N, assignment: A) -> Result - where - A: FnMut(Region<'_, F>) -> Result, - N: Fn() -> NR, - NR: Into, + where + A: FnMut(Region<'_, F>) -> Result, + N: Fn() -> NR, + NR: Into, { self.0.assign_region(name, assignment) } fn assign_table(&mut self, name: N, assignment: A) -> Result<(), Error> - where - A: FnMut(Table<'_, F>) -> Result<(), Error>, - N: Fn() -> NR, - NR: Into, + where + A: FnMut(Table<'_, F>) -> Result<(), Error>, + N: Fn() -> NR, + NR: Into, { self.0.assign_table(name, assignment) } @@ -524,9 +534,9 @@ impl<'a, F: Field, L: Layouter + 'a> Layouter for NamespacedLayouter<'a, F } fn push_namespace(&mut self, _name_fn: N) - where - NR: Into, - N: FnOnce() -> NR, + where + NR: Into, + N: FnOnce() -> NR, { panic!("Only the root's push_namespace should be called"); } @@ -567,4 +577,4 @@ impl<'a, F: Field, L: Layouter + 'a> Drop for NamespacedLayouter<'a, F, L> { self.get_root().pop_namespace(gadget_name); } -} +} \ No newline at end of file From 62fe6072f2643a0485196fa29fb298733aa9681c Mon Sep 17 00:00:00 2001 From: YaoGalteland Date: Thu, 28 Mar 2024 15:05:53 +0100 Subject: [PATCH 2/3] cargo fmt --- halo2_gadgets_optimized/src/ecc.rs | 8 +- halo2_gadgets_optimized/src/ecc/chip.rs | 24 ++-- .../src/ecc/chip/mul_fixed/short.rs | 6 +- .../src/ecc/chip/witness_point.rs | 4 +- halo2_gadgets_optimized/src/sinsemilla.rs | 30 ++--- .../src/sinsemilla/chip.rs | 52 ++++---- .../src/sinsemilla/chip/generator_table.rs | 2 +- .../src/sinsemilla/chip/hash_to_point.rs | 10 +- .../src/sinsemilla/merkle.rs | 46 +++---- .../src/sinsemilla/merkle/chip.rs | 72 +++++------ .../src/sinsemilla/primitives.rs | 6 +- .../src/utilities/cond_swap.rs | 4 +- .../src/utilities/lookup_range_check.rs | 14 +- halo2_proofs/src/circuit.rs | 120 +++++++++--------- 14 files changed, 199 insertions(+), 199 deletions(-) diff --git a/halo2_gadgets_optimized/src/ecc.rs b/halo2_gadgets_optimized/src/ecc.rs index 87af93d505..4861a20e52 100644 --- a/halo2_gadgets_optimized/src/ecc.rs +++ b/halo2_gadgets_optimized/src/ecc.rs @@ -14,7 +14,7 @@ pub mod chip; /// The set of circuit instructions required to use the ECC gadgets. pub trait EccInstructions: -Chip + UtilitiesInstructions + Clone + Debug + Eq + Chip + UtilitiesInstructions + Clone + Debug + Eq { /// Variable representing a scalar used in variable-base scalar mul. /// @@ -380,7 +380,7 @@ impl> NonIdentityPoint { } impl + Clone + Debug + Eq> -From> for Point + From> for Point { fn from(non_id_point: NonIdentityPoint) -> Self { Self { @@ -866,7 +866,7 @@ pub(crate) mod tests { layouter.namespace(|| "identity"), Value::known(pallas::Affine::identity()), ) - .expect_err("Trying to witness the identity should return an error"); + .expect_err("Trying to witness the identity should return an error"); } // Test witness non-identity point @@ -972,4 +972,4 @@ pub(crate) mod tests { .render(13, &circuit, &root) .unwrap(); } -} \ No newline at end of file +} diff --git a/halo2_gadgets_optimized/src/ecc/chip.rs b/halo2_gadgets_optimized/src/ecc/chip.rs index 8036b08338..402020384b 100644 --- a/halo2_gadgets_optimized/src/ecc/chip.rs +++ b/halo2_gadgets_optimized/src/ecc/chip.rs @@ -245,7 +245,7 @@ impl> Chip for Ecc } impl> UtilitiesInstructions -for EccChip + for EccChip { type Var = AssignedCell; } @@ -360,7 +360,7 @@ pub struct EccScalarFixedShort { /// The circuit-assigned running sum constraining this signed short scalar, or `None` /// if the scalar has not been used yet. running_sum: - Option, { NUM_WINDOWS_SHORT + 1 }>>, + Option, { NUM_WINDOWS_SHORT + 1 }>>, } /// A base field element used for fixed-base scalar multiplication. @@ -408,12 +408,12 @@ pub enum ScalarVar { } impl> EccInstructions for EccChip - where - >::Base: +where + >::Base: FixedPoint, - >::FullScalar: + >::FullScalar: FixedPoint, - >::ShortScalar: + >::ShortScalar: FixedPoint, { type ScalarFixed = EccScalarFixed; @@ -625,13 +625,13 @@ impl> EccInstructions for Ecc } impl> BaseFitsInScalarInstructions -for EccChip - where - >::Base: + for EccChip +where + >::Base: FixedPoint, - >::FullScalar: + >::FullScalar: FixedPoint, - >::ShortScalar: + >::ShortScalar: FixedPoint, { fn scalar_var_from_base( @@ -641,4 +641,4 @@ for EccChip ) -> Result { Ok(ScalarVar::BaseFieldElem(base.clone())) } -} \ No newline at end of file +} diff --git a/halo2_gadgets_optimized/src/ecc/chip/mul_fixed/short.rs b/halo2_gadgets_optimized/src/ecc/chip/mul_fixed/short.rs index 09a2a8d6df..a10c54c1ed 100644 --- a/halo2_gadgets_optimized/src/ecc/chip/mul_fixed/short.rs +++ b/halo2_gadgets_optimized/src/ecc/chip/mul_fixed/short.rs @@ -111,8 +111,8 @@ impl> Config { scalar: &EccScalarFixedShort, base: &>::ShortScalar, ) -> Result<(EccPoint, EccScalarFixedShort), Error> - where - >::ShortScalar: + where + >::ShortScalar: super::super::FixedPoint, { let (scalar, acc, mul_b) = layouter.assign_region( @@ -944,4 +944,4 @@ pub mod tests { ]) ); } -} \ No newline at end of file +} diff --git a/halo2_gadgets_optimized/src/ecc/chip/witness_point.rs b/halo2_gadgets_optimized/src/ecc/chip/witness_point.rs index bb21bbb511..98f865a6dc 100644 --- a/halo2_gadgets_optimized/src/ecc/chip/witness_point.rs +++ b/halo2_gadgets_optimized/src/ecc/chip/witness_point.rs @@ -206,6 +206,6 @@ pub mod tests { layouter.namespace(|| "witness identity"), Value::known(pallas::Affine::identity()), ) - .expect_err("witnessing 𝒪 should return an error"); + .expect_err("witnessing 𝒪 should return an error"); } -} \ No newline at end of file +} diff --git a/halo2_gadgets_optimized/src/sinsemilla.rs b/halo2_gadgets_optimized/src/sinsemilla.rs index c5b90441c6..2b04781613 100644 --- a/halo2_gadgets_optimized/src/sinsemilla.rs +++ b/halo2_gadgets_optimized/src/sinsemilla.rs @@ -113,17 +113,17 @@ pub trait SinsemillaInstructions - where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, +where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { chip: SinsemillaChip, inner: SinsemillaChip::Message, } impl -Message - where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, + Message +where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { #![allow(dead_code)] fn from_bitstring( @@ -177,16 +177,16 @@ Message /// A message piece with a bitlength of some multiple of `K`. #[derive(Copy, Clone, Debug)] pub struct MessagePiece - where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, +where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { inner: SinsemillaChip::MessagePiece, } impl -MessagePiece - where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, + MessagePiece +where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { /// Returns the inner MessagePiece contained in this gadget. pub fn inner(&self) -> SinsemillaChip::MessagePiece { @@ -195,9 +195,9 @@ MessagePiece } impl -MessagePiece - where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, + MessagePiece +where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { #![allow(dead_code)] fn from_bitstring( @@ -375,7 +375,7 @@ HashDomain /// Trait allowing circuit's Sinsemilla CommitDomains to be enumerated. pub trait CommitDomains, H: HashDomains>: -Clone + Debug + Clone + Debug { /// Returns the fixed point corresponding to the R constant used for /// randomization in this CommitDomain. @@ -846,4 +846,4 @@ pub(crate) mod tests { .render(11, &circuit, &root) .unwrap(); } -} \ No newline at end of file +} diff --git a/halo2_gadgets_optimized/src/sinsemilla/chip.rs b/halo2_gadgets_optimized/src/sinsemilla/chip.rs index 75bbaf7014..c55efd1105 100644 --- a/halo2_gadgets_optimized/src/sinsemilla/chip.rs +++ b/halo2_gadgets_optimized/src/sinsemilla/chip.rs @@ -31,10 +31,10 @@ mod hash_to_point; /// Configuration for the Sinsemilla hash chip #[derive(Eq, PartialEq, Clone, Debug)] pub struct SinsemillaConfig - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { /// Binary selector used in lookup argument and in the body of the Sinsemilla hash. q_sinsemilla1: Selector, @@ -63,10 +63,10 @@ pub struct SinsemillaConfig } impl SinsemillaConfig - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { /// Returns an array of all advice columns in this config, in arbitrary order. pub(super) fn advices(&self) -> [Column; 5] { @@ -97,19 +97,19 @@ impl SinsemillaConfig /// [Chip description](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html#plonk--halo-2-constraints). #[derive(Eq, PartialEq, Clone, Debug)] pub struct SinsemillaChip - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { config: SinsemillaConfig, } impl Chip for SinsemillaChip - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { type Config = SinsemillaConfig; type Loaded = (); @@ -124,10 +124,10 @@ impl Chip for SinsemillaChip SinsemillaChip - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { /// Reconstructs this chip from the given config. pub fn construct(config: >::Config) -> Self { @@ -266,11 +266,11 @@ impl SinsemillaChip // Implement `SinsemillaInstructions` for `SinsemillaChip` impl SinsemillaInstructions -for SinsemillaChip - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, + for SinsemillaChip +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type CellValue = AssignedCell; @@ -339,4 +339,4 @@ for SinsemillaChip fn extract(point: &Self::NonIdentityPoint) -> Self::X { point.x() } -} \ No newline at end of file +} diff --git a/halo2_gadgets_optimized/src/sinsemilla/chip/generator_table.rs b/halo2_gadgets_optimized/src/sinsemilla/chip/generator_table.rs index 8c75830f38..e77928b128 100644 --- a/halo2_gadgets_optimized/src/sinsemilla/chip/generator_table.rs +++ b/halo2_gadgets_optimized/src/sinsemilla/chip/generator_table.rs @@ -172,4 +172,4 @@ impl GeneratorTableConfig { }, ) } -} \ No newline at end of file +} diff --git a/halo2_gadgets_optimized/src/sinsemilla/chip/hash_to_point.rs b/halo2_gadgets_optimized/src/sinsemilla/chip/hash_to_point.rs index 15a8600a5b..165615efaa 100644 --- a/halo2_gadgets_optimized/src/sinsemilla/chip/hash_to_point.rs +++ b/halo2_gadgets_optimized/src/sinsemilla/chip/hash_to_point.rs @@ -17,10 +17,10 @@ use pasta_curves::{arithmetic::CurveAffine, pallas}; use std::ops::Deref; impl SinsemillaChip - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] @@ -569,4 +569,4 @@ impl Deref for Y { fn deref(&self) -> &Value> { &self.0 } -} \ No newline at end of file +} diff --git a/halo2_gadgets_optimized/src/sinsemilla/merkle.rs b/halo2_gadgets_optimized/src/sinsemilla/merkle.rs index 998335785a..1eabfaa870 100644 --- a/halo2_gadgets_optimized/src/sinsemilla/merkle.rs +++ b/halo2_gadgets_optimized/src/sinsemilla/merkle.rs @@ -24,10 +24,10 @@ pub trait MerkleInstructions< const K: usize, const MAX_WORDS: usize, >: -SinsemillaInstructions -+ CondSwapInstructions -+ UtilitiesInstructions -+ Chip + SinsemillaInstructions + + CondSwapInstructions + + UtilitiesInstructions + + Chip { /// Compute MerkleCRH for a given `layer`. The hash that computes the root /// is at layer 0, and the hashes that are applied to two leaves are at @@ -64,15 +64,15 @@ pub struct MerklePath< } impl< - C: CurveAffine, - MerkleChip, - const PATH_LENGTH: usize, - const K: usize, - const MAX_WORDS: usize, - const PAR: usize, -> MerklePath - where - MerkleChip: MerkleInstructions + Clone, + C: CurveAffine, + MerkleChip, + const PATH_LENGTH: usize, + const K: usize, + const MAX_WORDS: usize, + const PAR: usize, + > MerklePath +where + MerkleChip: MerkleInstructions + Clone, { /// Constructs a [`MerklePath`]. /// @@ -99,15 +99,15 @@ impl< #[allow(non_snake_case)] impl< - C: CurveAffine, - MerkleChip, - const PATH_LENGTH: usize, - const K: usize, - const MAX_WORDS: usize, - const PAR: usize, -> MerklePath - where - MerkleChip: MerkleInstructions + Clone, + C: CurveAffine, + MerkleChip, + const PATH_LENGTH: usize, + const K: usize, + const MAX_WORDS: usize, + const PAR: usize, + > MerklePath +where + MerkleChip: MerkleInstructions + Clone, { /// Calculates the root of the tree containing the given leaf at this Merkle path. /// @@ -397,4 +397,4 @@ pub mod tests { .render(11, &circuit, &root) .unwrap(); } -} \ No newline at end of file +} diff --git a/halo2_gadgets_optimized/src/sinsemilla/merkle/chip.rs b/halo2_gadgets_optimized/src/sinsemilla/merkle/chip.rs index 59ed3d40fc..cb3c5be4cc 100644 --- a/halo2_gadgets_optimized/src/sinsemilla/merkle/chip.rs +++ b/halo2_gadgets_optimized/src/sinsemilla/merkle/chip.rs @@ -29,10 +29,10 @@ use group::ff::PrimeField; /// Configuration for the `MerkleChip` implementation. #[derive(Clone, Debug, PartialEq, Eq)] pub struct MerkleConfig - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { advices: [Column; 5], q_decompose: Selector, @@ -52,19 +52,19 @@ pub struct MerkleConfig /// `left` and `right`. #[derive(Clone, Debug, PartialEq, Eq)] pub struct MerkleChip - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { config: MerkleConfig, } impl Chip for MerkleChip - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { type Config = MerkleConfig; type Loaded = (); @@ -79,10 +79,10 @@ impl Chip for MerkleChip } impl MerkleChip - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { /// Configures the [`MerkleChip`]. pub fn configure( @@ -197,12 +197,12 @@ impl MerkleChip } impl -MerkleInstructions -for MerkleChip - where - Hash: HashDomains + Eq, - F: FixedPoints, - Commit: CommitDomains + Eq, + MerkleInstructions + for MerkleChip +where + Hash: HashDomains + Eq, + F: FixedPoints, + Commit: CommitDomains + Eq, { #[allow(non_snake_case)] fn hash_layer( @@ -416,19 +416,19 @@ for MerkleChip } impl UtilitiesInstructions for MerkleChip - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type Var = AssignedCell; } impl CondSwapInstructions for MerkleChip - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { #[allow(clippy::type_complexity)] fn swap( @@ -456,11 +456,11 @@ impl CondSwapInstructions for MerkleChip SinsemillaInstructions -for MerkleChip - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, + for MerkleChip +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type CellValue = as SinsemillaInstructions< pallas::Affine, @@ -551,4 +551,4 @@ for MerkleChip fn extract(point: &Self::NonIdentityPoint) -> Self::X { SinsemillaChip::::extract(point) } -} \ No newline at end of file +} diff --git a/halo2_gadgets_optimized/src/sinsemilla/primitives.rs b/halo2_gadgets_optimized/src/sinsemilla/primitives.rs index 668558fed9..ad9e397b5e 100644 --- a/halo2_gadgets_optimized/src/sinsemilla/primitives.rs +++ b/halo2_gadgets_optimized/src/sinsemilla/primitives.rs @@ -303,7 +303,7 @@ mod tests { .iter() .cloned() ) - .collect::>(), + .collect::>(), vec![true, true, false, true, false, true, false, true, false, true] ); assert_eq!( @@ -312,7 +312,7 @@ mod tests { .iter() .cloned() ) - .collect::>(), + .collect::>(), vec![ true, true, false, true, false, true, false, true, false, true, true, false, false, false, false, false, false, false, false, false @@ -365,4 +365,4 @@ mod tests { // Test equality assert_eq!(commit1.unwrap(), commit2.unwrap()); } -} \ No newline at end of file +} diff --git a/halo2_gadgets_optimized/src/utilities/cond_swap.rs b/halo2_gadgets_optimized/src/utilities/cond_swap.rs index 6222fcab63..78049e742a 100644 --- a/halo2_gadgets_optimized/src/utilities/cond_swap.rs +++ b/halo2_gadgets_optimized/src/utilities/cond_swap.rs @@ -617,8 +617,8 @@ mod tests { circuit, instance.iter().map(|p| p.to_vec()).collect(), ) - .unwrap(); + .unwrap(); assert_eq!(prover.verify(), Ok(())); } } -} \ No newline at end of file +} diff --git a/halo2_gadgets_optimized/src/utilities/lookup_range_check.rs b/halo2_gadgets_optimized/src/utilities/lookup_range_check.rs index a2dddea4aa..64b4ce4c90 100644 --- a/halo2_gadgets_optimized/src/utilities/lookup_range_check.rs +++ b/halo2_gadgets_optimized/src/utilities/lookup_range_check.rs @@ -147,15 +147,15 @@ impl LookupRangeCheckConfig { // - 0 otherwise let num_bits = q_range_check_5.clone() * Expression::Constant(F::from(5_u64)) + (one.clone() - q_range_check_5) - * q_range_check_4 - * Expression::Constant(F::from(4_u64)); + * q_range_check_4 + * Expression::Constant(F::from(4_u64)); // Combine the running sum, short lookups and optimized range checks: vec![ ( q_lookup.clone() * ((one - q_range_check.clone()) * (running_sum_lookup + short_lookup) - + q_range_check.clone() * z_cur), + + q_range_check.clone() * z_cur), config.table_idx, ), ( @@ -317,7 +317,7 @@ impl LookupRangeCheckConfig { .map(|word| F::from(lebs2ip::(&(word.try_into().unwrap())))) .collect::>() }) - .transpose_vec(num_words) + .transpose_vec(num_words) }; let mut zs = vec![element.clone()]; @@ -721,8 +721,8 @@ mod tests { // => element = shifted * 2^{s-K} let element = shifted * pallas::Base::from(1 << (K as u64 - num_bits)) - .invert() - .unwrap(); + .invert() + .unwrap(); let circuit: MyCircuit = MyCircuit { element: Value::known(element), num_bits: num_bits as usize, @@ -769,4 +769,4 @@ mod tests { ); } } -} \ No newline at end of file +} diff --git a/halo2_proofs/src/circuit.rs b/halo2_proofs/src/circuit.rs index 9c3faf70cd..1083339fed 100644 --- a/halo2_proofs/src/circuit.rs +++ b/halo2_proofs/src/circuit.rs @@ -116,8 +116,8 @@ impl AssignedCell { } impl AssignedCell - where - for<'v> Assigned: From<&'v V>, +where + for<'v> Assigned: From<&'v V>, { /// Returns the field element value of the [`AssignedCell`]. pub fn value_field(&self) -> Value> { @@ -150,8 +150,8 @@ impl From> for AssignedCell, F> { } impl AssignedCell - where - for<'v> Assigned: From<&'v V>, +where + for<'v> Assigned: From<&'v V>, { /// Copies the value to a given advice cell and constrains them to be equal. /// @@ -164,9 +164,9 @@ impl AssignedCell column: Column, offset: usize, ) -> Result - where - A: Fn() -> AR, - AR: Into, + where + A: Fn() -> AR, + AR: Into, { let assigned_cell = region.assign_advice(annotation, column, offset, || self.value.clone())?; @@ -206,9 +206,9 @@ impl<'r, F: Field> Region<'r, F> { selector: &Selector, offset: usize, ) -> Result<(), Error> - where - A: Fn() -> AR, - AR: Into, + where + A: Fn() -> AR, + AR: Into, { self.region .enable_selector(&|| annotation().into(), selector, offset) @@ -224,11 +224,11 @@ impl<'r, F: Field> Region<'r, F> { offset: usize, mut to: V, ) -> Result, Error> - where - V: FnMut() -> Value + 'v, - for<'vr> Assigned: From<&'vr VR>, - A: Fn() -> AR, - AR: Into, + where + V: FnMut() -> Value + 'v, + for<'vr> Assigned: From<&'vr VR>, + A: Fn() -> AR, + AR: Into, { let mut value = Value::unknown(); let cell = @@ -260,10 +260,10 @@ impl<'r, F: Field> Region<'r, F> { offset: usize, constant: VR, ) -> Result, Error> - where - for<'vr> Assigned: From<&'vr VR>, - A: Fn() -> AR, - AR: Into, + where + for<'vr> Assigned: From<&'vr VR>, + A: Fn() -> AR, + AR: Into, { let cell = self.region.assign_advice_from_constant( &|| annotation().into(), @@ -291,9 +291,9 @@ impl<'r, F: Field> Region<'r, F> { advice: Column, offset: usize, ) -> Result, Error> - where - A: Fn() -> AR, - AR: Into, + where + A: Fn() -> AR, + AR: Into, { let (cell, value) = self.region.assign_advice_from_instance( &|| annotation().into(), @@ -333,11 +333,11 @@ impl<'r, F: Field> Region<'r, F> { offset: usize, mut to: V, ) -> Result, Error> - where - V: FnMut() -> Value + 'v, - for<'vr> Assigned: From<&'vr VR>, - A: Fn() -> AR, - AR: Into, + where + V: FnMut() -> Value + 'v, + for<'vr> Assigned: From<&'vr VR>, + A: Fn() -> AR, + AR: Into, { let mut value = Value::unknown(); let cell = @@ -360,8 +360,8 @@ impl<'r, F: Field> Region<'r, F> { /// /// Returns an error if the cell is in a column where equality has not been enabled. pub fn constrain_constant(&mut self, cell: Cell, constant: VR) -> Result<(), Error> - where - VR: Into>, + where + VR: Into>, { self.region.constrain_constant(cell, constant.into()) } @@ -400,11 +400,11 @@ impl<'r, F: Field> Table<'r, F> { offset: usize, mut to: V, ) -> Result<(), Error> - where - V: FnMut() -> Value + 'v, - VR: Into>, - A: Fn() -> AR, - AR: Into, + where + V: FnMut() -> Value + 'v, + VR: Into>, + A: Fn() -> AR, + AR: Into, { self.table .assign_cell(&|| annotation().into(), column, offset, &mut || { @@ -436,10 +436,10 @@ pub trait Layouter { /// }); /// ``` fn assign_region(&mut self, name: N, assignment: A) -> Result - where - A: FnMut(Region<'_, F>) -> Result, - N: Fn() -> NR, - NR: Into; + where + A: FnMut(Region<'_, F>) -> Result, + N: Fn() -> NR, + NR: Into; /// Assign a table region to an absolute row number. /// @@ -450,10 +450,10 @@ pub trait Layouter { /// }); /// ``` fn assign_table(&mut self, name: N, assignment: A) -> Result<(), Error> - where - A: FnMut(Table<'_, F>) -> Result<(), Error>, - N: Fn() -> NR, - NR: Into; + where + A: FnMut(Table<'_, F>) -> Result<(), Error>, + N: Fn() -> NR, + NR: Into; /// Constrains a [`Cell`] to equal an instance column's row value at an /// absolute position. @@ -473,9 +473,9 @@ pub trait Layouter { /// /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. fn push_namespace(&mut self, name_fn: N) - where - NR: Into, - N: FnOnce() -> NR; + where + NR: Into, + N: FnOnce() -> NR; /// Exits out of the existing namespace. /// @@ -484,9 +484,9 @@ pub trait Layouter { /// Enters into a namespace. fn namespace(&mut self, name_fn: N) -> NamespacedLayouter<'_, F, Self::Root> - where - NR: Into, - N: FnOnce() -> NR, + where + NR: Into, + N: FnOnce() -> NR, { self.get_root().push_namespace(name_fn); @@ -503,19 +503,19 @@ impl<'a, F: Field, L: Layouter + 'a> Layouter for NamespacedLayouter<'a, F type Root = L::Root; fn assign_region(&mut self, name: N, assignment: A) -> Result - where - A: FnMut(Region<'_, F>) -> Result, - N: Fn() -> NR, - NR: Into, + where + A: FnMut(Region<'_, F>) -> Result, + N: Fn() -> NR, + NR: Into, { self.0.assign_region(name, assignment) } fn assign_table(&mut self, name: N, assignment: A) -> Result<(), Error> - where - A: FnMut(Table<'_, F>) -> Result<(), Error>, - N: Fn() -> NR, - NR: Into, + where + A: FnMut(Table<'_, F>) -> Result<(), Error>, + N: Fn() -> NR, + NR: Into, { self.0.assign_table(name, assignment) } @@ -534,9 +534,9 @@ impl<'a, F: Field, L: Layouter + 'a> Layouter for NamespacedLayouter<'a, F } fn push_namespace(&mut self, _name_fn: N) - where - NR: Into, - N: FnOnce() -> NR, + where + NR: Into, + N: FnOnce() -> NR, { panic!("Only the root's push_namespace should be called"); } @@ -577,4 +577,4 @@ impl<'a, F: Field, L: Layouter + 'a> Drop for NamespacedLayouter<'a, F, L> { self.get_root().pop_namespace(gadget_name); } -} \ No newline at end of file +} From ebd2f90419936a238db923be27abc366d8292bce Mon Sep 17 00:00:00 2001 From: YaoGalteland Date: Thu, 28 Mar 2024 15:17:36 +0100 Subject: [PATCH 3/3] rename to halo2_gadgets_optimized --- halo2_gadgets_optimized/benches/poseidon.rs | 2 +- halo2_gadgets_optimized/benches/primitives.rs | 2 +- halo2_gadgets_optimized/benches/sha256.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/halo2_gadgets_optimized/benches/poseidon.rs b/halo2_gadgets_optimized/benches/poseidon.rs index 31f21d2795..f41190c93f 100644 --- a/halo2_gadgets_optimized/benches/poseidon.rs +++ b/halo2_gadgets_optimized/benches/poseidon.rs @@ -11,7 +11,7 @@ use halo2_proofs::{ }; use pasta_curves::{pallas, vesta}; -use halo2_gadgets::poseidon::{ +use halo2_gadgets_optimized::poseidon::{ primitives::{self as poseidon, generate_constants, ConstantLength, Mds, Spec}, Hash, Pow5Chip, Pow5Config, }; diff --git a/halo2_gadgets_optimized/benches/primitives.rs b/halo2_gadgets_optimized/benches/primitives.rs index a6cb824ac7..1b96b333f9 100644 --- a/halo2_gadgets_optimized/benches/primitives.rs +++ b/halo2_gadgets_optimized/benches/primitives.rs @@ -1,6 +1,6 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use ff::Field; -use halo2_gadgets::{ +use halo2_gadgets_optimized::{ poseidon::primitives::{self as poseidon, ConstantLength, P128Pow5T3}, sinsemilla::primitives as sinsemilla, }; diff --git a/halo2_gadgets_optimized/benches/sha256.rs b/halo2_gadgets_optimized/benches/sha256.rs index ffb4165a70..e856c76f16 100644 --- a/halo2_gadgets_optimized/benches/sha256.rs +++ b/halo2_gadgets_optimized/benches/sha256.rs @@ -17,7 +17,7 @@ use std::{ path::Path, }; -use halo2_gadgets::sha256::{BlockWord, Sha256, Table16Chip, Table16Config, BLOCK_SIZE}; +use halo2_gadgets_optimized::sha256::{BlockWord, Sha256, Table16Chip, Table16Config, BLOCK_SIZE}; #[allow(dead_code)] fn bench(name: &str, k: u32, c: &mut Criterion) {