Просмотр исходного кода

halo2: Add poseidon hash primitives

parazyd 5 лет назад
Родитель
Сommit
afc0759480

+ 2 - 0
examples/halo2/src/lib.rs

@@ -0,0 +1,2 @@
+pub mod circuit;
+pub mod primitives;

+ 1 - 0
examples/halo2/src/primitives.rs

@@ -0,0 +1 @@
+pub mod poseidon;

+ 363 - 0
examples/halo2/src/primitives/poseidon.rs

@@ -0,0 +1,363 @@
+use std::array;
+use std::fmt;
+use std::iter;
+use std::marker::PhantomData;
+
+use halo2::arithmetic::FieldExt;
+
+pub(crate) mod grain;
+pub(crate) mod mds;
+
+#[cfg(test)]
+pub(crate) mod test_vectors;
+
+mod nullifier;
+pub use nullifier::OrchardNullifier;
+
+use grain::SboxType;
+
+/// The type used to hold permutation state.
+pub type State<F, const T: usize> = [F; T];
+
+/// The type used to hold duplex sponge state.
+pub type SpongeState<F, const RATE: usize> = [Option<F>; RATE];
+
+/// The type used to hold the MDS matrix and its inverse.
+pub type Mds<F, const T: usize> = [[F; T]; T];
+
+/// A specification for a Poseidon permutation.
+pub trait Spec<F: FieldExt, const T: usize, const RATE: usize> {
+    /// 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(&self) -> usize;
+
+    /// Generates `(round_constants, mds, mds^-1)` corresponding to this specification.
+    fn constants(&self) -> (Vec<[F; T]>, Mds<F, T>, Mds<F, T>) {
+        let r_f = Self::full_rounds();
+        let r_p = Self::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::<F, T>(&mut grain, self.secure_mds());
+
+        (round_constants, mds, mds_inv)
+    }
+}
+
+/// Runs the Poseidon permutation on the given state.
+pub(crate) fn permute<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize>(
+    state: &mut State<F, T>,
+    mds: &Mds<F, T>,
+    round_constants: &[[F; T]],
+) {
+    let r_f = S::full_rounds() / 2;
+    let r_p = S::partial_rounds();
+
+    let apply_mds = |state: &mut State<F, T>| {
+        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<F, T>, 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<F, T>, 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>, &[F; T])).take(r_f))
+        .chain(iter::repeat(&part_round as &dyn Fn(&mut State<F, T>, &[F; T])).take(r_p))
+        .chain(iter::repeat(&full_round as &dyn Fn(&mut State<F, T>, &[F; T])).take(r_f))
+        .zip(round_constants.iter())
+        .fold(state, |state, (round, rcs)| {
+            round(state, rcs);
+            state
+        });
+}
+
+fn poseidon_duplex<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize>(
+    state: &mut State<F, T>,
+    input: &SpongeState<F, RATE>,
+    pad_and_add: &dyn Fn(&mut State<F, T>, &SpongeState<F, RATE>),
+    mds_matrix: &Mds<F, T>,
+    round_constants: &[[F; T]],
+) -> SpongeState<F, RATE> {
+    pad_and_add(state, input);
+
+    permute::<F, S, T, RATE>(state, mds_matrix, round_constants);
+
+    let mut output = [None; RATE];
+    for (word, value) in output.iter_mut().zip(state.iter()) {
+        *word = Some(*value);
+    }
+    output
+}
+
+pub(crate) enum Sponge<F, const RATE: usize> {
+    Absorbing(SpongeState<F, RATE>),
+    Squeezing(SpongeState<F, RATE>),
+}
+
+impl<F: Copy, const RATE: usize> Sponge<F, RATE> {
+    pub(crate) fn absorb(val: F) -> Self {
+        let mut input = [None; RATE];
+        input[0] = Some(val);
+        Sponge::Absorbing(input)
+    }
+}
+
+/// A Poseidon duplex sponge.
+pub struct Duplex<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize> {
+    sponge: Sponge<F, RATE>,
+    state: State<F, T>,
+    pad_and_add: Box<dyn Fn(&mut State<F, T>, &SpongeState<F, RATE>)>,
+    mds_matrix: Mds<F, T>,
+    round_constants: Vec<[F; T]>,
+    _marker: PhantomData<S>,
+}
+
+impl<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize> Duplex<F, S, T, RATE> {
+    /// Constructs a new duplex sponge for the given Poseidon specification.
+    pub fn new(
+        spec: S,
+        initial_capacity_element: F,
+        pad_and_add: Box<dyn Fn(&mut State<F, T>, &SpongeState<F, RATE>)>,
+    ) -> Self {
+        let (round_constants, mds_matrix, _) = spec.constants();
+
+        let input = [None; RATE];
+        let mut state = [F::zero(); T];
+        state[RATE] = initial_capacity_element;
+
+        Duplex {
+            sponge: Sponge::Absorbing(input),
+            state,
+            pad_and_add,
+            mds_matrix,
+            round_constants,
+            _marker: PhantomData::default(),
+        }
+    }
+
+    /// Absorbs an element into the sponge.
+    pub fn absorb(&mut self, value: F) {
+        match self.sponge {
+            Sponge::Absorbing(ref mut input) => {
+                for entry in input.iter_mut() {
+                    if entry.is_none() {
+                        *entry = Some(value);
+                        return;
+                    }
+                }
+
+                // We've already absorbed as many elements as we can
+                let _ = poseidon_duplex::<F, S, T, RATE>(
+                    &mut self.state,
+                    input,
+                    &self.pad_and_add,
+                    &self.mds_matrix,
+                    &self.round_constants,
+                );
+                self.sponge = Sponge::absorb(value);
+            }
+            Sponge::Squeezing(_) => {
+                // Drop the remaining output elements
+                self.sponge = Sponge::absorb(value);
+            }
+        }
+    }
+
+    /// Squeezes an element from the sponge.
+    pub fn squeeze(&mut self) -> F {
+        loop {
+            match self.sponge {
+                Sponge::Absorbing(ref input) => {
+                    self.sponge = Sponge::Squeezing(poseidon_duplex::<F, S, T, RATE>(
+                        &mut self.state,
+                        input,
+                        &self.pad_and_add,
+                        &self.mds_matrix,
+                        &self.round_constants,
+                    ));
+                }
+                Sponge::Squeezing(ref mut output) => {
+                    for entry in output.iter_mut() {
+                        if let Some(e) = entry.take() {
+                            return e;
+                        }
+                    }
+
+                    // We've already squeezed out all available elements
+                    self.sponge = Sponge::Absorbing([None; RATE]);
+                }
+            }
+        }
+    }
+}
+
+/// A domain in which a Poseidon hash function is being used.
+pub trait Domain<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize>:
+    Copy + fmt::Debug
+{
+    /// The initial capacity element, encoding this domain.
+    fn initial_capacity_element(&self) -> F;
+
+    fn padding(&self) -> SpongeState<F, RATE>;
+
+    /// Returns a function that will update the given state with the given input to a
+    /// duplex permutation round, applying padding according to this domain specification.
+    fn pad_and_add(&self) -> Box<dyn Fn(&mut State<F, T>, &SpongeState<F, RATE>)>;
+}
+
+/// A Poseidon hash function used with constant input length.
+///
+/// Domain specified in section 4.2 of https://eprint.iacr.org/2019/458.pdf
+#[derive(Clone, Copy, Debug)]
+pub struct ConstantLength<const L: usize>;
+
+impl<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize, const L: usize>
+    Domain<F, S, T, RATE> for ConstantLength<L>
+{
+    fn initial_capacity_element(&self) -> 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(&self) -> SpongeState<F, RATE> {
+        // For constant-input-length hashing, padding consists of the field elements being
+        // zero.
+        let mut padding = [None; RATE];
+        for word in padding.iter_mut().skip(L) {
+            *word = Some(F::zero());
+        }
+        padding
+    }
+
+    fn pad_and_add(&self) -> Box<dyn Fn(&mut State<F, T>, &SpongeState<F, RATE>)> {
+        Box::new(|state, 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()) {
+                // For constant-input-length hashing, padding consists of the field
+                // elements being zero, so we don't add anything to the state.
+                if let Some(value) = value {
+                    *word += value;
+                }
+            }
+        })
+    }
+}
+
+/// A Poseidon hash function, built around a duplex sponge.
+pub struct Hash<
+    F: FieldExt,
+    S: Spec<F, T, RATE>,
+    D: Domain<F, S, T, RATE>,
+    const T: usize,
+    const RATE: usize,
+> {
+    duplex: Duplex<F, S, T, RATE>,
+    domain: D,
+}
+
+impl<
+        F: FieldExt,
+        S: Spec<F, T, RATE>,
+        D: Domain<F, S, T, RATE>,
+        const T: usize,
+        const RATE: usize,
+    > Hash<F, S, D, T, RATE>
+{
+    /// Initializes a new hasher.
+    pub fn init(spec: S, domain: D) -> Self {
+        Hash {
+            duplex: Duplex::new(
+                spec,
+                domain.initial_capacity_element(),
+                domain.pad_and_add(),
+            ),
+            domain,
+        }
+    }
+}
+
+impl<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize, const L: usize>
+    Hash<F, S, ConstantLength<L>, T, RATE>
+{
+    /// Hashes the given input.
+    pub fn hash(mut self, message: [F; L]) -> F {
+        for value in array::IntoIter::new(message) {
+            self.duplex.absorb(value);
+        }
+        self.duplex.squeeze()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use halo2::arithmetic::FieldExt;
+    use pasta_curves::pallas;
+
+    use super::{permute, ConstantLength, Hash, OrchardNullifier, Spec};
+
+    #[test]
+    fn orchard_spec_equivalence() {
+        let message = [pallas::Base::from_u64(6), pallas::Base::from_u64(42)];
+
+        let (round_constants, mds, _) = OrchardNullifier.constants();
+
+        let hasher = Hash::init(OrchardNullifier, ConstantLength);
+        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);
+    }
+}

