Skip to content

Commit

Permalink
Merge branch 'master' into tf/unsafe-casts
Browse files Browse the repository at this point in the history
  • Loading branch information
TomAFrench authored Jan 4, 2024
2 parents 9966f8e + abf751a commit 75e0d8c
Show file tree
Hide file tree
Showing 29 changed files with 449 additions and 119 deletions.
38 changes: 38 additions & 0 deletions Cargo.lock

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

79 changes: 56 additions & 23 deletions acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,51 @@ impl RangeOptimizer {
/// only store the fact that we have constrained it to
/// be 16 bits.
fn collect_ranges(circuit: &Circuit) -> BTreeMap<Witness, u32> {
let mut witness_to_bit_sizes = BTreeMap::new();
let mut witness_to_bit_sizes: BTreeMap<Witness, u32> = BTreeMap::new();

for opcode in &circuit.opcodes {
// Extract the witness index and number of bits,
// if it is a range constraint
let (witness, num_bits) = match extract_range_opcode(opcode) {
Some(func_inputs) => func_inputs,
None => continue,
let Some((witness, num_bits)) = (match opcode {
Opcode::AssertZero(expr) => {
// If the opcode is constraining a witness to be equal to a value then it can be considered
// as a range opcode for the number of bits required to hold that value.
if expr.is_degree_one_univariate() {
let (k, witness) = expr.linear_combinations[0];
let constant = expr.q_c;
let witness_value = -constant / k;

if witness_value.is_zero() {
Some((witness, 0))
} else {
// We subtract off 1 bit from the implied witness value to give the weakest range constraint
// which would be stricter than the constraint imposed by this opcode.
let implied_range_constraint_bits = witness_value.num_bits() - 1;
Some((witness, implied_range_constraint_bits))
}
} else {
None
}
}


Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE {
input: FunctionInput { witness, num_bits },
}) => {
Some((*witness, *num_bits))
}

_ => None,
}) else {
continue;
};

// Check if the witness has already been recorded and if the witness
// size is more than the current one, we replace it
let should_replace = match witness_to_bit_sizes.get(&witness).copied() {
Some(old_range_bits) => old_range_bits > num_bits,
None => true,
};
if should_replace {
witness_to_bit_sizes.insert(witness, num_bits);
}
witness_to_bit_sizes
.entry(witness)
.and_modify(|old_range_bits| {
*old_range_bits = std::cmp::min(*old_range_bits, num_bits);
})
.or_insert(num_bits);
}
witness_to_bit_sizes
}
Expand Down Expand Up @@ -116,16 +142,10 @@ impl RangeOptimizer {
/// Extract the range opcode from the `Opcode` enum
/// Returns None, if `Opcode` is not the range opcode.
fn extract_range_opcode(opcode: &Opcode) -> Option<(Witness, u32)> {
// Range constraints are blackbox function calls
// so we first extract the function call
let func_call = match opcode {
acir::circuit::Opcode::BlackBoxFuncCall(func_call) => func_call,
_ => return None,
};

// Skip if it is not a range constraint
match func_call {
BlackBoxFuncCall::RANGE { input } => Some((input.witness, input.num_bits)),
match opcode {
Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { input }) => {
Some((input.witness, input.num_bits))
}
_ => None,
}
}
Expand Down Expand Up @@ -246,4 +266,17 @@ mod tests {
let (optimized_circuit, _) = optimizer.replace_redundant_ranges(acir_opcode_positions);
assert_eq!(optimized_circuit.opcodes.len(), 5);
}

#[test]
fn constant_implied_ranges() {
// The optimizer should use knowledge about constant witness assignments to remove range opcodes.
let mut circuit = test_circuit(vec![(Witness(1), 16)]);

circuit.opcodes.push(Opcode::AssertZero(Witness(1).into()));
let acir_opcode_positions = circuit.opcodes.iter().enumerate().map(|(i, _)| i).collect();
let optimizer = RangeOptimizer::new(circuit);
let (optimized_circuit, _) = optimizer.replace_redundant_ranges(acir_opcode_positions);
assert_eq!(optimized_circuit.opcodes.len(), 1);
assert_eq!(optimized_circuit.opcodes[0], Opcode::AssertZero(Witness(1).into()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,28 @@ pub(crate) fn convert_black_box_call(
)
}
}
BlackBoxFunc::EcdsaSecp256r1 => {
if let (
[BrilligVariable::BrilligArray(public_key_x), BrilligVariable::BrilligArray(public_key_y), BrilligVariable::BrilligArray(signature), message],
[BrilligVariable::Simple(result_register)],
) = (function_arguments, function_results)
{
let message_hash_vector =
convert_array_or_vector(brillig_context, message, bb_func);
brillig_context.black_box_op_instruction(BlackBoxOp::EcdsaSecp256r1 {
hashed_msg: message_hash_vector.to_heap_vector(),
public_key_x: public_key_x.to_heap_array(),
public_key_y: public_key_y.to_heap_array(),
signature: signature.to_heap_array(),
result: *result_register,
});
} else {
unreachable!(
"ICE: EcdsaSecp256r1 expects four array arguments and one register result"
)
}
}

BlackBoxFunc::PedersenCommitment => {
if let (
[message, BrilligVariable::Simple(domain_separator)],
Expand Down Expand Up @@ -160,7 +182,18 @@ pub(crate) fn convert_black_box_call(
)
}
}
_ => unimplemented!("ICE: Black box function {:?} is not implemented", bb_func),
BlackBoxFunc::AND => {
unreachable!("ICE: `BlackBoxFunc::AND` calls should be transformed into a `BinaryOp`")
}
BlackBoxFunc::XOR => {
unreachable!("ICE: `BlackBoxFunc::XOR` calls should be transformed into a `BinaryOp`")
}
BlackBoxFunc::RANGE => unreachable!(
"ICE: `BlackBoxFunc::RANGE` calls should be transformed into a `Instruction::Cast`"
),
BlackBoxFunc::RecursiveAggregation => unimplemented!(
"ICE: `BlackBoxFunc::RecursiveAggregation` is not implemented by the Brillig VM"
),
}
}

Expand Down
8 changes: 0 additions & 8 deletions compiler/noirc_frontend/src/hir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,14 +194,6 @@ impl Context<'_> {
.collect()
}

