Skip to content
This repository has been archived by the owner on Apr 9, 2024. It is now read-only.

Commit

Permalink
feat(stdlib): Add fallback implementation of HashToField128Security
Browse files Browse the repository at this point in the history
… black box function (#435)

Co-authored-by: kevaundray <kevtheappdev@gmail.com>
  • Loading branch information
Ethan-000 and kevaundray authored Jul 17, 2023
1 parent 0aacf23 commit ed40f22
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 22 deletions.
14 changes: 14 additions & 0 deletions acvm/src/compiler/transformers/fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,20 @@ impl FallbackTransformer {
current_witness_idx,
)
}
#[cfg(feature = "unstable-fallbacks")]
BlackBoxFuncCall::HashToField128Security { inputs, output } => {
let mut blake2s_input = Vec::new();
for input in inputs.iter() {
let witness_index = Expression::from(input.witness);
let num_bits = input.num_bits;
blake2s_input.push((witness_index, num_bits));
}
stdlib::blackbox_fallbacks::hash_to_field(
blake2s_input,
*output,
current_witness_idx,
)
}
_ => {
return Err(CompileError::UnsupportedBlackBox(gc.get_black_box_func()));
}
Expand Down
50 changes: 50 additions & 0 deletions acvm/tests/stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ fn does_not_support_sha256(opcode: &Opcode) -> bool {
fn does_not_support_blake2s(opcode: &Opcode) -> bool {
!matches!(opcode, Opcode::BlackBoxFuncCall(BlackBoxFuncCall::Blake2s { .. }))
}
fn does_not_support_hash_to_field(opcode: &Opcode) -> bool {
!matches!(opcode, Opcode::BlackBoxFuncCall(BlackBoxFuncCall::HashToField128Security { .. }))
}

#[macro_export]
macro_rules! test_hashes {
Expand Down Expand Up @@ -227,3 +230,50 @@ macro_rules! test_hashes {
}
};
}

proptest! {
#![proptest_config(ProptestConfig::with_cases(3))]
#[test]
fn test_hash_to_field(input_values in proptest::collection::vec(0..u8::MAX, 1..50)) {
let mut opcodes = Vec::new();
let mut witness_assignments = BTreeMap::new();
let mut input_witnesses: Vec<FunctionInput> = Vec::new();

// prepare test data
let mut counter = 0;
let output = Blake2s256::digest(input_values.clone());
for inp_v in input_values {
counter += 1;
let function_input = FunctionInput { witness: Witness(counter), num_bits: 8 };
input_witnesses.push(function_input);
witness_assignments.insert(Witness(counter), FieldElement::from(inp_v as u128));
}
let correct_result_of_hash_to_field = FieldElement::from_be_bytes_reduce(&output);

counter += 1;
let correct_result_witnesses: Witness = Witness(counter);
witness_assignments.insert(Witness(counter), correct_result_of_hash_to_field);

counter += 1;
let output_witness: Witness = Witness(counter);

let blackbox = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::HashToField128Security { inputs: input_witnesses, output: output_witness });
opcodes.push(blackbox);

// constrain the output to be the same as the hasher
let mut output_constraint = Expression::from(correct_result_witnesses);
output_constraint.push_addition_term(-FieldElement::one(), output_witness);
opcodes.push(Opcode::Arithmetic(output_constraint));

// compile circuit
let circuit = Circuit {current_witness_index: witness_assignments.len() as u32 + 1,
opcodes, public_parameters: PublicInputs(BTreeSet::new()), return_values: PublicInputs(BTreeSet::new()) };
let circuit = compile(circuit, Language::PLONKCSat{ width: 3 }, does_not_support_hash_to_field).unwrap().0;

// solve witnesses
let mut acvm = ACVM::new(StubbedBackend, circuit.opcodes, witness_assignments.into());
let solver_status = acvm.solve();

prop_assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved");
}
}
2 changes: 1 addition & 1 deletion stdlib/src/blackbox_fallbacks/blake2s.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub fn blake2s(
(num_witness, new_gates)
}

