diff --git a/.aztec-sync-commit b/.aztec-sync-commit index 6e1f033e292..8cfda4ff013 100644 --- a/.aztec-sync-commit +++ b/.aztec-sync-commit @@ -1 +1 @@ -07d6dc29db2eb04154b8f0c66bd1efa74c0e8b9d +d9de430e4a01d6908a9b1fe5e6ede9309aa8a10d diff --git a/acvm-repo/acir/codegen/acir.cpp b/acvm-repo/acir/codegen/acir.cpp index 1bb8931c642..0880b5a0cbe 100644 --- a/acvm-repo/acir/codegen/acir.cpp +++ b/acvm-repo/acir/codegen/acir.cpp @@ -694,7 +694,7 @@ namespace Program { }; struct Trap { - Program::HeapArray revert_data; + Program::HeapVector revert_data; friend bool operator==(const Trap&, const Trap&); std::vector bincodeSerialize() const; diff --git a/acvm-repo/acvm/tests/solver.rs b/acvm-repo/acvm/tests/solver.rs index efa8de289e5..2828ea3d79e 100644 --- a/acvm-repo/acvm/tests/solver.rs +++ b/acvm-repo/acvm/tests/solver.rs @@ -1,10 +1,10 @@ use std::collections::{BTreeMap, HashSet}; use std::sync::Arc; -use acir::brillig::{BitSize, IntegerBitSize}; +use acir::brillig::{BitSize, HeapVector, IntegerBitSize}; use acir::{ acir_field::GenericFieldElement, - brillig::{BinaryFieldOp, HeapArray, MemoryAddress, Opcode as BrilligOpcode, ValueOrArray}, + brillig::{BinaryFieldOp, MemoryAddress, Opcode as BrilligOpcode, ValueOrArray}, circuit::{ brillig::{BrilligBytecode, BrilligFunctionId, BrilligInputs, BrilligOutputs}, opcodes::{BlackBoxFuncCall, BlockId, BlockType, FunctionInput, MemOp}, @@ -667,7 +667,12 @@ fn unsatisfied_opcode_resolved_brillig() { let jmp_if_opcode = BrilligOpcode::JumpIf { condition: MemoryAddress::direct(2), location: location_of_stop }; - let trap_opcode = BrilligOpcode::Trap { revert_data: HeapArray::default() }; + let trap_opcode = BrilligOpcode::Trap { + revert_data: HeapVector { + pointer: MemoryAddress::direct(0), + size: MemoryAddress::direct(3), + }, + }; let stop_opcode = BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }; let brillig_bytecode = BrilligBytecode { @@ -682,6 +687,11 @@ fn unsatisfied_opcode_resolved_brillig() { bit_size: BitSize::Integer(IntegerBitSize::U32), value: FieldElement::from(0u64), }, + BrilligOpcode::Const { + destination: MemoryAddress::direct(3), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, calldata_copy_opcode, equal_opcode, jmp_if_opcode, @@ -739,7 +749,7 @@ fn unsatisfied_opcode_resolved_brillig() { ACVMStatus::Failure(OpcodeResolutionError::BrilligFunctionFailed { function_id: BrilligFunctionId(0), payload: None, - call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 5 }] + call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 6 }] }), "The first opcode is not satisfiable, expected an error indicating this" ); diff --git a/acvm-repo/brillig/src/opcodes.rs b/acvm-repo/brillig/src/opcodes.rs index 69ca9ed379a..0d87c5b9410 100644 --- a/acvm-repo/brillig/src/opcodes.rs +++ b/acvm-repo/brillig/src/opcodes.rs @@ -305,7 +305,7 @@ pub enum BrilligOpcode { BlackBox(BlackBoxOp), /// Used to denote execution failure, returning data after the offset Trap { - revert_data: HeapArray, + revert_data: HeapVector, }, /// Stop execution, returning data after the offset Stop { diff --git a/acvm-repo/brillig_vm/src/black_box.rs b/acvm-repo/brillig_vm/src/black_box.rs index 04dd85d9324..0d90a4c8502 100644 --- a/acvm-repo/brillig_vm/src/black_box.rs +++ b/acvm-repo/brillig_vm/src/black_box.rs @@ -330,18 +330,18 @@ pub(crate) fn evaluate_black_box let mut input = BigUint::from_bytes_be(&input.to_be_bytes()); let radix = BigUint::from_bytes_be(&radix.to_be_bytes()); - let mut limbs: Vec> = Vec::with_capacity(output.size); + let mut limbs: Vec> = vec![MemoryValue::default(); output.size]; - for _ in 0..output.size { + for i in (0..output.size).rev() { let limb = &input % &radix; if *output_bits { - limbs.push(MemoryValue::new_integer( + limbs[i] = MemoryValue::new_integer( if limb.is_zero() { 0 } else { 1 }, IntegerBitSize::U1, - )); + ); } else { let limb: u8 = limb.try_into().unwrap(); - limbs.push(MemoryValue::new_integer(limb as u128, IntegerBitSize::U8)); + limbs[i] = MemoryValue::new_integer(limb as u128, IntegerBitSize::U8); }; input /= &radix; } diff --git a/acvm-repo/brillig_vm/src/lib.rs b/acvm-repo/brillig_vm/src/lib.rs index 4d5ce4203f9..89d72c4614b 100644 --- a/acvm-repo/brillig_vm/src/lib.rs +++ b/acvm-repo/brillig_vm/src/lib.rs @@ -347,10 +347,11 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver> VM<'a, F, B> { self.increment_program_counter() } Opcode::Trap { revert_data } => { - if revert_data.size > 0 { + let revert_data_size = self.memory.read(revert_data.size).to_usize(); + if revert_data_size > 0 { self.trap( self.memory.read_ref(revert_data.pointer).unwrap_direct(), - revert_data.size, + revert_data_size, ) } else { self.trap(0, 0) @@ -937,8 +938,18 @@ mod tests { size_address: MemoryAddress::direct(0), offset_address: MemoryAddress::direct(1), }, - Opcode::Jump { location: 5 }, - Opcode::Trap { revert_data: HeapArray::default() }, + Opcode::Jump { location: 6 }, + Opcode::Const { + destination: MemoryAddress::direct(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }, + Opcode::Trap { + revert_data: HeapVector { + pointer: MemoryAddress::direct(0), + size: MemoryAddress::direct(0), + }, + }, Opcode::BinaryFieldOp { op: BinaryFieldOp::Equals, lhs: MemoryAddress::direct(0), @@ -966,6 +977,8 @@ mod tests { assert_eq!(status, VMStatus::InProgress); let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); let output_cmp_value = vm.memory.read(MemoryAddress::direct(2)); assert_eq!(output_cmp_value.to_field(), false.into()); @@ -978,7 +991,7 @@ mod tests { status, VMStatus::Failure { reason: FailureReason::Trap { revert_data_offset: 0, revert_data_size: 0 }, - call_stack: vec![4] + call_stack: vec![5] } ); diff --git a/compiler/integration-tests/package.json b/compiler/integration-tests/package.json index 62ae699fc4e..a438c2965c3 100644 --- a/compiler/integration-tests/package.json +++ b/compiler/integration-tests/package.json @@ -13,7 +13,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "0.60.0", + "@aztec/bb.js": "0.61.0", "@noir-lang/noir_js": "workspace:*", "@noir-lang/noir_wasm": "workspace:*", "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index e720bf14a4c..23c802e3a14 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -548,7 +548,7 @@ impl<'block> BrilligBlock<'block> { source, target_array, radix, - matches!(endianness, Endian::Big), + matches!(endianness, Endian::Little), false, ); } @@ -573,7 +573,7 @@ impl<'block> BrilligBlock<'block> { source, target_array, two, - matches!(endianness, Endian::Big), + matches!(endianness, Endian::Little), true, ); @@ -583,7 +583,31 @@ impl<'block> BrilligBlock<'block> { // `Intrinsic::AsWitness` is used to provide hints to acir-gen on optimal expression splitting. // It is then useless in the brillig runtime and so we can ignore it Value::Intrinsic(Intrinsic::AsWitness) => (), + Value::Intrinsic(Intrinsic::FieldLessThan) => { + let lhs = self.convert_ssa_single_addr_value(arguments[0], dfg); + assert!(lhs.bit_size == FieldElement::max_num_bits()); + let rhs = self.convert_ssa_single_addr_value(arguments[1], dfg); + assert!(rhs.bit_size == FieldElement::max_num_bits()); + let results = dfg.instruction_results(instruction_id); + let destination = self + .variables + .define_variable( + self.function_context, + self.brillig_context, + results[0], + dfg, + ) + .extract_single_addr(); + assert!(destination.bit_size == 1); + + self.brillig_context.binary_instruction( + lhs, + rhs, + destination, + BrilligBinaryOp::LessThan, + ); + } _ => { unreachable!("unsupported function call type {:?}", dfg[*func]) } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs index 416f0300508..0c6097b8f6d 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs @@ -146,8 +146,8 @@ pub(crate) mod tests { use std::vec; use acvm::acir::brillig::{ - BitSize, ForeignCallParam, ForeignCallResult, HeapArray, HeapVector, IntegerBitSize, - MemoryAddress, ValueOrArray, + BitSize, ForeignCallParam, ForeignCallResult, HeapVector, IntegerBitSize, MemoryAddress, + ValueOrArray, }; use acvm::brillig_vm::brillig::HeapValueType; use acvm::brillig_vm::{VMStatus, VM}; @@ -289,8 +289,18 @@ pub(crate) mod tests { // We push a JumpIf and Trap opcode directly as the constrain instruction // uses unresolved jumps which requires a block to be constructed in SSA and // we don't need this for Brillig IR tests - context.push_opcode(BrilligOpcode::JumpIf { condition: r_equality, location: 8 }); - context.push_opcode(BrilligOpcode::Trap { revert_data: HeapArray::default() }); + context.push_opcode(BrilligOpcode::JumpIf { condition: r_equality, location: 9 }); + context.push_opcode(BrilligOpcode::Const { + destination: MemoryAddress::direct(0), + bit_size: BitSize::Integer(IntegerBitSize::U32), + value: FieldElement::from(0u64), + }); + context.push_opcode(BrilligOpcode::Trap { + revert_data: HeapVector { + pointer: MemoryAddress::direct(0), + size: MemoryAddress::direct(0), + }, + }); context.stop_instruction(); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs index b74154c16be..540d957d5be 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs @@ -1,5 +1,5 @@ use acvm::{ - acir::brillig::{HeapArray, MemoryAddress}, + acir::brillig::{HeapVector, MemoryAddress}, AcirField, }; @@ -192,12 +192,12 @@ impl BrilligContext< assert!(condition.bit_size == 1); self.codegen_if_not(condition.address, |ctx| { - let revert_data = HeapArray { - pointer: ctx.allocate_register(), - // + 1 due to the revert data id being the first item returned - size: Self::flattened_tuple_size(&revert_data_types) + 1, - }; - ctx.codegen_allocate_immediate_mem(revert_data.pointer, revert_data.size); + // + 1 due to the revert data id being the first item returned + let revert_data_size = Self::flattened_tuple_size(&revert_data_types) + 1; + let revert_data_size_var = ctx.make_usize_constant_instruction(revert_data_size.into()); + let revert_data = + HeapVector { pointer: ctx.allocate_register(), size: revert_data_size_var.address }; + ctx.codegen_allocate_immediate_mem(revert_data.pointer, revert_data_size); let current_revert_data_pointer = ctx.allocate_register(); ctx.mov_instruction(current_revert_data_pointer, revert_data.pointer); @@ -243,6 +243,7 @@ impl BrilligContext< ); } ctx.trap_instruction(revert_data); + ctx.deallocate_single_addr(revert_data_size_var); ctx.deallocate_register(revert_data.pointer); ctx.deallocate_register(current_revert_data_pointer); }); @@ -258,7 +259,12 @@ impl BrilligContext< assert!(condition.bit_size == 1); self.codegen_if_not(condition.address, |ctx| { - ctx.trap_instruction(HeapArray::default()); + let revert_data_size_var = ctx.make_usize_constant_instruction(F::zero()); + ctx.trap_instruction(HeapVector { + pointer: MemoryAddress::direct(0), + size: revert_data_size_var.address, + }); + ctx.deallocate_single_addr(revert_data_size_var); if let Some(assert_message) = assert_message { ctx.obj.add_assert_message_to_last_opcode(assert_message); } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_intrinsic.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_intrinsic.rs index 5f4781788f5..ba89823ef13 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_intrinsic.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_intrinsic.rs @@ -69,7 +69,7 @@ impl BrilligContext< source_field: SingleAddrVariable, target_array: BrilligArray, radix: SingleAddrVariable, - big_endian: bool, + little_endian: bool, output_bits: bool, // If true will generate bit limbs, if false will generate byte limbs ) { assert!(source_field.bit_size == F::max_num_bits()); @@ -79,6 +79,7 @@ impl BrilligContext< let heap_array = self.codegen_brillig_array_to_heap_array(target_array); + // Perform big-endian ToRadix self.black_box_op_instruction(BlackBoxOp::ToRadix { input: source_field.address, radix: radix.address, @@ -86,7 +87,7 @@ impl BrilligContext< output_bits, }); - if big_endian { + if little_endian { let items_len = self.make_usize_constant_instruction(target_array.size.into()); self.codegen_array_reverse(heap_array.pointer, items_len.address); self.deallocate_single_addr(items_len); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs index 2a46a04cc91..4e82a0d3af5 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs @@ -117,7 +117,7 @@ impl DebugShow { } /// Emits a `trap` instruction. - pub(crate) fn trap_instruction(&self, revert_data: HeapArray) { + pub(crate) fn trap_instruction(&self, revert_data: HeapVector) { debug_println!(self.enable_debug_trace, " TRAP {}", revert_data); } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs index 1ac672687f3..5f0aedb9c5e 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs @@ -1,7 +1,7 @@ use acvm::{ acir::{ brillig::{ - BinaryFieldOp, BinaryIntOp, BitSize, BlackBoxOp, HeapArray, HeapValueType, + BinaryFieldOp, BinaryIntOp, BitSize, BlackBoxOp, HeapValueType, HeapVector, MemoryAddress, Opcode as BrilligOpcode, ValueOrArray, }, AcirField, @@ -425,7 +425,7 @@ impl BrilligContext< self.deallocate_single_addr(offset_var); } - pub(super) fn trap_instruction(&mut self, revert_data: HeapArray) { + pub(super) fn trap_instruction(&mut self, revert_data: HeapVector) { self.debug_show.trap_instruction(revert_data); self.push_opcode(BrilligOpcode::Trap { revert_data }); diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index a5c51392114..50a895df237 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -2789,6 +2789,9 @@ impl<'a> Context<'a> { Intrinsic::DerivePedersenGenerators => { unreachable!("DerivePedersenGenerators can only be called with constants") } + Intrinsic::FieldLessThan => { + unreachable!("FieldLessThan can only be called in unconstrained") + } } } diff --git a/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs index 7bee18d24a0..90eb79ccb69 100644 --- a/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs +++ b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs @@ -218,7 +218,8 @@ impl Context { | Intrinsic::StaticAssert | Intrinsic::StrAsBytes | Intrinsic::ToBits(..) - | Intrinsic::ToRadix(..) => { + | Intrinsic::ToRadix(..) + | Intrinsic::FieldLessThan => { self.value_sets.push(instruction_arguments_and_results); } }, diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index d8dba499a43..f187a279b9b 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -72,6 +72,7 @@ pub(crate) enum Intrinsic { AsWitness, IsUnconstrained, DerivePedersenGenerators, + FieldLessThan, } impl std::fmt::Display for Intrinsic { @@ -100,6 +101,7 @@ impl std::fmt::Display for Intrinsic { Intrinsic::AsWitness => write!(f, "as_witness"), Intrinsic::IsUnconstrained => write!(f, "is_unconstrained"), Intrinsic::DerivePedersenGenerators => write!(f, "derive_pedersen_generators"), + Intrinsic::FieldLessThan => write!(f, "field_less_than"), } } } @@ -131,7 +133,8 @@ impl Intrinsic { | Intrinsic::FromField | Intrinsic::AsField | Intrinsic::IsUnconstrained - | Intrinsic::DerivePedersenGenerators => false, + | Intrinsic::DerivePedersenGenerators + | Intrinsic::FieldLessThan => false, // Some black box functions have side-effects Intrinsic::BlackBox(func) => matches!( @@ -169,6 +172,8 @@ impl Intrinsic { "as_witness" => Some(Intrinsic::AsWitness), "is_unconstrained" => Some(Intrinsic::IsUnconstrained), "derive_pedersen_generators" => Some(Intrinsic::DerivePedersenGenerators), + "field_less_than" => Some(Intrinsic::FieldLessThan), + other => BlackBoxFunc::lookup(other).map(Intrinsic::BlackBox), } } diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 0bf7fe6a146..9dbd2c56993 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -355,6 +355,16 @@ pub(super) fn simplify_call( unreachable!("Derive Pedersen Generators must return an array"); } } + Intrinsic::FieldLessThan => { + if let Some(constants) = constant_args { + let lhs = constants[0]; + let rhs = constants[1]; + let result = dfg.make_constant((lhs < rhs).into(), Type::bool()); + SimplifyResult::SimplifiedTo(result) + } else { + SimplifyResult::None + } + } } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs index 222ae0aaf29..012f6e6b27d 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs @@ -178,7 +178,8 @@ impl Context { | Intrinsic::AsSlice | Intrinsic::AsWitness | Intrinsic::IsUnconstrained - | Intrinsic::DerivePedersenGenerators => false, + | Intrinsic::DerivePedersenGenerators + | Intrinsic::FieldLessThan => false, }, // We must assume that functions contain a side effect as we cannot inspect more deeply. diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs index bfcfada2d94..c387e0b6234 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs @@ -237,6 +237,7 @@ fn slice_capacity_change( | Intrinsic::IsUnconstrained | Intrinsic::DerivePedersenGenerators | Intrinsic::ToBits(_) - | Intrinsic::ToRadix(_) => SizeChange::None, + | Intrinsic::ToRadix(_) + | Intrinsic::FieldLessThan => SizeChange::None, } } diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 273f34a8a5e..d8842215a29 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -109,6 +109,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { "expr_is_continue" => expr_is_continue(interner, arguments, location), "expr_resolve" => expr_resolve(self, arguments, location), "is_unconstrained" => Ok(Value::Bool(true)), + "field_less_than" => field_less_than(arguments, location), "fmtstr_as_ctstring" => fmtstr_as_ctstring(interner, arguments, location), "fmtstr_quoted_contents" => fmtstr_quoted_contents(interner, arguments, location), "fresh_type_variable" => fresh_type_variable(interner), @@ -2849,3 +2850,12 @@ fn derive_generators( Ok(Value::Array(results, return_type)) } + +fn field_less_than(arguments: Vec<(Value, Location)>, location: Location) -> IResult { + let (lhs, rhs) = check_two_arguments(arguments, location)?; + + let lhs = get_field(lhs)?; + let rhs = get_field(rhs)?; + + Ok(Value::Bool(lhs < rhs)) +} diff --git a/noir_stdlib/src/field/bn254.nr b/noir_stdlib/src/field/bn254.nr index 9642c2aa1b8..a7ca7d77373 100644 --- a/noir_stdlib/src/field/bn254.nr +++ b/noir_stdlib/src/field/bn254.nr @@ -1,3 +1,4 @@ +use crate::field::field_less_than; use crate::runtime::is_unconstrained; // The low and high decomposition of the field modulus @@ -25,47 +26,20 @@ pub(crate) unconstrained fn decompose_hint(x: Field) -> (Field, Field) { compute_decomposition(x) } -fn compute_lt(x: Field, y: Field, num_bytes: u32) -> bool { - let x_bytes: [u8; 32] = x.to_le_bytes(); - let y_bytes: [u8; 32] = y.to_le_bytes(); - let mut x_is_lt = false; - let mut done = false; - for i in 0..num_bytes { - if (!done) { - let x_byte = x_bytes[num_bytes - 1 - i]; - let y_byte = y_bytes[num_bytes - 1 - i]; - let bytes_match = x_byte == y_byte; - if !bytes_match { - x_is_lt = x_byte < y_byte; - done = true; - } - } - } - x_is_lt -} - -fn compute_lte(x: Field, y: Field, num_bytes: u32) -> bool { +unconstrained fn lte_hint(x: Field, y: Field) -> bool { if x == y { true } else { - compute_lt(x, y, num_bytes) + field_less_than(x, y) } } -unconstrained fn lt_32_hint(x: Field, y: Field) -> bool { - compute_lt(x, y, 32) -} - -unconstrained fn lte_16_hint(x: Field, y: Field) -> bool { - compute_lte(x, y, 16) -} - // Assert that (alo > blo && ahi >= bhi) || (alo <= blo && ahi > bhi) fn assert_gt_limbs(a: (Field, Field), b: (Field, Field)) { let (alo, ahi) = a; let (blo, bhi) = b; unsafe { - let borrow = lte_16_hint(alo, blo); + let borrow = lte_hint(alo, blo); let rlo = alo - blo - 1 + (borrow as Field) * TWO_POW_128; let rhi = ahi - bhi - (borrow as Field); @@ -100,7 +74,7 @@ pub fn decompose(x: Field) -> (Field, Field) { pub fn assert_gt(a: Field, b: Field) { if is_unconstrained() { - assert(compute_lt(b, a, 32)); + assert(unsafe { field_less_than(b, a) }); } else { // Decompose a and b let a_limbs = decompose(a); @@ -117,13 +91,15 @@ pub fn assert_lt(a: Field, b: Field) { pub fn gt(a: Field, b: Field) -> bool { if is_unconstrained() { - compute_lt(b, a, 32) + unsafe { + field_less_than(b, a) + } } else if a == b { false } else { // Take a hint of the comparison and verify it unsafe { - if lt_32_hint(a, b) { + if field_less_than(a, b) { assert_gt(b, a); false } else { @@ -140,9 +116,7 @@ pub fn lt(a: Field, b: Field) -> bool { mod tests { // TODO: Allow imports from "super" - use crate::field::bn254::{ - assert_gt, compute_lt, compute_lte, decompose, gt, PHI, PLO, TWO_POW_128, - }; + use crate::field::bn254::{assert_gt, decompose, gt, lte_hint, PHI, PLO, TWO_POW_128}; #[test] fn check_decompose() { @@ -159,24 +133,15 @@ mod tests { } #[test] - fn check_compute_lt() { - assert(compute_lt(0, 1, 16)); - assert(compute_lt(0, 0x100, 16)); - assert(compute_lt(0x100, TWO_POW_128 - 1, 16)); - assert(!compute_lt(0, TWO_POW_128, 16)); - } - - #[test] - fn check_compute_lte() { - assert(compute_lte(0, 1, 16)); - assert(compute_lte(0, 0x100, 16)); - assert(compute_lte(0x100, TWO_POW_128 - 1, 16)); - assert(!compute_lte(0, TWO_POW_128, 16)); - - assert(compute_lte(0, 0, 16)); - assert(compute_lte(0x100, 0x100, 16)); - assert(compute_lte(TWO_POW_128 - 1, TWO_POW_128 - 1, 16)); - assert(compute_lte(TWO_POW_128, TWO_POW_128, 16)); + unconstrained fn check_lte_hint() { + assert(lte_hint(0, 1)); + assert(lte_hint(0, 0x100)); + assert(lte_hint(0x100, TWO_POW_128 - 1)); + assert(!lte_hint(0 - 1, 0)); + + assert(lte_hint(0, 0)); + assert(lte_hint(0x100, 0x100)); + assert(lte_hint(0 - 1, 0 - 1)); } #[test] diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr index b632cf1f7a2..4b89cae4f30 100644 --- a/noir_stdlib/src/field/mod.nr +++ b/noir_stdlib/src/field/mod.nr @@ -211,6 +211,14 @@ pub comptime fn modulus_be_bytes() -> [u8] {} #[builtin(modulus_le_bytes)] pub comptime fn modulus_le_bytes() -> [u8] {} +/// An unconstrained only built in to efficiently compare fields. +#[builtin(field_less_than)] +unconstrained fn __field_less_than(x: Field, y: Field) -> bool {} + +pub(crate) unconstrained fn field_less_than(x: Field, y: Field) -> bool { + __field_less_than(x, y) +} + // Convert a 32 byte array to a field element by modding pub fn bytes32_to_field(bytes32: [u8; 32]) -> Field { // Convert it to a field element @@ -228,25 +236,33 @@ pub fn bytes32_to_field(bytes32: [u8; 32]) -> Field { } fn lt_fallback(x: Field, y: Field) -> bool { - let x_bytes: [u8; 32] = x.to_le_bytes(); - let y_bytes: [u8; 32] = y.to_le_bytes(); - let mut x_is_lt = false; - let mut done = false; - for i in 0..32 { - if (!done) { - let x_byte = x_bytes[32 - 1 - i] as u8; - let y_byte = y_bytes[32 - 1 - i] as u8; - let bytes_match = x_byte == y_byte; - if !bytes_match { - x_is_lt = x_byte < y_byte; - done = true; + if is_unconstrained() { + unsafe { + field_less_than(x, y) + } + } else { + let x_bytes: [u8; 32] = x.to_le_bytes(); + let y_bytes: [u8; 32] = y.to_le_bytes(); + let mut x_is_lt = false; + let mut done = false; + for i in 0..32 { + if (!done) { + let x_byte = x_bytes[32 - 1 - i] as u8; + let y_byte = y_bytes[32 - 1 - i] as u8; + let bytes_match = x_byte == y_byte; + if !bytes_match { + x_is_lt = x_byte < y_byte; + done = true; + } } } + x_is_lt } - x_is_lt } mod tests { + use super::field_less_than; + #[test] // docs:start:to_be_bits_example fn test_to_be_bits() { @@ -304,4 +320,12 @@ mod tests { assert_eq(Field::from_le_bytes::<8>(bits), field); } // docs:end:to_le_radix_example + + #[test] + unconstrained fn test_field_less_than() { + assert(field_less_than(0, 1)); + assert(field_less_than(0, 0x100)); + assert(field_less_than(0x100, 0 - 1)); + assert(!field_less_than(0 - 1, 0)); + } } diff --git a/noir_stdlib/src/hash/sha256.nr b/noir_stdlib/src/hash/sha256.nr index d3ab1b7f1b8..d55044907ac 100644 --- a/noir_stdlib/src/hash/sha256.nr +++ b/noir_stdlib/src/hash/sha256.nr @@ -508,9 +508,9 @@ fn hash_final_block(msg_block: MSG_BLOCK, mut state: STATE) -> HASH { // Return final hash as byte array for j in 0..8 { - let h_bytes: [u8; 4] = (state[7 - j] as Field).to_le_bytes(); + let h_bytes: [u8; 4] = (state[j] as Field).to_be_bytes(); for k in 0..4 { - out_h[31 - 4 * j - k] = h_bytes[k]; + out_h[4 * j + k] = h_bytes[k]; } } diff --git a/scripts/install_bb.sh b/scripts/install_bb.sh index 64baf78c182..cff81eedbac 100755 --- a/scripts/install_bb.sh +++ b/scripts/install_bb.sh @@ -1,6 +1,6 @@ #!/bin/bash -VERSION="0.60.0" +VERSION="0.61.0" BBUP_PATH=~/.bb/bbup diff --git a/yarn.lock b/yarn.lock index b39f9c257c6..748a6d18ecb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -221,9 +221,9 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@npm:0.60.0": - version: 0.60.0 - resolution: "@aztec/bb.js@npm:0.60.0" +"@aztec/bb.js@npm:0.61.0": + version: 0.61.0 + resolution: "@aztec/bb.js@npm:0.61.0" dependencies: comlink: ^4.4.1 commander: ^10.0.1 @@ -232,7 +232,7 @@ __metadata: tslib: ^2.4.0 bin: bb.js: dest/node/main.js - checksum: 74ab79d060624362e9d44dda11dc2445973460577b93dc0c16801158db7efb71cd612966d6169b46bfbbb4fd7c9c1b95ad8b6198b3c69d9f6de0ab0fb92387aa + checksum: db544eeb1378e121554fda7c5b0161aa5b8ed92217eb47740e1c0d68d423084e05c5136acb75720cb39ef9f79aff9ac1d3037be86837d2e2a8d02ff500e175ae languageName: node linkType: hard @@ -14123,7 +14123,7 @@ __metadata: version: 0.0.0-use.local resolution: "integration-tests@workspace:compiler/integration-tests" dependencies: - "@aztec/bb.js": 0.60.0 + "@aztec/bb.js": 0.61.0 "@noir-lang/noir_js": "workspace:*" "@noir-lang/noir_wasm": "workspace:*" "@nomicfoundation/hardhat-chai-matchers": ^2.0.0