Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Guard ScalarField byte representations to always be little-endian #38

Merged
merged 1 commit into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions halo2-base/src/gates/flex_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1107,13 +1107,7 @@ impl<F: ScalarField> GateInstructions<F> for GateChip<F> {
a: AssignedValue<F>,
range_bits: usize,
) -> Vec<AssignedValue<F>> {
let a_bytes = a.value().to_repr();
let bits = a_bytes
.as_ref()
.iter()
.flat_map(|byte| (0..8u32).map(|i| (*byte as u64 >> i) & 1))
.map(|x| Witness(F::from(x)))
.take(range_bits);
let bits = a.value().to_u64_limbs(range_bits, 1).into_iter().map(|x| Witness(F::from(x)));

let mut bit_cells = Vec::with_capacity(range_bits);
let row_offset = ctx.advice.len();
Expand Down
2 changes: 1 addition & 1 deletion halo2-base/src/gates/tests/flex_gate_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ pub fn test_is_equal<F: ScalarField>(inputs: &[QuantumCell<F>]) -> F {
*a.value()
}

#[test_case((Fr::one(), 2) => vec![Fr::one(), Fr::zero()] ; "num_to_bits(): 1 -> [1, 0]")]
#[test_case((Fr::from(6u64), 3) => vec![Fr::zero(), Fr::one(), Fr::one()] ; "num_to_bits(): 6")]
pub fn test_num_to_bits<F: ScalarField>(input: (F, usize)) -> Vec<F> {
let mut builder = GateThreadBuilder::mock();
let ctx = builder.main(0);
Expand Down
15 changes: 15 additions & 0 deletions halo2-base/src/gates/tests/pos_prop_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,21 @@ proptest! {
prop_assert_eq!(result, ground_truth);
}

#[test]
fn prop_test_num_to_bits(num in any::<u64>()) {
let mut tmp = num;
let mut bits = vec![];
if num == 0 {
bits.push(0);
}
while tmp > 0 {
bits.push(tmp & 1);
tmp /= 2;
}
let result = flex_gate_tests::test_num_to_bits((Fr::from(num), bits.len()));
prop_assert_eq!(bits.into_iter().map(Fr::from).collect::<Vec<_>>(), result);
}

/*
#[test]
fn prop_test_lagrange_eval(inputs in vec(rand_fr(), 3)) {
Expand Down
1 change: 1 addition & 0 deletions halo2-base/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub mod utils;
/// Constant representing whether the Layouter calls `synthesize` once just to get region shape.
#[cfg(feature = "halo2-axiom")]
pub const SKIP_FIRST_PASS: bool = false;
/// Constant representing whether the Layouter calls `synthesize` once just to get region shape.
#[cfg(feature = "halo2-pse")]
pub const SKIP_FIRST_PASS: bool = true;

Expand Down
116 changes: 87 additions & 29 deletions halo2-base/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub trait BigPrimeField: ScalarField {
#[cfg(feature = "halo2-axiom")]
impl<F> BigPrimeField for F
where
F: FieldExt + Hash + Into<[u64; 4]> + From<[u64; 4]>,
F: ScalarField + From<[u64; 4]>, // Assume [u64; 4] is little-endian. We only implement ScalarField when this is true.
{
#[inline(always)]
fn from_u64_digits(val: &[u64]) -> Self {
Expand All @@ -30,38 +30,36 @@ where
}
}

/// Helper trait to convert to and from a [ScalarField] by decomposing its an field element into [u64] limbs.
/// Helper trait to represent a field element that can be converted into [u64] limbs.
///
/// Note: Since the number of bits necessary to represent a field element is larger than the number of bits in a u64, we decompose the bit representation of the field element into multiple [u64] values e.g. `limbs`.
#[cfg(feature = "halo2-axiom")]
/// Note: Since the number of bits necessary to represent a field element is larger than the number of bits in a u64, we decompose the integer representation of the field element into multiple [u64] values e.g. `limbs`.
pub trait ScalarField: FieldExt + Hash {
/// Returns the base `2<sup>bit_len</sup>` little endian representation of the [ScalarField] element up to `num_limbs` number of limbs (truncates any extra limbs).
///
/// Assumes `bit_len < 64`.
/// * `num_limbs`: number of limbs to return
/// * `bit_len`: number of bits in each limb
fn to_u64_limbs(self, num_limbs: usize, bit_len: usize) -> Vec<u64>;
}
#[cfg(feature = "halo2-axiom")]
impl<F> ScalarField for F
where
F: FieldExt + Hash + Into<[u64; 4]>,
{
#[inline(always)]
fn to_u64_limbs(self, num_limbs: usize, bit_len: usize) -> Vec<u64> {
// Basically same as `to_repr` but does not go further into bytes
let tmp: [u64; 4] = self.into();
decompose_u64_digits_to_limbs(tmp, num_limbs, bit_len)

/// Returns the little endian byte representation of the element.
fn to_bytes_le(&self) -> Vec<u8>;

/// Creates a field element from a little endian byte representation.
///
/// The default implementation assumes that `PrimeField::from_repr` is implemented for little-endian.
/// It should be overriden if this is not the case.
fn from_bytes_le(bytes: &[u8]) -> Self {
let mut repr = Self::Repr::default();
repr.as_mut()[..bytes.len()].copy_from_slice(bytes);
Self::from_repr(repr).unwrap()
}
}
// See below for implementations

// Later: will need to separate BigPrimeField from ScalarField when Goldilocks is introduced

#[cfg(feature = "halo2-pse")]
pub trait BigPrimeField = FieldExt<Repr = [u8; 32]> + Hash;

#[cfg(feature = "halo2-pse")]
pub trait ScalarField = FieldExt + Hash;
pub trait BigPrimeField = FieldExt<Repr = [u8; 32]> + ScalarField;

/// Converts an [Iterator] of u64 digits into `number_of_limbs` limbs of `bit_len` bits returned as a [Vec].
///
Expand Down Expand Up @@ -149,10 +147,8 @@ pub fn biguint_to_fe<F: BigPrimeField>(e: &BigUint) -> F {

#[cfg(feature = "halo2-pse")]
{
let mut repr = F::Repr::default();
let bytes = e.to_bytes_le();
repr.as_mut()[..bytes.len()].copy_from_slice(&bytes);
F::from_repr(repr).unwrap()
F::from_bytes_le(&bytes)
}
}

Expand All @@ -171,9 +167,7 @@ pub fn bigint_to_fe<F: BigPrimeField>(e: &BigInt) -> F {
#[cfg(feature = "halo2-pse")]
{
let (sign, bytes) = e.to_bytes_le();
let mut repr = F::Repr::default();
repr.as_mut()[..bytes.len()].copy_from_slice(&bytes);
let f_abs = F::from_repr(repr).unwrap();
let f_abs = F::from_bytes_le(&bytes);
if sign == Sign::Minus {
-f_abs
} else {
Expand All @@ -184,12 +178,14 @@ pub fn bigint_to_fe<F: BigPrimeField>(e: &BigInt) -> F {

/// Converts an immutable reference to an PrimeField element into a [BigUint] element.
/// * `fe`: immutable reference to PrimeField element to convert
pub fn fe_to_biguint<F: ff::PrimeField>(fe: &F) -> BigUint {
BigUint::from_bytes_le(fe.to_repr().as_ref())
pub fn fe_to_biguint<F: ScalarField>(fe: &F) -> BigUint {
BigUint::from_bytes_le(fe.to_bytes_le().as_ref())
}

/// Converts an immutable reference to a [BigPrimeField] element into a [BigInt] element.
/// * `fe`: immutable reference to [BigPrimeField] element to convert
/// Converts a [BigPrimeField] element into a [BigInt] element by sending `fe` in `[0, F::modulus())` to
/// ```
/// fe < F::modulus() / 2 ? fe : fe - F::modulus()
/// ```
pub fn fe_to_bigint<F: BigPrimeField>(fe: &F) -> BigInt {
// TODO: `F` should just have modulus as lazy_static or something
let modulus = modulus::<F>();
Expand Down Expand Up @@ -332,6 +328,7 @@ pub fn compose(input: Vec<BigUint>, bit_len: usize) -> BigUint {
#[cfg(feature = "halo2-axiom")]
pub use halo2_proofs_axiom::halo2curves::CurveAffineExt;

/// Helper trait
#[cfg(feature = "halo2-pse")]
pub trait CurveAffineExt: CurveAffine {
/// Unlike the `Coordinates` trait, this just returns the raw affine (X, Y) coordinantes without checking `is_on_curve`
Expand All @@ -343,6 +340,67 @@ pub trait CurveAffineExt: CurveAffine {
#[cfg(feature = "halo2-pse")]
impl<C: CurveAffine> CurveAffineExt for C {}

mod scalar_field_impls {
use super::{decompose_u64_digits_to_limbs, ScalarField};
use crate::halo2_proofs::halo2curves::{
bn256::{Fq as bn254Fq, Fr as bn254Fr},
secp256k1::{Fp as secpFp, Fq as secpFq},
};
#[cfg(feature = "halo2-pse")]
use ff::PrimeField;

/// To ensure `ScalarField` is only implemented for `ff:Field` where `Repr` is little endian, we use the following macro
/// to implement the trait for each field.
#[cfg(feature = "halo2-axiom")]
#[macro_export]
macro_rules! impl_scalar_field {
($field:ident) => {
impl ScalarField for $field {
#[inline(always)]
fn to_u64_limbs(self, num_limbs: usize, bit_len: usize) -> Vec<u64> {
// Basically same as `to_repr` but does not go further into bytes
let tmp: [u64; 4] = self.into();
decompose_u64_digits_to_limbs(tmp, num_limbs, bit_len)
}

#[inline(always)]
fn to_bytes_le(&self) -> Vec<u8> {
let tmp: [u64; 4] = (*self).into();
tmp.iter().flat_map(|x| x.to_le_bytes()).collect()
}
}
};
}

/// To ensure `ScalarField` is only implemented for `ff:Field` where `Repr` is little endian, we use the following macro
/// to implement the trait for each field.
#[cfg(feature = "halo2-pse")]
#[macro_export]
macro_rules! impl_scalar_field {
($field:ident) => {
impl ScalarField for $field {
#[inline(always)]
fn to_u64_limbs(self, num_limbs: usize, bit_len: usize) -> Vec<u64> {
let bytes = self.to_repr();
let digits = (0..4)
.map(|i| u64::from_le_bytes(bytes[i * 8..(i + 1) * 8].try_into().unwrap()));
decompose_u64_digits_to_limbs(digits, num_limbs, bit_len)
}

#[inline(always)]
fn to_bytes_le(&self) -> Vec<u8> {
self.to_repr().to_vec()
}
}
};
}

impl_scalar_field!(bn254Fr);
impl_scalar_field!(bn254Fq);
impl_scalar_field!(secpFp);
impl_scalar_field!(secpFq);
}

/// Module for reading parameters for Halo2 proving system from the file system.
pub mod fs {
use std::{
Expand Down
2 changes: 1 addition & 1 deletion hashes/zkevm-keccak/src/keccak_packed_multi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1941,7 +1941,7 @@ pub fn keccak_phase0<F: Field>(
.take(4)
.map(|a| {
pack_with_base::<F>(&unpack(a[0]), 2)
.to_repr()
.to_bytes_le()
.into_iter()
.take(8)
.collect::<Vec<_>>()
Expand Down
8 changes: 4 additions & 4 deletions hashes/zkevm-keccak/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ pub fn pack_part(bits: &[u8], info: &PartInfo) -> u64 {
/// Unpack a sparse keccak word into bits in the range [0,BIT_SIZE[
pub fn unpack<F: Field>(packed: F) -> [u8; NUM_BITS_PER_WORD] {
let mut bits = [0; NUM_BITS_PER_WORD];
let packed = Word::from_little_endian(packed.to_repr().as_ref());
let packed = Word::from_little_endian(packed.to_bytes_le().as_ref());
let mask = Word::from(BIT_SIZE - 1);
for (idx, bit) in bits.iter_mut().enumerate() {
*bit = ((packed >> (idx * BIT_COUNT)) & mask).as_u32() as u8;
Expand All @@ -200,10 +200,10 @@ pub fn pack_u64<F: Field>(value: u64) -> F {
/// Calculates a ^ b with a and b field elements
pub fn field_xor<F: Field>(a: F, b: F) -> F {
let mut bytes = [0u8; 32];
for (idx, (a, b)) in a.to_repr().as_ref().iter().zip(b.to_repr().as_ref().iter()).enumerate() {
bytes[idx] = *a ^ *b;
for (idx, (a, b)) in a.to_bytes_le().into_iter().zip(b.to_bytes_le()).enumerate() {
bytes[idx] = a ^ b;
}
F::from_repr(bytes).unwrap()
F::from_bytes_le(&bytes)
}

/// Returns the size (in bits) of each part size when splitting up a keccak word
Expand Down
4 changes: 2 additions & 2 deletions hashes/zkevm-keccak/src/util/eth_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl<F: Field> ToScalar<F> for U256 {
fn to_scalar(&self) -> Option<F> {
let mut bytes = [0u8; 32];
self.to_little_endian(&mut bytes);
F::from_repr(bytes).into()
Some(F::from_bytes_le(&bytes))
}
}

Expand Down Expand Up @@ -113,7 +113,7 @@ impl<F: Field> ToScalar<F> for Address {
let mut bytes = [0u8; 32];
bytes[32 - Self::len_bytes()..].copy_from_slice(self.as_bytes());
bytes.reverse();
F::from_repr(bytes).into()
Some(F::from_bytes_le(&bytes))
}
}

Expand Down