/// Returns the [Location] of the definition of the given Ident found at [Span] of the given [FileId].
/// Returns [None] when definition is not found.
pub fn get_definition_location_from(&self, location: Location) -> Option<Location> {
let interner = &self.def_interner;

interner.find_location_index(location).and_then(|index| interner.resolve_location(index))
}

/// Return a Vec of all `contract` declarations in the source code and the functions they contain
pub fn get_all_contracts(&self, crate_id: &CrateId) -> Vec<Contract> {
self.def_map(crate_id)
Expand Down
8 changes: 7 additions & 1 deletion compiler/noirc_frontend/src/node_interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1271,10 +1271,16 @@ impl NodeInterner {
self.selected_trait_implementations.get(&ident_id).cloned()
}

/// Returns the [Location] of the definition of the given Ident found at [Span] of the given [FileId].
/// Returns [None] when definition is not found.
pub fn get_definition_location_from(&self, location: Location) -> Option<Location> {
self.find_location_index(location).and_then(|index| self.resolve_location(index))
}

/// For a given [Index] we return [Location] to which we resolved to
/// We currently return None for features not yet implemented
/// TODO(#3659): LSP goto def should error when Ident at Location could not resolve
pub(crate) fn resolve_location(&self, index: impl Into<Index>) -> Option<Location> {
fn resolve_location(&self, index: impl Into<Index>) -> Option<Location> {
let node = self.nodes.get(index.into())?;

match node {
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/noir/standard_library/black_box_fns.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ Here is a list of the current black box functions that are supported by UltraPlo

Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. To ensure compatibility across backends, the ACVM has fallback implementations of `AND`, `XOR` and `RANGE` defined in its standard library which it can seamlessly fallback to if the backend doesn't support them.

You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/acvm/blob/acir-v0.12.0/acir/src/circuit/black_box_functions.rs).
You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/noir/blob/master/acvm-repo/acir/src/circuit/black_box_functions.rs).
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ Here is a list of the current black box functions that are supported by UltraPlo

Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. To ensure compatibility across backends, the ACVM has fallback implementations of `AND`, `XOR` and `RANGE` defined in its standard library which it can seamlessly fallback to if the backend doesn't support them.

You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/acvm/blob/acir-v0.12.0/acir/src/circuit/black_box_functions.rs).
You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/noir/blob/master/acvm-repo/acir/src/circuit/black_box_functions.rs).
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ Here is a list of the current black box functions that are supported by UltraPlo

Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. To ensure compatibility across backends, the ACVM has fallback implementations of `AND`, `XOR` and `RANGE` defined in its standard library which it can seamlessly fallback to if the backend doesn't support them.

You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/acvm/blob/acir-v0.12.0/acir/src/circuit/black_box_functions.rs).
You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/noir/blob/master/acvm-repo/acir/src/circuit/black_box_functions.rs).
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ Here is a list of the current black box functions that are supported by UltraPlo

Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. To ensure compatibility across backends, the ACVM has fallback implementations of `AND`, `XOR` and `RANGE` defined in its standard library which it can seamlessly fallback to if the backend doesn't support them.

