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

feat: implement bigint in Noir, using bigint opcodes #4198

Merged
merged 19 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from 17 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
5 changes: 4 additions & 1 deletion noir/compiler/noirc_evaluator/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ pub enum RuntimeError {
AssertConstantFailed { call_stack: CallStack },
#[error("Nested slices are not supported")]
NestedSlice { call_stack: CallStack },
#[error("Big Integer modulus do no match")]
BigIntModulus { call_stack: CallStack },
}

// We avoid showing the actual lhs and rhs since most of the time they are just 0
Expand Down Expand Up @@ -132,7 +134,8 @@ impl RuntimeError {
| RuntimeError::AssertConstantFailed { call_stack }
| RuntimeError::IntegerOutOfBounds { call_stack, .. }
| RuntimeError::UnsupportedIntegerSize { call_stack, .. }
| RuntimeError::NestedSlice { call_stack, .. } => call_stack,
| RuntimeError::NestedSlice { call_stack, .. }
| RuntimeError::BigIntModulus { call_stack, .. } => call_stack,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions noir/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub(crate) mod acir_variable;
pub(crate) mod big_int;
pub(crate) mod generated_acir;
pub(crate) mod sort;
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::big_int::BigIntContext;
use super::generated_acir::GeneratedAcir;
use crate::brillig::brillig_gen::brillig_directive;
use crate::brillig::brillig_ir::artifact::GeneratedBrillig;
Expand Down Expand Up @@ -107,6 +108,9 @@ pub(crate) struct AcirContext {
/// then the `acir_ir` will be populated to assert this
/// addition.
acir_ir: GeneratedAcir,

/// The BigIntContext, used to generate identifiers for BigIntegers
big_int_ctx: BigIntContext,
}

impl AcirContext {
Expand Down Expand Up @@ -1140,10 +1144,10 @@ impl AcirContext {
&mut self,
name: BlackBoxFunc,
mut inputs: Vec<AcirValue>,
output_count: usize,
mut output_count: usize,
) -> Result<Vec<AcirVar>, RuntimeError> {
// Separate out any arguments that should be constants
let constants = match name {
let (constant_inputs, constant_outputs) = match name {
BlackBoxFunc::PedersenCommitment | BlackBoxFunc::PedersenHash => {
// The last argument of pedersen is the domain separator, which must be a constant
let domain_var = match inputs.pop() {
Expand All @@ -1167,23 +1171,116 @@ impl AcirContext {
}
};

vec![domain_constant]
(vec![domain_constant], Vec::new())
}
BlackBoxFunc::BigIntAdd
| BlackBoxFunc::BigIntNeg
| BlackBoxFunc::BigIntMul
| BlackBoxFunc::BigIntDiv => {
assert_eq!(inputs.len(), 4, "ICE - bigint operation requires 4 inputs");
let const_inputs = vecmap(inputs, |i| {
let var = i.into_var()?;
match self.vars[&var].as_constant() {
Some(const_var) => Ok(const_var),
None => Err(RuntimeError::InternalError(InternalError::NotAConstant {
name: "big integer".to_string(),
call_stack: self.get_call_stack(),
})),
}
});
inputs = Vec::new();
output_count = 0;
let mut field_inputs = Vec::new();
for i in const_inputs {
field_inputs.push(i?);
}
if field_inputs[1] != field_inputs[3] {
return Err(RuntimeError::BigIntModulus { call_stack: self.get_call_stack() });
}

let result_id = self.big_int_ctx.new_big_int(field_inputs[1]);
(
vec![field_inputs[0], field_inputs[2]],
vec![result_id.as_field(), result_id.modulus_id()],
)
}
BlackBoxFunc::BigIntToLeBytes => {
let const_inputs = vecmap(inputs, |i| {
let var = i.into_var()?;
match self.vars[&var].as_constant() {
Some(const_var) => Ok(const_var),
None => Err(RuntimeError::InternalError(InternalError::NotAConstant {
name: "big integer".to_string(),
call_stack: self.get_call_stack(),
})),
}
});
inputs = Vec::new();
let mut field_inputs = Vec::new();
for i in const_inputs {
field_inputs.push(i?);
}
let modulus = self.big_int_ctx.modulus(field_inputs[0]);
let bytes_nb = ((modulus - BigUint::from(1_u32)).bits() - 1) / 8 + 1;
guipublic marked this conversation as resolved.
Show resolved Hide resolved
output_count = bytes_nb as usize;
(field_inputs, vec![FieldElement::from(bytes_nb as u128)])
}
BlackBoxFunc::BigIntFromLeBytes => {
let invalid_input = "ICE - bigint operation requires 2 inputs";
assert_eq!(inputs.len(), 2, "{invalid_input}");
let mut modulus = Vec::new();
match inputs.pop().expect(invalid_input) {
AcirValue::Array(values) => {
for value in values {
modulus.push(self.vars[&value.into_var()?].as_constant().ok_or(
RuntimeError::InternalError(InternalError::NotAConstant {
name: "big integer".to_string(),
call_stack: self.get_call_stack(),
}),
)?);
}
}
_ => {
return Err(RuntimeError::InternalError(InternalError::MissingArg {
name: "big_int_from_le_bytes".to_owned(),
arg: "modulus".to_owned(),
call_stack: self.get_call_stack(),
}));
}
}
let big_modulus = BigUint::from_bytes_le(&vecmap(&modulus, |b| b.to_u128() as u8));
output_count = 0;

let modulus_id = self.big_int_ctx.get_or_insert_modulus(big_modulus);
let result_id =
self.big_int_ctx.new_big_int(FieldElement::from(modulus_id as u128));
(modulus, vec![result_id.as_field(), result_id.modulus_id()])
}
_ => vec![],
_ => (vec![], vec![]),
};

// Convert `AcirVar` to `FunctionInput`
let inputs = self.prepare_inputs_for_black_box_func_call(inputs)?;

// Call Black box with `FunctionInput`
let outputs = self.acir_ir.call_black_box(name, &inputs, constants, output_count)?;
let mut results = vecmap(&constant_outputs, |c| self.add_constant(*c));
let outputs = self.acir_ir.call_black_box(
name,
&inputs,
constant_inputs,
constant_outputs,
output_count,
)?;

// Convert `Witness` values which are now constrained to be the output of the
// black box function call into `AcirVar`s.
//
// We do not apply range information on the output of the black box function.
// See issue #1439
Ok(vecmap(&outputs, |witness_index| self.add_data(AcirVarData::Witness(*witness_index))))
results.extend(vecmap(&outputs, |witness_index| {
self.add_data(AcirVarData::Witness(*witness_index))
}));
Ok(results)
}

/// Black box function calls expect their inputs to be in a specific data structure (FunctionInput).
Expand Down
54 changes: 54 additions & 0 deletions noir/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/big_int.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use acvm::FieldElement;
use num_bigint::BigUint;

/// Represents a bigint value in the form (id, modulus) where
/// id is the identifier of the big integer number, and
/// modulus is the identifier of the big integer size
#[derive(Default, Clone, Copy, Debug)]
pub(crate) struct BigIntId(pub(crate) u32, pub(crate) u32); //bigint id, modulus id
guipublic marked this conversation as resolved.
Show resolved Hide resolved

impl BigIntId {
pub(crate) fn as_field(&self) -> FieldElement {
FieldElement::from(self.0 as u128)
}
guipublic marked this conversation as resolved.
Show resolved Hide resolved

pub(crate) fn modulus_id(&self) -> FieldElement {
FieldElement::from(self.1 as u128)
}
}

/// BigIntContext is used to generate identifiers for big integers and their modulus
#[derive(Default, Debug)]
pub(crate) struct BigIntContext {
modulus: Vec<BigUint>,
big_integers: Vec<BigIntId>,
}

impl BigIntContext {
/// Creates a new BigIntId for the given modulus and returns it.
pub(crate) fn new_big_int(&mut self, modulus: FieldElement) -> BigIntId {
let id = self.big_integers.len() as u32;
let result = BigIntId(id, modulus.to_u128() as u32);
guipublic marked this conversation as resolved.
Show resolved Hide resolved
self.big_integers.push(result);
result
}

/// Returns the modulus corresponding to the given modulus index
pub(crate) fn modulus(&self, idx: FieldElement) -> BigUint {
self.modulus[idx.to_u128() as usize].clone()
}

/// Returns the BigIntId corresponding to the given identifier
pub(crate) fn get(&self, id: FieldElement) -> BigIntId {
self.big_integers[id.to_u128() as usize]
}

/// Adds a modulus to the context (if it is not already present)
pub(crate) fn get_or_insert_modulus(&mut self, modulus: BigUint) -> u32 {
if let Some(pos) = self.modulus.iter().position(|x| x == &modulus) {
return pos as u32;
}
self.modulus.push(modulus);
(self.modulus.len() - 1) as u32
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ impl GeneratedAcir {
&mut self,
func_name: BlackBoxFunc,
inputs: &[Vec<FunctionInput>],
constants: Vec<FieldElement>,
constant_inputs: Vec<FieldElement>,
constant_outputs: Vec<FieldElement>,
output_count: usize,
) -> Result<Vec<Witness>, InternalError> {
let input_count = inputs.iter().fold(0usize, |sum, val| sum + val.len());
Expand Down Expand Up @@ -176,12 +177,12 @@ impl GeneratedAcir {
BlackBoxFunc::PedersenCommitment => BlackBoxFuncCall::PedersenCommitment {
inputs: inputs[0].clone(),
outputs: (outputs[0], outputs[1]),
domain_separator: constants[0].to_u128() as u32,
domain_separator: constant_inputs[0].to_u128() as u32,
},
BlackBoxFunc::PedersenHash => BlackBoxFuncCall::PedersenHash {
inputs: inputs[0].clone(),
output: outputs[0],
domain_separator: constants[0].to_u128() as u32,
domain_separator: constant_inputs[0].to_u128() as u32,
},
BlackBoxFunc::EcdsaSecp256k1 => {
BlackBoxFuncCall::EcdsaSecp256k1 {
Expand Down Expand Up @@ -252,33 +253,34 @@ impl GeneratedAcir {
key_hash: inputs[3][0],
},
BlackBoxFunc::BigIntAdd => BlackBoxFuncCall::BigIntAdd {
lhs: constants[0].to_u128() as u32,
rhs: constants[1].to_u128() as u32,
output: constants[2].to_u128() as u32,
lhs: constant_inputs[0].to_u128() as u32,
rhs: constant_inputs[1].to_u128() as u32,
output: constant_outputs[0].to_u128() as u32,
},
BlackBoxFunc::BigIntNeg => BlackBoxFuncCall::BigIntNeg {
lhs: constants[0].to_u128() as u32,
rhs: constants[1].to_u128() as u32,
output: constants[2].to_u128() as u32,
lhs: constant_inputs[0].to_u128() as u32,
rhs: constant_inputs[1].to_u128() as u32,
output: constant_outputs[0].to_u128() as u32,
},
BlackBoxFunc::BigIntMul => BlackBoxFuncCall::BigIntMul {
lhs: constants[0].to_u128() as u32,
rhs: constants[1].to_u128() as u32,
output: constants[2].to_u128() as u32,
lhs: constant_inputs[0].to_u128() as u32,
rhs: constant_inputs[1].to_u128() as u32,
output: constant_outputs[0].to_u128() as u32,
},
BlackBoxFunc::BigIntDiv => BlackBoxFuncCall::BigIntDiv {
lhs: constants[0].to_u128() as u32,
rhs: constants[1].to_u128() as u32,
output: constants[2].to_u128() as u32,
lhs: constant_inputs[0].to_u128() as u32,
rhs: constant_inputs[1].to_u128() as u32,
output: constant_outputs[0].to_u128() as u32,
},
BlackBoxFunc::BigIntFromLeBytes => BlackBoxFuncCall::BigIntFromLeBytes {
inputs: inputs[0].clone(),
modulus: vecmap(constants, |c| c.to_u128() as u8),
output: todo!(),
modulus: vecmap(constant_inputs, |c| c.to_u128() as u8),
output: constant_outputs[0].to_u128() as u32,
},
BlackBoxFunc::BigIntToLeBytes => BlackBoxFuncCall::BigIntToLeBytes {
input: constant_inputs[0].to_u128() as u32,
outputs,
},
BlackBoxFunc::BigIntToLeBytes => {
BlackBoxFuncCall::BigIntToLeBytes { input: constants[0].to_u128() as u32, outputs }
}
};

self.push_opcode(AcirOpcode::BlackBoxFuncCall(black_box_func_call));
Expand Down Expand Up @@ -636,16 +638,15 @@ fn black_box_func_expected_input_size(name: BlackBoxFunc) -> Option<usize> {
// Doubling over the embedded curve: input is (x,y) coordinate of the point.
BlackBoxFunc::EmbeddedCurveDouble => Some(2),

// Big integer operations take in 2 inputs
// Big integer operations take in 0 inputs. THey use constants for their inputs.
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
BlackBoxFunc::BigIntAdd
| BlackBoxFunc::BigIntNeg
| BlackBoxFunc::BigIntMul
| BlackBoxFunc::BigIntDiv => Some(2),
| BlackBoxFunc::BigIntDiv
| BlackBoxFunc::BigIntToLeBytes => Some(0),

// FromLeBytes takes a variable array of bytes as input
BlackBoxFunc::BigIntFromLeBytes => None,
// ToLeBytes takes a single big integer as input
BlackBoxFunc::BigIntToLeBytes => Some(1),
}
}

Expand Down Expand Up @@ -691,7 +692,7 @@ fn black_box_expected_output_size(name: BlackBoxFunc) -> Option<usize> {
| BlackBoxFunc::BigIntNeg
| BlackBoxFunc::BigIntMul
| BlackBoxFunc::BigIntDiv
| BlackBoxFunc::BigIntFromLeBytes => Some(1),
| BlackBoxFunc::BigIntFromLeBytes => Some(0),

// ToLeBytes returns a variable array of bytes
BlackBoxFunc::BigIntToLeBytes => None,
Expand Down Expand Up @@ -752,5 +753,5 @@ fn intrinsics_check_outputs(name: BlackBoxFunc, output_count: usize) {
None => return,
};

assert_eq!(expected_num_outputs,output_count,"Tried to call black box function {name} with {output_count} inputs, but this function's definition requires {expected_num_outputs} inputs");
assert_eq!(expected_num_outputs,output_count,"Tried to call black box function {name} with {output_count} outputs, but this function's definition requires {expected_num_outputs} outputs");
}
29 changes: 24 additions & 5 deletions noir/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1541,7 +1541,7 @@ impl Context {

let vars = self.acir_context.black_box_function(black_box, inputs, output_count)?;

Ok(Self::convert_vars_to_values(vars, dfg, result_ids))
Ok(self.convert_vars_to_values(vars, dfg, result_ids))
}
Intrinsic::ApplyRangeConstraint => {
unreachable!("ICE: `Intrinsic::ApplyRangeConstraint` calls should be transformed into an `Instruction::RangeCheck`");
Expand Down Expand Up @@ -1588,7 +1588,7 @@ impl Context {
.sort(input_vars, bit_size, self.current_side_effects_enabled_var)
.expect("Could not sort");

Ok(Self::convert_vars_to_values(out_vars, dfg, result_ids))
Ok(self.convert_vars_to_values(out_vars, dfg, result_ids))
}
Intrinsic::ArrayLen => {
let len = match self.convert_value(arguments[0], dfg) {
Expand Down Expand Up @@ -2101,16 +2101,35 @@ impl Context {
/// Convert a Vec<AcirVar> into a Vec<AcirValue> using the given result ids.
/// If the type of a result id is an array, several acir vars are collected into
/// a single AcirValue::Array of the same length.
/// If the type of a result id is a slice, the slice length must precede it and we can
/// convert to an AcirValue::Array when the length is known (constant).
fn convert_vars_to_values(
&self,
vars: Vec<AcirVar>,
dfg: &DataFlowGraph,
result_ids: &[ValueId],
) -> Vec<AcirValue> {
let mut vars = vars.into_iter();
vecmap(result_ids, |result| {
let mut values: Vec<AcirValue> = Vec::new();
for result in result_ids {
let result_type = dfg.type_of_value(*result);
Self::convert_var_type_to_values(&result_type, &mut vars)
})
if let Type::Slice(elements_type) = result_type {
let error = "ICE - cannot get slice length when converting slice to AcirValue";
let len = values.last().expect(error).borrow_var().expect(error);
let len = self.acir_context.constant(len).to_u128();
let mut element_values = im::Vector::new();
for _ in 0..len {
for element_type in elements_type.iter() {
let element = Self::convert_var_type_to_values(element_type, &mut vars);
element_values.push_back(element);
}
}
values.push(AcirValue::Array(element_values));
} else {
values.push(Self::convert_var_type_to_values(&result_type, &mut vars));
}
}
values
}

/// Recursive helper for convert_vars_to_values.
Expand Down
Loading
Loading