+ 193 - 0
examples/halo2/src/primitives/poseidon/grain.rs

@@ -0,0 +1,193 @@
+//! The Grain LFSR in self-shrinking mode, as used by Poseidon.
+
+use std::marker::PhantomData;
+
+use bitvec::prelude::*;
+use halo2::arithmetic::FieldExt;
+
+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<F: FieldExt> {
+    state: BitArr!(for 80, in Msb0, u8),
+    next_bit: usize,
+    _field: PhantomData<F>,
+}
+
+impl<F: FieldExt> Grain<F> {
+    pub(super) fn new(sbox: SboxType, t: u16, r_f: u16, r_p: u16) -> Self {
+        // Initialize the LFSR state.
+        let mut state = bitarr![Msb0, u8; 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(bytes) {
+                break f;
+            }
+        }
+    }
+
+    /// 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_bytes_wide(&bytes)
+    }
+}
+
+impl<F: FieldExt> Iterator for Grain<F> {
+    type Item = bool;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        // 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::<Fp>::new(SboxType::Pow, 3, 8, 56);
+        let _f = grain.next_field_element();
+    }
+}

+ 123 - 0
examples/halo2/src/primitives/poseidon/mds.rs

@@ -0,0 +1,123 @@
+use halo2::arithmetic::FieldExt;
+
+use super::{grain::Grain, Mds};
+
+pub(super) fn generate_mds<F: FieldExt, const T: usize>(
+    grain: &mut Grain<F>,
+    mut select: usize,
+) -> (Mds<F, T>, Mds<F, T>) {
+    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());
+                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 can invert freely; by construction, the elements of xs are distinct.
+                acc * (x - x_m) * (x_j - x_m).invert().unwrap()
+            }
+        })
+    };
+    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 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::<Fp, T>(&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
+                );
+            }
+        }
+    }
+}

