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

Commit

Permalink
feat(brillig): Support integers which fit inside a FieldElement (#403)
Browse files Browse the repository at this point in the history
Co-authored-by: TomAFrench <tom@tomfren.ch>
Co-authored-by: sirasistant <sirasistant@gmail.com>
  • Loading branch information
3 people authored Jul 20, 2023
1 parent ed40f22 commit f992412
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 27 deletions.
2 changes: 2 additions & 0 deletions brillig_vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ repository.workspace = true
[dependencies]
acir.workspace = true
blackbox_solver.workspace = true
num-bigint.workspace = true
num-traits.workspace = true

[features]
default = ["bn254"]
Expand Down
106 changes: 83 additions & 23 deletions brillig_vm/src/arithmetic.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use acir::brillig::{BinaryFieldOp, BinaryIntOp};
use acir::FieldElement;
use num_bigint::{BigInt, BigUint};
use num_traits::{One, ToPrimitive, Zero};

/// Evaluate a binary operation on two FieldElements and return the result as a FieldElement.
pub(crate) fn evaluate_binary_field_op(
Expand All @@ -17,50 +19,81 @@ pub(crate) fn evaluate_binary_field_op(
}
}

/// Evaluate a binary operation on two unsigned integers (u128) with a given bit size and return the result as a u128.
pub(crate) fn evaluate_binary_int_op(op: &BinaryIntOp, a: u128, b: u128, bit_size: u32) -> u128 {
let bit_modulo = 1_u128 << bit_size;
/// Evaluate a binary operation on two unsigned big integers with a given bit size and return the result as a big integer.
pub(crate) fn evaluate_binary_bigint_op(
op: &BinaryIntOp,
a: BigUint,
b: BigUint,
bit_size: u32,
) -> BigUint {
let bit_modulo = &(BigUint::one() << bit_size);
match op {
// Perform addition, subtraction, and multiplication, applying a modulo operation to keep the result within the bit size.
BinaryIntOp::Add => a.wrapping_add(b) % bit_modulo,
BinaryIntOp::Sub => a.wrapping_sub(b) % bit_modulo,
BinaryIntOp::Mul => a.wrapping_mul(b) % bit_modulo,
BinaryIntOp::Add => (a + b) % bit_modulo,
BinaryIntOp::Sub => (bit_modulo + a - b) % bit_modulo,
BinaryIntOp::Mul => (a * b) % bit_modulo,
// Perform unsigned division using the modulo operation on a and b.
BinaryIntOp::UnsignedDiv => (a % bit_modulo) / (b % bit_modulo),
// Perform signed division by first converting a and b to signed integers and then back to unsigned after the operation.
BinaryIntOp::SignedDiv => {
to_unsigned(to_signed(a, bit_size) / to_signed(b, bit_size), bit_size)
let signed_div = to_big_signed(a, bit_size) / to_big_signed(b, bit_size);
to_big_unsigned(signed_div, bit_size)
}
// Perform a == operation, returning 0 or 1
BinaryIntOp::Equals => ((a % bit_modulo) == (b % bit_modulo)).into(),
BinaryIntOp::Equals => {
if (a % bit_modulo) == (b % bit_modulo) {
BigUint::one()
} else {
BigUint::zero()
}
}
// Perform a < operation, returning 0 or 1
BinaryIntOp::LessThan => ((a % bit_modulo) < (b % bit_modulo)).into(),
BinaryIntOp::LessThan => {
if (a % bit_modulo) < (b % bit_modulo) {
BigUint::one()
} else {
BigUint::zero()
}
}
// Perform a <= operation, returning 0 or 1
BinaryIntOp::LessThanEquals => ((a % bit_modulo) <= (b % bit_modulo)).into(),
BinaryIntOp::LessThanEquals => {
if (a % bit_modulo) <= (b % bit_modulo) {
BigUint::one()
} else {
BigUint::zero()
}
}
// Perform bitwise AND, OR, XOR, left shift, and right shift operations, applying a modulo operation to keep the result within the bit size.
BinaryIntOp::And => (a & b) % bit_modulo,
BinaryIntOp::Or => (a | b) % bit_modulo,
BinaryIntOp::Xor => (a ^ b) % bit_modulo,
BinaryIntOp::Shl => (a << b) % bit_modulo,
BinaryIntOp::Shr => (a >> b) % bit_modulo,
BinaryIntOp::Shl => {
assert!(bit_size <= 128, "unsupported bit size for right shift");
let b = b.to_u128().unwrap();
(a << b) % bit_modulo
}
BinaryIntOp::Shr => {
assert!(bit_size <= 128, "unsupported bit size for right shift");
let b = b.to_u128().unwrap();
(a >> b) % bit_modulo
}
}
}

fn to_signed(a: u128, bit_size: u32) -> i128 {
assert!(bit_size < 128);
let pow_2 = 2_u128.pow(bit_size - 1);
fn to_big_signed(a: BigUint, bit_size: u32) -> BigInt {
let pow_2 = BigUint::from(2_u32).pow(bit_size - 1);
if a < pow_2 {
a as i128
BigInt::from(a)
} else {
(a.wrapping_sub(2 * pow_2)) as i128
BigInt::from(a) - 2 * BigInt::from(pow_2)
}
}

fn to_unsigned(a: i128, bit_size: u32) -> u128 {
if a >= 0 {
a as u128
fn to_big_unsigned(a: BigInt, bit_size: u32) -> BigUint {
if a >= BigInt::zero() {
BigUint::from_bytes_le(&a.to_bytes_le().1)
} else {
(a + 2_i128.pow(bit_size)) as u128
BigUint::from(2_u32).pow(bit_size) - BigUint::from_bytes_le(&a.to_bytes_le().1)
}
}

Expand All @@ -74,6 +107,33 @@ mod tests {
result: u128,
}

fn evaluate_u128(op: &BinaryIntOp, a: u128, b: u128, bit_size: u32) -> u128 {
// Convert to big integers
let lhs_big = BigUint::from(a);
let rhs_big = BigUint::from(b);
let result_value = evaluate_binary_bigint_op(op, lhs_big, rhs_big, bit_size);
// Convert back to u128
result_value.to_u128().unwrap()
}

fn to_signed(a: u128, bit_size: u32) -> i128 {
assert!(bit_size < 128);
let pow_2 = 2_u128.pow(bit_size - 1);
if a < pow_2 {
a as i128
} else {
(a.wrapping_sub(2 * pow_2)) as i128
}
}

fn to_unsigned(a: i128, bit_size: u32) -> u128 {
if a >= 0 {
a as u128
} else {
(a + 2_i128.pow(bit_size)) as u128
}
}

fn to_negative(a: u128, bit_size: u32) -> u128 {
assert!(a > 0);
let two_pow = 2_u128.pow(bit_size);
Expand All @@ -82,7 +142,7 @@ mod tests {

fn evaluate_int_ops(test_params: Vec<TestParams>, op: BinaryIntOp, bit_size: u32) {
for test in test_params {
assert_eq!(evaluate_binary_int_op(&op, test.a, test.b, bit_size), test.result);
assert_eq!(evaluate_u128(&op, test.a, test.b, bit_size), test.result);
}
}

Expand Down Expand Up @@ -140,7 +200,7 @@ mod tests {
let b = 3;

// ( 2**(n-1) - 1 ) * 3 = 2*2**(n-1) - 2 + (2**(n-1) - 1) => wraps to (2**(n-1) - 1) - 2
assert_eq!(evaluate_binary_int_op(&BinaryIntOp::Mul, a, b, bit_size), a - 2);
assert_eq!(evaluate_u128(&BinaryIntOp::Mul, a, b, bit_size), a - 2);
}

#[test]
Expand Down
13 changes: 9 additions & 4 deletions brillig_vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ mod black_box;
mod memory;
mod registers;

use arithmetic::{evaluate_binary_field_op, evaluate_binary_int_op};
use arithmetic::{evaluate_binary_bigint_op, evaluate_binary_field_op};
use black_box::evaluate_black_box;
use blackbox_solver::{BlackBoxFunctionSolver, BlackBoxResolutionError};

pub use memory::Memory;
use num_bigint::BigUint;
pub use registers::Registers;

#[derive(Debug, PartialEq, Eq, Clone)]
Expand Down Expand Up @@ -371,9 +372,13 @@ impl<'bb_solver, B: BlackBoxFunctionSolver> VM<'bb_solver, B> {
let lhs_value = self.registers.get(lhs);
let rhs_value = self.registers.get(rhs);

let result_value =
evaluate_binary_int_op(&op, lhs_value.to_u128(), rhs_value.to_u128(), bit_size);
self.registers.set(result, result_value.into());
// Convert to big integers
let lhs_big = BigUint::from_bytes_be(&lhs_value.to_field().to_be_bytes());
let rhs_big = BigUint::from_bytes_be(&rhs_value.to_field().to_be_bytes());
let result_value = evaluate_binary_bigint_op(&op, lhs_big, rhs_big, bit_size);
// Convert back to field element
self.registers
.set(result, FieldElement::from_be_bytes_reduce(&result_value.to_bytes_be()).into());
}
}

Expand Down

0 comments on commit f992412

Please sign in to comment.