From 1a0582bfbbc14ee311da79da2b6518b122344b6a Mon Sep 17 00:00:00 2001 From: Tom French Date: Sun, 26 May 2024 05:34:07 +0100 Subject: [PATCH 1/3] chore: break up impls for more granular traits --- acvm-repo/acir/src/circuit/mod.rs | 4 +- .../acir/src/native_types/expression/mod.rs | 130 +++++------------- .../src/native_types/expression/ordering.rs | 8 +- acvm-repo/acir/src/native_types/witness.rs | 4 - .../acvm/src/compiler/transformers/csat.rs | 57 +++++++- acvm-repo/acvm/src/pwg/mod.rs | 2 +- acvm-repo/brillig_vm/src/memory.rs | 44 +++--- .../src/ssa/acir_gen/acir_ir/acir_variable.rs | 12 +- 8 files changed, 124 insertions(+), 137 deletions(-) diff --git a/acvm-repo/acir/src/circuit/mod.rs b/acvm-repo/acir/src/circuit/mod.rs index 7632afda423..7f3c1890717 100644 --- a/acvm-repo/acir/src/circuit/mod.rs +++ b/acvm-repo/acir/src/circuit/mod.rs @@ -204,7 +204,7 @@ impl FromStr for OpcodeLocation { } } -impl Circuit { +impl Circuit { pub fn num_vars(&self) -> u32 { self.current_witness_index + 1 } @@ -425,7 +425,7 @@ mod tests { }; let program = Program { functions: vec![circuit], unconstrained_functions: Vec::new() }; - fn read_write Deserialize<'a>>( + fn read_write Deserialize<'a>>( program: Program, ) -> (Program, Program) { let bytes = Program::serialize_program(&program); diff --git a/acvm-repo/acir/src/native_types/expression/mod.rs b/acvm-repo/acir/src/native_types/expression/mod.rs index b34862429e7..1feda5703c8 100644 --- a/acvm-repo/acir/src/native_types/expression/mod.rs +++ b/acvm-repo/acir/src/native_types/expression/mod.rs @@ -42,29 +42,12 @@ impl std::fmt::Display for Expression { } } -impl Expression { - // TODO: possibly remove, and move to noir repo. - pub const fn can_defer_constraint(&self) -> bool { - false - } - +impl Expression { /// Returns the number of multiplication terms pub fn num_mul_terms(&self) -> usize { self.mul_terms.len() } - pub fn from_field(q_c: F) -> Self { - Self { q_c, ..Default::default() } - } - - pub fn one() -> Self { - Self::from_field(F::one()) - } - - pub fn zero() -> Self { - Self::default() - } - /// Adds a new linear term to the `Expression`. pub fn push_addition_term(&mut self, coefficient: F, variable: Witness) { self.linear_combinations.push((coefficient, variable)); @@ -85,6 +68,19 @@ impl Expression { self.mul_terms.is_empty() && self.linear_combinations.is_empty() } + /// Returns a `FieldElement` if the expression represents a constant polynomial. + /// Otherwise returns `None`. + /// + /// Examples: + /// - f(x,y) = x would return `None` + /// - f(x,y) = x + 6 would return `None` + /// - f(x,y) = 2*y + 6 would return `None` + /// - f(x,y) = x + y would return `None` + /// - f(x,y) = 5 would return `FieldElement(5)` + pub fn to_const(&self) -> Option<&F> { + self.is_const().then_some(&self.q_c) + } + /// Returns `true` if highest degree term in the expression is one or less. /// /// - `mul_term` in an expression contains degree-2 terms @@ -122,21 +118,29 @@ impl Expression { self.is_linear() && self.linear_combinations.len() == 1 } + /// Sorts opcode in a deterministic order + /// XXX: We can probably make this more efficient by sorting on each phase. We only care if it is deterministic + pub fn sort(&mut self) { + self.mul_terms.sort_by(|a, b| a.1.cmp(&b.1).then(a.2.cmp(&b.2))); + self.linear_combinations.sort_by(|a, b| a.1.cmp(&b.1)); + } +} + +impl Expression { + pub fn from_field(q_c: F) -> Self { + Self { q_c, ..Default::default() } + } + + pub fn zero() -> Self { + Self::default() + } + pub fn is_zero(&self) -> bool { *self == Self::zero() } - /// Returns a `FieldElement` if the expression represents a constant polynomial. - /// Otherwise returns `None`. - /// - /// Examples: - /// - f(x,y) = x would return `None` - /// - f(x,y) = x + 6 would return `None` - /// - f(x,y) = 2*y + 6 would return `None` - /// - f(x,y) = x + y would return `None` - /// - f(x,y) = 5 would return `FieldElement(5)` - pub fn to_const(&self) -> Option { - self.is_const().then_some(self.q_c) + pub fn one() -> Self { + Self::from_field(F::one()) } /// Returns a `Witness` if the `Expression` can be represented as a degree-1 @@ -161,74 +165,6 @@ impl Expression { None } - /// Sorts opcode in a deterministic order - /// XXX: We can probably make this more efficient by sorting on each phase. We only care if it is deterministic - pub fn sort(&mut self) { - self.mul_terms.sort_by(|a, b| a.1.cmp(&b.1).then(a.2.cmp(&b.2))); - self.linear_combinations.sort_by(|a, b| a.1.cmp(&b.1)); - } - - /// Checks if this expression can fit into one arithmetic identity - /// TODO: This needs to be reworded, arithmetic identity only makes sense in the context - /// TODO of PLONK, whereas we want expressions to be generic. - /// TODO: We just need to reword it to say exactly what its doing and - /// TODO then reference the fact that this is what plonk will accept. - /// TODO alternatively, we can define arithmetic identity in the context of expressions - /// TODO and then reference that. - pub fn fits_in_one_identity(&self, width: usize) -> bool { - // A Polynomial with more than one mul term cannot fit into one opcode - if self.mul_terms.len() > 1 { - return false; - }; - // A Polynomial with more terms than fan-in cannot fit within a single opcode - if self.linear_combinations.len() > width { - return false; - } - - // A polynomial with no mul term and a fan-in that fits inside of the width can fit into a single opcode - if self.mul_terms.is_empty() { - return true; - } - - // A polynomial with width-2 fan-in terms and a single non-zero mul term can fit into one opcode - // Example: Axy + Dz . Notice, that the mul term places a constraint on the first two terms, but not the last term - // XXX: This would change if our arithmetic polynomial equation was changed to Axyz for example, but for now it is not. - if self.linear_combinations.len() <= (width - 2) { - return true; - } - - // We now know that we have a single mul term. We also know that the mul term must match up with two other terms - // A polynomial whose mul terms are non zero which do not match up with two terms in the fan-in cannot fit into one opcode - // An example of this is: Axy + Bx + Cy + ... - // Notice how the bivariate monomial xy has two univariate monomials with their respective coefficients - // XXX: note that if x or y is zero, then we could apply a further optimization, but this would be done in another algorithm. - // It would be the same as when we have zero coefficients - Can only work if wire is constrained to be zero publicly - let mul_term = &self.mul_terms[0]; - - // The coefficient should be non-zero, as this method is ran after the compiler removes all zero coefficient terms - assert_ne!(mul_term.0, F::zero()); - - let mut found_x = false; - let mut found_y = false; - - for term in self.linear_combinations.iter() { - let witness = &term.1; - let x = &mul_term.1; - let y = &mul_term.2; - if witness == x { - found_x = true; - }; - if witness == y { - found_y = true; - }; - if found_x & found_y { - break; - } - } - - found_x & found_y - } - /// Returns `self + k*b` pub fn add_mul(&self, k: F, b: &Self) -> Self { if k.is_zero() { diff --git a/acvm-repo/acir/src/native_types/expression/ordering.rs b/acvm-repo/acir/src/native_types/expression/ordering.rs index b0e5e88454f..f1d217b3bd9 100644 --- a/acvm-repo/acir/src/native_types/expression/ordering.rs +++ b/acvm-repo/acir/src/native_types/expression/ordering.rs @@ -1,5 +1,3 @@ -use acir_field::AcirField; - use crate::native_types::Witness; use std::cmp::Ordering; @@ -8,7 +6,7 @@ use super::Expression; // TODO: It's undecided whether `Expression` should implement `Ord/PartialOrd`. // This is currently used in ACVM in the compiler. -impl Ord for Expression { +impl Ord for Expression { fn cmp(&self, other: &Self) -> Ordering { let mut i1 = self.get_max_idx(); let mut i2 = other.get_max_idx(); @@ -25,7 +23,7 @@ impl Ord for Expression { } } -impl PartialOrd for Expression { +impl PartialOrd for Expression { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } @@ -37,7 +35,7 @@ struct WitnessIdx { second_term: bool, } -impl Expression { +impl Expression { fn get_max_idx(&self) -> WitnessIdx { WitnessIdx { linear: self.linear_combinations.len(), diff --git a/acvm-repo/acir/src/native_types/witness.rs b/acvm-repo/acir/src/native_types/witness.rs index 3e9beb510bc..a570968f948 100644 --- a/acvm-repo/acir/src/native_types/witness.rs +++ b/acvm-repo/acir/src/native_types/witness.rs @@ -17,10 +17,6 @@ impl Witness { // This is safe as long as the architecture is 32bits minimum self.0 as usize } - - pub const fn can_defer_constraint(&self) -> bool { - true - } } impl From for Witness { diff --git a/acvm-repo/acvm/src/compiler/transformers/csat.rs b/acvm-repo/acvm/src/compiler/transformers/csat.rs index 6cf74c04205..f2a3cc2c84e 100644 --- a/acvm-repo/acvm/src/compiler/transformers/csat.rs +++ b/acvm-repo/acvm/src/compiler/transformers/csat.rs @@ -326,7 +326,7 @@ impl CSatTransformer { // First check if this polynomial actually needs a partial opcode optimization // There is the chance that it fits perfectly within the assert-zero opcode - if opcode.fits_in_one_identity(self.width) { + if fits_in_one_identity(&opcode, self.width) { return opcode; } @@ -409,6 +409,61 @@ impl CSatTransformer { } } +/// Checks if this expression can fit into one arithmetic identity +fn fits_in_one_identity(expr: &Expression, width: usize) -> bool { + // A Polynomial with more than one mul term cannot fit into one opcode + if expr.mul_terms.len() > 1 { + return false; + }; + // A Polynomial with more terms than fan-in cannot fit within a single opcode + if expr.linear_combinations.len() > width { + return false; + } + + // A polynomial with no mul term and a fan-in that fits inside of the width can fit into a single opcode + if expr.mul_terms.is_empty() { + return true; + } + + // A polynomial with width-2 fan-in terms and a single non-zero mul term can fit into one opcode + // Example: Axy + Dz . Notice, that the mul term places a constraint on the first two terms, but not the last term + // XXX: This would change if our arithmetic polynomial equation was changed to Axyz for example, but for now it is not. + if expr.linear_combinations.len() <= (width - 2) { + return true; + } + + // We now know that we have a single mul term. We also know that the mul term must match up with two other terms + // A polynomial whose mul terms are non zero which do not match up with two terms in the fan-in cannot fit into one opcode + // An example of this is: Axy + Bx + Cy + ... + // Notice how the bivariate monomial xy has two univariate monomials with their respective coefficients + // XXX: note that if x or y is zero, then we could apply a further optimization, but this would be done in another algorithm. + // It would be the same as when we have zero coefficients - Can only work if wire is constrained to be zero publicly + let mul_term = &expr.mul_terms[0]; + + // The coefficient should be non-zero, as this method is ran after the compiler removes all zero coefficient terms + assert_ne!(mul_term.0, F::zero()); + + let mut found_x = false; + let mut found_y = false; + + for term in expr.linear_combinations.iter() { + let witness = &term.1; + let x = &mul_term.1; + let y = &mul_term.2; + if witness == x { + found_x = true; + }; + if witness == y { + found_y = true; + }; + if found_x & found_y { + break; + } + } + + found_x & found_y +} + #[cfg(test)] mod tests { use super::*; diff --git a/acvm-repo/acvm/src/pwg/mod.rs b/acvm-repo/acvm/src/pwg/mod.rs index da4510db63a..4f88e17d109 100644 --- a/acvm-repo/acvm/src/pwg/mod.rs +++ b/acvm-repo/acvm/src/pwg/mod.rs @@ -637,7 +637,7 @@ pub fn get_value( ) -> Result> { let expr = ExpressionSolver::evaluate(expr, initial_witness); match expr.to_const() { - Some(value) => Ok(value), + Some(value) => Ok(*value), None => Err(OpcodeResolutionError::OpcodeNotSolvable( OpcodeNotSolvable::MissingAssignment(any_witness_from_expression(&expr).unwrap().0), )), diff --git a/acvm-repo/brillig_vm/src/memory.rs b/acvm-repo/brillig_vm/src/memory.rs index 4092cd06ae0..cc33eb5d6cf 100644 --- a/acvm-repo/brillig_vm/src/memory.rs +++ b/acvm-repo/brillig_vm/src/memory.rs @@ -16,6 +16,29 @@ pub enum MemoryTypeError { MismatchedBitSize { value_bit_size: u32, expected_bit_size: u32 }, } +impl MemoryValue { + /// Builds a field-typed memory value. + pub fn new_field(value: F) -> Self { + MemoryValue::Field(value) + } + + /// Extracts the field element from the memory value, if it is typed as field element. + pub fn extract_field(&self) -> Option<&F> { + match self { + MemoryValue::Field(value) => Some(value), + _ => None, + } + } + + /// Extracts the integer from the memory value, if it is typed as integer. + pub fn extract_integer(&self) -> Option<(&BigUint, u32)> { + match self { + MemoryValue::Integer(value, bit_size) => Some((value, *bit_size)), + _ => None, + } + } +} + impl MemoryValue { /// Builds a memory value from a field element. pub fn new_from_field(value: F, bit_size: u32) -> Self { @@ -44,11 +67,6 @@ impl MemoryValue { Some(MemoryValue::new_from_field(value, bit_size)) } - /// Builds a field-typed memory value. - pub fn new_field(value: F) -> Self { - MemoryValue::Field(value) - } - /// Builds an integer-typed memory value. pub fn new_integer(value: BigUint, bit_size: u32) -> Self { assert!( @@ -58,22 +76,6 @@ impl MemoryValue { MemoryValue::Integer(value, bit_size) } - /// Extracts the field element from the memory value, if it is typed as field element. - pub fn extract_field(&self) -> Option<&F> { - match self { - MemoryValue::Field(value) => Some(value), - _ => None, - } - } - - /// Extracts the integer from the memory value, if it is typed as integer. - pub fn extract_integer(&self) -> Option<(&BigUint, u32)> { - match self { - MemoryValue::Integer(value, bit_size) => Some((value, *bit_size)), - _ => None, - } - } - /// Converts the memory value to a field element, independent of its type. pub fn to_field(&self) -> F { match self { diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index 4a0f9f798ff..0c826cc8a38 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -261,7 +261,7 @@ impl AcirContext { // Check if a witness has been assigned this value already, if so reuse it. *self .constant_witnesses - .entry(constant) + .entry(*constant) .or_insert_with(|| self.acir_ir.get_or_create_witness(&expression)) } else { self.acir_ir.get_or_create_witness(&expression) @@ -724,7 +724,7 @@ impl AcirContext { // If `lhs` and `rhs` are known constants then we can calculate the result at compile time. // `rhs` must be non-zero. - (Some(lhs_const), Some(rhs_const), _) if rhs_const != FieldElement::zero() => { + (Some(lhs_const), Some(rhs_const), _) if !rhs_const.is_zero() => { let quotient = lhs_const.to_u128() / rhs_const.to_u128(); let remainder = lhs_const.to_u128() - quotient * rhs_const.to_u128(); @@ -734,7 +734,7 @@ impl AcirContext { } // If `rhs` is one then the division is a noop. - (_, Some(rhs_const), _) if rhs_const == FieldElement::one() => { + (_, Some(rhs_const), _) if rhs_const.is_one() => { return Ok((lhs, zero)); } @@ -1949,7 +1949,7 @@ impl From> for AcirVarData { fn from(expr: Expression) -> Self { // Prefer simpler variants if possible. if let Some(constant) = expr.to_const() { - AcirVarData::from(constant) + AcirVarData::from(*constant) } else if let Some(witness) = expr.to_witness() { AcirVarData::from(witness) } else { @@ -1979,12 +1979,12 @@ fn execute_brillig( for input in inputs { match input { BrilligInputs::Single(expr) => { - calldata.push(expr.to_const()?); + calldata.push(*expr.to_const()?); } BrilligInputs::Array(expr_arr) => { // Attempt to fetch all array input values for expr in expr_arr.iter() { - calldata.push(expr.to_const()?); + calldata.push(*expr.to_const()?); } } BrilligInputs::MemoryArray(_) => { From c463f2c5ccee96b5b99aa274910b2a9fba8527a6 Mon Sep 17 00:00:00 2001 From: Tom French Date: Sun, 26 May 2024 18:15:54 +0100 Subject: [PATCH 2/3] chore: separate trait from field implementation --- acvm-repo/acir_field/src/field_element.rs | 544 +++++++++++++++++++++ acvm-repo/acir_field/src/generic_ark.rs | 565 +--------------------- acvm-repo/acir_field/src/lib.rs | 8 +- 3 files changed, 559 insertions(+), 558 deletions(-) create mode 100644 acvm-repo/acir_field/src/field_element.rs diff --git a/acvm-repo/acir_field/src/field_element.rs b/acvm-repo/acir_field/src/field_element.rs new file mode 100644 index 00000000000..245ce7af1b3 --- /dev/null +++ b/acvm-repo/acir_field/src/field_element.rs @@ -0,0 +1,544 @@ +use ark_ff::PrimeField; +use ark_ff::Zero; +use num_bigint::BigUint; +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; +use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; + +use crate::AcirField; + +// XXX: Switch out for a trait and proper implementations +// This implementation is in-efficient, can definitely remove hex usage and Iterator instances for trivial functionality +#[derive(Default, Clone, Copy, Eq, PartialOrd, Ord)] +pub struct FieldElement(F); + +impl std::fmt::Display for FieldElement { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + // First check if the number is zero + // + let number = BigUint::from_bytes_be(&self.to_be_bytes()); + if number == BigUint::zero() { + return write!(f, "0"); + } + // Check if the negative version is smaller to represent + // + let minus_number = BigUint::from_bytes_be(&(self.neg()).to_be_bytes()); + let (smaller_repr, is_negative) = + if minus_number.to_string().len() < number.to_string().len() { + (minus_number, true) + } else { + (number, false) + }; + if is_negative { + write!(f, "-")?; + } + + // Number of bits needed to represent the smaller representation + let num_bits = smaller_repr.bits(); + + // Check if the number represents a power of 2 + if smaller_repr.count_ones() == 1 { + let mut bit_index = 0; + for i in 0..num_bits { + if smaller_repr.bit(i) { + bit_index = i; + break; + } + } + return match bit_index { + 0 => write!(f, "1"), + 1 => write!(f, "2"), + 2 => write!(f, "4"), + 3 => write!(f, "8"), + _ => write!(f, "2{}", superscript(bit_index)), + }; + } + + // Check if number is a multiple of a power of 2. + // This is used because when computing the quotient + // we usually have numbers in the form 2^t * q + r + // We focus on 2^64, 2^32, 2^16, 2^8, 2^4 because + // they are common. We could extend this to a more + // general factorization strategy, but we pay in terms of CPU time + let mul_sign = "×"; + for power in [64, 32, 16, 8, 4] { + let power_of_two = BigUint::from(2_u128).pow(power); + if &smaller_repr % &power_of_two == BigUint::zero() { + return write!( + f, + "2{}{}{}", + superscript(power as u64), + mul_sign, + smaller_repr / &power_of_two, + ); + } + } + write!(f, "{smaller_repr}") + } +} + +impl std::fmt::Debug for FieldElement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self, f) + } +} + +impl std::hash::Hash for FieldElement { + fn hash(&self, state: &mut H) { + state.write(&self.to_be_bytes()); + } +} + +impl PartialEq for FieldElement { + fn eq(&self, other: &Self) -> bool { + self.to_be_bytes() == other.to_be_bytes() + } +} + +impl From for FieldElement { + fn from(mut a: i128) -> FieldElement { + let mut negative = false; + if a < 0 { + a = -a; + negative = true; + } + + let mut result = match F::from_str(&a.to_string()) { + Ok(result) => result, + Err(_) => panic!("Cannot convert i128 as a string to a field element"), + }; + + if negative { + result = -result; + } + FieldElement(result) + } +} + +impl Serialize for FieldElement { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_hex().serialize(serializer) + } +} + +impl<'de, T: ark_ff::PrimeField> Deserialize<'de> for FieldElement { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s: Cow<'de, str> = Deserialize::deserialize(deserializer)?; + match Self::from_hex(&s) { + Some(value) => Ok(value), + None => Err(serde::de::Error::custom(format!("Invalid hex for FieldElement: {s}",))), + } + } +} + +impl From for FieldElement { + fn from(a: u128) -> FieldElement { + let result = match F::from_str(&a.to_string()) { + Ok(result) => result, + Err(_) => panic!("Cannot convert u128 as a string to a field element"), + }; + FieldElement(result) + } +} + +impl From for FieldElement { + fn from(a: usize) -> FieldElement { + FieldElement::from(a as u128) + } +} + +impl From for FieldElement { + fn from(boolean: bool) -> FieldElement { + if boolean { + FieldElement::one() + } else { + FieldElement::zero() + } + } +} + +impl FieldElement { + pub fn from_repr(field: F) -> Self { + Self(field) + } + + // XXX: This method is used while this field element + // implementation is not generic. + pub fn into_repr(self) -> F { + self.0 + } + + fn is_negative(&self) -> bool { + self.neg().num_bits() < self.num_bits() + } + + fn fits_in_u128(&self) -> bool { + self.num_bits() <= 128 + } + + /// Returns None, if the string is not a canonical + /// representation of a field element; less than the order + /// or if the hex string is invalid. + /// This method can be used for both hex and decimal representations. + pub fn try_from_str(input: &str) -> Option> { + if input.contains('x') { + return FieldElement::from_hex(input); + } + + let fr = F::from_str(input).ok()?; + Some(FieldElement(fr)) + } + + // mask_to methods will not remove any bytes from the field + // they are simply zeroed out + // Whereas truncate_to will remove those bits and make the byte array smaller + fn mask_to_be_bytes(&self, num_bits: u32) -> Vec { + let mut bytes = self.to_be_bytes(); + mask_vector_le(&mut bytes, num_bits as usize); + bytes + } + + fn bits(&self) -> Vec { + fn byte_to_bit(byte: u8) -> Vec { + let mut bits = Vec::with_capacity(8); + for index in (0..=7).rev() { + bits.push((byte & (1 << index)) >> index == 1); + } + bits + } + + let bytes = self.to_be_bytes(); + let mut bits = Vec::with_capacity(bytes.len() * 8); + for byte in bytes { + let _bits = byte_to_bit(byte); + bits.extend(_bits); + } + bits + } + + fn and_xor(&self, rhs: &FieldElement, num_bits: u32, is_xor: bool) -> FieldElement { + // XXX: Gadgets like SHA256 need to have their input be a multiple of 8 + // This is not a restriction caused by SHA256, as it works on bits + // but most backends assume bytes. + // We could implicitly pad, however this may not be intuitive for users. + // assert!( + // num_bits % 8 == 0, + // "num_bits is not a multiple of 8, it is {}", + // num_bits + // ); + + let lhs_bytes = self.mask_to_be_bytes(num_bits); + let rhs_bytes = rhs.mask_to_be_bytes(num_bits); + + let and_byte_arr: Vec<_> = lhs_bytes + .into_iter() + .zip(rhs_bytes) + .map(|(lhs, rhs)| if is_xor { lhs ^ rhs } else { lhs & rhs }) + .collect(); + + FieldElement::from_be_bytes_reduce(&and_byte_arr) + } +} + +impl AcirField for FieldElement { + fn one() -> FieldElement { + FieldElement(F::one()) + } + fn zero() -> FieldElement { + FieldElement(F::zero()) + } + + fn is_zero(&self) -> bool { + self == &Self::zero() + } + fn is_one(&self) -> bool { + self == &Self::one() + } + + fn pow(&self, exponent: &Self) -> Self { + FieldElement(self.0.pow(exponent.0.into_bigint())) + } + + /// Maximum number of bits needed to represent a field element + /// This is not the amount of bits being used to represent a field element + /// Example, you only need 254 bits to represent a field element in BN256 + /// But the representation uses 256 bits, so the top two bits are always zero + /// This method would return 254 + fn max_num_bits() -> u32 { + F::MODULUS_BIT_SIZE + } + + /// Maximum numbers of bytes needed to represent a field element + /// We are not guaranteed that the number of bits being used to represent a field element + /// will always be divisible by 8. If the case that it is not, we add one to the max number of bytes + /// For example, a max bit size of 254 would give a max byte size of 32. + fn max_num_bytes() -> u32 { + let num_bytes = Self::max_num_bits() / 8; + if Self::max_num_bits() % 8 == 0 { + num_bytes + } else { + num_bytes + 1 + } + } + + fn modulus() -> BigUint { + F::MODULUS.into() + } + + /// This is the number of bits required to represent this specific field element + fn num_bits(&self) -> u32 { + let bits = self.bits(); + // Iterate the number of bits and pop off all leading zeroes + let iter = bits.iter().skip_while(|x| !(**x)); + // Note: count will panic if it goes over usize::MAX. + // This may not be suitable for devices whose usize < u16 + iter.count() as u32 + } + + fn to_u128(self) -> u128 { + let bytes = self.to_be_bytes(); + u128::from_be_bytes(bytes[16..32].try_into().unwrap()) + } + + fn try_into_u128(self) -> Option { + self.fits_in_u128().then(|| self.to_u128()) + } + + fn to_i128(self) -> i128 { + let is_negative = self.is_negative(); + let bytes = if is_negative { self.neg() } else { self }.to_be_bytes(); + i128::from_be_bytes(bytes[16..32].try_into().unwrap()) * if is_negative { -1 } else { 1 } + } + + fn try_to_u64(&self) -> Option { + (self.num_bits() <= 64).then(|| self.to_u128() as u64) + } + + /// Computes the inverse or returns zero if the inverse does not exist + /// Before using this FieldElement, please ensure that this behavior is necessary + fn inverse(&self) -> FieldElement { + let inv = self.0.inverse().unwrap_or_else(F::zero); + FieldElement(inv) + } + + fn to_hex(self) -> String { + let mut bytes = Vec::new(); + self.0.serialize_uncompressed(&mut bytes).unwrap(); + bytes.reverse(); + hex::encode(bytes) + } + fn from_hex(hex_str: &str) -> Option> { + let value = hex_str.strip_prefix("0x").unwrap_or(hex_str); + // Values of odd length require an additional "0" prefix + let sanitized_value = + if value.len() % 2 == 0 { value.to_string() } else { format!("0{}", value) }; + let hex_as_bytes = hex::decode(sanitized_value).ok()?; + Some(FieldElement::from_be_bytes_reduce(&hex_as_bytes)) + } + + fn to_be_bytes(self) -> Vec { + // to_be_bytes! uses little endian which is why we reverse the output + // TODO: Add a little endian equivalent, so the caller can use whichever one + // TODO they desire + let mut bytes = Vec::new(); + self.0.serialize_uncompressed(&mut bytes).unwrap(); + bytes.reverse(); + bytes + } + + /// Converts bytes into a FieldElement and applies a + /// reduction if needed. + fn from_be_bytes_reduce(bytes: &[u8]) -> FieldElement { + FieldElement(F::from_be_bytes_mod_order(bytes)) + } + + /// Returns the closest number of bytes to the bits specified + /// This method truncates + fn fetch_nearest_bytes(&self, num_bits: usize) -> Vec { + fn nearest_bytes(num_bits: usize) -> usize { + ((num_bits + 7) / 8) * 8 + } + + let num_bytes = nearest_bytes(num_bits); + let num_elements = num_bytes / 8; + + let mut bytes = self.to_be_bytes(); + bytes.reverse(); // put it in big endian format. XXX(next refactor): we should be explicit about endianness. + + bytes[0..num_elements].to_vec() + } + + fn and(&self, rhs: &FieldElement, num_bits: u32) -> FieldElement { + self.and_xor(rhs, num_bits, false) + } + fn xor(&self, rhs: &FieldElement, num_bits: u32) -> FieldElement { + self.and_xor(rhs, num_bits, true) + } +} + +impl Neg for FieldElement { + type Output = FieldElement; + + fn neg(self) -> Self::Output { + FieldElement(-self.0) + } +} + +impl Mul for FieldElement { + type Output = FieldElement; + fn mul(mut self, rhs: FieldElement) -> Self::Output { + self.0.mul_assign(&rhs.0); + FieldElement(self.0) + } +} +impl Div for FieldElement { + type Output = FieldElement; + #[allow(clippy::suspicious_arithmetic_impl)] + fn div(self, rhs: FieldElement) -> Self::Output { + self * rhs.inverse() + } +} +impl Add for FieldElement { + type Output = FieldElement; + fn add(mut self, rhs: FieldElement) -> Self::Output { + self.0.add_assign(&rhs.0); + FieldElement(self.0) + } +} +impl AddAssign for FieldElement { + fn add_assign(&mut self, rhs: FieldElement) { + self.0.add_assign(&rhs.0); + } +} + +impl Sub for FieldElement { + type Output = FieldElement; + fn sub(mut self, rhs: FieldElement) -> Self::Output { + self.0.sub_assign(&rhs.0); + FieldElement(self.0) + } +} +impl SubAssign for FieldElement { + fn sub_assign(&mut self, rhs: FieldElement) { + self.0.sub_assign(&rhs.0); + } +} + +fn mask_vector_le(bytes: &mut [u8], num_bits: usize) { + // reverse to big endian format + bytes.reverse(); + + let mask_power = num_bits % 8; + let array_mask_index = num_bits / 8; + + for (index, byte) in bytes.iter_mut().enumerate() { + match index.cmp(&array_mask_index) { + std::cmp::Ordering::Less => { + // do nothing if the current index is less than + // the array index. + } + std::cmp::Ordering::Equal => { + let mask = 2u8.pow(mask_power as u32) - 1; + // mask the byte + *byte &= mask; + } + std::cmp::Ordering::Greater => { + // Anything greater than the array index + // will be set to zero + *byte = 0; + } + } + } + // reverse back to little endian + bytes.reverse(); +} + +// For pretty printing powers +fn superscript(n: u64) -> String { + if n == 0 { + "⁰".to_owned() + } else if n == 1 { + "¹".to_owned() + } else if n == 2 { + "²".to_owned() + } else if n == 3 { + "³".to_owned() + } else if n == 4 { + "⁴".to_owned() + } else if n == 5 { + "⁵".to_owned() + } else if n == 6 { + "⁶".to_owned() + } else if n == 7 { + "⁷".to_owned() + } else if n == 8 { + "⁸".to_owned() + } else if n == 9 { + "⁹".to_owned() + } else if n >= 10 { + superscript(n / 10) + &superscript(n % 10) + } else { + panic!("{}", n.to_string() + " can't be converted to superscript."); + } +} + +#[cfg(test)] +mod tests { + use super::{AcirField, FieldElement}; + + #[test] + fn and() { + let max = 10_000u32; + + let num_bits = (std::mem::size_of::() * 8) as u32 - max.leading_zeros(); + + for x in 0..max { + let x = FieldElement::::from(x as i128); + let res = x.and(&x, num_bits); + assert_eq!(res.to_be_bytes(), x.to_be_bytes()); + } + } + + #[test] + fn serialize_fixed_test_vectors() { + // Serialized field elements from of 0, -1, -2, -3 + let hex_strings = vec![ + "0000000000000000000000000000000000000000000000000000000000000000", + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000", + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffffff", + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffffe", + ]; + + for (i, string) in hex_strings.into_iter().enumerate() { + let minus_i_field_element = -FieldElement::::from(i as i128); + assert_eq!(minus_i_field_element.to_hex(), string); + } + } + + #[test] + fn deserialize_even_and_odd_length_hex() { + // Test cases of (odd, even) length hex strings + let hex_strings = + vec![("0x0", "0x00"), ("0x1", "0x01"), ("0x002", "0x0002"), ("0x00003", "0x000003")]; + for (i, case) in hex_strings.into_iter().enumerate() { + let i_field_element = FieldElement::::from(i as i128); + let odd_field_element = FieldElement::::from_hex(case.0).unwrap(); + let even_field_element = FieldElement::::from_hex(case.1).unwrap(); + + assert_eq!(i_field_element, odd_field_element); + assert_eq!(odd_field_element, even_field_element); + } + } + + #[test] + fn max_num_bits_smoke() { + let max_num_bits_bn254 = FieldElement::::max_num_bits(); + assert_eq!(max_num_bits_bn254, 254); + } +} diff --git a/acvm-repo/acir_field/src/generic_ark.rs b/acvm-repo/acir_field/src/generic_ark.rs index c1bc7971922..1e0215223ef 100644 --- a/acvm-repo/acir_field/src/generic_ark.rs +++ b/acvm-repo/acir_field/src/generic_ark.rs @@ -1,25 +1,21 @@ -use ark_ff::PrimeField; -use ark_ff::Zero; use num_bigint::BigUint; -use serde::{Deserialize, Serialize}; -use std::borrow::Cow; /// This trait is extremely unstable and WILL have breaking changes. pub trait AcirField: std::marker::Sized - + Display - + Debug + + std::fmt::Display + + std::fmt::Debug + Default + Clone + Copy - + Neg - + Add - + Sub - + Mul - + Div + + std::ops::Neg + + std::ops::Add + + std::ops::Sub + + std::ops::Mul + + std::ops::Div + + std::ops::AddAssign + + std::ops::SubAssign + PartialOrd - + AddAssign - + SubAssign + From + From // + From @@ -27,7 +23,7 @@ pub trait AcirField: // + From // + From + From - + Hash + + std::hash::Hash + std::cmp::Eq { fn one() -> Self; @@ -84,544 +80,3 @@ pub trait AcirField: fn and(&self, rhs: &Self, num_bits: u32) -> Self; fn xor(&self, rhs: &Self, num_bits: u32) -> Self; } - -// XXX: Switch out for a trait and proper implementations -// This implementation is in-efficient, can definitely remove hex usage and Iterator instances for trivial functionality -#[derive(Default, Clone, Copy, Eq, PartialOrd, Ord)] -pub struct FieldElement(F); - -impl std::fmt::Display for FieldElement { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - // First check if the number is zero - // - let number = BigUint::from_bytes_be(&self.to_be_bytes()); - if number == BigUint::zero() { - return write!(f, "0"); - } - // Check if the negative version is smaller to represent - // - let minus_number = BigUint::from_bytes_be(&(self.neg()).to_be_bytes()); - let (smaller_repr, is_negative) = - if minus_number.to_string().len() < number.to_string().len() { - (minus_number, true) - } else { - (number, false) - }; - if is_negative { - write!(f, "-")?; - } - - // Number of bits needed to represent the smaller representation - let num_bits = smaller_repr.bits(); - - // Check if the number represents a power of 2 - if smaller_repr.count_ones() == 1 { - let mut bit_index = 0; - for i in 0..num_bits { - if smaller_repr.bit(i) { - bit_index = i; - break; - } - } - return match bit_index { - 0 => write!(f, "1"), - 1 => write!(f, "2"), - 2 => write!(f, "4"), - 3 => write!(f, "8"), - _ => write!(f, "2{}", superscript(bit_index)), - }; - } - - // Check if number is a multiple of a power of 2. - // This is used because when computing the quotient - // we usually have numbers in the form 2^t * q + r - // We focus on 2^64, 2^32, 2^16, 2^8, 2^4 because - // they are common. We could extend this to a more - // general factorization strategy, but we pay in terms of CPU time - let mul_sign = "×"; - for power in [64, 32, 16, 8, 4] { - let power_of_two = BigUint::from(2_u128).pow(power); - if &smaller_repr % &power_of_two == BigUint::zero() { - return write!( - f, - "2{}{}{}", - superscript(power as u64), - mul_sign, - smaller_repr / &power_of_two, - ); - } - } - write!(f, "{smaller_repr}") - } -} - -impl std::fmt::Debug for FieldElement { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(self, f) - } -} - -impl std::hash::Hash for FieldElement { - fn hash(&self, state: &mut H) { - state.write(&self.to_be_bytes()); - } -} - -impl PartialEq for FieldElement { - fn eq(&self, other: &Self) -> bool { - self.to_be_bytes() == other.to_be_bytes() - } -} - -impl From for FieldElement { - fn from(mut a: i128) -> FieldElement { - let mut negative = false; - if a < 0 { - a = -a; - negative = true; - } - - let mut result = match F::from_str(&a.to_string()) { - Ok(result) => result, - Err(_) => panic!("Cannot convert i128 as a string to a field element"), - }; - - if negative { - result = -result; - } - FieldElement(result) - } -} - -impl Serialize for FieldElement { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.to_hex().serialize(serializer) - } -} - -impl<'de, T: ark_ff::PrimeField> Deserialize<'de> for FieldElement { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let s: Cow<'de, str> = Deserialize::deserialize(deserializer)?; - match Self::from_hex(&s) { - Some(value) => Ok(value), - None => Err(serde::de::Error::custom(format!("Invalid hex for FieldElement: {s}",))), - } - } -} - -impl From for FieldElement { - fn from(a: u128) -> FieldElement { - let result = match F::from_str(&a.to_string()) { - Ok(result) => result, - Err(_) => panic!("Cannot convert u128 as a string to a field element"), - }; - FieldElement(result) - } -} - -impl From for FieldElement { - fn from(a: usize) -> FieldElement { - FieldElement::from(a as u128) - } -} - -impl From for FieldElement { - fn from(boolean: bool) -> FieldElement { - if boolean { - FieldElement::one() - } else { - FieldElement::zero() - } - } -} - -impl FieldElement { - pub fn from_repr(field: F) -> Self { - Self(field) - } - - // XXX: This method is used while this field element - // implementation is not generic. - pub fn into_repr(self) -> F { - self.0 - } - - fn is_negative(&self) -> bool { - self.neg().num_bits() < self.num_bits() - } - - fn fits_in_u128(&self) -> bool { - self.num_bits() <= 128 - } - - /// Returns None, if the string is not a canonical - /// representation of a field element; less than the order - /// or if the hex string is invalid. - /// This method can be used for both hex and decimal representations. - pub fn try_from_str(input: &str) -> Option> { - if input.contains('x') { - return FieldElement::from_hex(input); - } - - let fr = F::from_str(input).ok()?; - Some(FieldElement(fr)) - } - - // mask_to methods will not remove any bytes from the field - // they are simply zeroed out - // Whereas truncate_to will remove those bits and make the byte array smaller - fn mask_to_be_bytes(&self, num_bits: u32) -> Vec { - let mut bytes = self.to_be_bytes(); - mask_vector_le(&mut bytes, num_bits as usize); - bytes - } - - fn bits(&self) -> Vec { - fn byte_to_bit(byte: u8) -> Vec { - let mut bits = Vec::with_capacity(8); - for index in (0..=7).rev() { - bits.push((byte & (1 << index)) >> index == 1); - } - bits - } - - let bytes = self.to_be_bytes(); - let mut bits = Vec::with_capacity(bytes.len() * 8); - for byte in bytes { - let _bits = byte_to_bit(byte); - bits.extend(_bits); - } - bits - } - - fn and_xor(&self, rhs: &FieldElement, num_bits: u32, is_xor: bool) -> FieldElement { - // XXX: Gadgets like SHA256 need to have their input be a multiple of 8 - // This is not a restriction caused by SHA256, as it works on bits - // but most backends assume bytes. - // We could implicitly pad, however this may not be intuitive for users. - // assert!( - // num_bits % 8 == 0, - // "num_bits is not a multiple of 8, it is {}", - // num_bits - // ); - - let lhs_bytes = self.mask_to_be_bytes(num_bits); - let rhs_bytes = rhs.mask_to_be_bytes(num_bits); - - let and_byte_arr: Vec<_> = lhs_bytes - .into_iter() - .zip(rhs_bytes) - .map(|(lhs, rhs)| if is_xor { lhs ^ rhs } else { lhs & rhs }) - .collect(); - - FieldElement::from_be_bytes_reduce(&and_byte_arr) - } -} - -impl AcirField for FieldElement { - fn one() -> FieldElement { - FieldElement(F::one()) - } - fn zero() -> FieldElement { - FieldElement(F::zero()) - } - - fn is_zero(&self) -> bool { - self == &Self::zero() - } - fn is_one(&self) -> bool { - self == &Self::one() - } - - fn pow(&self, exponent: &Self) -> Self { - FieldElement(self.0.pow(exponent.0.into_bigint())) - } - - /// Maximum number of bits needed to represent a field element - /// This is not the amount of bits being used to represent a field element - /// Example, you only need 254 bits to represent a field element in BN256 - /// But the representation uses 256 bits, so the top two bits are always zero - /// This method would return 254 - fn max_num_bits() -> u32 { - F::MODULUS_BIT_SIZE - } - - /// Maximum numbers of bytes needed to represent a field element - /// We are not guaranteed that the number of bits being used to represent a field element - /// will always be divisible by 8. If the case that it is not, we add one to the max number of bytes - /// For example, a max bit size of 254 would give a max byte size of 32. - fn max_num_bytes() -> u32 { - let num_bytes = Self::max_num_bits() / 8; - if Self::max_num_bits() % 8 == 0 { - num_bytes - } else { - num_bytes + 1 - } - } - - fn modulus() -> BigUint { - F::MODULUS.into() - } - - /// This is the number of bits required to represent this specific field element - fn num_bits(&self) -> u32 { - let bits = self.bits(); - // Iterate the number of bits and pop off all leading zeroes - let iter = bits.iter().skip_while(|x| !(**x)); - // Note: count will panic if it goes over usize::MAX. - // This may not be suitable for devices whose usize < u16 - iter.count() as u32 - } - - fn to_u128(self) -> u128 { - let bytes = self.to_be_bytes(); - u128::from_be_bytes(bytes[16..32].try_into().unwrap()) - } - - fn try_into_u128(self) -> Option { - self.fits_in_u128().then(|| self.to_u128()) - } - - fn to_i128(self) -> i128 { - let is_negative = self.is_negative(); - let bytes = if is_negative { self.neg() } else { self }.to_be_bytes(); - i128::from_be_bytes(bytes[16..32].try_into().unwrap()) * if is_negative { -1 } else { 1 } - } - - fn try_to_u64(&self) -> Option { - (self.num_bits() <= 64).then(|| self.to_u128() as u64) - } - - /// Computes the inverse or returns zero if the inverse does not exist - /// Before using this FieldElement, please ensure that this behavior is necessary - fn inverse(&self) -> FieldElement { - let inv = self.0.inverse().unwrap_or_else(F::zero); - FieldElement(inv) - } - - fn to_hex(self) -> String { - let mut bytes = Vec::new(); - self.0.serialize_uncompressed(&mut bytes).unwrap(); - bytes.reverse(); - hex::encode(bytes) - } - fn from_hex(hex_str: &str) -> Option> { - let value = hex_str.strip_prefix("0x").unwrap_or(hex_str); - // Values of odd length require an additional "0" prefix - let sanitized_value = - if value.len() % 2 == 0 { value.to_string() } else { format!("0{}", value) }; - let hex_as_bytes = hex::decode(sanitized_value).ok()?; - Some(FieldElement::from_be_bytes_reduce(&hex_as_bytes)) - } - - fn to_be_bytes(self) -> Vec { - // to_be_bytes! uses little endian which is why we reverse the output - // TODO: Add a little endian equivalent, so the caller can use whichever one - // TODO they desire - let mut bytes = Vec::new(); - self.0.serialize_uncompressed(&mut bytes).unwrap(); - bytes.reverse(); - bytes - } - - /// Converts bytes into a FieldElement and applies a - /// reduction if needed. - fn from_be_bytes_reduce(bytes: &[u8]) -> FieldElement { - FieldElement(F::from_be_bytes_mod_order(bytes)) - } - - /// Returns the closest number of bytes to the bits specified - /// This method truncates - fn fetch_nearest_bytes(&self, num_bits: usize) -> Vec { - fn nearest_bytes(num_bits: usize) -> usize { - ((num_bits + 7) / 8) * 8 - } - - let num_bytes = nearest_bytes(num_bits); - let num_elements = num_bytes / 8; - - let mut bytes = self.to_be_bytes(); - bytes.reverse(); // put it in big endian format. XXX(next refactor): we should be explicit about endianness. - - bytes[0..num_elements].to_vec() - } - - fn and(&self, rhs: &FieldElement, num_bits: u32) -> FieldElement { - self.and_xor(rhs, num_bits, false) - } - fn xor(&self, rhs: &FieldElement, num_bits: u32) -> FieldElement { - self.and_xor(rhs, num_bits, true) - } -} - -use std::fmt::Debug; -use std::fmt::Display; -use std::hash::Hash; -use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; - -impl Neg for FieldElement { - type Output = FieldElement; - - fn neg(self) -> Self::Output { - FieldElement(-self.0) - } -} - -impl Mul for FieldElement { - type Output = FieldElement; - fn mul(mut self, rhs: FieldElement) -> Self::Output { - self.0.mul_assign(&rhs.0); - FieldElement(self.0) - } -} -impl Div for FieldElement { - type Output = FieldElement; - #[allow(clippy::suspicious_arithmetic_impl)] - fn div(self, rhs: FieldElement) -> Self::Output { - self * rhs.inverse() - } -} -impl Add for FieldElement { - type Output = FieldElement; - fn add(mut self, rhs: FieldElement) -> Self::Output { - self.0.add_assign(&rhs.0); - FieldElement(self.0) - } -} -impl AddAssign for FieldElement { - fn add_assign(&mut self, rhs: FieldElement) { - self.0.add_assign(&rhs.0); - } -} - -impl Sub for FieldElement { - type Output = FieldElement; - fn sub(mut self, rhs: FieldElement) -> Self::Output { - self.0.sub_assign(&rhs.0); - FieldElement(self.0) - } -} -impl SubAssign for FieldElement { - fn sub_assign(&mut self, rhs: FieldElement) { - self.0.sub_assign(&rhs.0); - } -} - -fn mask_vector_le(bytes: &mut [u8], num_bits: usize) { - // reverse to big endian format - bytes.reverse(); - - let mask_power = num_bits % 8; - let array_mask_index = num_bits / 8; - - for (index, byte) in bytes.iter_mut().enumerate() { - match index.cmp(&array_mask_index) { - std::cmp::Ordering::Less => { - // do nothing if the current index is less than - // the array index. - } - std::cmp::Ordering::Equal => { - let mask = 2u8.pow(mask_power as u32) - 1; - // mask the byte - *byte &= mask; - } - std::cmp::Ordering::Greater => { - // Anything greater than the array index - // will be set to zero - *byte = 0; - } - } - } - // reverse back to little endian - bytes.reverse(); -} - -// For pretty printing powers -fn superscript(n: u64) -> String { - if n == 0 { - "⁰".to_owned() - } else if n == 1 { - "¹".to_owned() - } else if n == 2 { - "²".to_owned() - } else if n == 3 { - "³".to_owned() - } else if n == 4 { - "⁴".to_owned() - } else if n == 5 { - "⁵".to_owned() - } else if n == 6 { - "⁶".to_owned() - } else if n == 7 { - "⁷".to_owned() - } else if n == 8 { - "⁸".to_owned() - } else if n == 9 { - "⁹".to_owned() - } else if n >= 10 { - superscript(n / 10) + &superscript(n % 10) - } else { - panic!("{}", n.to_string() + " can't be converted to superscript."); - } -} - -#[cfg(test)] -mod tests { - use super::{AcirField, FieldElement}; - - #[test] - fn and() { - let max = 10_000u32; - - let num_bits = (std::mem::size_of::() * 8) as u32 - max.leading_zeros(); - - for x in 0..max { - let x = FieldElement::::from(x as i128); - let res = x.and(&x, num_bits); - assert_eq!(res.to_be_bytes(), x.to_be_bytes()); - } - } - - #[test] - fn serialize_fixed_test_vectors() { - // Serialized field elements from of 0, -1, -2, -3 - let hex_strings = vec![ - "0000000000000000000000000000000000000000000000000000000000000000", - "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000", - "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffffff", - "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffffe", - ]; - - for (i, string) in hex_strings.into_iter().enumerate() { - let minus_i_field_element = -FieldElement::::from(i as i128); - assert_eq!(minus_i_field_element.to_hex(), string); - } - } - - #[test] - fn deserialize_even_and_odd_length_hex() { - // Test cases of (odd, even) length hex strings - let hex_strings = - vec![("0x0", "0x00"), ("0x1", "0x01"), ("0x002", "0x0002"), ("0x00003", "0x000003")]; - for (i, case) in hex_strings.into_iter().enumerate() { - let i_field_element = FieldElement::::from(i as i128); - let odd_field_element = FieldElement::::from_hex(case.0).unwrap(); - let even_field_element = FieldElement::::from_hex(case.1).unwrap(); - - assert_eq!(i_field_element, odd_field_element); - assert_eq!(odd_field_element, even_field_element); - } - } - - #[test] - fn max_num_bits_smoke() { - let max_num_bits_bn254 = FieldElement::::max_num_bits(); - assert_eq!(max_num_bits_bn254, 254); - } -} diff --git a/acvm-repo/acir_field/src/lib.rs b/acvm-repo/acir_field/src/lib.rs index 7f06330ed8d..a4616078b22 100644 --- a/acvm-repo/acir_field/src/lib.rs +++ b/acvm-repo/acir_field/src/lib.rs @@ -5,19 +5,21 @@ use num_bigint::BigUint; use num_traits::Num; + +mod field_element; mod generic_ark; pub use generic_ark::AcirField; /// Temporarily exported generic field to aid migration to `AcirField` -pub use generic_ark::FieldElement as GenericFieldElement; +pub use field_element::FieldElement as GenericFieldElement; cfg_if::cfg_if! { if #[cfg(feature = "bls12_381")] { - pub type FieldElement = generic_ark::FieldElement; + pub type FieldElement = field_element::FieldElement; pub const CHOSEN_FIELD : FieldOptions = FieldOptions::BLS12_381; } else { - pub type FieldElement = generic_ark::FieldElement; + pub type FieldElement = field_element::FieldElement; pub const CHOSEN_FIELD : FieldOptions = FieldOptions::BN254; } } From 365bba2c200d58e09fcc43b19762e6a03c030114 Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:48:52 +0100 Subject: [PATCH 3/3] Update acvm-repo/acir_field/src/field_element.rs --- acvm-repo/acir_field/src/field_element.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/acvm-repo/acir_field/src/field_element.rs b/acvm-repo/acir_field/src/field_element.rs index 245ce7af1b3..a45e8c9946a 100644 --- a/acvm-repo/acir_field/src/field_element.rs +++ b/acvm-repo/acir_field/src/field_element.rs @@ -216,8 +216,7 @@ impl FieldElement { let bytes = self.to_be_bytes(); let mut bits = Vec::with_capacity(bytes.len() * 8); for byte in bytes { - let _bits = byte_to_bit(byte); - bits.extend(_bits); + bits.extend(byte_to_bit(byte)); } bits }