fn create_blake2s_constraint(
pub(crate) fn create_blake2s_constraint(
input: Vec<Witness>,
num_witness: u32,
) -> (Vec<Witness>, u32, Vec<Opcode>) {
Expand Down
170 changes: 170 additions & 0 deletions stdlib/src/blackbox_fallbacks/hash_to_field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
//! HashToField128Security fallback function.
use super::{
blake2s::create_blake2s_constraint,
utils::{byte_decomposition, round_to_nearest_byte},
UInt32,
};
use crate::helpers::VariableStore;
use acir::{
brillig::{self, RegisterIndex},
circuit::{
brillig::{Brillig, BrilligInputs, BrilligOutputs},
Opcode,
},
native_types::{Expression, Witness},
FieldElement,
};

pub fn hash_to_field(
inputs: Vec<(Expression, u32)>,
outputs: Witness,
mut num_witness: u32,
) -> (u32, Vec<Opcode>) {
let mut new_gates = Vec::new();
let mut new_inputs = Vec::new();

// Decompose the input field elements into bytes and collect the resulting witnesses.
for (witness, num_bits) in inputs {
let num_bytes = round_to_nearest_byte(num_bits);
let (extra_gates, inputs, updated_witness_counter) =
byte_decomposition(witness, num_bytes, num_witness);
new_gates.extend(extra_gates);
new_inputs.extend(inputs);
num_witness = updated_witness_counter;
}

let (result, num_witness, extra_gates) = create_blake2s_constraint(new_inputs, num_witness);
new_gates.extend(extra_gates);

// transform bytes to a single field
let (result, extra_gates, num_witness) = field_from_be_bytes(&result, num_witness);
new_gates.extend(extra_gates);

// constrain the outputs to be the same as the result of the circuit
let mut expr = Expression::from(outputs);
expr.push_addition_term(-FieldElement::one(), result);
new_gates.push(Opcode::Arithmetic(expr));
(num_witness, new_gates)
}

/// Convert bytes represented by [Witness]es to a single [FieldElement]
fn field_from_be_bytes(result: &[Witness], num_witness: u32) -> (Witness, Vec<Opcode>, u32) {
let mut new_gates = Vec::new();

// Load `0` and `256` using the load constant function from UInt32
let (new_witness, extra_gates, num_witness) = UInt32::load_constant(0, num_witness);
let mut new_witness = new_witness.inner;
new_gates.extend(extra_gates);
let (const_256, extra_gates, mut num_witness) = UInt32::load_constant(256, num_witness);
let const_256 = const_256.inner;
new_gates.extend(extra_gates);

// add byte and multiply 256 each round
for r in result.iter().take(result.len() - 1) {
let (updated_witness, extra_gates, updated_witness_counter) =
field_addition(&new_witness, r, num_witness);
new_gates.extend(extra_gates);
let (updated_witness, extra_gates, updated_witness_counter) =
field_mul(&updated_witness, &const_256, updated_witness_counter);
new_gates.extend(extra_gates);
new_witness = updated_witness;
num_witness = updated_witness_counter;
}

let (new_witness, extra_gates, num_witness) =
field_addition(&new_witness, &result[result.len() - 1], num_witness);
new_gates.extend(extra_gates);

(new_witness, new_gates, num_witness)
}

/// Caculate and constrain `self` + `rhs` as field
fn field_addition(
lhs: &Witness,
rhs: &Witness,
mut num_witness: u32,
) -> (Witness, Vec<Opcode>, u32) {
let mut new_gates = Vec::new();
let mut variables = VariableStore::new(&mut num_witness);
let new_witness = variables.new_variable();

// calculate `self` + `rhs` as field
let brillig_opcode = Opcode::Brillig(Brillig {
inputs: vec![
BrilligInputs::Single(Expression {
mul_terms: vec![],
linear_combinations: vec![(FieldElement::one(), *lhs)],
q_c: FieldElement::zero(),
}),
BrilligInputs::Single(Expression {
mul_terms: vec![],
linear_combinations: vec![(FieldElement::one(), *rhs)],
q_c: FieldElement::zero(),
}),
],
outputs: vec![BrilligOutputs::Simple(new_witness)],
foreign_call_results: vec![],
bytecode: vec![brillig::Opcode::BinaryFieldOp {
op: brillig::BinaryFieldOp::Add,
lhs: RegisterIndex::from(0),
rhs: RegisterIndex::from(1),
destination: RegisterIndex::from(0),
}],
predicate: None,
});
new_gates.push(brillig_opcode);
let num_witness = variables.finalize();

// constrain addition
let mut add_expr = Expression::from(new_witness);
add_expr.push_addition_term(-FieldElement::one(), *lhs);
add_expr.push_addition_term(-FieldElement::one(), *rhs);
new_gates.push(Opcode::Arithmetic(add_expr));

(new_witness, new_gates, num_witness)
}

/// Calculate and constrain `self` * `rhs` as field
pub(crate) fn field_mul(
lhs: &Witness,
rhs: &Witness,
mut num_witness: u32,
) -> (Witness, Vec<Opcode>, u32) {
let mut new_gates = Vec::new();
let mut variables = VariableStore::new(&mut num_witness);
let new_witness = variables.new_variable();

// calulate `self` * `rhs` with overflow
let brillig_opcode = Opcode::Brillig(Brillig {
inputs: vec![
BrilligInputs::Single(Expression {
mul_terms: vec![],
linear_combinations: vec![(FieldElement::one(), *lhs)],
q_c: FieldElement::zero(),
}),
BrilligInputs::Single(Expression {
mul_terms: vec![],
linear_combinations: vec![(FieldElement::one(), *rhs)],
q_c: FieldElement::zero(),
}),
],
outputs: vec![BrilligOutputs::Simple(new_witness)],
foreign_call_results: vec![],
bytecode: vec![brillig::Opcode::BinaryFieldOp {
op: brillig::BinaryFieldOp::Mul,
lhs: RegisterIndex::from(0),
rhs: RegisterIndex::from(1),
destination: RegisterIndex::from(0),
}],
predicate: None,
});
new_gates.push(brillig_opcode);
let num_witness = variables.finalize();

// constrain mul
let mut mul_constraint = Expression::from(new_witness);
mul_constraint.push_multiplication_term(-FieldElement::one(), *lhs, *rhs);
new_gates.push(Opcode::Arithmetic(mul_constraint));

(new_witness, new_gates, num_witness)
}
2 changes: 2 additions & 0 deletions stdlib/src/blackbox_fallbacks/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod blake2s;
mod hash_to_field;
mod logic_fallbacks;
mod sha256;
mod uint32;
mod utils;
pub use blake2s::blake2s;
pub use hash_to_field::hash_to_field;
pub use logic_fallbacks::{and, range, xor};
pub use sha256::sha256;
pub use uint32::UInt32;
22 changes: 7 additions & 15 deletions stdlib/src/blackbox_fallbacks/sha256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use super::uint32::UInt32;
use super::utils::{byte_decomposition, round_to_nearest_byte};
use crate::helpers::VariableStore;
use acir::{
brillig::{self, BinaryFieldOp, RegisterIndex},
brillig,
circuit::{
brillig::{Brillig, BrilligInputs, BrilligOutputs},
opcodes::{BlackBoxFuncCall, FunctionInput},
Expand Down Expand Up @@ -149,22 +149,14 @@ fn pad(number: u32, bit_size: u32, mut num_witness: u32) -> (u32, Witness, Vec<O
let pad = variables.new_variable();

let brillig_opcode = Opcode::Brillig(Brillig {
inputs: vec![
BrilligInputs::Single(Expression {
mul_terms: vec![],
linear_combinations: vec![],
q_c: FieldElement::from(number as u128),
}),
BrilligInputs::Single(Expression::default()),
],
inputs: vec![BrilligInputs::Single(Expression {
mul_terms: vec![],
linear_combinations: vec![],
q_c: FieldElement::from(number as u128),
})],
outputs: vec![BrilligOutputs::Simple(pad)],
foreign_call_results: vec![],
bytecode: vec![brillig::Opcode::BinaryFieldOp {
op: BinaryFieldOp::Add,
lhs: RegisterIndex::from(0),
rhs: RegisterIndex::from(1),
destination: RegisterIndex::from(0),
}],
bytecode: vec![brillig::Opcode::Stop],
predicate: None,
});
new_gates.push(brillig_opcode);
Expand Down
12 changes: 6 additions & 6 deletions stdlib/src/blackbox_fallbacks/uint32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl UInt32 {
) -> (Vec<UInt32>, Vec<Opcode>, u32) {
let mut new_gates = Vec::new();
let mut variables = VariableStore::new(&mut num_witness);
let mut uint32 = Vec::new();
let mut uint32s = Vec::new();

for i in 0..witnesses.len() / 4 {
let new_witness = variables.new_variable();
Expand Down Expand Up @@ -148,7 +148,7 @@ impl UInt32 {
],
predicate: None,
});
uint32.push(UInt32::new(new_witness));
uint32s.push(UInt32::new(new_witness));
new_gates.push(brillig_opcode);
let mut expr = Expression::from(new_witness);
for j in 0..4 {
Expand All @@ -161,7 +161,7 @@ impl UInt32 {
}
let num_witness = variables.finalize();

(uint32, new_gates, num_witness)
(uint32s, new_gates, num_witness)
}

/// Returns the quotient and remainder such that lhs = rhs * quotient + remainder
Expand Down Expand Up @@ -513,7 +513,7 @@ impl UInt32 {
foreign_call_results: vec![],
bytecode: vec![brillig::Opcode::BinaryIntOp {
op: brillig::BinaryIntOp::And,
bit_size: 32,
bit_size: self.width,
lhs: RegisterIndex::from(0),
rhs: RegisterIndex::from(1),
destination: RegisterIndex::from(0),
Expand Down Expand Up @@ -556,7 +556,7 @@ impl UInt32 {
foreign_call_results: vec![],
bytecode: vec![brillig::Opcode::BinaryIntOp {
op: brillig::BinaryIntOp::Xor,
bit_size: 32,
bit_size: self.width,
lhs: RegisterIndex::from(0),
rhs: RegisterIndex::from(1),
destination: RegisterIndex::from(0),
Expand Down Expand Up @@ -600,7 +600,7 @@ impl UInt32 {
foreign_call_results: vec![],
bytecode: vec![brillig::Opcode::BinaryIntOp {
op: brillig::BinaryIntOp::Sub,
bit_size: 32,
bit_size: self.width,
lhs: RegisterIndex::from(1),
rhs: RegisterIndex::from(0),
destination: RegisterIndex::from(0),
Expand Down

0 comments on commit ed40f22

Please sign in to comment.