Skip to content

Commit

Permalink
feat: simplify constant calls to poseidon2_permutation, `schnorr_ve…
Browse files Browse the repository at this point in the history
…rify` and `embedded_curve_add` (#5140)

# Description

## Problem\*

Resolves <!-- Link to GitHub Issue -->

## Summary\*

Now that we have rust implementations of all blackbox functions, we can
perform any pedersen operations with constant inputs at compile-time. We
couldn't do this before as it would have required an async
initialisation step for the wasm compiler but that is no longer an
issue.

I've done this by using compile-time flags to select the blackbox solver
we're using. If nargo is compiled with a non-bn254 field then it will
not perform these optimizations. The ultimate plan is for this solver to
be specified by the end user in a similar way to the way the field
definition is.





## Additional Context



## Documentation\*

Check one:
- [x] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[For Experimental Features]** Documentation to be submitted in a
separate PR.

# PR Checklist\*

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
TomAFrench authored Aug 28, 2024
1 parent 716a774 commit 2823ba7
Show file tree
Hide file tree
Showing 17 changed files with 357 additions and 22 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ criterion = "0.5.0"
# https://github.com/tikv/pprof-rs/pull/172
pprof = { version = "0.13", features = ["flamegraph", "criterion"] }


cfg-if = "1.0.0"
dirs = "4"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion acvm-repo/acir_field/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ ark-bn254.workspace = true
ark-bls12-381 = { workspace = true, optional = true }
ark-ff.workspace = true

cfg-if = "1.0.0"
cfg-if.workspace = true

[dev-dependencies]
proptest.workspace = true
Expand Down
4 changes: 4 additions & 0 deletions compiler/noirc_driver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ rust-embed.workspace = true
tracing.workspace = true

aztec_macros = { path = "../../aztec_macros" }

[features]
bn254 = ["noirc_frontend/bn254", "noirc_evaluator/bn254"]
bls12_381 = ["noirc_frontend/bls12_381", "noirc_evaluator/bls12_381"]
7 changes: 6 additions & 1 deletion compiler/noirc_evaluator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ serde_json.workspace = true
serde_with = "3.2.0"
tracing.workspace = true
chrono = "0.4.37"
cfg-if.workspace = true

[dev-dependencies]
proptest.workspace = true
proptest.workspace = true

[features]
bn254 = ["noirc_frontend/bn254"]
bls12_381= []
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use crate::ssa::ir::{instruction::Endian, types::NumericType};
use acvm::acir::circuit::brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs};
use acvm::acir::circuit::opcodes::{AcirFunctionId, BlockId, BlockType, MemOp};
use acvm::acir::circuit::{AssertionPayload, ExpressionOrMemory, ExpressionWidth, Opcode};
use acvm::blackbox_solver;
use acvm::brillig_vm::{MemoryValue, VMStatus, VM};
use acvm::{
acir::AcirField,
Expand Down Expand Up @@ -2128,7 +2127,11 @@ fn execute_brillig<F: AcirField>(
}

// Instantiate a Brillig VM given the solved input registers and memory, along with the Brillig bytecode.
let mut vm = VM::new(calldata, code, Vec::new(), &blackbox_solver::StubbedBlackBoxSolver);
//
// We pass a stubbed solver here as a concrete solver implies a field choice which conflicts with this function
// being generic.
let solver = acvm::blackbox_solver::StubbedBlackBoxSolver;
let mut vm = VM::new(calldata, code, Vec::new(), &solver);

// Run the Brillig VM on these inputs, bytecode, etc!
let vm_status = vm.process_opcodes();
Expand Down
45 changes: 31 additions & 14 deletions compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use fxhash::FxHashMap as HashMap;
use std::{collections::VecDeque, rc::Rc};

use acvm::{acir::AcirField, acir::BlackBoxFunc, BlackBoxResolutionError, FieldElement};
use acvm::{
acir::{AcirField, BlackBoxFunc},
BlackBoxResolutionError, FieldElement,
};
use bn254_blackbox_solver::derive_generators;
use iter_extended::vecmap;
use num_bigint::BigUint;
Expand All @@ -20,6 +23,8 @@ use crate::ssa::{

use super::{Binary, BinaryOp, Endian, Instruction, SimplifyResult};

mod blackbox;

/// Try to simplify this call instruction. If the instruction can be simplified to a known value,
/// that value is returned. Otherwise None is returned.
///
Expand Down Expand Up @@ -468,11 +473,17 @@ fn simplify_black_box_func(
arguments: &[ValueId],
dfg: &mut DataFlowGraph,
) -> SimplifyResult {
cfg_if::cfg_if! {
if #[cfg(feature = "bn254")] {
let solver = bn254_blackbox_solver::Bn254BlackBoxSolver;
} else {
let solver = acvm::blackbox_solver::StubbedBlackBoxSolver;
}
};
match bb_func {
BlackBoxFunc::SHA256 => simplify_hash(dfg, arguments, acvm::blackbox_solver::sha256),
BlackBoxFunc::Blake2s => simplify_hash(dfg, arguments, acvm::blackbox_solver::blake2s),
BlackBoxFunc::Blake3 => simplify_hash(dfg, arguments, acvm::blackbox_solver::blake3),
BlackBoxFunc::PedersenCommitment | BlackBoxFunc::PedersenHash => SimplifyResult::None,
BlackBoxFunc::Keccakf1600 => {
if let Some((array_input, _)) = dfg.get_array_constant(arguments[0]) {
if array_is_constant(dfg, &array_input) {
Expand Down Expand Up @@ -503,20 +514,26 @@ fn simplify_black_box_func(
BlackBoxFunc::Keccak256 => {
unreachable!("Keccak256 should have been replaced by calls to Keccakf1600")
}
BlackBoxFunc::Poseidon2Permutation => SimplifyResult::None, //TODO(Guillaume)
BlackBoxFunc::EcdsaSecp256k1 => {
simplify_signature(dfg, arguments, acvm::blackbox_solver::ecdsa_secp256k1_verify)
}
BlackBoxFunc::EcdsaSecp256r1 => {
simplify_signature(dfg, arguments, acvm::blackbox_solver::ecdsa_secp256r1_verify)
BlackBoxFunc::Poseidon2Permutation => {
blackbox::simplify_poseidon2_permutation(dfg, solver, arguments)
}
BlackBoxFunc::EcdsaSecp256k1 => blackbox::simplify_signature(
dfg,
arguments,
acvm::blackbox_solver::ecdsa_secp256k1_verify,
),
BlackBoxFunc::EcdsaSecp256r1 => blackbox::simplify_signature(
dfg,
arguments,
acvm::blackbox_solver::ecdsa_secp256r1_verify,
),

BlackBoxFunc::PedersenCommitment
| BlackBoxFunc::PedersenHash
| BlackBoxFunc::MultiScalarMul => SimplifyResult::None,
BlackBoxFunc::EmbeddedCurveAdd => blackbox::simplify_ec_add(dfg, solver, arguments),
BlackBoxFunc::SchnorrVerify => blackbox::simplify_schnorr_verify(dfg, solver, arguments),

BlackBoxFunc::MultiScalarMul
| BlackBoxFunc::SchnorrVerify
| BlackBoxFunc::EmbeddedCurveAdd => {
// Currently unsolvable here as we rely on an implementation in the backend.
SimplifyResult::None
}
BlackBoxFunc::BigIntAdd
| BlackBoxFunc::BigIntSub
| BlackBoxFunc::BigIntMul
Expand Down
190 changes: 190 additions & 0 deletions compiler/noirc_evaluator/src/ssa/ir/instruction/call/blackbox.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use std::rc::Rc;

use acvm::{acir::AcirField, BlackBoxFunctionSolver, BlackBoxResolutionError, FieldElement};
use iter_extended::vecmap;

use crate::ssa::ir::{
dfg::DataFlowGraph, instruction::SimplifyResult, types::Type, value::ValueId,
};

use super::{array_is_constant, make_constant_array, to_u8_vec};

pub(super) fn simplify_ec_add(
dfg: &mut DataFlowGraph,
solver: impl BlackBoxFunctionSolver<FieldElement>,
arguments: &[ValueId],
) -> SimplifyResult {
match (
dfg.get_numeric_constant(arguments[0]),
dfg.get_numeric_constant(arguments[1]),
dfg.get_numeric_constant(arguments[2]),
dfg.get_numeric_constant(arguments[3]),
dfg.get_numeric_constant(arguments[4]),
dfg.get_numeric_constant(arguments[5]),
) {
(
Some(point1_x),
Some(point1_y),
Some(point1_is_infinity),
Some(point2_x),
Some(point2_y),
Some(point2_is_infinity),
) => {
let Ok((result_x, result_y, result_is_infinity)) = solver.ec_add(
&point1_x,
&point1_y,
&point1_is_infinity,
&point2_x,
&point2_y,
&point2_is_infinity,
) else {
return SimplifyResult::None;
};

let result_x = dfg.make_constant(result_x, Type::field());
let result_y = dfg.make_constant(result_y, Type::field());
let result_is_infinity = dfg.make_constant(result_is_infinity, Type::bool());

let typ = Type::Array(Rc::new(vec![Type::field()]), 3);
let result_array =
dfg.make_array(im::vector![result_x, result_y, result_is_infinity], typ);

SimplifyResult::SimplifiedTo(result_array)
}
_ => SimplifyResult::None,
}
}

pub(super) fn simplify_poseidon2_permutation(
dfg: &mut DataFlowGraph,
solver: impl BlackBoxFunctionSolver<FieldElement>,
arguments: &[ValueId],
) -> SimplifyResult {
match (dfg.get_array_constant(arguments[0]), dfg.get_numeric_constant(arguments[1])) {
(Some((state, _)), Some(state_length)) if array_is_constant(dfg, &state) => {
let state: Vec<FieldElement> = state
.iter()
.map(|id| {
dfg.get_numeric_constant(*id)
.expect("value id from array should point at constant")
})
.collect();

let Some(state_length) = state_length.try_to_u32() else {
return SimplifyResult::None;
};

let Ok(new_state) = solver.poseidon2_permutation(&state, state_length) else {
return SimplifyResult::None;
};

let result_array = make_constant_array(dfg, new_state, Type::field());

SimplifyResult::SimplifiedTo(result_array)
}
_ => SimplifyResult::None,
}
}

pub(super) fn simplify_schnorr_verify(
dfg: &mut DataFlowGraph,
solver: impl BlackBoxFunctionSolver<FieldElement>,
arguments: &[ValueId],
) -> SimplifyResult {
match (
dfg.get_numeric_constant(arguments[0]),
dfg.get_numeric_constant(arguments[1]),
dfg.get_array_constant(arguments[2]),
dfg.get_array_constant(arguments[3]),
) {
(Some(public_key_x), Some(public_key_y), Some((signature, _)), Some((message, _)))
if array_is_constant(dfg, &signature) && array_is_constant(dfg, &message) =>
{
let signature = to_u8_vec(dfg, signature);
let signature: [u8; 64] =
signature.try_into().expect("Compiler should produce correctly sized signature");

let message = to_u8_vec(dfg, message);

let Ok(valid_signature) =
solver.schnorr_verify(&public_key_x, &public_key_y, &signature, &message)
else {
return SimplifyResult::None;
};

let valid_signature = dfg.make_constant(valid_signature.into(), Type::bool());
SimplifyResult::SimplifiedTo(valid_signature)
}
_ => SimplifyResult::None,
}
}

pub(super) fn simplify_hash(
dfg: &mut DataFlowGraph,
arguments: &[ValueId],
hash_function: fn(&[u8]) -> Result<[u8; 32], BlackBoxResolutionError>,
) -> SimplifyResult {
match dfg.get_array_constant(arguments[0]) {
Some((input, _)) if array_is_constant(dfg, &input) => {
let input_bytes: Vec<u8> = to_u8_vec(dfg, input);

let hash = hash_function(&input_bytes)
.expect("Rust solvable black box function should not fail");

let hash_values = vecmap(hash, |byte| FieldElement::from_be_bytes_reduce(&[byte]));

let result_array = make_constant_array(dfg, hash_values, Type::unsigned(8));
SimplifyResult::SimplifiedTo(result_array)
}
_ => SimplifyResult::None,
}
}

type ECDSASignatureVerifier = fn(
hashed_msg: &[u8],
public_key_x: &[u8; 32],
public_key_y: &[u8; 32],
signature: &[u8; 64],
) -> Result<bool, BlackBoxResolutionError>;

pub(super) fn simplify_signature(
dfg: &mut DataFlowGraph,
arguments: &[ValueId],
signature_verifier: ECDSASignatureVerifier,
) -> SimplifyResult {
match (
dfg.get_array_constant(arguments[0]),
dfg.get_array_constant(arguments[1]),
dfg.get_array_constant(arguments[2]),
dfg.get_array_constant(arguments[3]),
) {
(
Some((public_key_x, _)),
Some((public_key_y, _)),
Some((signature, _)),
Some((hashed_message, _)),
) if array_is_constant(dfg, &public_key_x)
&& array_is_constant(dfg, &public_key_y)
&& array_is_constant(dfg, &signature)
&& array_is_constant(dfg, &hashed_message) =>
{
let public_key_x: [u8; 32] = to_u8_vec(dfg, public_key_x)
.try_into()
.expect("ECDSA public key fields are 32 bytes");
let public_key_y: [u8; 32] = to_u8_vec(dfg, public_key_y)
.try_into()
.expect("ECDSA public key fields are 32 bytes");
let signature: [u8; 64] =
to_u8_vec(dfg, signature).try_into().expect("ECDSA signatures are 64 bytes");
let hashed_message: Vec<u8> = to_u8_vec(dfg, hashed_message);

let valid_signature =
signature_verifier(&hashed_message, &public_key_x, &public_key_y, &signature)
.expect("Rust solvable black box function should not fail");

let valid_signature = dfg.make_constant(valid_signature.into(), Type::bool());
SimplifyResult::SimplifiedTo(valid_signature)
}
_ => SimplifyResult::None,
}
}
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ num-traits.workspace = true
rustc-hash = "1.1.0"
small-ord-set = "0.1.3"
regex = "1.9.1"
cfg-if = "1.0.0"
cfg-if.workspace = true
tracing.workspace = true
petgraph = "0.6"
rangemap = "1.4.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "embedded_curve_add_simplification"
type = "bin"
authors = [""]
compiler_version = ">=0.23.0"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use std::embedded_curve_ops::{EmbeddedCurvePoint, EmbeddedCurveScalar, multi_scalar_mul};

fn main() {
let zero = EmbeddedCurvePoint::point_at_infinity();
let g1 = EmbeddedCurvePoint { x: 1, y: 17631683881184975370165255887551781615748388533673675138860, is_infinite: false };

assert(g1 + zero == g1);
assert(g1 - g1 == zero);
assert(g1 - zero == g1);
assert(zero + zero == zero);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "poseidon2_simplification"
type = "bin"
authors = [""]
compiler_version = ">=0.33.0"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use std::hash::poseidon2;

fn main() {
let digest = poseidon2::Poseidon2::hash([0], 1);
let expected_digest = 0x2710144414c3a5f2354f4c08d52ed655b9fe253b4bf12cb9ad3de693d9b1db11;
assert_eq(digest, expected_digest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "schnorr_simplification"
type = "bin"
authors = [""]

[dependencies]
Loading

0 comments on commit 2823ba7

Please sign in to comment.