You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/acvm/blob/acir-v0.12.0/acir/src/circuit/black_box_functions.rs).
You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/noir/blob/master/acvm-repo/acir/src/circuit/black_box_functions.rs).
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ Here is a list of the current black box functions that are supported by UltraPlo

Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. To ensure compatibility across backends, the ACVM has fallback implementations of `AND`, `XOR` and `RANGE` defined in its standard library which it can seamlessly fallback to if the backend doesn't support them.

You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/acvm/blob/acir-v0.12.0/acir/src/circuit/black_box_functions.rs).
You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/noir/blob/master/acvm-repo/acir/src/circuit/black_box_functions.rs).
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ Here is a list of the current black box functions that are supported by UltraPlo

Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. To ensure compatibility across backends, the ACVM has fallback implementations of `AND`, `XOR` and `RANGE` defined in its standard library which it can seamlessly fallback to if the backend doesn't support them.

You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/acvm/blob/acir-v0.12.0/acir/src/circuit/black_box_functions.rs).
You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/noir/blob/master/acvm-repo/acir/src/circuit/black_box_functions.rs).
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ Here is a list of the current black box functions that are supported by UltraPlo

Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. To ensure compatibility across backends, the ACVM has fallback implementations of `AND`, `XOR` and `RANGE` defined in its standard library which it can seamlessly fallback to if the backend doesn't support them.

You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/acvm/blob/acir-v0.12.0/acir/src/circuit/black_box_functions.rs).
You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/noir/blob/master/acvm-repo/acir/src/circuit/black_box_functions.rs).
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ Here is a list of the current black box functions that are supported by UltraPlo

Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. To ensure compatibility across backends, the ACVM has fallback implementations of `AND`, `XOR` and `RANGE` defined in its standard library which it can seamlessly fallback to if the backend doesn't support them.

You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/acvm/blob/acir-v0.12.0/acir/src/circuit/black_box_functions.rs).
You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/noir/blob/master/acvm-repo/acir/src/circuit/black_box_functions.rs).
25 changes: 15 additions & 10 deletions noir_stdlib/src/ec/montcurve.nr
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ mod affine {
use crate::ec::safe_inverse;
use crate::ec::sqrt;
use crate::ec::ZETA;
use crate::cmp::Eq;

// Curve specification
struct Curve { // Montgomery Curve configuration (ky^2 = x^3 + j*x^2 + x)
j: Field,
Expand All @@ -32,11 +34,6 @@ mod affine {
Self {x, y, infty: false}
}

// Check for equality
fn eq(self, p: Self) -> bool {
(self.infty & p.infty) | (!self.infty & !p.infty & (self.x == p.x) & (self.y == p.y))
}

// Check if zero
pub fn is_zero(self) -> bool {
self.infty
Expand Down Expand Up @@ -76,6 +73,12 @@ mod affine {
}
}

impl Eq for Point {
fn eq(self, p: Self) -> bool {
(self.infty & p.infty) | (!self.infty & !p.infty & (self.x == p.x) & (self.y == p.y))
}
}

impl Curve {
// Curve constructor
pub fn new(j: Field, k: Field, gen: Point) -> Self {
Expand Down Expand Up @@ -219,6 +222,7 @@ mod curvegroup {
use crate::ec::swcurve::curvegroup::Point as SWPoint;
use crate::ec::tecurve::curvegroup::Curve as TECurve;
use crate::ec::tecurve::curvegroup::Point as TEPoint;
use crate::cmp::Eq;

struct Curve { // Montgomery Curve configuration (ky^2 z = x*(x^2 + j*x*z + z*z))
j: Field,
Expand All @@ -239,11 +243,6 @@ mod curvegroup {
Self {x, y, z}
}

// Check for equality
fn eq(self, p: Self) -> bool {
(self.z == p.z) | (((self.x * self.z) == (p.x * p.z)) & ((self.y * self.z) == (p.y * p.z)))
}

// Check if zero
pub fn is_zero(self) -> bool {
self.z == 0
Expand Down Expand Up @@ -277,6 +276,12 @@ mod curvegroup {
}
}

impl Eq for Point {
fn eq(self, p: Self) -> bool {
(self.z == p.z) | (((self.x * self.z) == (p.x * p.z)) & ((self.y * self.z) == (p.y * p.z)))
}
}

impl Curve {
// Curve constructor
pub fn new(j: Field, k: Field, gen: Point) -> Self {
Expand Down
Loading

0 comments on commit 75e0d8c

Please sign in to comment.