+ 1619 - 0
examples/halo2/src/primitives/poseidon/nullifier.rs

@@ -0,0 +1,1619 @@
+use halo2::arithmetic::Field;
+use pasta_curves::pallas;
+
+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 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 OrchardNullifier;
+
+impl Spec<pallas::Base, 3, 2> for OrchardNullifier {
+    fn full_rounds() -> usize {
+        8
+    }
+
+    fn partial_rounds() -> usize {
+        56
+    }
+
+    fn sbox(val: pallas::Base) -> pallas::Base {
+        val.pow_vartime(&[5])
+    }
+
+    fn secure_mds(&self) -> usize {
+        unimplemented!()
+    }
+
+    fn constants(
+        &self,
+    ) -> (
+        Vec<[pallas::Base; 3]>,
+        Mds<pallas::Base, 3>,
+        Mds<pallas::Base, 3>,
+    ) {
+        (ROUND_CONSTANTS[..].to_vec(), MDS, MDS_INV)
+    }
+}
+
+// $ sage generate_parameters_grain.sage 1 0 255 3 8 56 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001
+// Number of round constants: 192
+// Round constants for GF(p):
+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:
+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,
+        ]),
+    ],
+];
+
+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,
+        ]),
+    ],
+];
+
+#[cfg(test)]
+mod tests {
+    use std::marker::PhantomData;
+
+    use ff::PrimeField;
+    use halo2::arithmetic::FieldExt;
+    use pasta_curves::pallas;
+
+    use crate::primitives::poseidon::{permute, ConstantLength, Hash, Spec};
+
+    use super::{OrchardNullifier, MDS, MDS_INV, ROUND_CONSTANTS};
+
+    /// The same Poseidon specification as poseidon::OrchardNullifier, but constructed
+    /// such that its constants will be generated at runtime.
+    #[derive(Debug)]
+    pub struct P128Pow5T3<F: FieldExt> {
+        secure_mds: usize,
+        _field: PhantomData<F>,
+    }
+
+    impl<F: FieldExt> P128Pow5T3<F> {
+        pub fn new(secure_mds: usize) -> Self {
+            P128Pow5T3 {
+                secure_mds,
+                _field: PhantomData::default(),
+            }
+        }
+    }
+
+    impl<F: FieldExt> Spec<F, 3, 2> for P128Pow5T3<F> {
+        fn full_rounds() -> usize {
+            8
+        }
+
+        fn partial_rounds() -> usize {
+            56
+        }
+
+        fn sbox(val: F) -> F {
+            val.pow_vartime(&[5])
+        }
+
+        fn secure_mds(&self) -> usize {
+            self.secure_mds
+        }
+    }
+
+    #[test]
+    fn verify_constants() {
+        let poseidon = P128Pow5T3::<pallas::Base>::new(0);
+        let (round_constants, mds, mds_inv) = poseidon.constants();
+
+        for (actual, expected) in round_constants
+            .iter()
+            .flatten()
+            .zip(ROUND_CONSTANTS.iter().flatten())
+        {
+            assert_eq!(actual, expected);
+        }
+
+        for (actual, expected) in mds.iter().flatten().zip(MDS.iter().flatten()) {
+            assert_eq!(actual, expected);
+        }
+
+        for (actual, expected) in mds_inv.iter().flatten().zip(MDS_INV.iter().flatten()) {
+            assert_eq!(actual, expected);
+        }
+    }
+
+    #[test]
+    fn test_against_reference() {
+        // This is the test vector output by the reference code at
+        // <https://extgit.iaik.tugraz.at/krypto/hadeshash>, using parameters from
+        // `generate_parameters_grain.sage 1 0 255 3 8 56 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001`.
+
+        let mut input = [
+            pallas::Base::from_raw([
+                0x0000_0000_0000_0000,
+                0x0000_0000_0000_0000,
+                0x0000_0000_0000_0000,
+                0x0000_0000_0000_0000,
+            ]),
+            pallas::Base::from_raw([
+                0x0000_0000_0000_0001,
+                0x0000_0000_0000_0000,
+                0x0000_0000_0000_0000,
+                0x0000_0000_0000_0000,
+            ]),
+            pallas::Base::from_raw([
+                0x0000_0000_0000_0002,
+                0x0000_0000_0000_0000,
+                0x0000_0000_0000_0000,
+                0x0000_0000_0000_0000,
+            ]),
+        ];
+
+        let expected_output = [
+            pallas::Base::from_raw([
+                0xaeb1_bc02_4aec_a456,
+                0xf7e6_9a71_d0b6_42a0,
+                0x94ef_b364_f966_240f,
+                0x2a52_6acd_0b64_b453,
+            ]),
+            pallas::Base::from_raw([
+                0x012a_3e96_28e5_b82a,
+                0xdcd4_2e7f_bed9_dafe,
+                0x76ff_7dae_343d_5512,
+                0x13c5_d156_8b4a_a430,
+            ]),
+            pallas::Base::from_raw([
+                0x3590_29a1_d34e_9ddd,
+                0xf7cf_dfe1_bda4_2c7b,
+                0x256f_cd59_7984_561a,
+                0x0a49_c868_c697_6544,
+            ]),
+        ];
+
+        permute::<pallas::Base, P128Pow5T3<pallas::Base>, 3, 2>(&mut input, &MDS, &ROUND_CONSTANTS);
+        assert_eq!(input, expected_output);
+    }
+
+    #[test]
+    fn permute_test_vectors() {
+        let (round_constants, mds, _) = OrchardNullifier.constants();
+
+        for tv in crate::primitives::poseidon::test_vectors::permute() {
+            let mut state = [
+                pallas::Base::from_repr(tv.initial_state[0]).unwrap(),
+                pallas::Base::from_repr(tv.initial_state[1]).unwrap(),
+                pallas::Base::from_repr(tv.initial_state[2]).unwrap(),
+            ];
+
+            permute::<pallas::Base, OrchardNullifier, 3, 2>(&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::primitives::poseidon::test_vectors::hash() {
+            let message = [
+                pallas::Base::from_repr(tv.input[0]).unwrap(),
+                pallas::Base::from_repr(tv.input[1]).unwrap(),
+            ];
+
+            let result = Hash::init(OrchardNullifier, ConstantLength).hash(message);
+
+            assert_eq!(result.to_repr(), tv.output);
+        }
+    }
+}

+ 632 - 0
examples/halo2/src/primitives/poseidon/test_vectors.rs

@@ -0,0 +1,632 @@
+//! 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) fn permute() -> Vec<PermuteTestVector> {
+    use PermuteTestVector as TestVector;
+
+    // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_poseidon.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<HashTestVector> {
+    use HashTestVector as TestVector;
+
+    // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_poseidon_hash.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,
+            ],
+        },
+    ]
+}