diff --git a/Cargo.lock b/Cargo.lock index 0f2c1894e15..754a1c3d1fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,6 +60,7 @@ dependencies = [ "blake3", "k256", "keccak", + "num-bigint", "p256", "sha2", "sha3", diff --git a/acvm-repo/acvm/src/pwg/blackbox/bigint.rs b/acvm-repo/acvm/src/pwg/blackbox/bigint.rs index a47afa5049a..3c05fb2761d 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/bigint.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/bigint.rs @@ -1,53 +1,23 @@ -use std::collections::HashMap; - use acir::{ circuit::opcodes::FunctionInput, native_types::{Witness, WitnessMap}, BlackBoxFunc, FieldElement, }; -use num_bigint::BigUint; +use acvm_blackbox_solver::BigIntSolver; use crate::pwg::OpcodeResolutionError; -/// Resolve BigInt opcodes by storing BigInt values (and their moduli) by their ID in a HashMap: +/// Resolve BigInt opcodes by storing BigInt values (and their moduli) by their ID in the BigIntSolver /// - When it encounters a bigint operation opcode, it performs the operation on the stored values /// and store the result using the provided ID. /// - When it gets a to_bytes opcode, it simply looks up the value and resolves the output witness accordingly. #[derive(Default)] -pub(crate) struct BigIntSolver { - bigint_id_to_value: HashMap, - bigint_id_to_modulus: HashMap, +pub(crate) struct AcvmBigIntSolver { + bigint_solver: BigIntSolver, } -impl BigIntSolver { - pub(crate) fn get_bigint( - &self, - id: u32, - func: BlackBoxFunc, - ) -> Result { - self.bigint_id_to_value - .get(&id) - .ok_or(OpcodeResolutionError::BlackBoxFunctionFailed( - func, - format!("could not find bigint of id {id}"), - )) - .cloned() - } - - pub(crate) fn get_modulus( - &self, - id: u32, - func: BlackBoxFunc, - ) -> Result { - self.bigint_id_to_modulus - .get(&id) - .ok_or(OpcodeResolutionError::BlackBoxFunctionFailed( - func, - format!("could not find bigint of id {id}"), - )) - .cloned() - } +impl AcvmBigIntSolver { pub(crate) fn bigint_from_bytes( &mut self, inputs: &[FunctionInput], @@ -59,10 +29,7 @@ impl BigIntSolver { .iter() .map(|input| initial_witness.get(&input.witness).unwrap().to_u128() as u8) .collect::>(); - let bigint = BigUint::from_bytes_le(&bytes); - self.bigint_id_to_value.insert(output, bigint); - let modulus = BigUint::from_bytes_le(modulus); - self.bigint_id_to_modulus.insert(output, modulus); + self.bigint_solver.bigint_from_bytes(&bytes, modulus, output)?; Ok(()) } @@ -72,9 +39,7 @@ impl BigIntSolver { outputs: &[Witness], initial_witness: &mut WitnessMap, ) -> Result<(), OpcodeResolutionError> { - let bigint = self.get_bigint(input, BlackBoxFunc::BigIntToLeBytes)?; - - let mut bytes = bigint.to_bytes_le(); + let mut bytes = self.bigint_solver.bigint_to_bytes(input)?; while bytes.len() < outputs.len() { bytes.push(0); } @@ -91,30 +56,7 @@ impl BigIntSolver { output: u32, func: BlackBoxFunc, ) -> Result<(), OpcodeResolutionError> { - let modulus = self.get_modulus(lhs, func)?; - let lhs = self.get_bigint(lhs, func)?; - let rhs = self.get_bigint(rhs, func)?; - let mut result = match func { - BlackBoxFunc::BigIntAdd => lhs + rhs, - BlackBoxFunc::BigIntSub => { - if lhs >= rhs { - &lhs - &rhs - } else { - &lhs + &modulus - &rhs - } - } - BlackBoxFunc::BigIntMul => lhs * rhs, - BlackBoxFunc::BigIntDiv => { - lhs * rhs.modpow(&(&modulus - BigUint::from(2_u32)), &modulus) - } //TODO ensure that modulus is prime - _ => unreachable!("ICE - bigint_op must be called for an operation"), - }; - if result > modulus { - let q = &result / &modulus; - result -= q * &modulus; - } - self.bigint_id_to_value.insert(output, result); - self.bigint_id_to_modulus.insert(output, modulus); + self.bigint_solver.bigint_op(lhs, rhs, output, func)?; Ok(()) } } diff --git a/acvm-repo/acvm/src/pwg/blackbox/mod.rs b/acvm-repo/acvm/src/pwg/blackbox/mod.rs index e7ed402a8eb..e6b3bb574bc 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/mod.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/mod.rs @@ -6,7 +6,7 @@ use acir::{ use acvm_blackbox_solver::{blake2s, blake3, keccak256, keccakf1600, sha256}; use self::{ - bigint::BigIntSolver, hash::solve_poseidon2_permutation_opcode, pedersen::pedersen_hash, + bigint::AcvmBigIntSolver, hash::solve_poseidon2_permutation_opcode, pedersen::pedersen_hash, }; use super::{insert_value, OpcodeNotSolvable, OpcodeResolutionError}; @@ -56,7 +56,7 @@ pub(crate) fn solve( backend: &impl BlackBoxFunctionSolver, initial_witness: &mut WitnessMap, bb_func: &BlackBoxFuncCall, - bigint_solver: &mut BigIntSolver, + bigint_solver: &mut AcvmBigIntSolver, ) -> Result<(), OpcodeResolutionError> { let inputs = bb_func.get_inputs_vec(); if !contains_all_inputs(initial_witness, &inputs) { diff --git a/acvm-repo/acvm/src/pwg/mod.rs b/acvm-repo/acvm/src/pwg/mod.rs index bb98eda2689..8f16316f318 100644 --- a/acvm-repo/acvm/src/pwg/mod.rs +++ b/acvm-repo/acvm/src/pwg/mod.rs @@ -11,7 +11,7 @@ use acir::{ use acvm_blackbox_solver::BlackBoxResolutionError; use self::{ - arithmetic::ExpressionSolver, blackbox::bigint::BigIntSolver, directives::solve_directives, + arithmetic::ExpressionSolver, blackbox::bigint::AcvmBigIntSolver, directives::solve_directives, memory_op::MemoryOpSolver, }; use crate::BlackBoxFunctionSolver; @@ -148,7 +148,7 @@ pub struct ACVM<'a, B: BlackBoxFunctionSolver> { /// Stores the solver for memory operations acting on blocks of memory disambiguated by [block][`BlockId`]. block_solvers: HashMap, - bigint_solver: BigIntSolver, + bigint_solver: AcvmBigIntSolver, /// A list of opcodes which are to be executed by the ACVM. opcodes: &'a [Opcode], @@ -174,7 +174,7 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { status, backend, block_solvers: HashMap::default(), - bigint_solver: BigIntSolver::default(), + bigint_solver: AcvmBigIntSolver::default(), opcodes, instruction_pointer: 0, witness_map: initial_witness, diff --git a/acvm-repo/blackbox_solver/Cargo.toml b/acvm-repo/blackbox_solver/Cargo.toml index d093b24a129..1d6629c8223 100644 --- a/acvm-repo/blackbox_solver/Cargo.toml +++ b/acvm-repo/blackbox_solver/Cargo.toml @@ -15,6 +15,7 @@ repository.workspace = true [dependencies] acir.workspace = true thiserror.workspace = true +num-bigint = "0.4" blake2 = "0.10.6" blake3 = "1.5.0" diff --git a/acvm-repo/blackbox_solver/src/bigint.rs b/acvm-repo/blackbox_solver/src/bigint.rs new file mode 100644 index 00000000000..5b19f03a238 --- /dev/null +++ b/acvm-repo/blackbox_solver/src/bigint.rs @@ -0,0 +1,99 @@ +use std::collections::HashMap; + +use acir::BlackBoxFunc; + +use num_bigint::BigUint; + +use crate::BlackBoxResolutionError; + +/// Resolve BigInt opcodes by storing BigInt values (and their moduli) by their ID in a HashMap: +/// - When it encounters a bigint operation opcode, it performs the operation on the stored values +/// and store the result using the provided ID. +/// - When it gets a to_bytes opcode, it simply looks up the value and resolves the output witness accordingly. +#[derive(Default, Debug, Clone, PartialEq, Eq)] + +pub struct BigIntSolver { + bigint_id_to_value: HashMap, + bigint_id_to_modulus: HashMap, +} + +impl BigIntSolver { + pub(crate) fn get_bigint( + &self, + id: u32, + func: BlackBoxFunc, + ) -> Result { + self.bigint_id_to_value + .get(&id) + .ok_or(BlackBoxResolutionError::Failed( + func, + format!("could not find bigint of id {id}"), + )) + .cloned() + } + + pub(crate) fn get_modulus( + &self, + id: u32, + func: BlackBoxFunc, + ) -> Result { + self.bigint_id_to_modulus + .get(&id) + .ok_or(BlackBoxResolutionError::Failed( + func, + format!("could not find bigint of id {id}"), + )) + .cloned() + } + pub fn bigint_from_bytes( + &mut self, + inputs: &[u8], + modulus: &[u8], + output: u32, + ) -> Result<(), BlackBoxResolutionError> { + let bigint = BigUint::from_bytes_le(inputs); + self.bigint_id_to_value.insert(output, bigint); + let modulus = BigUint::from_bytes_le(modulus); + self.bigint_id_to_modulus.insert(output, modulus); + Ok(()) + } + + pub fn bigint_to_bytes(&self, input: u32) -> Result, BlackBoxResolutionError> { + let bigint = self.get_bigint(input, BlackBoxFunc::BigIntToLeBytes)?; + Ok(bigint.to_bytes_le()) + } + + pub fn bigint_op( + &mut self, + lhs: u32, + rhs: u32, + output: u32, + func: BlackBoxFunc, + ) -> Result<(), BlackBoxResolutionError> { + let modulus = self.get_modulus(lhs, func)?; + let lhs = self.get_bigint(lhs, func)?; + let rhs = self.get_bigint(rhs, func)?; + let mut result = match func { + BlackBoxFunc::BigIntAdd => lhs + rhs, + BlackBoxFunc::BigIntSub => { + if lhs >= rhs { + &lhs - &rhs + } else { + &lhs + &modulus - &rhs + } + } + BlackBoxFunc::BigIntMul => lhs * rhs, + BlackBoxFunc::BigIntDiv => { + lhs * rhs.modpow(&(&modulus - BigUint::from(2_u32)), &modulus) + } //TODO ensure that modulus is prime + _ => unreachable!("ICE - bigint_op must be called for an operation"), + }; + if result > modulus { + let q = &result / &modulus; + result -= q * &modulus; + } + self.bigint_id_to_value.insert(output, result); + self.bigint_id_to_modulus.insert(output, modulus); + Ok(()) + } +} diff --git a/acvm-repo/blackbox_solver/src/lib.rs b/acvm-repo/blackbox_solver/src/lib.rs index dc798bdab32..0f57f2ce7da 100644 --- a/acvm-repo/blackbox_solver/src/lib.rs +++ b/acvm-repo/blackbox_solver/src/lib.rs @@ -10,10 +10,12 @@ use acir::BlackBoxFunc; use thiserror::Error; +mod bigint; mod curve_specific_solver; mod ecdsa; mod hash; +pub use bigint::BigIntSolver; pub use curve_specific_solver::{BlackBoxFunctionSolver, StubbedBlackBoxSolver}; pub use ecdsa::{ecdsa_secp256k1_verify, ecdsa_secp256r1_verify}; pub use hash::{blake2s, blake3, keccak256, keccakf1600, sha256, sha256compression}; diff --git a/acvm-repo/brillig_vm/src/black_box.rs b/acvm-repo/brillig_vm/src/black_box.rs index 73981fb0625..600e9d96b08 100644 --- a/acvm-repo/brillig_vm/src/black_box.rs +++ b/acvm-repo/brillig_vm/src/black_box.rs @@ -1,5 +1,6 @@ use acir::brillig::{BlackBoxOp, HeapArray, HeapVector}; use acir::{BlackBoxFunc, FieldElement}; +use acvm_blackbox_solver::BigIntSolver; use acvm_blackbox_solver::{ blake2s, blake3, ecdsa_secp256k1_verify, ecdsa_secp256r1_verify, keccak256, keccakf1600, sha256, sha256compression, BlackBoxFunctionSolver, BlackBoxResolutionError, @@ -34,6 +35,7 @@ pub(crate) fn evaluate_black_box( op: &BlackBoxOp, solver: &Solver, memory: &mut Memory, + bigint_solver: &mut BigIntSolver, ) -> Result<(), BlackBoxResolutionError> { match op { BlackBoxOp::Sha256 { message, output } => { @@ -177,12 +179,57 @@ pub(crate) fn evaluate_black_box( memory.write(*output, hash.into()); Ok(()) } - BlackBoxOp::BigIntAdd { .. } => todo!(), - BlackBoxOp::BigIntSub { .. } => todo!(), - BlackBoxOp::BigIntMul { .. } => todo!(), - BlackBoxOp::BigIntDiv { .. } => todo!(), - BlackBoxOp::BigIntFromLeBytes { .. } => todo!(), - BlackBoxOp::BigIntToLeBytes { .. } => todo!(), + BlackBoxOp::BigIntAdd { lhs, rhs, output } => { + let lhs = memory.read(*lhs).try_into().unwrap(); + let rhs = memory.read(*rhs).try_into().unwrap(); + let output = memory.read(*output).try_into().unwrap(); + bigint_solver.bigint_op(lhs, rhs, output, BlackBoxFunc::BigIntAdd)?; + Ok(()) + } + BlackBoxOp::BigIntSub { lhs, rhs, output } => { + let lhs = memory.read(*lhs).try_into().unwrap(); + let rhs = memory.read(*rhs).try_into().unwrap(); + let output = memory.read(*output).try_into().unwrap(); + bigint_solver.bigint_op(lhs, rhs, output, BlackBoxFunc::BigIntSub)?; + Ok(()) + } + BlackBoxOp::BigIntMul { lhs, rhs, output } => { + let lhs = memory.read(*lhs).try_into().unwrap(); + let rhs = memory.read(*rhs).try_into().unwrap(); + let output = memory.read(*output).try_into().unwrap(); + bigint_solver.bigint_op(lhs, rhs, output, BlackBoxFunc::BigIntMul)?; + Ok(()) + } + BlackBoxOp::BigIntDiv { lhs, rhs, output } => { + let lhs = memory.read(*lhs).try_into().unwrap(); + let rhs = memory.read(*rhs).try_into().unwrap(); + let output = memory.read(*output).try_into().unwrap(); + bigint_solver.bigint_op(lhs, rhs, output, BlackBoxFunc::BigIntDiv)?; + Ok(()) + } + BlackBoxOp::BigIntFromLeBytes { inputs, modulus, output } => { + let input = read_heap_vector(memory, inputs); + let input: Vec = input.iter().map(|x| x.try_into().unwrap()).collect(); + let modulus = read_heap_vector(memory, modulus); + let modulus: Vec = modulus.iter().map(|x| x.try_into().unwrap()).collect(); + let output = memory.read(*output).try_into().unwrap(); + bigint_solver.bigint_from_bytes(&input, &modulus, output)?; + Ok(()) + } + BlackBoxOp::BigIntToLeBytes { input, output } => { + let input: u32 = memory.read(*input).try_into().unwrap(); + let bytes = bigint_solver.bigint_to_bytes(input)?; + let mut values = Vec::new(); + for i in 0..32 { + if i < bytes.len() { + values.push(bytes[i].into()); + } else { + values.push(0_u8.into()); + } + } + memory.write_slice(memory.read_ref(output.pointer), &values); + Ok(()) + } BlackBoxOp::Poseidon2Permutation { message, output, len } => { let input = read_heap_vector(memory, message); let input: Vec = input.iter().map(|x| x.try_into().unwrap()).collect(); @@ -256,7 +303,7 @@ fn black_box_function_from_op(op: &BlackBoxOp) -> BlackBoxFunc { #[cfg(test)] mod test { use acir::brillig::{BlackBoxOp, MemoryAddress}; - use acvm_blackbox_solver::StubbedBlackBoxSolver; + use acvm_blackbox_solver::{BigIntSolver, StubbedBlackBoxSolver}; use crate::{ black_box::{evaluate_black_box, to_u8_vec, to_value_vec}, @@ -281,7 +328,8 @@ mod test { output: HeapArray { pointer: 2.into(), size: 32 }, }; - evaluate_black_box(&op, &StubbedBlackBoxSolver, &mut memory).unwrap(); + evaluate_black_box(&op, &StubbedBlackBoxSolver, &mut memory, &mut BigIntSolver::default()) + .unwrap(); let result = memory.read_slice(MemoryAddress(result_pointer), 32); diff --git a/acvm-repo/brillig_vm/src/lib.rs b/acvm-repo/brillig_vm/src/lib.rs index 26d5da67576..a2380aa4652 100644 --- a/acvm-repo/brillig_vm/src/lib.rs +++ b/acvm-repo/brillig_vm/src/lib.rs @@ -16,7 +16,7 @@ use acir::brillig::{ HeapVector, MemoryAddress, Opcode, ValueOrArray, }; use acir::FieldElement; -use acvm_blackbox_solver::BlackBoxFunctionSolver; +use acvm_blackbox_solver::{BigIntSolver, BlackBoxFunctionSolver}; use arithmetic::{evaluate_binary_field_op, evaluate_binary_int_op, BrilligArithmeticError}; use black_box::evaluate_black_box; use num_bigint::BigUint; @@ -81,6 +81,8 @@ pub struct VM<'a, B: BlackBoxFunctionSolver> { call_stack: Vec, /// The solver for blackbox functions black_box_solver: &'a B, + // The solver for big integers + bigint_solver: BigIntSolver, } impl<'a, B: BlackBoxFunctionSolver> VM<'a, B> { @@ -101,6 +103,7 @@ impl<'a, B: BlackBoxFunctionSolver> VM<'a, B> { memory: Memory::default(), call_stack: Vec::new(), black_box_solver, + bigint_solver: Default::default(), } } @@ -311,7 +314,12 @@ impl<'a, B: BlackBoxFunctionSolver> VM<'a, B> { self.increment_program_counter() } Opcode::BlackBox(black_box_op) => { - match evaluate_black_box(black_box_op, self.black_box_solver, &mut self.memory) { + match evaluate_black_box( + black_box_op, + self.black_box_solver, + &mut self.memory, + &mut self.bigint_solver, + ) { Ok(()) => self.increment_program_counter(), Err(e) => self.fail(e.to_string()), } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs index 36b5f8793cb..1f86d66fd4e 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs @@ -1,8 +1,11 @@ -use acvm::acir::{brillig::BlackBoxOp, BlackBoxFunc}; +use acvm::{ + acir::{brillig::BlackBoxOp, BlackBoxFunc}, + FieldElement, +}; use crate::brillig::brillig_ir::{ brillig_variable::{BrilligVariable, BrilligVector, SingleAddrVariable}, - BrilligContext, + BrilligBinaryOp, BrilligContext, }; /// Transforms SSA's black box function calls into the corresponding brillig instructions @@ -235,10 +238,17 @@ pub(crate) fn convert_black_box_call( ), BlackBoxFunc::BigIntAdd => { if let ( - [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(rhs)], - [BrilligVariable::SingleAddr(output)], + [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(lhs_modulus), BrilligVariable::SingleAddr(rhs), BrilligVariable::SingleAddr(rhs_modulus)], + [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(modulus_id)], ) = (function_arguments, function_results) { + prepare_bigint_output( + brillig_context, + lhs_modulus, + rhs_modulus, + output, + modulus_id, + ); brillig_context.black_box_op_instruction(BlackBoxOp::BigIntAdd { lhs: lhs.address, rhs: rhs.address, @@ -246,16 +256,23 @@ pub(crate) fn convert_black_box_call( }); } else { unreachable!( - "ICE: BigIntAdd expects two register arguments and one result register" + "ICE: BigIntAdd expects four register arguments and two result registers" ) } } BlackBoxFunc::BigIntSub => { if let ( - [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(rhs)], - [BrilligVariable::SingleAddr(output)], + [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(lhs_modulus), BrilligVariable::SingleAddr(rhs), BrilligVariable::SingleAddr(rhs_modulus)], + [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(modulus_id)], ) = (function_arguments, function_results) { + prepare_bigint_output( + brillig_context, + lhs_modulus, + rhs_modulus, + output, + modulus_id, + ); brillig_context.black_box_op_instruction(BlackBoxOp::BigIntSub { lhs: lhs.address, rhs: rhs.address, @@ -263,16 +280,23 @@ pub(crate) fn convert_black_box_call( }); } else { unreachable!( - "ICE: BigIntSub expects two register arguments and one result register" + "ICE: BigIntSub expects four register arguments and two result registers" ) } } BlackBoxFunc::BigIntMul => { if let ( - [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(rhs)], - [BrilligVariable::SingleAddr(output)], + [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(lhs_modulus), BrilligVariable::SingleAddr(rhs), BrilligVariable::SingleAddr(rhs_modulus)], + [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(modulus_id)], ) = (function_arguments, function_results) { + prepare_bigint_output( + brillig_context, + lhs_modulus, + rhs_modulus, + output, + modulus_id, + ); brillig_context.black_box_op_instruction(BlackBoxOp::BigIntMul { lhs: lhs.address, rhs: rhs.address, @@ -280,16 +304,23 @@ pub(crate) fn convert_black_box_call( }); } else { unreachable!( - "ICE: BigIntMul expects two register arguments and one result register" + "ICE: BigIntMul expects four register arguments and two result registers" ) } } BlackBoxFunc::BigIntDiv => { if let ( - [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(rhs)], - [BrilligVariable::SingleAddr(output)], + [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(lhs_modulus), BrilligVariable::SingleAddr(rhs), BrilligVariable::SingleAddr(rhs_modulus)], + [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(modulus_id)], ) = (function_arguments, function_results) { + prepare_bigint_output( + brillig_context, + lhs_modulus, + rhs_modulus, + output, + modulus_id, + ); brillig_context.black_box_op_instruction(BlackBoxOp::BigIntDiv { lhs: lhs.address, rhs: rhs.address, @@ -297,16 +328,20 @@ pub(crate) fn convert_black_box_call( }); } else { unreachable!( - "ICE: BigIntDiv expects two register arguments and one result register" + "ICE: BigIntDiv expects four register arguments and two result registers" ) } } BlackBoxFunc::BigIntFromLeBytes => { - if let ([inputs, modulus], [BrilligVariable::SingleAddr(output)]) = - (function_arguments, function_results) + if let ( + [inputs, modulus], + [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(_modulus_id)], + ) = (function_arguments, function_results) { let inputs_vector = convert_array_or_vector(brillig_context, inputs, bb_func); let modulus_vector = convert_array_or_vector(brillig_context, modulus, bb_func); + let output_id = brillig_context.get_new_bigint_id(); + brillig_context.const_instruction(*output, FieldElement::from(output_id as u128)); brillig_context.black_box_op_instruction(BlackBoxOp::BigIntFromLeBytes { inputs: inputs_vector.to_heap_vector(), modulus: modulus_vector.to_heap_vector(), @@ -314,23 +349,24 @@ pub(crate) fn convert_black_box_call( }); } else { unreachable!( - "ICE: BigIntFromLeBytes expects two register arguments and one result register" + "ICE: BigIntFromLeBytes expects a register and an array as arguments and two result registers" ) } } BlackBoxFunc::BigIntToLeBytes => { if let ( - [BrilligVariable::SingleAddr(input)], - [BrilligVariable::BrilligVector(result_vector)], + [BrilligVariable::SingleAddr(input), BrilligVariable::SingleAddr(_modulus)], + [result_array], ) = (function_arguments, function_results) { + let output = convert_array_or_vector(brillig_context, result_array, bb_func); brillig_context.black_box_op_instruction(BlackBoxOp::BigIntToLeBytes { input: input.address, - output: result_vector.to_heap_vector(), + output: output.to_heap_vector(), }); } else { unreachable!( - "ICE: BigIntToLeBytes expects one register argument and one array result" + "ICE: BigIntToLeBytes expects two register arguments and one array result" ) } } @@ -383,3 +419,30 @@ fn convert_array_or_vector( ), } } + +fn prepare_bigint_output( + brillig_context: &mut BrilligContext, + lhs_modulus: &SingleAddrVariable, + rhs_modulus: &SingleAddrVariable, + output: &SingleAddrVariable, + modulus_id: &SingleAddrVariable, +) { + // Check moduli + let condition = brillig_context.allocate_register(); + let condition_adr = SingleAddrVariable { address: condition, bit_size: 1 }; + brillig_context.binary_instruction( + *lhs_modulus, + *rhs_modulus, + condition_adr, + BrilligBinaryOp::Equals, + ); + brillig_context.constrain_instruction( + condition_adr, + Some("moduli should be identical in BigInt operation".to_string()), + ); + brillig_context.deallocate_register(condition); + // Set output id + let output_id = brillig_context.get_new_bigint_id(); + brillig_context.const_instruction(*output, FieldElement::from(output_id as u128)); + brillig_context.mov_instruction(modulus_id.address, lhs_modulus.address); +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs index e5c731be679..4132089b36f 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs @@ -86,6 +86,8 @@ pub(crate) struct BrilligContext { next_section: usize, /// IR printer debug_show: DebugShow, + /// Counter for generating bigint ids in unconstrained functions + bigint_new_id: u32, } impl BrilligContext { @@ -98,9 +100,15 @@ impl BrilligContext { section_label: 0, next_section: 1, debug_show: DebugShow::new(enable_debug_trace), + bigint_new_id: 0, } } + pub(crate) fn get_new_bigint_id(&mut self) -> u32 { + let result = self.bigint_new_id; + self.bigint_new_id += 1; + result + } /// Adds a brillig instruction to the brillig byte code fn push_opcode(&mut self, opcode: BrilligOpcode) { self.obj.push_opcode(opcode); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs index fe3c5e0bb9c..88cf987325d 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs @@ -23,6 +23,7 @@ impl BrilligContext { section_label: 0, next_section: 1, debug_show: DebugShow::new(false), + bigint_new_id: 0, }; context.codegen_entry_point(&arguments, &return_parameters); 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 775571f4a41..e49d1427fce 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 @@ -1261,7 +1261,8 @@ impl AcirContext { let modulus = self.big_int_ctx.modulus(bigint.modulus_id()); 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)]) + assert!(bytes_len == 32); + (field_inputs, vec![]) } BlackBoxFunc::BigIntFromLeBytes => { let invalid_input = "ICE - bigint operation requires 2 inputs"; diff --git a/docs/docs/noir/standard_library/bigint.md b/docs/docs/noir/standard_library/bigint.md index 9aa4fb77112..54d791b82d3 100644 --- a/docs/docs/noir/standard_library/bigint.md +++ b/docs/docs/noir/standard_library/bigint.md @@ -48,7 +48,10 @@ The available operations for each big integer are: Construct a big integer from its little-endian bytes representation. Example: ```rust + // Construct a big integer from a slice of bytes let a = Secpk1Fq::from_le_bytes(&[x, y, 0, 45, 2]); + // Construct a big integer from an array of 32 bytes + let a = Secpk1Fq::from_le_bytes_32([1;32]); ``` Sure, here's the formatted version of the remaining methods: diff --git a/noir_stdlib/src/bigint.nr b/noir_stdlib/src/bigint.nr index 39ec40a1480..ee9f8e44625 100644 --- a/noir_stdlib/src/bigint.nr +++ b/noir_stdlib/src/bigint.nr @@ -32,7 +32,7 @@ impl BigInt { #[builtin(bigint_from_le_bytes)] fn from_le_bytes(bytes: [u8], modulus: [u8]) -> BigInt {} #[builtin(bigint_to_le_bytes)] - fn to_le_bytes(self) -> [u8] {} + fn to_le_bytes(self) -> [u8; 32] {} fn check_32_bytes(self: Self, other: BigInt) -> bool { let bytes = self.to_le_bytes(); @@ -47,305 +47,420 @@ impl BigInt { trait BigField { fn from_le_bytes(bytes: [u8]) -> Self; + fn from_le_bytes_32(bytes: [u8; 32]) -> Self; fn to_le_bytes(self) -> [u8]; } struct Secpk1Fq { - inner: BigInt, + array: [u8;32], } impl BigField for Secpk1Fq { fn from_le_bytes(bytes: [u8]) -> Secpk1Fq { + assert(bytes.len() <= 32); + let mut array = [0;32]; + for i in 0..bytes.len() { + array[i] = bytes[i]; + } + Secpk1Fq { + array: array, + } + } + + fn from_le_bytes_32(bytes: [u8;32]) -> Secpk1Fq { Secpk1Fq { - inner: BigInt::from_le_bytes(bytes, secpk1_fq) + array: bytes, } } + fn to_le_bytes(self) -> [u8] { - self.inner.to_le_bytes() + self.array } } impl Add for Secpk1Fq { fn add(self: Self, other: Secpk1Fq) -> Secpk1Fq { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpk1Fq { - inner: self.inner.bigint_add(other.inner) + array: a.bigint_add(b).to_le_bytes() } } } impl Sub for Secpk1Fq { fn sub(self: Self, other: Secpk1Fq) -> Secpk1Fq { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpk1Fq { - inner: self.inner.bigint_sub(other.inner) + array: a.bigint_sub(b).to_le_bytes() } } } impl Mul for Secpk1Fq { fn mul(self: Self, other: Secpk1Fq) -> Secpk1Fq { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpk1Fq { - inner: self.inner.bigint_mul(other.inner) + array: a.bigint_mul(b).to_le_bytes() } - } } impl Div for Secpk1Fq { fn div(self: Self, other: Secpk1Fq) -> Secpk1Fq { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpk1Fq { - inner: self.inner.bigint_div(other.inner) + array: a.bigint_div(b).to_le_bytes() } } } impl Eq for Secpk1Fq { fn eq(self: Self, other: Secpk1Fq) -> bool { - self.inner.check_32_bytes(other.inner) + self.array == other.array } } struct Secpk1Fr { - inner: BigInt, + array: [u8;32], } impl BigField for Secpk1Fr { fn from_le_bytes(bytes: [u8]) -> Secpk1Fr { + assert(bytes.len() <= 32); + let mut array = [0;32]; + for i in 0..bytes.len() { + array[i] = bytes[i]; + } Secpk1Fr { - inner: BigInt::from_le_bytes(bytes, secpk1_fr) + array: array, } } + + fn from_le_bytes_32(bytes: [u8;32]) -> Secpk1Fr { + Secpk1Fr { + array: bytes, + } + } + fn to_le_bytes(self) -> [u8] { - self.inner.to_le_bytes() + self.array } } impl Add for Secpk1Fr { fn add(self: Self, other: Secpk1Fr) -> Secpk1Fr { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpk1Fr { - inner: self.inner.bigint_add(other.inner) + array: a.bigint_add(b).to_le_bytes() } } } impl Sub for Secpk1Fr { fn sub(self: Self, other: Secpk1Fr) -> Secpk1Fr { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpk1Fr { - inner: self.inner.bigint_sub(other.inner) + array: a.bigint_sub(b).to_le_bytes() } } } impl Mul for Secpk1Fr { fn mul(self: Self, other: Secpk1Fr) -> Secpk1Fr { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpk1Fr { - inner: self.inner.bigint_mul(other.inner) + array: a.bigint_mul(b).to_le_bytes() } - } } impl Div for Secpk1Fr { fn div(self: Self, other: Secpk1Fr) -> Secpk1Fr { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpk1Fr { - inner: self.inner.bigint_div(other.inner) + array: a.bigint_div(b).to_le_bytes() } } } impl Eq for Secpk1Fr { fn eq(self: Self, other: Secpk1Fr) -> bool { - self.inner.check_32_bytes(other.inner) + self.array == other.array } } struct Bn254Fr { - inner: BigInt, + array: [u8;32], } impl BigField for Bn254Fr { fn from_le_bytes(bytes: [u8]) -> Bn254Fr { + assert(bytes.len() <= 32); + let mut array = [0;32]; + for i in 0..bytes.len() { + array[i] = bytes[i]; + } + Bn254Fr { + array: array, + } + } + + fn from_le_bytes_32(bytes: [u8;32]) -> Bn254Fr { Bn254Fr { - inner: BigInt::from_le_bytes(bytes, bn254_fr) + array: bytes, } } + fn to_le_bytes(self) -> [u8] { - self.inner.to_le_bytes() + self.array } } impl Add for Bn254Fr { fn add(self: Self, other: Bn254Fr) -> Bn254Fr { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Bn254Fr { - inner: self.inner.bigint_add(other.inner) + array: a.bigint_add(b).to_le_bytes() } } } impl Sub for Bn254Fr { fn sub(self: Self, other: Bn254Fr) -> Bn254Fr { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Bn254Fr { - inner: self.inner.bigint_sub(other.inner) + array: a.bigint_sub(b).to_le_bytes() } } } impl Mul for Bn254Fr { fn mul(self: Self, other: Bn254Fr) -> Bn254Fr { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Bn254Fr { - inner: self.inner.bigint_mul(other.inner) + array: a.bigint_mul(b).to_le_bytes() } - } } impl Div for Bn254Fr { fn div(self: Self, other: Bn254Fr) -> Bn254Fr { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Bn254Fr { - inner: self.inner.bigint_div(other.inner) + array: a.bigint_div(b).to_le_bytes() } } } impl Eq for Bn254Fr { fn eq(self: Self, other: Bn254Fr) -> bool { - self.inner.check_32_bytes(other.inner) + self.array == other.array } } struct Bn254Fq { - inner: BigInt, + array: [u8;32], } impl BigField for Bn254Fq { fn from_le_bytes(bytes: [u8]) -> Bn254Fq { + assert(bytes.len() <= 32); + let mut array = [0;32]; + for i in 0..bytes.len() { + array[i] = bytes[i]; + } Bn254Fq { - inner: BigInt::from_le_bytes(bytes, bn254_fq) + array: array, } } + + fn from_le_bytes_32(bytes: [u8;32]) -> Bn254Fq { + Bn254Fq { + array: bytes, + } + } + fn to_le_bytes(self) -> [u8] { - self.inner.to_le_bytes() + self.array } } impl Add for Bn254Fq { fn add(self: Self, other: Bn254Fq) -> Bn254Fq { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Bn254Fq { - inner: self.inner.bigint_add(other.inner) + array: a.bigint_add(b).to_le_bytes() } } } impl Sub for Bn254Fq { fn sub(self: Self, other: Bn254Fq) -> Bn254Fq { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Bn254Fq { - inner: self.inner.bigint_sub(other.inner) + array: a.bigint_sub(b).to_le_bytes() } } } impl Mul for Bn254Fq { fn mul(self: Self, other: Bn254Fq) -> Bn254Fq { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Bn254Fq { - inner: self.inner.bigint_mul(other.inner) + array: a.bigint_mul(b).to_le_bytes() } - } } impl Div for Bn254Fq { fn div(self: Self, other: Bn254Fq) -> Bn254Fq { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Bn254Fq { - inner: self.inner.bigint_div(other.inner) + array: a.bigint_div(b).to_le_bytes() } } } impl Eq for Bn254Fq { fn eq(self: Self, other: Bn254Fq) -> bool { - self.inner.check_32_bytes(other.inner) + self.array == other.array } } struct Secpr1Fq { - inner: BigInt, + array: [u8;32], } impl BigField for Secpr1Fq { fn from_le_bytes(bytes: [u8]) -> Secpr1Fq { + assert(bytes.len() <= 32); + let mut array = [0;32]; + for i in 0..bytes.len() { + array[i] = bytes[i]; + } + Secpr1Fq { + array: array, + } + } + + fn from_le_bytes_32(bytes: [u8;32]) -> Secpr1Fq { Secpr1Fq { - inner: BigInt::from_le_bytes(bytes, secpr1_fq) + array: bytes, } } + fn to_le_bytes(self) -> [u8] { - self.inner.to_le_bytes() + self.array } } impl Add for Secpr1Fq { fn add(self: Self, other: Secpr1Fq) -> Secpr1Fq { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpr1Fq { - inner: self.inner.bigint_add(other.inner) + array: a.bigint_add(b).to_le_bytes() } } } impl Sub for Secpr1Fq { fn sub(self: Self, other: Secpr1Fq) -> Secpr1Fq { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpr1Fq { - inner: self.inner.bigint_sub(other.inner) + array: a.bigint_sub(b).to_le_bytes() } } } impl Mul for Secpr1Fq { fn mul(self: Self, other: Secpr1Fq) -> Secpr1Fq { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpr1Fq { - inner: self.inner.bigint_mul(other.inner) + array: a.bigint_mul(b).to_le_bytes() } - } } impl Div for Secpr1Fq { fn div(self: Self, other: Secpr1Fq) -> Secpr1Fq { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpr1Fq { - inner: self.inner.bigint_div(other.inner) + array: a.bigint_div(b).to_le_bytes() } } } impl Eq for Secpr1Fq { fn eq(self: Self, other: Secpr1Fq) -> bool { - self.inner.check_32_bytes(other.inner) + self.array == other.array } } struct Secpr1Fr { - inner: BigInt, + array: [u8;32], } impl BigField for Secpr1Fr { fn from_le_bytes(bytes: [u8]) -> Secpr1Fr { + assert(bytes.len() <= 32); + let mut array = [0;32]; + for i in 0..bytes.len() { + array[i] = bytes[i]; + } + Secpr1Fr { + array: array, + } + } + + fn from_le_bytes_32(bytes: [u8;32]) -> Secpr1Fr { Secpr1Fr { - inner: BigInt::from_le_bytes(bytes, secpr1_fr) + array: bytes, } } + fn to_le_bytes(self) -> [u8] { - self.inner.to_le_bytes() + self.array } } impl Add for Secpr1Fr { fn add(self: Self, other: Secpr1Fr) -> Secpr1Fr { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpr1Fr { - inner: self.inner.bigint_add(other.inner) + array: a.bigint_add(b).to_le_bytes() } } } impl Sub for Secpr1Fr { fn sub(self: Self, other: Secpr1Fr) -> Secpr1Fr { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpr1Fr { - inner: self.inner.bigint_sub(other.inner) + array: a.bigint_sub(b).to_le_bytes() } } } impl Mul for Secpr1Fr { fn mul(self: Self, other: Secpr1Fr) -> Secpr1Fr { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpr1Fr { - inner: self.inner.bigint_mul(other.inner) + array: a.bigint_mul(b).to_le_bytes() } - } } impl Div for Secpr1Fr { fn div(self: Self, other: Secpr1Fr) -> Secpr1Fr { + let a = BigInt::from_le_bytes(self.array.as_slice(), secpk1_fq); + let b = BigInt::from_le_bytes(other.array.as_slice(), secpk1_fq); Secpr1Fr { - inner: self.inner.bigint_div(other.inner) + array: a.bigint_div(b).to_le_bytes() } } } impl Eq for Secpr1Fr { fn eq(self: Self, other: Secpr1Fr) -> bool { - self.inner.check_32_bytes(other.inner) + self.array == other.array } } diff --git a/test_programs/execution_success/bigint/src/main.nr b/test_programs/execution_success/bigint/src/main.nr index 908cab8accc..5645e4e9e1b 100644 --- a/test_programs/execution_success/bigint/src/main.nr +++ b/test_programs/execution_success/bigint/src/main.nr @@ -4,6 +4,23 @@ use dep::std::{bigint::Secpk1Fq, println}; fn main(mut x: [u8; 5], y: [u8; 5]) { let a = bigint::Secpk1Fq::from_le_bytes(&[x[0], x[1], x[2], x[3], x[4]]); let b = bigint::Secpk1Fq::from_le_bytes(&[y[0], y[1], y[2], y[3], y[4]]); + let mut a_be_bytes = [0; 32]; + let mut b_be_bytes = [0; 32]; + for i in 0..5 { + a_be_bytes[31-i] = x[i]; + b_be_bytes[31-i] = y[i]; + } + let a_field = dep::std::field::bytes32_to_field(a_be_bytes); + let b_field = dep::std::field::bytes32_to_field(b_be_bytes); + + // Regression for #4682 + let c = if x[0] != 0 { + test_unconstrained1(a, b) + } else { + test_unconstrained2(a, b) + }; + assert(c.array[0] == dep::std::wrapping_mul(x[0], y[0])); + let a_bytes = a.to_le_bytes(); let b_bytes = b.to_le_bytes(); for i in 0..5 { @@ -14,9 +31,26 @@ fn main(mut x: [u8; 5], y: [u8; 5]) { let d = a * b; assert(d / b == a); + let d = d - b; + let mut result = [0; 32]; + let result_slice = (a_field * b_field - b_field).to_le_bytes(32); + for i in 0..32 { + result[i] = result_slice[i]; + } + let d1 = bigint::Secpk1Fq::from_le_bytes_32(result); + assert(d1 == d); big_int_example(x[0], x[1]); } +fn test_unconstrained1(a: Secpk1Fq, b: Secpk1Fq) -> Secpk1Fq { + let c = a * b; + c +} +unconstrained fn test_unconstrained2(a: Secpk1Fq, b: Secpk1Fq) -> Secpk1Fq { + let c = a + b; + test_unconstrained1(a, c) +} + // docs:start:big_int_example fn big_int_example(x: u8, y: u8) { let a = Secpk1Fq::from_le_bytes(&[x, y, 0, 45, 2]);