From b5c1dc3c1d728c99617eede81c4c55d87789c510 Mon Sep 17 00:00:00 2001 From: guipublic Date: Tue, 27 Jun 2023 16:01:00 +0000 Subject: [PATCH 1/3] Support integers which fit inside a field element --- brillig_vm/Cargo.toml | 3 ++ brillig_vm/src/lib.rs | 16 +++++-- brillig_vm/src/opcodes.rs | 96 +++++++++++++++++++++++++++++++-------- 3 files changed, 92 insertions(+), 23 deletions(-) diff --git a/brillig_vm/Cargo.toml b/brillig_vm/Cargo.toml index 59f294446..239d7cf30 100644 --- a/brillig_vm/Cargo.toml +++ b/brillig_vm/Cargo.toml @@ -8,6 +8,7 @@ license.workspace = true rust-version.workspace = true repository.workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -17,6 +18,8 @@ blake2.workspace = true sha2.workspace = true sha3.workspace = true k256.workspace = true +num-bigint = "0.4" +num-traits = "0.2.8" [features] default = ["bn254"] diff --git a/brillig_vm/src/lib.rs b/brillig_vm/src/lib.rs index 53ea57b96..9b579895c 100644 --- a/brillig_vm/src/lib.rs +++ b/brillig_vm/src/lib.rs @@ -16,11 +16,14 @@ mod opcodes; mod registers; mod value; +use acir_field::FieldElement; pub use black_box::BlackBoxOp; pub use foreign_call::{ForeignCallOutput, ForeignCallResult}; pub use memory::Memory; -pub use opcodes::{BinaryFieldOp, BinaryIntOp, HeapArray, HeapVector, RegisterOrMemory}; -pub use opcodes::{Label, Opcode}; +use num_bigint::BigUint; +pub use opcodes::{ + BinaryFieldOp, BinaryIntOp, HeapArray, HeapVector, Label, Opcode, RegisterOrMemory, +}; pub use registers::{RegisterIndex, Registers}; pub use value::Typ; pub use value::Value; @@ -355,8 +358,13 @@ impl VM { let lhs_value = self.registers.get(lhs); let rhs_value = self.registers.get(rhs); - let result_value = op.evaluate_int(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 = op.evaluate_bigint(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()); } } diff --git a/brillig_vm/src/opcodes.rs b/brillig_vm/src/opcodes.rs index 01af6c1bd..33912d9d2 100644 --- a/brillig_vm/src/opcodes.rs +++ b/brillig_vm/src/opcodes.rs @@ -1,5 +1,7 @@ use crate::{black_box::BlackBoxOp, RegisterIndex, Value}; use acir_field::FieldElement; +use num_bigint::{BigInt, BigUint}; +use num_traits::{One, ToPrimitive, Zero}; use serde::{Deserialize, Serialize}; pub type Label = usize; @@ -193,53 +195,91 @@ impl BinaryFieldOp { impl BinaryIntOp { /// Evaluate a binary operation on two unsigned integers (u128) with a given bit size and return the result as a u128. + /// the functions uses evaluate_bigint() internally so that we can re-use the existing test cases also for evaluate_bigint() pub fn evaluate_int(&self, a: u128, b: u128, bit_size: u32) -> u128 { - let bit_modulo = 1_u128 << bit_size; + // Convert to big integers + let lhs_big = BigUint::from(a); + let rhs_big = BigUint::from(b); + let result_value = self.evaluate_bigint(lhs_big, rhs_big, bit_size); + // Convert back to u128 + result_value.to_u128().unwrap() + } + + pub fn evaluate_bigint(&self, a: BigUint, b: BigUint, bit_size: u32) -> BigUint { + let bit_modulo = &(BigUint::one() << bit_size); match self { // 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) } } + + #[cfg(test)] mod tests { use super::*; @@ -250,6 +290,24 @@ mod tests { result: u128, } + 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); From 95b55ac4aa4357a24107407c0a628bd99c1a11be Mon Sep 17 00:00:00 2001 From: TomAFrench Date: Tue, 11 Jul 2023 02:13:30 +0000 Subject: [PATCH 2/3] chore: cargo fmt --- brillig_vm/src/opcodes.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/brillig_vm/src/opcodes.rs b/brillig_vm/src/opcodes.rs index 33912d9d2..de21b1358 100644 --- a/brillig_vm/src/opcodes.rs +++ b/brillig_vm/src/opcodes.rs @@ -278,8 +278,6 @@ fn to_big_unsigned(a: BigInt, bit_size: u32) -> BigUint { } } - - #[cfg(test)] mod tests { use super::*; @@ -299,7 +297,7 @@ mod tests { (a.wrapping_sub(2 * pow_2)) as i128 } } - + fn to_unsigned(a: i128, bit_size: u32) -> u128 { if a >= 0 { a as u128 From e148118aaf01b7713f55d932470ae890385923eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Rodr=C3=ADguez?= Date: Thu, 20 Jul 2023 14:29:25 +0200 Subject: [PATCH 3/3] Update brillig_vm/Cargo.toml --- brillig_vm/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/brillig_vm/Cargo.toml b/brillig_vm/Cargo.toml index a90372160..3b774ba06 100644 --- a/brillig_vm/Cargo.toml +++ b/brillig_vm/Cargo.toml @@ -8,7 +8,6 @@ license.workspace = true rust-version.workspace = true repository.workspace = true - # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies]