diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 9b166f6ae94..1430cb5788e 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -23,6 +23,7 @@ mod cmp; mod ops; mod default; mod prelude; +mod uint128; // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident diff --git a/noir_stdlib/src/prelude.nr b/noir_stdlib/src/prelude.nr index 56020509122..b57ff460371 100644 --- a/noir_stdlib/src/prelude.nr +++ b/noir_stdlib/src/prelude.nr @@ -1,5 +1,6 @@ use crate::collections::vec::Vec; use crate::option::Option; use crate::{print, println, assert_constant}; +use crate::uint128::U128; use crate::cmp::{Eq, Ord}; use crate::default::Default; diff --git a/noir_stdlib/src/uint128.nr b/noir_stdlib/src/uint128.nr new file mode 100644 index 00000000000..4a58b3868be --- /dev/null +++ b/noir_stdlib/src/uint128.nr @@ -0,0 +1,292 @@ +use crate::ops::{Add, Sub, Mul, Div, Rem, BitOr, BitAnd, BitXor, Shl, Shr}; +use crate::cmp::{Eq, Ord, Ordering}; + +global pow64 : Field = 18446744073709551616; //2^64; + +struct U128 { + lo: Field, + hi: Field, +} + +impl U128 { + + pub fn from_u64s_le(lo: u64, hi: u64) -> U128 { + // in order to handle multiplication, we need to represent the product of two u64 without overflow + assert(crate::field::modulus_num_bits() as u32 > 128); + U128 { + lo: lo as Field, + hi: hi as Field, + } + } + + pub fn from_u64s_be(hi: u64, lo: u64) -> U128 { + U128::from_u64s_le(lo,hi) + } + + pub fn from_le_bytes(bytes: [u8; 16]) -> U128 { + let mut lo = 0; + let mut base = 1; + for i in 0..8 { + lo += (bytes[i] as Field)*base; + base *= 256; + } + let mut hi = 0; + base = 1; + for i in 8..16 { + hi += (bytes[i] as Field)*base; + base *= 256; + } + U128 { + lo, + hi, + } + } + + pub fn to_le_bytes(self: Self) -> [u8; 16] { + let lo = self.lo.to_le_bytes(8); + let hi = self.hi.to_le_bytes(8); + let mut bytes = [0;16]; + for i in 0..8 { + bytes[i] = lo[i]; + bytes[i+8] = hi[i]; + } + bytes + } + + pub fn from_hex(hex: str) -> U128 { + let N = N as u32; + let bytes = hex.as_bytes(); + // string must starts with "0x" + assert((bytes[0] == 48) & (bytes[1] == 120), "Invalid hexadecimal string"); + assert(N < 35, "Input does not fit into a U128"); + + let mut lo = 0; + let mut hi = 0; + let mut base = 1; + if N <= 18 { + for i in 0..N-2 { + lo += U128::decode_ascii(bytes[N-i-1])*base; + base = base*16; + } + } else { + for i in 0..16 { + lo += U128::decode_ascii(bytes[N-i-1])*base; + base = base*16; + } + base = 1; + for i in 17..N-1 { + hi += U128::decode_ascii(bytes[N-i])*base; + base = base*16; + } + } + U128 { + lo: lo as Field, + hi: hi as Field, + } + } + + fn decode_ascii(ascii: u8) -> Field { + if ascii < 58 { + ascii - 48 + } else { + if ascii < 71 { + ascii - 55 + } else { + ascii - 87 + } + + } as Field + } + + unconstrained fn unconstrained_div(self: Self, b: U128) -> (U128, U128) { + if self < b { + (U128::from_u64s_le(0, 0), self) + } else { + //TODO check if this can overflow? + let (q,r) = self.unconstrained_div(b * U128::from_u64s_le(2,0)); + let q_mul_2 = q * U128::from_u64s_le(2,0); + if r < b { + (q_mul_2, r) + } else { + (q_mul_2 + U128::from_u64s_le(1,0), r - b) + } + + } + } + + pub fn from_integer(i: T) -> U128 { + let f = crate::as_field(i); + let lo = f as u64 as Field; + let hi = (f-lo) / pow64; + U128 { + lo, + hi, + } + } + + pub fn to_integer(self) -> T { + crate::from_field(self.lo+self.hi*pow64) + } + + fn wrapping_mul(self: Self, b: U128) -> U128 { + let low = self.lo*b.lo; + let lo = low as u64 as Field; + let carry = (low - lo) / pow64; + let high = if crate::field::modulus_num_bits() as u32 > 196 { + (self.lo+self.hi)*(b.lo+b.hi) - low + carry + } else { + self.lo*b.hi + self.hi*b.lo + carry + }; + let hi = high as u64 as Field; + U128 { + lo, + hi, + } + } +} + +impl Add for U128 { + pub fn add(self: Self, b: U128) -> U128 { + let low = self.lo + b.lo; + let lo = low as u64 as Field; + let carry = (low - lo) / pow64; + let high = self.hi + b.hi + carry; + let hi = high as u64 as Field; + assert(hi == high, "attempt to add with overflow"); + U128 { + lo, + hi, + } + } +} + +impl Sub for U128 { + pub fn sub(self: Self, b: U128) -> U128 { + let low = pow64 + self.lo - b.lo; + let lo = low as u64 as Field; + let borrow = (low == lo) as Field; + let high = self.hi - b.hi - borrow; + let hi = high as u64 as Field; + assert(hi == high, "attempt to subtract with overflow"); + U128 { + lo, + hi, + } + } +} + +impl Mul for U128 { + pub fn mul(self: Self, b: U128) -> U128 { + assert(self.hi*b.hi == 0, "attempt to multiply with overflow"); + let low = self.lo*b.lo; + let lo = low as u64 as Field; + let carry = (low - lo) / pow64; + let high = if crate::field::modulus_num_bits() as u32 > 196 { + (self.lo+self.hi)*(b.lo+b.hi) - low + carry + } else { + self.lo*b.hi + self.hi*b.lo + carry + }; + let hi = high as u64 as Field; + assert(hi == high, "attempt to multiply with overflow"); + U128 { + lo, + hi, + } + } +} + +impl Div for U128 { + pub fn div(self: Self, b: U128) -> U128 { + let (q,r) = self.unconstrained_div(b); + let a = b * q + r; + assert_eq(self, a); + assert(r < b); + q + } +} + +impl Rem for U128 { + pub fn rem(self: Self, b: U128) -> U128 { + let (q,r) = self.unconstrained_div(b); + let a = b * q + r; + assert_eq(self, a); + assert(r < b); + r + } +} + +impl Eq for U128 { + pub fn eq(self: Self, b: U128) -> bool { + (self.lo == b.lo) & (self.hi == b.hi) + } +} + +impl Ord for U128 { + fn cmp(self, other: Self) -> Ordering { + let hi_ordering = (self.hi as u64).cmp((other.hi as u64)); + let lo_ordering = (self.lo as u64).cmp((other.lo as u64)); + + if hi_ordering == Ordering::equal() { + lo_ordering + } else { + hi_ordering + } + } +} + +impl BitOr for U128 { + fn bitor(self, other: U128) -> U128 { + U128 { + lo: ((self.lo as u64) | (other.lo as u64)) as Field, + hi: ((self.hi as u64) | (other.hi as u64))as Field + } + } +} + +impl BitAnd for U128 { + fn bitand(self, other: U128) -> U128 { + U128 { + lo: ((self.lo as u64) & (other.lo as u64)) as Field, + hi: ((self.hi as u64) & (other.hi as u64)) as Field + } + } +} + +impl BitXor for U128 { + fn bitxor(self, other: U128) -> U128 { + U128 { + lo: ((self.lo as u64) ^ (other.lo as u64)) as Field, + hi: ((self.hi as u64) ^ (other.hi as u64)) as Field + } + } +} + +impl Shl for U128 { + fn shl(self, other: U128) -> U128 { + assert(other < U128::from_u64s_le(128,0), "attempt to shift left with overflow"); + let exp_bits = other.lo.to_be_bits(7); + + let mut r: Field = 2; + let mut y: Field = 1; + for i in 1..8 { + y = (exp_bits[7-i] as Field) * (r * y) + (1 - exp_bits[7-i] as Field) * y; + r *= r; + } + self.wrapping_mul(U128::from_integer(y)) + } +} + +impl Shr for U128 { + fn shr(self, other: U128) -> U128 { + assert(other < U128::from_u64s_le(128,0), "attempt to shift right with overflow"); + let exp_bits = other.lo.to_be_bits(7); + + let mut r: Field = 2; + let mut y: Field = 1; + for i in 1..8 { + y = (exp_bits[7-i] as Field) * (r * y) + (1 - exp_bits[7-i] as Field) * y; + r *= r; + } + self / U128::from_integer(y) + } +} \ No newline at end of file diff --git a/test_programs/execution_success/u128/Nargo.toml b/test_programs/execution_success/u128/Nargo.toml new file mode 100644 index 00000000000..c1dcd84db04 --- /dev/null +++ b/test_programs/execution_success/u128/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "u128" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/execution_success/u128/Prover.toml b/test_programs/execution_success/u128/Prover.toml new file mode 100644 index 00000000000..961db9825a7 --- /dev/null +++ b/test_programs/execution_success/u128/Prover.toml @@ -0,0 +1,7 @@ +x = "3" +y = "4" +z = "7" +hexa ="0x1f03a" +[big_int] +lo = 1 +hi = 2 \ No newline at end of file diff --git a/test_programs/execution_success/u128/src/main.nr b/test_programs/execution_success/u128/src/main.nr new file mode 100644 index 00000000000..4c734f3a8f9 --- /dev/null +++ b/test_programs/execution_success/u128/src/main.nr @@ -0,0 +1,44 @@ +use dep::std; + +fn main(mut x: u32, y: u32, z: u32, big_int: U128, hexa: str<7>) { + let a = U128::from_u64s_le(x as u64, x as u64); + let b = U128::from_u64s_le(y as u64, x as u64); + let c = a + b; + assert(c.lo == z as Field); + assert(c.hi == 2 * x as Field); + assert(U128::from_hex(hexa).lo == 0x1f03a); + let t1 = U128::from_hex("0x9d9c7a87771f03a23783f9d9c7a8777"); + let t2 = U128::from_hex("0x45a26c708BFCF39041"); + let t = t1 + t2; + assert(t.lo == 0xc5e4b029996e17b8); + assert(t.hi == 0x09d9c7a87771f07f); + let t3 = U128::from_le_bytes(t.to_le_bytes()); + assert(t == t3); + + let t4 = t - t2; + assert(t4 == t1); + + let t5 = U128::from_u64s_le(0, 1); + let t6 = U128::from_u64s_le(1, 0); + assert((t5 - t6).hi == 0); + + assert( + (U128::from_hex("0x71f03a23783f9d9c7a8777") * U128::from_hex("0x8BFCF39041")).hi + == U128::from_hex("0x3e4e0471b873470e247c824e61445537").hi + ); + let q = U128::from_hex("0x3e4e0471b873470e247c824e61445537") / U128::from_hex("0x8BFCF39041"); + assert(q == U128::from_hex("0x71f03a23783f9d9c7a8777")); + + assert(big_int.hi == 2); + + let mut small_int = U128::from_integer(x); + assert(small_int.lo == x as Field); + assert(x == small_int.to_integer()); + let shift = small_int << small_int; + assert(shift == U128::from_integer(x << x)); + assert(shift >> small_int == small_int); + assert(shift >> U128::from_integer(127) == U128::from_integer(0)); + assert(shift << U128::from_integer(127) == U128::from_integer(0)); + +} +