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 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
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.bigint_id(), 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_len = ((modulus - BigUint::from(1_u32)).bits() - 1) / 8 + 1;
output_count = bytes_len as usize;
(field_inputs, vec![FieldElement::from(bytes_len 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.bigint_id(), 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
57 changes: 57 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,57 @@
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) bigint_id: u32,
pub(crate) modulus_id: u32,
}

impl BigIntId {
pub(crate) fn bigint_id(&self) -> FieldElement {
FieldElement::from(self.bigint_id as u128)
}

pub(crate) fn modulus_id(&self) -> FieldElement {
FieldElement::from(self.modulus_id 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 identifier and returns it.
pub(crate) fn new_big_int(&mut self, modulus_id: FieldElement) -> BigIntId {
let id = self.big_integers.len() as u32;
let result = BigIntId { bigint_id: id, modulus_id: modulus_id.to_u128() as u32 };
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.
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");
}
Loading
Loading