mirror of https://github.com/zcash/halo2.git
Remove all uses of `PrimeField::Repr` in generic code
`PrimeField::from_repr` explicitly leaves the endianness opaque. We therefore can't use it in places we were using `FieldExt::from_bytes` (which was specifically little-endian) generically, but the previous commit replaced it everywhere. We now handle generic contexts on a case-by-case basis: - Where we needed to convert bitstrings into field elements, we now use double-and-add on the field elements directly instead of on bytes. This is less efficient, but visible correct (and a future change to the `ff` crate APIs could enable the more efficient version). - `INV_TWO_POW_K`, which is pre-computed for `pallas::Base`, was being incorrectly used in a field-generic circuit. We now compute it live. - `test_zs_and_us` was only used in tests, and hard-coded a field element encoding length of 32 bytes. It now uses Pallas concretely.
This commit is contained in:
parent
0378898289
commit
5dd7de3cc7
|
@ -3,7 +3,7 @@ use crate::circuit::gadget::{
|
||||||
ecc::{self, EccInstructions},
|
ecc::{self, EccInstructions},
|
||||||
utilities::Var,
|
utilities::Var,
|
||||||
};
|
};
|
||||||
use ff::PrimeField;
|
use group::ff::{Field, PrimeField};
|
||||||
use halo2::{circuit::Layouter, plonk::Error};
|
use halo2::{circuit::Layouter, plonk::Error};
|
||||||
use pasta_curves::arithmetic::CurveAffine;
|
use pasta_curves::arithmetic::CurveAffine;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
@ -194,25 +194,19 @@ where
|
||||||
|
|
||||||
// Closure to parse a bitstring (little-endian) into a base field element.
|
// Closure to parse a bitstring (little-endian) into a base field element.
|
||||||
let to_base_field = |bits: &[Option<bool>]| -> Option<C::Base> {
|
let to_base_field = |bits: &[Option<bool>]| -> Option<C::Base> {
|
||||||
assert!(bits.len() <= C::Base::NUM_BITS as usize);
|
// To simplify the following logic, require that the all-ones bitstring is
|
||||||
|
// canonical in the field, by not allowing a length of NUM_BITS.
|
||||||
|
assert!(bits.len() < C::Base::NUM_BITS as usize);
|
||||||
|
|
||||||
let bits: Option<Vec<bool>> = bits.iter().cloned().collect();
|
let bits: Option<Vec<bool>> = bits.iter().cloned().collect();
|
||||||
let bytes: Option<Vec<u8>> = bits.map(|bits| {
|
bits.map(|bits| {
|
||||||
// Pad bits to 256 bits
|
bits.into_iter().rev().fold(C::Base::zero(), |acc, bit| {
|
||||||
let pad_len = 256 - bits.len();
|
if bit {
|
||||||
let mut bits = bits;
|
acc.double() + C::Base::one()
|
||||||
bits.extend_from_slice(&vec![false; pad_len]);
|
} else {
|
||||||
|
acc.double()
|
||||||
bits.chunks_exact(8)
|
}
|
||||||
.map(|byte| byte.iter().rev().fold(0u8, |acc, bit| acc * 2 + *bit as u8))
|
})
|
||||||
.collect()
|
|
||||||
});
|
|
||||||
bytes.map(|bytes| {
|
|
||||||
let mut repr = <C::Base as PrimeField>::Repr::default();
|
|
||||||
// The above code assumes the byte representation is 256 bits.
|
|
||||||
assert_eq!(repr.as_ref().len(), 32);
|
|
||||||
repr.as_mut().copy_from_slice(&bytes);
|
|
||||||
C::Base::from_repr(repr).unwrap()
|
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -87,27 +87,24 @@ pub fn ternary<F: FieldExt>(a: Expression<F>, b: Expression<F>, c: Expression<F>
|
||||||
/// Takes a specified subsequence of the little-endian bit representation of a field element.
|
/// Takes a specified subsequence of the little-endian bit representation of a field element.
|
||||||
/// The bits are numbered from 0 for the LSB.
|
/// The bits are numbered from 0 for the LSB.
|
||||||
pub fn bitrange_subset<F: PrimeFieldBits>(field_elem: &F, bitrange: Range<usize>) -> F {
|
pub fn bitrange_subset<F: PrimeFieldBits>(field_elem: &F, bitrange: Range<usize>) -> F {
|
||||||
|
// We can allow a subsequence of length NUM_BITS, because
|
||||||
|
// field_elem.to_le_bits() returns canonical bitstrings.
|
||||||
assert!(bitrange.end <= F::NUM_BITS as usize);
|
assert!(bitrange.end <= F::NUM_BITS as usize);
|
||||||
|
|
||||||
let bits: Vec<bool> = field_elem
|
field_elem
|
||||||
.to_le_bits()
|
.to_le_bits()
|
||||||
.iter()
|
.iter()
|
||||||
.by_val()
|
.by_val()
|
||||||
.skip(bitrange.start)
|
.skip(bitrange.start)
|
||||||
.take(bitrange.end - bitrange.start)
|
.take(bitrange.end - bitrange.start)
|
||||||
.chain(std::iter::repeat(false))
|
.rev()
|
||||||
.take(256)
|
.fold(F::zero(), |acc, bit| {
|
||||||
.collect();
|
if bit {
|
||||||
let bytearray: Vec<u8> = bits
|
acc.double() + F::one()
|
||||||
.chunks_exact(8)
|
} else {
|
||||||
.map(|byte| byte.iter().rev().fold(0u8, |acc, bit| acc * 2 + *bit as u8))
|
acc.double()
|
||||||
.collect();
|
}
|
||||||
|
})
|
||||||
let mut repr = F::Repr::default();
|
|
||||||
// The above code assumes the byte representation is 256 bits.
|
|
||||||
assert_eq!(repr.as_ref().len(), 32);
|
|
||||||
repr.as_mut().copy_from_slice(&bytearray);
|
|
||||||
F::from_repr(repr).unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check that an expression is in the small range [0..range),
|
/// Check that an expression is in the small range [0..range),
|
||||||
|
|
|
@ -364,7 +364,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::LookupRangeCheckConfig;
|
use super::LookupRangeCheckConfig;
|
||||||
|
|
||||||
use crate::primitives::sinsemilla::{INV_TWO_POW_K, K};
|
use crate::primitives::sinsemilla::K;
|
||||||
use crate::spec::lebs2ip;
|
use crate::spec::lebs2ip;
|
||||||
use ff::{Field, PrimeFieldBits};
|
use ff::{Field, PrimeFieldBits};
|
||||||
use halo2::{
|
use halo2::{
|
||||||
|
@ -431,9 +431,7 @@ mod tests {
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
let expected_zs = {
|
let expected_zs = {
|
||||||
let mut repr = F::Repr::default();
|
let inv_two_pow_k = F::from(1 << K).invert().unwrap();
|
||||||
repr.as_mut().copy_from_slice(&INV_TWO_POW_K);
|
|
||||||
let inv_two_pow_k = F::from_repr(repr).unwrap();
|
|
||||||
chunks.iter().fold(vec![element], |mut zs, a_i| {
|
chunks.iter().fold(vec![element], |mut zs, a_i| {
|
||||||
// z_{i + 1} = (z_i - a_i) / 2^{K}
|
// z_{i + 1} = (z_i - a_i) / 2^{K}
|
||||||
let z = (zs[zs.len() - 1] - a_i) * inv_two_pow_k;
|
let z = (zs[zs.len() - 1] - a_i) * inv_two_pow_k;
|
||||||
|
|
|
@ -248,17 +248,15 @@ fn test_lagrange_coeffs<C: CurveAffine>(base: C, num_windows: usize) {
|
||||||
// 1. z + y = u^2,
|
// 1. z + y = u^2,
|
||||||
// 2. z - y is not a square
|
// 2. z - y is not a square
|
||||||
// for the y-coordinate of each fixed-base multiple in each window.
|
// for the y-coordinate of each fixed-base multiple in each window.
|
||||||
fn test_zs_and_us<C: CurveAffine>(base: C, z: &[u64], u: &[[[u8; 32]; H]], num_windows: usize) {
|
fn test_zs_and_us(base: pallas::Affine, z: &[u64], u: &[[[u8; 32]; H]], num_windows: usize) {
|
||||||
let window_table = compute_window_table(base, num_windows);
|
let window_table = compute_window_table(base, num_windows);
|
||||||
|
|
||||||
for ((u, z), window_points) in u.iter().zip(z.iter()).zip(window_table) {
|
for ((u, z), window_points) in u.iter().zip(z.iter()).zip(window_table) {
|
||||||
for (u, point) in u.iter().zip(window_points.iter()) {
|
for (u, point) in u.iter().zip(window_points.iter()) {
|
||||||
let y = *point.coordinates().unwrap().y();
|
let y = *point.coordinates().unwrap().y();
|
||||||
let mut u_repr = <C::Base as PrimeField>::Repr::default();
|
let u = pallas::Base::from_repr(*u).unwrap();
|
||||||
u_repr.as_mut().copy_from_slice(u);
|
assert_eq!(pallas::Base::from(*z) + y, u * u); // allow either square root
|
||||||
let u = C::Base::from_repr(u_repr).unwrap();
|
assert!(bool::from((pallas::Base::from(*z) - y).sqrt().is_none()));
|
||||||
assert_eq!(C::Base::from(*z) + y, u * u); // allow either square root
|
|
||||||
assert!(bool::from((C::Base::from(*z) - y).sqrt().is_none()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue