-
Notifications
You must be signed in to change notification settings - Fork 219
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# Description ## Problem\* Resolves <!-- Link to GitHub Issue --> ## Summary\* First basic implementation of u128 in Noir, doing addition, subtraction, multiplication and division, and with some conversion utilities. It assumes the base field is larger than 128 bits. ## Additional Context ## Documentation\* No documentation yet because it is an on-going development (more PRs will follow) of an experimental feature Check one: - [X No documentation needed. - [ ] Documentation included in this PR. - [ ] **[Exceptional Case]** Documentation to be submitted in a separate PR. # PR Checklist\* - [X] I have tested the changes locally. - [X] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --------- Co-authored-by: Tom French <tom@tomfren.ch>
- Loading branch information
1 parent
38c732c
commit b4911dc
Showing
6 changed files
with
351 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<N>(hex: str<N>) -> 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<T>(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<T>(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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[package] | ||
name = "u128" | ||
type = "bin" | ||
authors = [""] | ||
|
||
[dependencies] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
x = "3" | ||
y = "4" | ||
z = "7" | ||
hexa ="0x1f03a" | ||
[big_int] | ||
lo = 1 | ||
hi = 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); | ||
|
||
} | ||
|