diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/6/Nargo.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/6/Nargo.toml new file mode 100644 index 00000000000..e0b467ce5da --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/6/Nargo.toml @@ -0,0 +1,5 @@ +[package] +authors = [""] +compiler_version = "0.1" + +[dependencies] \ No newline at end of file diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/6/Prover.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/6/Prover.toml new file mode 100644 index 00000000000..1c52aef063c --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/6/Prover.toml @@ -0,0 +1,39 @@ + +# hello as bytes +# used : https://emn178.github.io/online-tools/sha256.html +x = [104, 101, 108, 108, 111] + +result = [ + 0x2c, + 0xf2, + 0x4d, + 0xba, + 0x5f, + 0xb0, + 0xa3, + 0x0e, + 0x26, + 0xe8, + 0x3b, + 0x2a, + 0xc5, + 0xb9, + 0xe2, + 0x9e, + 0x1b, + 0x16, + 0x1e, + 0x5c, + 0x1f, + 0xa7, + 0x42, + 0x5e, + 0x73, + 0x04, + 0x33, + 0x62, + 0x93, + 0x8b, + 0x98, + 0x24, +] diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/6/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/6/src/main.nr new file mode 100644 index 00000000000..8b350de16c1 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/6/src/main.nr @@ -0,0 +1,20 @@ +// Sha256 circuit where the input is 5 bytes +// not five field elements since sha256 operates over +// bytes. +// +// If you do not cast, it will take all the bytes from the field element! + +// Mimc input is an array of field elements +// The function is called mimc_bn254 to emphasize its parameters are chosen for bn254 curve, it should be used only with a proving system using the same curve (e.g Plonk from Aztec) +use dep::std; + +fn main(x: [u8; 5], result: pub [u8; 32]) { + let mut digest = std::hash::sha256(x); + digest[0] = 5 as u8; + digest = std::hash::sha256(x); + assert(digest == result); + + let y = [12,45,78,41]; + let h = std::hash::mimc_bn254(y); + assert(h == 18226366069841799622585958305961373004333097209608110160936134895615261821931); +} diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/merkle_insert/Nargo.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/merkle_insert/Nargo.toml new file mode 100644 index 00000000000..d9434868157 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/merkle_insert/Nargo.toml @@ -0,0 +1,7 @@ + + [package] + authors = [""] + compiler_version = "0.1" + + [dependencies] + \ No newline at end of file diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/merkle_insert/Prover.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/merkle_insert/Prover.toml new file mode 100644 index 00000000000..fca4a077df4 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/merkle_insert/Prover.toml @@ -0,0 +1,11 @@ +old_root = "0x285785b10eca49cf456b935f1c9787ff571f306c1bc62549c31a9199a633f9f8" +old_leaf = "0x1cdcf02431ba623767fe389337d011df1048dcc24b98ed81cec97627bab454a0" +old_hash_path = [ + "0x1cdcf02431ba623767fe389337d011df1048dcc24b98ed81cec97627bab454a0", + "0x0b5e9666e7323ce925c28201a97ddf4144ac9d148448ed6f49f9008719c1b85b", + "0x22ec636f8ad30ef78c42b7fe2be4a4cacf5a445cfb5948224539f59a11d70775", +] +new_root = "0x2d05c2650e6c2ef02c6dc7fae7f517b8ac191386666c0b5a68130a8c11092f5f" +leaf = "0x085ca53be9c9d95b57e6e5fc91c5d531ad9e63e85dd71af7e35562991774b435" +index = "0" +mimc_input = [12,45,78,41] diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/merkle_insert/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/merkle_insert/src/main.nr new file mode 100644 index 00000000000..3de10520037 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/merkle_insert/src/main.nr @@ -0,0 +1,21 @@ +use dep::std; + +fn main( + old_root: Field, + old_leaf: Field, + old_hash_path: [Field; 3], + new_root: pub Field, + leaf: Field, + index: Field, + mimc_input: [Field; 4], +) { + assert(old_root == std::merkle::compute_merkle_root(old_leaf, index, old_hash_path)); + + let calculated_root = std::merkle::compute_merkle_root(leaf, index, old_hash_path); + assert(new_root == calculated_root); + + let h = std::hash::mimc_bn254(mimc_input); + // Regression test for PR #891 + std::println(h); + assert(h == 18226366069841799622585958305961373004333097209608110160936134895615261821931); +} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs index 5722b8e2a27..bb238f9a82a 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use acvm::{acir::BlackBoxFunc, FieldElement}; use iter_extended::vecmap; use num_bigint::BigUint; @@ -239,33 +241,7 @@ impl Instruction { use SimplifyResult::*; match self { Instruction::Binary(binary) => binary.simplify(dfg), - Instruction::Cast(value, typ) => { - if let Some(constant) = dfg.get_numeric_constant(*value) { - let src_typ = dfg.type_of_value(*value); - match (typ, src_typ) { - ( - Type::Numeric(NumericType::Unsigned { bit_size }), - Type::Numeric(NumericType::Unsigned { .. }), - ) - | ( - Type::Numeric(NumericType::Unsigned { bit_size }), - Type::Numeric(NumericType::NativeField), - ) => { - let integer_modulus = BigUint::from(2u128).pow(*bit_size); - let constant: BigUint = BigUint::from_bytes_be(&constant.to_be_bytes()); - let truncated = constant % integer_modulus; - let truncated = - FieldElement::from_be_bytes_reduce(&truncated.to_bytes_be()); - SimplifiedTo(dfg.make_constant(truncated, typ.clone())) - } - _ => None, - } - } else if let Some(value) = (*typ == dfg.type_of_value(*value)).then_some(*value) { - SimplifiedTo(value) - } else { - None - } - } + Instruction::Cast(value, typ) => simplify_cast(*value, typ, dfg), Instruction::Not(value) => { match &dfg[dfg.resolve(*value)] { // Limit optimizing ! on constants to only booleans. If we tried it on fields, @@ -329,7 +305,7 @@ impl Instruction { None } } - Instruction::Call { .. } => None, + Instruction::Call { func, arguments } => simplify_call(*func, arguments, dfg), Instruction::Allocate { .. } => None, Instruction::Load { .. } => None, Instruction::Store { .. } => None, @@ -338,6 +314,110 @@ impl Instruction { } } +/// Try to simplify this cast instruction. If the instruction can be simplified to a known value, +/// that value is returned. Otherwise None is returned. +fn simplify_cast(value: ValueId, dst_typ: &Type, dfg: &mut DataFlowGraph) -> SimplifyResult { + use SimplifyResult::*; + if let Some(constant) = dfg.get_numeric_constant(value) { + let src_typ = dfg.type_of_value(value); + match (src_typ, dst_typ) { + (Type::Numeric(NumericType::NativeField), Type::Numeric(NumericType::NativeField)) => { + // Field -> Field: use src value + SimplifiedTo(value) + } + ( + Type::Numeric(NumericType::Unsigned { .. }), + Type::Numeric(NumericType::NativeField), + ) => { + // Unsigned -> Field: redefine same constant as Field + SimplifiedTo(dfg.make_constant(constant, dst_typ.clone())) + } + ( + Type::Numeric(NumericType::NativeField | NumericType::Unsigned { .. }), + Type::Numeric(NumericType::Unsigned { bit_size }), + ) => { + // Field/Unsigned -> unsigned: truncate + let integer_modulus = BigUint::from(2u128).pow(*bit_size); + let constant: BigUint = BigUint::from_bytes_be(&constant.to_be_bytes()); + let truncated = constant % integer_modulus; + let truncated = FieldElement::from_be_bytes_reduce(&truncated.to_bytes_be()); + SimplifiedTo(dfg.make_constant(truncated, dst_typ.clone())) + } + _ => None, + } + } else if *dst_typ == dfg.type_of_value(value) { + SimplifiedTo(value) + } else { + None + } +} + +/// Try to simplify this call instruction. If the instruction can be simplified to a known value, +/// that value is returned. Otherwise None is returned. +fn simplify_call(func: ValueId, arguments: &[ValueId], dfg: &mut DataFlowGraph) -> SimplifyResult { + use SimplifyResult::*; + let intrinsic = match &dfg[func] { + Value::Intrinsic(intrinsic) => *intrinsic, + _ => return None, + }; + let constant_args: Option> = + arguments.iter().map(|value_id| dfg.get_numeric_constant(*value_id)).collect(); + let constant_args = match constant_args { + Some(constant_args) => constant_args, + Option::None => return None, + }; + match intrinsic { + Intrinsic::ToBits(endian) => { + let field = constant_args[0]; + let limb_count = constant_args[1].to_u128() as u32; + SimplifiedTo(constant_to_radix(endian, field, 2, limb_count, dfg)) + } + Intrinsic::ToRadix(endian) => { + let field = constant_args[0]; + let radix = constant_args[1].to_u128() as u32; + let limb_count = constant_args[1].to_u128() as u32; + SimplifiedTo(constant_to_radix(endian, field, radix, limb_count, dfg)) + } + Intrinsic::BlackBox(_) | Intrinsic::Println | Intrinsic::Sort => None, + } +} + +/// Returns a Value::Array of constants corresponding to the limbs of the radix decomposition. +fn constant_to_radix( + endian: Endian, + field: FieldElement, + radix: u32, + limb_count: u32, + dfg: &mut DataFlowGraph, +) -> ValueId { + let bit_size = u32::BITS - (radix - 1).leading_zeros(); + let radix_big = BigUint::from(radix); + assert_eq!(BigUint::from(2u128).pow(bit_size), radix_big, "ICE: Radix must be a power of 2"); + let big_integer = BigUint::from_bytes_be(&field.to_be_bytes()); + + // Decompose the integer into its radix digits in little endian form. + let decomposed_integer = big_integer.to_radix_le(radix); + let mut limbs = vecmap(0..limb_count, |i| match decomposed_integer.get(i as usize) { + Some(digit) => FieldElement::from_be_bytes_reduce(&[*digit]), + None => FieldElement::zero(), + }); + if endian == Endian::Big { + limbs.reverse(); + } + + // For legacy reasons (see #617) the to_radix interface supports 256 bits even though + // FieldElement::max_num_bits() is only 254 bits. Any limbs beyond the specified count + // become zero padding. + let max_decomposable_bits: u32 = 256; + let limb_count_with_padding = max_decomposable_bits / bit_size; + while limbs.len() < limb_count_with_padding as usize { + limbs.push(FieldElement::zero()); + } + let result_constants = + limbs.into_iter().map(|limb| dfg.make_constant(limb, Type::unsigned(bit_size))).collect(); + dfg.make_array(result_constants, Rc::new(vec![Type::unsigned(bit_size)])) +} + /// The possible return values for Instruction::return_types pub(crate) enum InstructionResultType { /// The result type of this instruction matches that of this operand