diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1948c70 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sw linguist-language=Rust diff --git a/.gitignore b/.gitignore index 088ba6b..6440baf 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,9 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk + +*/Cargo.lock +*/Forc.lock + +*/out/* +*/target/* diff --git a/README.md b/README.md index 6639299..fa75b84 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,42 @@ # fuel-crypto Various Cryptographic Primitives in Sway for the Fuel VM + +# Testing + +## BLS + + To run tests for bls folder: + ``` + cd testing/tests_bls12_381 + forc test + ``` + +## Testing with a script +You can use scripts locally to do intermediate tests. To run a script a local Fuel node must be spun up. + +### Spin Up a Fuel node +From [here](https://fuellabs.github.io/sway/v0.19.0/introduction/overview.html). +In a separate tab in your terminal, spin up a local Fuel node: + + +`fuel-core --db-type in-memory` + +This starts a Fuel node with a volatile database that will be cleared when shut down (good for testing purposes). + + Make sure `fuel-core` is up to date. This can be done with [fuelup](https://github.com/FuelLabs/fuelup). Also, make sure there's only 1 `fuel-core` installed (check this with `which -a fuel-core`). + + ### Create and run a script + +For example in `bls12_381/src` create `main.sw`. Change in `bls12_381/Forc.toml` `entry` to `main.sw`. + +Start the file with `script;` and whatever code is in `fn main () { .. }` will be executed with the following command: + +``` +forc run --unsigned --pretty-print +``` + +The `--unsigned` part is to avoid signing with a contract. The `--pretty-print` is for if you do some logging; it will get printed nicely. + +# FuelVM Instruction Set + +Find all assembly instructions that can be used [here](https://github.com/FuelLabs/fuel-specs/blob/master/specs/vm/instruction_set.md#sub-subtract). \ No newline at end of file diff --git a/bls12_381/.gitignore b/bls12_381/.gitignore new file mode 100644 index 0000000..77d3844 --- /dev/null +++ b/bls12_381/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/bls12_381/Cargo.toml b/bls12_381/Cargo.toml new file mode 100644 index 0000000..19f18a3 --- /dev/null +++ b/bls12_381/Cargo.toml @@ -0,0 +1,15 @@ +[project] +name = "bls12_381" +version = "0.1.0" +authors = ["Hashcloak"] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +fuels = { version = "0.23", features = ["fuel-core-lib"] } +tokio = { version = "1.12", features = ["rt", "macros"] } + +[[test]] +harness = true +name = "integration_tests" +path = "tests/harness.rs" diff --git a/bls12_381/Forc.toml b/bls12_381/Forc.toml new file mode 100644 index 0000000..f439d39 --- /dev/null +++ b/bls12_381/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Hashcloak"] +entry = "lib.sw" +license = "Apache-2.0" +name = "bls12_381" + +[dependencies] +utils = { path = "../utils" } \ No newline at end of file diff --git a/bls12_381/src/f12.sw b/bls12_381/src/f12.sw new file mode 100644 index 0000000..e9e870c --- /dev/null +++ b/bls12_381/src/f12.sw @@ -0,0 +1,114 @@ +library fp12; + +dep fp6; + +use fp6::Fp6; +use utils::choice::{ConstantTimeEq}; +use core::ops::{Eq, Add, Subtract, Multiply}; + +// Element in F_{p^12} +pub struct Fp12 { + c0: Fp6, + c1: Fp6, +} + +impl ConditionallySelectable for Fp12 { + // Select a if choice == 1 or select b if choice == 0, in constant time + fn conditional_select(a: Self, b: Self, choice: Choice) -> Self { + Fp12 { + c0: ~Fp6::conditional_select(a.c0, b.c0, choice), + c1: ~Fp6::conditional_select(a.c1, b.c1, choice), + } + } +} + +impl ConstantTimeEq for Fp12 { + // returns (self == other), as a choice + fn ct_eq(self, other: Self) -> Choice { + self.c0.ct_eq(other.c0) & self.c1.ct_eq(other.c1) + } +} + +impl Fp12 { + fn eq(self, other: Self) -> bool { + self.ct_eq(other).unwrap_as_bool() + } + + pub fn zero() -> Self { + Fp12 { + c0: ~Fp6::zero(), + c1: ~Fp6::zero(), + } + } + + pub fn one() -> Self { + Fp12 { + c0: ~Fp6::one(), + c1: ~Fp6::zero(), + } + } + + fn from(f: Fp) -> Fp12 { + Fp12 { + c0: ~Fp6::from(f), + c1: ~Fp6::zero(), + } + } + + fn from(f: Fp2) -> Fp12 { + Fp12 { + c0: ~Fp6::from(f), + c1: ~Fp6::zero(), + } + } + + fn from(f: Fp6) -> Fp12 { + Fp12 { + c0: f, + c1: ~Fp6::zero(), + } + } + + fn is_zero(self) -> Choice { + self.c0.is_zero().binary_and(self.c1.is_zero()) + } + + fn neg(self) -> Self { + Fp12 { + c0: self.c0.neg(), + c1: self.c1.neg(), + } + } + + fn add(self, rhs: Fp12) -> Self { + Fp12 { + c0: self.c0 + rhs.c0, + c1: self.c1 + rhs.c1, + } + } + + fn sub(self, rhs: Fp12) -> Self { + Fp12 { + c0: self.c0 - rhs.c0, + c1: self.c1 - rhs.c1, + } + } +} + +impl Eq for Fp12 { + fn eq(self, other: Self) -> bool { + self.eq(other) + } +} + +impl Add for Fp12 { + fn add(self, other: Fp12) -> Self { + self.add(other) + } +} + +impl Subtract for Fp12 { + fn subtract(self, other: Fp12) -> Self { + self.sub(other) + } +} \ No newline at end of file diff --git a/bls12_381/src/fp.sw b/bls12_381/src/fp.sw new file mode 100644 index 0000000..bf9a0c7 --- /dev/null +++ b/bls12_381/src/fp.sw @@ -0,0 +1,592 @@ +library fp; + +dep util; + +//This wildcard import is needed because of importing ConstantTimeEq for u64 (since it's a trait for a primitive type) +use utils::choice::*; +use util::*; +use std::{option::Option, u128::U128, vec::Vec}; +use core::ops::{Eq, Add, Subtract, Multiply}; + +// Little endian big integer with 6 limbs +// in Montgomery form +pub struct Fp{ls: [u64;6]} + +/// p = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 +pub const MODULUS: [u64; 6] = [ + 0xb9fe_ffff_ffff_aaab, + 0x1eab_fffe_b153_ffff, + 0x6730_d2a0_f6b0_f624, + 0x6477_4b84_f385_12bf, + 0x4b1b_a7b6_434b_acd7, + 0x1a01_11ea_397f_e69a, +]; + +/// INV = -(P^{-1} mod 2^64) mod 2^64 +pub const INV: u64 = 0x89f3_fffc_fffc_fffd; + +/// R = 2^384 mod p +const R: Fp = Fp{ls: [ + 0x7609_0000_0002_fffd, + 0xebf4_000b_c40c_0002, + 0x5f48_9857_53c7_58ba, + 0x77ce_5853_7052_5745, + 0x5c07_1a97_a256_ec6d, + 0x15f6_5ec3_fa80_e493, +]}; + +/// R2 = 2^(384*2) mod p +const R2: Fp = Fp{ls: [ + 0xf4df_1f34_1c34_1746, + 0x0a76_e6a6_09d1_04f1, + 0x8de5_476c_4c95_b6d5, + 0x67eb_88a9_939d_83c0, + 0x9a79_3e85_b519_952d, + 0x1198_8fe5_92ca_e3aa, +]}; + +/// R3 = 2^(384*3) mod p +const R3: Fp = Fp{ls: [ + 0xed48_ac6b_d94c_a1e0, + 0x315f_831e_03a7_adf8, + 0x9a53_352a_615e_29dd, + 0x34c0_4e5e_921e_1761, + 0x2512_d435_6572_4728, + 0x0aa6_3460_9175_5d4d, +]}; + +impl ConditionallySelectable for Fp { + // Select a if choice == 1 or select b if choice == 0, in constant time. + fn conditional_select(a: Fp, b: Fp, choice: Choice) -> Fp { + Fp{ ls: [ + ~u64::conditional_select(a.ls[0], b.ls[0], choice), + ~u64::conditional_select(a.ls[1], b.ls[1], choice), + ~u64::conditional_select(a.ls[2], b.ls[2], choice), + ~u64::conditional_select(a.ls[3], b.ls[3], choice), + ~u64::conditional_select(a.ls[4], b.ls[4], choice), + ~u64::conditional_select(a.ls[5], b.ls[5], choice), + ]} + } +} + +// returns the binary not for u64 +fn not(input: u64) -> u64 { + ~u64::max() - input +} + +impl ConstantTimeEq for Fp { + // returns (self == other), as a choice + fn ct_eq(self, other: Fp) -> Choice { + ~u64::ct_eq(self.ls[0], other.ls[0]) + & ~u64::ct_eq(self.ls[1], other.ls[1]) + & ~u64::ct_eq(self.ls[2], other.ls[2]) + & ~u64::ct_eq(self.ls[3], other.ls[3]) + & ~u64::ct_eq(self.ls[4], other.ls[4]) + & ~u64::ct_eq(self.ls[5], other.ls[5]) + } +} + +impl Fp { + pub fn zero() -> Fp { + Fp{ ls: [0, 0, 0, 0, 0, 0] } + } + + pub fn one() -> Fp { + R + } + + // returns -a mod p + pub fn neg(self) -> Fp { + let (d0, borrow) = sbb(MODULUS[0], self.ls[0], 0); + let (d1, borrow) = sbb(MODULUS[1], self.ls[1], borrow); + let (d2, borrow) = sbb(MODULUS[2], self.ls[2], borrow); + let (d3, borrow) = sbb(MODULUS[3], self.ls[3], borrow); + let (d4, borrow) = sbb(MODULUS[4], self.ls[4], borrow); + let (d5, _) = sbb(MODULUS[5], self.ls[5], borrow); + + // The mask should be 0 when a==p, otherwise 2^65-1 (= 11..11) + // limbs = 0 when self = 0 + let limbs = (self.ls[0] | self.ls[1] | self.ls[2] | self.ls[3] | self.ls[4] | self.ls[5]); + // p mod p = 0, so this checks whether self is p + // a_is_p = 0 when self = 0, otherwise a_is_p = 1 + let a_is_p: u64 = is_zero_u64(limbs); + // mask = a_is_p - 1. This will give either 1-1 (=0) or 0-1 (wrap around to 2^64-1) + let mask = subtract_1_wrap(a_is_p); + + Fp { + ls: [d0 & mask, d1 & mask, d2 & mask, d3 & mask, d4 & mask, d5 & mask] + } + } + + // If a >= p, return a-p, else return a + pub fn subtract_p(self) -> Fp { + let (r0, borrow) = sbb(self.ls[0], MODULUS[0], 0); + let (r1, borrow) = sbb(self.ls[1], MODULUS[1], borrow); + let (r2, borrow) = sbb(self.ls[2], MODULUS[2], borrow); + let (r3, borrow) = sbb(self.ls[3], MODULUS[3], borrow); + let (r4, borrow) = sbb(self.ls[4], MODULUS[4], borrow); + let (r5, borrow) = sbb(self.ls[5], MODULUS[5], borrow); + + // The final borrow is 11..11 if there was underflow. Otherwise 0. Therefore, it's used as a mask + let mut mask = borrow; + let r0 = (self.ls[0] & mask) | (r0 & not(mask)); + let r1 = (self.ls[1] & mask) | (r1 & not(mask)); + let r2 = (self.ls[2] & mask) | (r2 & not(mask)); + let r3 = (self.ls[3] & mask) | (r3 & not(mask)); + let r4 = (self.ls[4] & mask) | (r4 & not(mask)); + let r5 = (self.ls[5] & mask) | (r5 & not(mask)); + + Fp { + ls: [r0, r1, r2, r3, r4, r5] + } + } +} + +pub fn from_raw_unchecked(v: [u64; 6]) -> Fp { + Fp{ ls: v } +} + +impl Fp { + // This goes in a separate impl, because if we use previously defined functions in Fp impl, + // Sway will not recognize them from inside the same impl + + pub fn is_zero(self) -> Choice { + self.ct_eq(~Fp::zero()) + } + + /* + returns self + rhs mod p. + + each limbs is added and the possible carry is carried over to next limb + if needed, 1 reduction by p is done to make it mod p + */ + fn add(self, rhs: Fp) -> Fp { + let (d0, carry) = adc(self.ls[0], rhs.ls[0], 0); + let (d1, carry) = adc(self.ls[1], rhs.ls[1], carry); + let (d2, carry) = adc(self.ls[2], rhs.ls[2], carry); + let (d3, carry) = adc(self.ls[3], rhs.ls[3], carry); + let (d4, carry) = adc(self.ls[4], rhs.ls[4], carry); + let (d5, _) = adc(self.ls[5], rhs.ls[5], carry); + + // Subtract p if necessary, so the element is always mod p + (Fp{ ls: [d0, d1, d2, d3, d4, d5] }).subtract_p() + } + + /* + returns self * rhs mod p + + schoolbook mult, followed by montgomery reduction + */ + pub fn mul(self, rhs: Fp) -> Fp { + let self0 = self.ls[0]; + let self1 = self.ls[1]; + let self2 = self.ls[2]; + let self3 = self.ls[3]; + let self4 = self.ls[4]; + let self5 = self.ls[5]; + + let rhs0 = rhs.ls[0]; + let rhs1 = rhs.ls[1]; + let rhs2 = rhs.ls[2]; + let rhs3 = rhs.ls[3]; + let rhs4 = rhs.ls[4]; + let rhs5 = rhs.ls[5]; + + let (t0, carry) = mac(0, self0, rhs0, 0); + let (t1, carry) = mac(0, self0, rhs1, carry); + let (t2, carry) = mac(0, self0, rhs2, carry); + let (t3, carry) = mac(0, self0, rhs3, carry); + let (t4, carry) = mac(0, self0, rhs4, carry); + let (t5, t6) = mac(0, self0, rhs5, carry); + + let (t1, carry) = mac(t1, self1, rhs0, 0); + let (t2, carry) = mac(t2, self1, rhs1, carry); + let (t3, carry) = mac(t3, self1, rhs2, carry); + let (t4, carry) = mac(t4, self1, rhs3, carry); + let (t5, carry) = mac(t5, self1, rhs4, carry); + let (t6, t7) = mac(t6, self1, rhs5, carry); + + let (t2, carry) = mac(t2, self2, rhs0, 0); + let (t3, carry) = mac(t3, self2, rhs1, carry); + let (t4, carry) = mac(t4, self2, rhs2, carry); + let (t5, carry) = mac(t5, self2, rhs3, carry); + let (t6, carry) = mac(t6, self2, rhs4, carry); + let (t7, t8) = mac(t7, self2, rhs5, carry); + + let (t3, carry) = mac(t3, self3, rhs0, 0); + let (t4, carry) = mac(t4, self3, rhs1, carry); + let (t5, carry) = mac(t5, self3, rhs2, carry); + let (t6, carry) = mac(t6, self3, rhs3, carry); + let (t7, carry) = mac(t7, self3, rhs4, carry); + let (t8, t9) = mac(t8, self3, rhs5, carry); + + let (t4, carry) = mac(t4, self4, rhs0, 0); + let (t5, carry) = mac(t5, self4, rhs1, carry); + let (t6, carry) = mac(t6, self4, rhs2, carry); + let (t7, carry) = mac(t7, self4, rhs3, carry); + let (t8, carry) = mac(t8, self4, rhs4, carry); + let (t9, t10) = mac(t9, self4, rhs5, carry); + + let (t5, carry) = mac(t5, self5, rhs0, 0); + let (t6, carry) = mac(t6, self5, rhs1, carry); + let (t7, carry) = mac(t7, self5, rhs2, carry); + let (t8, carry) = mac(t8, self5, rhs3, carry); + let (t9, carry) = mac(t9, self5, rhs4, carry); + let (t10, t11) = mac(t10, self5, rhs5, carry); + + let res: [u64;12] = [t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11]; + montgomery_reduce(res) + } + + // returns self^2 mod p + pub fn square(self) -> Fp { + let (t1, carry) = mac(0, self.ls[0], self.ls[1], 0); + let (t2, carry) = mac(0, self.ls[0], self.ls[2], carry); + let (t3, carry) = mac(0, self.ls[0], self.ls[3], carry); + let (t4, carry) = mac(0, self.ls[0], self.ls[4], carry); + let (t5, t6) = mac(0, self.ls[0], self.ls[5], carry); + + let (t3, carry) = mac(t3, self.ls[1], self.ls[2], 0); + let (t4, carry) = mac(t4, self.ls[1], self.ls[3], carry); + let (t5, carry) = mac(t5, self.ls[1], self.ls[4], carry); + let (t6, t7) = mac(t6, self.ls[1], self.ls[5], carry); + + let (t5, carry) = mac(t5, self.ls[2], self.ls[3], 0); + let (t6, carry) = mac(t6, self.ls[2], self.ls[4], carry); + let (t7, t8) = mac(t7, self.ls[2], self.ls[5], carry); + + let (t7, carry) = mac(t7, self.ls[3], self.ls[4], 0); + let (t8, t9) = mac(t8, self.ls[3], self.ls[5], carry); + + let (t9, t10) = mac(t9, self.ls[4], self.ls[5], 0); + + let t11 = t10 >> 63; + let t10 = (t10 << 1) | (t9 >> 63); + let t9 = (t9 << 1) | (t8 >> 63); + let t8 = (t8 << 1) | (t7 >> 63); + let t7 = (t7 << 1) | (t6 >> 63); + let t6 = (t6 << 1) | (t5 >> 63); + let t5 = (t5 << 1) | (t4 >> 63); + let t4 = (t4 << 1) | (t3 >> 63); + let t3 = (t3 << 1) | (t2 >> 63); + let t2 = (t2 << 1) | (t1 >> 63); + let t1 = t1 << 1; + + let (t0, carry) = mac(0, self.ls[0], self.ls[0], 0); + let (t1, carry) = adc(t1, 0, carry); + let (t2, carry) = mac(t2, self.ls[1], self.ls[1], carry); + let (t3, carry) = adc(t3, 0, carry); + let (t4, carry) = mac(t4, self.ls[2], self.ls[2], carry); + let (t5, carry) = adc(t5, 0, carry); + let (t6, carry) = mac(t6, self.ls[3], self.ls[3], carry); + let (t7, carry) = adc(t7, 0, carry); + let (t8, carry) = mac(t8, self.ls[4], self.ls[4], carry); + let (t9, carry) = adc(t9, 0, carry); + let (t10, carry) = mac(t10, self.ls[5], self.ls[5], carry); + let (t11, _) = adc(t11, 0, carry); + + let res: [u64;12] = [t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11]; + montgomery_reduce(res) + } +} + +impl Fp { + // This goes in a separate impl, because if we use previously defined functions in Fp impl, + // Sway will not recognize them from inside the same impl + + // returns self - rhs mod p + fn sub(self, rhs: Fp) -> Fp { + (rhs.neg()).add(self) + } + +// TODO implement when performing *many* squares is possible + pub fn pow_vartime(self, by: [u64; 6]) -> Self { + // let mut res = Self::one(); + // for e in by.iter().rev() { + // for i in (0..64).rev() { + // res = res.square(); + + // if ((*e >> i) & 1) == 1 { + // res *= self; + // } + // } + // } + // res + ~Fp::zero() + } + + /* + returns c = a.zip(b).fold(0, |acc, (a_i, b_i)| acc + a_i * b_i) + + according to zkcrypto this implements Algorithm 2 from Patrick Longa's + [ePrint 2022-367](https://eprint.iacr.org/2022/367) §3 + + EXTRA NOTE: In the zkcrypto repo (Rust) this is implemented as sum_of_products for T, but this is not possible in Sway. Since specifically T=2 and T=6 is used, we implement both of them separately + */ + pub fn sum_of_products_6(a: [Fp; 6], b: [Fp; 6]) -> Fp { + let mut u1 = 0; + let mut u2 = 0; + let mut u3 = 0; + let mut u4 = 0; + let mut u5 = 0; + let mut u6 = 0; + + let mut j = 0; + + // Algorithm 2, line 3. For all pairs (a_i, b_j) calculate the sum of products + while j < 6 { + let mut t0 = u1; + let mut t1 = u2; + let mut t2 = u3; + let mut t3 = u4; + let mut t4 = u5; + let mut t5 = u6; + let mut t6 = 0; + + let mut i = 0; + while i < 6 { + let (t0_temp, carry) = mac(t0, a[i].ls[j], b[i].ls[0], 0); + let (t1_temp, carry) = mac(t1, a[i].ls[j], b[i].ls[1], carry); + let (t2_temp, carry) = mac(t2, a[i].ls[j], b[i].ls[2], carry); + let (t3_temp, carry) = mac(t3, a[i].ls[j], b[i].ls[3], carry); + let (t4_temp, carry) = mac(t4, a[i].ls[j], b[i].ls[4], carry); + let (t5_temp, carry) = mac(t5, a[i].ls[j], b[i].ls[5], carry); + let (t6_temp, _) = adc(t6, 0, carry); + // assigning directly to t0..t6 in the tuples didn't work, so we assign manually here + t0 = t0_temp; + t1 = t1_temp; + t2 = t2_temp; + t3 = t3_temp; + t4 = t4_temp; + t5 = t5_temp; + t6 = t6_temp; + i += 1; + } + + // Algorithm 2, lines 4-5 + let k = wrapping_mul(t0, INV); + let (_, carry) = mac(t0, k, MODULUS[0], 0); + let (u1_temp, carry) = mac(t1, k, MODULUS[1], carry); + let (u2_temp, carry) = mac(t2, k, MODULUS[2], carry); + let (u3_temp, carry) = mac(t3, k, MODULUS[3], carry); + let (u4_temp, carry) = mac(t4, k, MODULUS[4], carry); + let (u5_temp, carry) = mac(t5, k, MODULUS[5], carry); + let (u6_temp, _) = adc(t6, 0, carry); + // assigning directly to u1..u6 in the tuples didn't work, so we assign manually here + u1 = u1_temp; + u2 = u2_temp; + u3 = u3_temp; + u4 = u4_temp; + u5 = u5_temp; + u6 = u6_temp; + j += 1; + } + + // Subtract p if necessary, so the element is always mod p + (Fp{ ls: [u1, u2, u3, u4, u5, u6] }).subtract_p() + } + + pub fn sum_of_products_2(a: [Fp; 2], b: [Fp; 2]) -> Fp { + let mut u1 = 0; + let mut u2 = 0; + let mut u3 = 0; + let mut u4 = 0; + let mut u5 = 0; + let mut u6 = 0; + + let mut j = 0; + + // Algorithm 2, line 3. For all pairs (a_i, b_j) calculate the sum of products + while j < 6 { + let mut t0 = u1; + let mut t1 = u2; + let mut t2 = u3; + let mut t3 = u4; + let mut t4 = u5; + let mut t5 = u6; + let mut t6 = 0; + + let mut i = 0; + while i < 2 { + let (t0_temp, carry) = mac(t0, a[i].ls[j], b[i].ls[0], 0); + let (t1_temp, carry) = mac(t1, a[i].ls[j], b[i].ls[1], carry); + let (t2_temp, carry) = mac(t2, a[i].ls[j], b[i].ls[2], carry); + let (t3_temp, carry) = mac(t3, a[i].ls[j], b[i].ls[3], carry); + let (t4_temp, carry) = mac(t4, a[i].ls[j], b[i].ls[4], carry); + let (t5_temp, carry) = mac(t5, a[i].ls[j], b[i].ls[5], carry); + let (t6_temp, _) = adc(t6, 0, carry); + // assigning directly to t0..t6 in the tuples didn't work, so we assign manually here + t0 = t0_temp; + t1 = t1_temp; + t2 = t2_temp; + t3 = t3_temp; + t4 = t4_temp; + t5 = t5_temp; + t6 = t6_temp; + i += 1; + } + + // Algorithm 2, lines 4-5 + let k = wrapping_mul(t0, INV); + let (_, carry) = mac(t0, k, MODULUS[0], 0); + let (u1_temp, carry) = mac(t1, k, MODULUS[1], carry); + let (u2_temp, carry) = mac(t2, k, MODULUS[2], carry); + let (u3_temp, carry) = mac(t3, k, MODULUS[3], carry); + let (u4_temp, carry) = mac(t4, k, MODULUS[4], carry); + let (u5_temp, carry) = mac(t5, k, MODULUS[5], carry); + let (u6_temp, _) = adc(t6, 0, carry); + // assigning directly to u1..u6 in the tuples didn't work, so we assign manually here + u1 = u1_temp; + u2 = u2_temp; + u3 = u3_temp; + u4 = u4_temp; + u5 = u5_temp; + u6 = u6_temp; + j += 1; + } + + // Subtract p if necessary, so the element is always mod p + (Fp{ ls: [u1, u2, u3, u4, u5, u6] }).subtract_p() + } + + /// Returns whether or not this element is strictly lexicographically + /// larger than its negation. + + // returns whether self > -self, lexographically speaking + pub fn lexicographically_largest(self) -> Choice { + + // Check whether self >= (p-1)/2. If this is the case, we return "true" + // Subtract (p-1)/2 + 1. If there is no underflow, self was larger. + + + // Make sure it's mod p + let tmp = montgomery_reduce( + [self.ls[0], self.ls[1], self.ls[2], self.ls[3], self.ls[4], self.ls[5], 0, 0, 0, 0, 0, 0,] + ); + + // Subtract (p-1)/2 + 1 + let (_, borrow) = sbb(tmp.ls[0], 0xdcff_7fff_ffff_d556, 0); + let (_, borrow) = sbb(tmp.ls[1], 0x0f55_ffff_58a9_ffff, borrow); + let (_, borrow) = sbb(tmp.ls[2], 0xb398_6950_7b58_7b12, borrow); + let (_, borrow) = sbb(tmp.ls[3], 0xb23b_a5c2_79c2_895f, borrow); + let (_, borrow) = sbb(tmp.ls[4], 0x258d_d3db_21a5_d66b, borrow); + let (_, borrow) = sbb(tmp.ls[5], 0x0d00_88f5_1cbf_f34d, borrow); + + // If there was underflow, borrow is 11..11. Otherwise, it is 0. + let borrow_u8: u8 = borrow; + // Return "true" if there was no underflow. Otherwise return "false" + ~Choice::from(borrow & 1).not() + } + +} + +impl Fp { + // This goes in a separate impl, because if we use previously defined functions in Fp impl, + // Sway will not recognize them from inside the same impl + +//TODO pow_vartime has to be implemented for this to work + // returns Some(self^-1 mod p) or None if self == 0 + pub fn invert(self) -> CtOption { + // Exponentiate by p - 2 + let t = self.pow_vartime([ + 0xb9fe_ffff_ffff_aaa9, + 0x1eab_fffe_b153_ffff, + 0x6730_d2a0_f6b0_f624, + 0x6477_4b84_f385_12bf, + 0x4b1b_a7b6_434b_acd7, + 0x1a01_11ea_397f_e69a, + ]); + + ~CtOption::new_from_bool(t, !self.is_zero().unwrap_as_bool()) + } +} + +// Eq in Sway requires bool return type +impl Eq for Fp { + fn eq(self, other: Self) -> bool { + self.ct_eq(other).unwrap_as_bool() + } +} + +impl Add for Fp { + fn add(self, other: Self) -> Self { + self.add(other) + } +} + +impl Subtract for Fp { + fn subtract(self, other: Self) -> Self { + self.sub(other) + } +} + +impl Multiply for Fp { + fn multiply(self, other: Self) -> Self { + self.mul(other) + } +} + +/* +returns t mod p (as Fp) + +according to zkcrypto repo this is based on Algorithm 14.32 in Handbook of Applied Cryptography + +*/ +pub fn montgomery_reduce(t: [u64;12]) -> Fp { + let k = wrapping_mul(t[0], INV); + + let r0: (u64, u64) = mac(t[0], k, MODULUS[0], 0); + let r1: (u64, u64) = mac(t[1], k, MODULUS[1], r0.1); + let r2: (u64, u64) = mac(t[2], k, MODULUS[2], r1.1); + let r3: (u64, u64) = mac(t[3], k, MODULUS[3], r2.1); + let r4: (u64, u64) = mac(t[4], k, MODULUS[4], r3.1); + let r5: (u64, u64) = mac(t[5], k, MODULUS[5], r4.1); + let r6_7: (u64, u64) = adc(t[6], 0, r5.1); + + let k = wrapping_mul(r1.0, INV); + let r0: (u64, u64) = mac(r1.0, k, MODULUS[0], 0); + let r2: (u64, u64) = mac(r2.0, k, MODULUS[1], r0.1); + let r3: (u64, u64) = mac(r3.0, k, MODULUS[2], r2.1); + let r4: (u64, u64) = mac(r4.0, k, MODULUS[3], r3.1); + let r5: (u64, u64) = mac(r5.0, k, MODULUS[4], r4.1); + let r6: (u64, u64) = mac(r6_7.0, k, MODULUS[5], r5.1); + let r7_8: (u64, u64) = adc(t[7], r6_7.1, r6.1); + + let k = wrapping_mul(r2.0, INV); + let r0: (u64, u64) = mac(r2.0, k, MODULUS[0], 0); + let r3: (u64, u64) = mac(r3.0, k, MODULUS[1], r0.1); + let r4: (u64, u64) = mac(r4.0, k, MODULUS[2], r3.1); + let r5: (u64, u64) = mac(r5.0, k, MODULUS[3], r4.1); + let r6: (u64, u64) = mac(r6.0, k, MODULUS[4], r5.1); + let r7: (u64, u64) = mac(r7_8.0, k, MODULUS[5], r6.1); + let r8_9: (u64, u64) = adc(t[8], r7_8.1, r7.1); + + let k = wrapping_mul(r3.0, INV); + let r0: (u64, u64) = mac(r3.0, k, MODULUS[0], 0); + let r4: (u64, u64) = mac(r4.0, k, MODULUS[1], r0.1); + let r5: (u64, u64) = mac(r5.0, k, MODULUS[2], r4.1); + let r6: (u64, u64) = mac(r6.0, k, MODULUS[3], r5.1); + let r7: (u64, u64) = mac(r7.0, k, MODULUS[4], r6.1); + let r8: (u64, u64) = mac(r8_9.0, k, MODULUS[5], r7.1); + let r9_10: (u64, u64) = adc(t[9], r8_9.1, r8.1); + + let k = wrapping_mul(r4.0, INV); + let r0: (u64, u64) = mac(r4.0, k, MODULUS[0], 0); + let r5: (u64, u64) = mac(r5.0, k, MODULUS[1], r0.1); + let r6: (u64, u64) = mac(r6.0, k, MODULUS[2], r5.1); + let r7: (u64, u64) = mac(r7.0, k, MODULUS[3], r6.1); + let r8: (u64, u64) = mac(r8.0, k, MODULUS[4], r7.1); + let r9: (u64, u64) = mac(r9_10.0, k, MODULUS[5], r8.1); + let r10_11: (u64, u64) = adc(t[10], r9_10.1, r9.1); + + let k = wrapping_mul(r5.0, INV); + let r0: (u64, u64) = mac(r5.0, k, MODULUS[0], 0); + let r6: (u64, u64) = mac(r6.0, k, MODULUS[1], r0.1); + let r7: (u64, u64) = mac(r7.0, k, MODULUS[2], r6.1); + let r8: (u64, u64) = mac(r8.0, k, MODULUS[3], r7.1); + let r9: (u64, u64) = mac(r9.0, k, MODULUS[4], r8.1); + let r10: (u64, u64) = mac(r10_11.0, k, MODULUS[5], r9.1); + let r11_12 = adc(t[11], r10_11.1, r10.1); + + (Fp{ ls: [r6.0, r7.0, r8.0, r9.0, r10.0, r11_12.0] }).subtract_p() +} diff --git a/bls12_381/src/fp2.sw b/bls12_381/src/fp2.sw new file mode 100644 index 0000000..dc27021 --- /dev/null +++ b/bls12_381/src/fp2.sw @@ -0,0 +1,193 @@ +library fp2; + +dep fp; + +use fp::Fp; +use core::ops::{Eq, Add, Subtract, Multiply}; +use utils::choice::*; + +// Element in the quadratic extension field F_{p^2} +pub struct Fp2 { + c0: Fp, + c1: Fp, +} + +impl ConstantTimeEq for Fp2 { + // returns (self == other), as a choice + fn ct_eq(self, other: Self) -> Choice { + self.c0.ct_eq(other.c0) & self.c1.ct_eq(other.c1) + } +} + +impl ConditionallySelectable for Fp2 { + // Select a if choice == 1 or select b if choice == 0, in constant time + fn conditional_select(a: Self, b: Self, choice: Choice) -> Self { + Fp2 { + c0: ~Fp::conditional_select(a.c0, b.c0, choice), + c1: ~Fp::conditional_select(a.c1, b.c1, choice), + } + } +} + +impl Fp2 { + // in the zkcrypto repo this is implemented as trait From, but this isn't possible in Sway + fn from(f: Fp) -> Fp2 { + Fp2 { + c0: f, + c1: ~Fp::zero(), + } + } + + fn zero() -> Fp2 { + Fp2 { + c0: ~Fp::zero(), + c1: ~Fp::zero(), + } + } + + fn one() -> Fp2 { + Fp2 { + c0: ~Fp::one(), + c1: ~Fp::zero(), + } + } + + fn is_zero(self) -> Choice { + self.c0.is_zero().binary_and(self.c1.is_zero()) + } + + fn eq(self, other: Self) -> bool { + self.ct_eq(other).unwrap_as_bool() + } + +/* + // not tested, gives Immediate18TooLarge error + fn square(self) -> Fp2 { + // Complex squaring: + // + // v0 = c0 * c1 + // c0' = (c0 + c1) * (c0 + \beta*c1) - v0 - \beta * v0 + // c1' = 2 * v0 + // + // In BLS12-381's F_{p^2}, our \beta is -1 so we + // can modify this formula: + // + // c0' = (c0 + c1) * (c0 - c1) + // c1' = 2 * c0 * c1 + + let a = self.c0 + self.c1; + let b = self.c0 - self.c1; + let c = self.c0 + self.c0; + + Fp2 { + c0: a * b, + c1: c * self.c1, + } + } + */ + + fn mul(self, rhs: Fp2) -> Fp2 { + // Explanation from zkcrypto repo: + // F_{p^2} x F_{p^2} multiplication implemented with operand scanning (schoolbook) + // computes the result as: + // + // a·b = (a_0 b_0 + a_1 b_1 β) + (a_0 b_1 + a_1 b_0)i + // + // In BLS12-381's F_{p^2}, our β is -1, so the resulting F_{p^2} element is: + // + // c_0 = a_0 b_0 - a_1 b_1 + // c_1 = a_0 b_1 + a_1 b_0 + // + // Each of these is a "sum of products", which we can compute efficiently. + Fp2 { + c0: ~Fp::sum_of_products_2([self.c0, self.c1.neg()], [rhs.c0, rhs.c1]), + c1: ~Fp::sum_of_products_2([self.c0, self.c1], [rhs.c1, rhs.c0]), + } + } + + fn add(self, rhs: Fp2) -> Fp2 { + Fp2 { + c0: self.c0 + rhs.c0, + c1: self.c1 + rhs.c1, + } + } + + fn sub(self, rhs: Fp2) -> Fp2 { + Fp2 { + c0: self.c0 - rhs.c0, + c1: self.c1 - rhs.c1, + } + } + + fn neg(self) -> Fp2 { + Fp2 { + c0: (self.c0).neg(), + c1: (self.c1).neg(), + } + } + + // Is not tested directly, but will be indirectly in consequent extension fields + fn mul_by_nonresidue(self) -> Fp2 { + // Explanation from zkcrypto + // Multiply a + bu by u + 1, getting + // au + a + bu^2 + bu + // and because u^2 = -1, we get + // (a - b) + (a + b)u + + Fp2 { + c0: self.c0 - self.c1, + c1: self.c0 + self.c1, + } + } + + // returns whether self > -self, lexographically speaking + fn lexicographically_largest(self) -> Choice { + // lexicographically_largest(self.c1) || (self.c1 == 0 && lexicographically_largest(self.c0)) + self.c1.lexicographically_largest() + .binary_or(self.c1.is_zero().binary_and(self.c0.lexicographically_largest())) + } + + // returns (self.c0, -self.c1) + fn conjugate(self) -> Fp2 { + Fp2{ + c0: self.c0, + c1: (self.c1).neg(), + } + } +} + +impl Fp2 { + // This goes in a separate impl, because if we use previously defined functions in Fp2 impl, + // Sway will not recognize them from inside the same impl + + /// returns self^p, the Frobenius map + fn frobenius_map(self) -> Fp2 { + // For fp2, self^p equals the conjugate. + // Example explanation here: https://alicebob.modp.net/the-frobenius-endomorphism-with-finite-fields/ + self.conjugate() + } +} + +impl Eq for Fp2 { + fn eq(self, other: Self) -> bool { + self.eq(other) + } +} + +impl Add for Fp2 { + fn add(self, other: Self) -> Self { + self.add(other) + } +} + +impl Subtract for Fp2 { + fn subtract(self, other: Self) -> Self { + self.sub(other) + } +} + +impl Multiply for Fp2 { + fn multiply(self, other: Self) -> Self { + self.mul(other) + } +} diff --git a/bls12_381/src/fp6.sw b/bls12_381/src/fp6.sw new file mode 100644 index 0000000..f876a18 --- /dev/null +++ b/bls12_381/src/fp6.sw @@ -0,0 +1,133 @@ +library fp6; + +dep fp2; +dep fp; + +use fp::{Fp, from_raw_unchecked}; +use fp2::Fp2; +use utils::choice::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable}; +use core::ops::{Eq, Add, Subtract, Multiply}; + +// Element in F_{p^6} +pub struct Fp6 { + c0: Fp2, + c1: Fp2, + c2: Fp2, +} + +impl ConstantTimeEq for Fp6 { + // returns (self == other), as a choice + fn ct_eq(self, other: Self) -> Choice { + self.c0.ct_eq(other.c0) & self.c1.ct_eq(other.c1) & self.c2.ct_eq(other.c2) + } +} + +impl ConditionallySelectable for Fp6 { + // Select a if choice == 1 or select b if choice == 0, in constant time + fn conditional_select(a: Self, b: Self, choice: Choice) -> Self { + Fp6 { + c0: ~Fp2::conditional_select(a.c0, b.c0, choice), + c1: ~Fp2::conditional_select(a.c1, b.c1, choice), + c2: ~Fp2::conditional_select(a.c2, b.c2, choice), + } + } +} + +impl Fp6 { + fn eq(self, other: Self) -> bool { + self.ct_eq(other).unwrap_as_bool() + } + + fn from(f: Fp) -> Fp6 {//TODO is it possibly to have multiple functions with same name and different arguments? + Fp6 { + c0: ~Fp2::from(f), + c1: ~Fp2::zero(), + c2: ~Fp2::zero(), + } + } + + fn from(f: Fp2) -> Fp6 { + Fp6 { + c0: f, + c1: ~Fp2::zero(), + c2: ~Fp2::zero(), + } + } + + fn zero() -> Self { + Fp6 { + c0: ~Fp2::zero(), + c1: ~Fp2::zero(), + c2: ~Fp2::zero(), + } + } + + fn one() -> Self { + Fp6 { + c0: ~Fp2::one(), + c1: ~Fp2::zero(), + c2: ~Fp2::zero(), + } + } + + fn is_zero(self) -> Choice { + self.c0.is_zero().binary_and(self.c1.is_zero()).binary_and(self.c2.is_zero()) + } + + fn mul_by_nonresidue(self) -> Self { + // Explanation from zkcrypto + // Given a + bv + cv^2, this produces + // av + bv^2 + cv^3 + // but because v^3 = u + 1, we have + // c(u + 1) + av + v^2 + + Fp6 { + c0: self.c2.mul_by_nonresidue(), + c1: self.c0, + c2: self.c1, + } + } + + //TODO: Testing. Has no dedicated tests in zkcrypto + fn add(self, rhs: Fp6) -> Fp6 { + Fp6 { + c0: self.c0 + rhs.c0, + c1: self.c1 + rhs.c1, + c2: self.c2 + rhs.c2, + } + } + //TODO: Testing. Has no dedicated tests in zkcrypto + fn sub(self, rhs: Fp6) -> Fp6 { + Fp6 { + c0: self.c0 - rhs.c0, + c1: self.c1 - rhs.c1, + c2: self.c2 - rhs.c2, + } + } + //TODO: Testing. Has no dedicated tests in zkcrypto + fn neg(self) -> Fp6 { + Fp6 { + c0: self.c0.neg(), + c1: self.c1.neg(), + c2: self.c2.neg(), + } + } +} + +impl Eq for Fp6 { + fn eq(self, other: Self) -> bool { + self.eq(other) + } +} + +impl Add for Fp6 { + fn add(self, other: Self) -> Self { + self.add(other) + } +} + +impl Subtract for Fp6 { + fn subtract(self, other: Self) -> Self { + self.sub(other) + } +} diff --git a/bls12_381/src/g1.sw b/bls12_381/src/g1.sw new file mode 100644 index 0000000..2dcf115 --- /dev/null +++ b/bls12_381/src/g1.sw @@ -0,0 +1,432 @@ +library g1; + +dep fp; + +use fp::{Fp, from_raw_unchecked}; +use utils::choice::{Choice, CtOption, ConditionallySelectable, ConstantTimeEq}; +use core::ops::{Eq, Add, Subtract}; + +// Comment from zkcrypto +/// This is an element of $\mathbb{G}_1$ represented in the affine coordinate space. +/// It is ideal to keep elements in this representation to reduce memory usage and +/// improve performance through the use of mixed curve model arithmetic. +/// +/// Values of `G1Affine` are guaranteed to be in the $q$-order subgroup unless an +/// "unchecked" API was misused. +pub struct G1Affine { + x: Fp, + y: Fp, + infinity: Choice, +} + +pub const B: Fp = from_raw_unchecked([ + 0xaa27_0000_000c_fff3, + 0x53cc_0032_fc34_000a, + 0x478f_e97a_6b0a_807f, + 0xb1d3_7ebe_e6ba_24d7, + 0x8ec9_733b_bf78_ab2f, + 0x09d6_4551_3d83_de7e, +]); + +fn mul_by_3b(a: Fp) -> Fp { + let a = a + a; // 2 + let a = a + a; // 4 + a + a + a // 12 +} + +impl ConstantTimeEq for G1Affine { + // returns (self == other), as a choice + fn ct_eq(self, other: Self) -> Choice { + // Comment from zkcrypto + // The only cases in which two points are equal are + // 1. infinity is set on both + // 2. infinity is not set on both, and their coordinates are equal + self.infinity.binary_and(other.infinity) + .binary_or( + (self.infinity.not()) + .binary_and(other.infinity.not()) + .binary_and(self.x.ct_eq(other.x)) + .binary_and(self.y.ct_eq(other.y)) + ) + } +} + +pub trait FROM_PROJ { + fn from(p: G1Projective) -> Self; +} + +fn unwrap_or(input: CtOption, default: Fp) -> Fp { + match input.is_some() { + true => input.unwrap(), + false => default, + } +} + +//TODO +// - needs fp invert, which is not working yet +// - will use already created fn `unwrap_or` since adding unwrap_or to trait CtOption can't work yet +// impl FROM_PROJ for G1Affine { +// fn from(p: G1Projective) -> Self { +// .. +// } +// } + +impl ConditionallySelectable for G1Affine { + // Select a if choice == 1 or select b if choice == 0, in constant time. + fn conditional_select(a: Self, b: Self, choice: Choice) -> Self { + G1Affine { + x: ~Fp::conditional_select(a.x, b.x, choice), + y: ~Fp::conditional_select(a.y, b.y, choice), + infinity: ~Choice::conditional_select(a.infinity, b.infinity, choice), + } + } +} + +impl Eq for G1Affine { + fn eq(self, other: Self) -> bool { + self.ct_eq(other).unwrap_as_bool() + } +} + +impl G1Affine { + /// Returns the identity of the group: the point at infinity. + fn identity() -> G1Affine { + G1Affine { + x: ~Fp::zero(), + y: ~Fp::one(), + infinity: ~Choice::from(1u8), + } + } + + // returns true if this is the point at infinity + fn is_identity(self) -> Choice { + self.infinity + } + + // TODO TEST WHEN POSSIBLE: Uses mul_by_x on G1Projective which uses double, which can't compile + // fn is_torsion_free(self) -> Choice { + // Comment from zkcrypto + // // Algorithm from Section 6 of https://eprint.iacr.org/2021/1130 + // // Updated proof of correctness in https://eprint.iacr.org/2022/352 + // // + // // Check that endomorphism_p(P) == -[x^2] P + + // let minus_x_squared_times_p = from(self).mul_by_x().mul_by_x().neg(); + // let endomorphism_p = endomorphism(self); + // minus_x_squared_times_p.ct_eq(from(endomorphism_p)) + // } + + //Errors to Immediate18TooLarge + // fn is_on_curve(self) -> Choice { + // // y^2 - x^3 ?= 4 + // (self.y.square() - (self.x.square() * self.x)).ct_eq(B) | self.infinity + // } + + // returns a fixed generator of the group + // see notes of zkcrypto on how this was chosen [here at paragraph `Fixed generators`](https://github.com/zkcrypto/bls12_381/blob/main/src/notes/design.rs) + fn generator() -> G1Affine { + G1Affine { + x: from_raw_unchecked([ + 0x5cb3_8790_fd53_0c16, + 0x7817_fc67_9976_fff5, + 0x154f_95c7_143b_a1c1, + 0xf0ae_6acd_f3d0_e747, + 0xedce_6ecc_21db_f440, + 0x1201_7741_9e0b_fb75, + ]), + y: from_raw_unchecked([ + 0xbaac_93d5_0ce7_2271, + 0x8c22_631a_7918_fd8e, + 0xdd59_5f13_5707_25ce, + 0x51ac_5829_5040_5194, + 0x0e1c_8c3f_ad00_59c0, + 0x0bbc_3efc_5008_a26a, + ]), + infinity: ~Choice::from(0u8), + } + } + + // returns negation of point + fn neg(self) -> G1Affine {//will be tested with subtraction (TODO) + G1Affine { + x: self.x, + y: ~Fp::conditional_select(self.y.neg(), ~Fp::one(), self.infinity), + infinity: self.infinity, + } + } +} + +// Comment from zkcrypto +/// A nontrivial third root of unity in Fp +pub const BETA: Fp = from_raw_unchecked([ + 0x30f1_361b_798a_64e8, + 0xf3b8_ddab_7ece_5a2a, + 0x16a8_ca3a_c615_77f7, + 0xc26a_2ff8_74fd_029b, + 0x3636_b766_6070_1c6e, + 0x051b_a4ab_241b_6160, +]); + +// returns new point with coordinates (BETA * x, y) +fn endomorphism(p: G1Affine) -> G1Affine { + // Comment from zkcrypto + // Endomorphism of the points on the curve. + // endomorphism_p(x,y) = (BETA * x, y) + // where BETA is a non-trivial cubic root of unity in Fq. + let mut res = p; + res.x *= BETA; + res +} + +// Element of G1, represented with projective coordinates +pub struct G1Projective { + x: Fp, + y: Fp, + z: Fp, +} + + +impl G1Projective { + // Comment from zkcrypto + /// Returns the identity of the group: the point at infinity. + fn identity() -> G1Projective { + G1Projective { + x: ~Fp::zero(), + y: ~Fp::one(), + z: ~Fp::zero(), + } + } + + // returns true if self is the point at infinity + fn is_identity(self) -> Choice { + self.z.is_zero() + } + + // returns point negation + fn neg(self) -> G1Projective { //will be tested with subtraction (TODO) + G1Projective { + x: self.x, + y: self.y.neg(), + z: self.z, + } + } + + //Errors to Immediate18TooLarge + // fn is_on_curve(self) -> Choice { + // // Y^2 Z = X^3 + b Z^3 + + // (self.y.square() * self.z).ct_eq(self.x.square() * self.x + self.z.square() * self.z * B) + // | self.z.is_zero() + // } + + // returns a fixed generator of the group + // see notes of zkcrypto on how this was chosen [here at paragraph `Fixed generators`](https://github.com/zkcrypto/bls12_381/blob/main/src/notes/design.rs) + fn generator() -> G1Projective { + G1Projective { + x: from_raw_unchecked([ + 0x5cb3_8790_fd53_0c16, + 0x7817_fc67_9976_fff5, + 0x154f_95c7_143b_a1c1, + 0xf0ae_6acd_f3d0_e747, + 0xedce_6ecc_21db_f440, + 0x1201_7741_9e0b_fb75, + ]), + y: from_raw_unchecked([ + 0xbaac_93d5_0ce7_2271, + 0x8c22_631a_7918_fd8e, + 0xdd59_5f13_5707_25ce, + 0x51ac_5829_5040_5194, + 0x0e1c_8c3f_ad00_59c0, + 0x0bbc_3efc_5008_a26a, + ]), + z: ~Fp::one(), + } + } +} + +impl ConditionallySelectable for G1Projective { + // Select a if choice == 1 or select b if choice == 0, in constant time. + fn conditional_select(a: Self, b: Self, choice: Choice) -> Self { + G1Projective { + x: ~Fp::conditional_select(a.x, b.x, choice), + y: ~Fp::conditional_select(a.y, b.y, choice), + z: ~Fp::conditional_select(a.z, b.z, choice), + } + } +} + +impl G1Projective { + + // Not able to test this yet, doesn't terminate + // returns doubling of point + // uses Algorithm 9, https://eprint.iacr.org/2015/1060.pdf + fn double(self) -> G1Projective { + let t0 = self.y.square(); + let z3 = t0 + t0; + let z3 = z3 + z3; + let z3 = z3 + z3; + let t1 = self.y * self.z; + let t2 = self.z.square(); + let t2 = mul_by_3b(t2); + let x3 = t2 * z3; + let y3 = t0 + t2; + let z3 = t1 * z3; + let t1 = t2 + t2; + let t2 = t1 + t2; + let t0 = t0 - t2; + let y3 = t0 * y3; + let y3 = x3 + y3; + let t1 = self.x * self.y; + let x3 = t0 * t1; + let x3 = x3 + x3; + + let tmp = G1Projective { + x: x3, + y: y3, + z: z3, + }; + + ~G1Projective::conditional_select(tmp, ~G1Projective::identity(), self.is_identity()) + } + + // return self + rhs + // Uses Algorithm 7, https://eprint.iacr.org/2015/1060.pdf + fn add(self, rhs: G1Projective) -> G1Projective { + let t0 = self.x * rhs.x; + let t1 = self.y * rhs.y; + let t2 = self.z * rhs.z; + let t3 = self.x + self.y; + let t4 = rhs.x + rhs.y; + let t3 = t3 * t4; + let t4 = t0 + t1; + let t3 = t3 - t4; + let t4 = self.y + self.z; + let x3 = rhs.y + rhs.z; + let t4 = t4 * x3; + let x3 = t1 + t2; + let t4 = t4 - x3; + let x3 = self.x + self.z; + let y3 = rhs.x + rhs.z; + let x3 = x3 * y3; + let y3 = t0 + t2; + let y3 = x3 - y3; + let x3 = t0 + t0; + let t0 = x3 + t0; + let t2 = mul_by_3b(t2); + let z3 = t1 + t2; + let t1 = t1 - t2; + let y3 = mul_by_3b(y3); + let x3 = t4 * y3; + let t2 = t3 * t1; + let x3 = t2 - x3; + let y3 = y3 * t0; + let t1 = t1 * z3; + let y3 = t1 + y3; + let t0 = t0 * t3; + let z3 = z3 * t4; + let z3 = z3 + t0; + + G1Projective { + x: x3, + y: y3, + z: z3, + } + } + + // returns self added to another point that is in the affine representation + // Uses Algorithm 8, https://eprint.iacr.org/2015/1060.pdf + fn add_mixed(self, rhs: G1Affine) -> G1Projective { + let t0 = self.x * rhs.x; + let t1 = self.y * rhs.y; + let t3 = rhs.x + rhs.y; + let t4 = self.x + self.y; + let t3 = t3 * t4; + let t4 = t0 + t1; + let t3 = t3 - t4; + let t4 = rhs.y * self.z; + let t4 = t4 + self.y; + let y3 = rhs.x * self.z; + let y3 = y3 + self.x; + let x3 = t0 + t0; + let t0 = x3 + t0; + let t2 = mul_by_3b(self.z); + let z3 = t1 + t2; + let t1 = t1 - t2; + let y3 = mul_by_3b(y3); + let x3 = t4 * y3; + let t2 = t3 * t1; + let x3 = t2 - x3; + let y3 = y3 * t0; + let t1 = t1 * z3; + let y3 = t1 + y3; + let t0 = t0 * t3; + let z3 = z3 * t4; + let z3 = z3 + t0; + + let tmp = G1Projective { + x: x3, + y: y3, + z: z3, + }; + + ~G1Projective::conditional_select(tmp, self, rhs.is_identity()) + } +} + +pub trait FROM_AFF { + fn from(p: G1Affine) -> Self; +} + +impl FROM_AFF for G1Projective { + fn from(p: G1Affine) -> Self { + G1Projective { + x: p.x, + y: p.y, + z: ~Fp::conditional_select(~Fp::one(), ~Fp::zero(), p.infinity), + } + } +} + +impl ConstantTimeEq for G1Projective { + // returns (self == other), as a choice + fn ct_eq(self, other: Self) -> Choice { + // Comments from zkcrypto + // Is (xz, yz, z) equal to (x'z', y'z', z') when converted to affine? + + let x1 = self.x * other.z; + let x2 = other.x * self.z; + + let y1 = self.y * other.z; + let y2 = other.y * self.z; + + let self_is_zero = self.z.is_zero(); + let other_is_zero = other.z.is_zero(); + + // they are equal if: + // - both points are infinity + // - neither is infinity, and coordinates are the same + self_is_zero.binary_and(other_is_zero) + .binary_or( + ((~Choice::not(self_is_zero)).binary_and(~Choice::not(other_is_zero)) + .binary_and(x1.ct_eq(x2).binary_and(y1.ct_eq(y2)))) + ) + } +} + +impl Eq for G1Projective { + fn eq(self, other: Self) -> bool { + self.ct_eq(other).unwrap_as_bool() + } +} + +impl Add for G1Projective { + fn add(self, other: Self) -> Self { + self.add(other) + } +} + +impl Subtract for G1Projective { + fn subtract(self, other: Self) -> Self { + self + (other.neg()) + } +} \ No newline at end of file diff --git a/bls12_381/src/lib.sw b/bls12_381/src/lib.sw new file mode 100644 index 0000000..7510e60 --- /dev/null +++ b/bls12_381/src/lib.sw @@ -0,0 +1,7 @@ +library bls12_381; + +dep fp; +dep fp2; +dep fp6; +dep scalar; +dep g1; \ No newline at end of file diff --git a/bls12_381/src/scalar.sw b/bls12_381/src/scalar.sw new file mode 100644 index 0000000..7bd2e07 --- /dev/null +++ b/bls12_381/src/scalar.sw @@ -0,0 +1,423 @@ +library scalar; + +dep util; + +use utils::choice::*; +use util::*; + +use core::ops::{Eq, Add, Subtract, Multiply}; + +// element of scalar field Fq +// Montgomery form: aR mod q, where R = 2^256 +pub struct Scalar { ls: [u64; 4] } + +/// Constant representing the modulus +/// q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +pub const MODULUS_SCALAR: Scalar = Scalar{ ls: [ + 0xffff_ffff_0000_0001, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48, +]}; + +/// The modulus as u32 limbs. +const MODULUS_LIMBS_32: [u32; 8] = [ + 0x0000_0001, + 0xffff_ffff, + 0xfffe_5bfe, + 0x53bd_a402, + 0x09a1_d805, + 0x3339_d808, + 0x299d_7d48, + 0x73ed_a753, +]; + +// The number of bits needed to represent the modulus. +const MODULUS_BITS: u32 = 255; + +// GENERATOR = 7 +// Explanation from zkcrypto: multiplicative generator of r-1 order, that is also quadratic nonresidue +const GENERATOR: Scalar = Scalar{ ls: [ + 0x0000_000e_ffff_fff1, + 0x17e3_63d3_0018_9c0f, + 0xff9c_5787_6f84_57b0, + 0x3513_3220_8fc5_a8c4, +]}; + +/// INV = -(q^{-1} mod 2^64) mod 2^64 +const INV: u64 = 0xffff_fffe_ffff_ffff; + +/// R = 2^256 mod q +const R: Scalar = Scalar{ ls: [ + 0x0000_0001_ffff_fffe, + 0x5884_b7fa_0003_4802, + 0x998c_4fef_ecbc_4ff5, + 0x1824_b159_acc5_056f, +]}; + +/// R^2 = 2^512 mod q +pub const R2: Scalar = Scalar{ ls: [ + 0xc999_e990_f3f2_9c6d, + 0x2b6c_edcb_8792_5c23, + 0x05d3_1496_7254_398f, + 0x0748_d9d9_9f59_ff11, +]}; + +/// R^3 = 2^768 mod q +const R3: Scalar = Scalar{ ls: [ + 0xc62c_1807_439b_73af, + 0x1b3e_0d18_8cf0_6990, + 0x73d1_3c71_c7b5_f418, + 0x6e2a_5bb9_c8db_33e9, +]}; + +// 2^S * t = MODULUS - 1 with t odd +const S: u32 = 32; + +// Explanation from zkcrypto: +/// GENERATOR^t where t * 2^s + 1 = q +/// with t odd. In other words, this +/// is a 2^s root of unity. +/// +/// `GENERATOR = 7 mod q` is a generator +/// of the q - 1 order multiplicative +/// subgroup. +const ROOT_OF_UNITY: Scalar = Scalar{ ls: [ + 0xb9b5_8d8c_5f0e_466a, + 0x5b1b_4c80_1819_d7ec, + 0x0af5_3ae3_52a3_1e64, + 0x5bf3_adda_19e9_b27b, +]}; + +impl ConditionallySelectable for Scalar { + // Select a if choice == 1 or select b if choice == 0, in constant time. + fn conditional_select(a: Self, b: Self, choice: Choice) -> Self { + Scalar{ ls: [ + ~u64::conditional_select(a.ls[0], b.ls[0], choice), + ~u64::conditional_select(a.ls[1], b.ls[1], choice), + ~u64::conditional_select(a.ls[2], b.ls[2], choice), + ~u64::conditional_select(a.ls[3], b.ls[3], choice), + ]} + } +} + +impl ConstantTimeEq for Scalar { + // returns (self == other), as a choice + fn ct_eq(self, other: Self) -> Choice { + ~u64::ct_eq(self.ls[0], other.ls[0]) + & ~u64::ct_eq(self.ls[1], other.ls[1]) + & ~u64::ct_eq(self.ls[2], other.ls[2]) + & ~u64::ct_eq(self.ls[3], other.ls[3]) + } +} + +impl Scalar { + fn zero() -> Scalar { + Scalar{ ls: [0, 0, 0, 0]} + } + + fn one() -> Scalar { + R + } + + // returns self - rhs mod q + fn sub(self, rhs: Self) -> Self { + let (d0, borrow) = sbb(self.ls[0], rhs.ls[0], 0); + let (d1, borrow) = sbb(self.ls[1], rhs.ls[1], borrow); + let (d2, borrow) = sbb(self.ls[2], rhs.ls[2], borrow); + let (d3, borrow) = sbb(self.ls[3], rhs.ls[3], borrow); + + // The final borrow is 11..11 if there was underflow. Otherwise 0. Therefore, it's used as a mask + let (d0, carry) = adc(d0, MODULUS_SCALAR.ls[0] & borrow, 0); + let (d1, carry) = adc(d1, MODULUS_SCALAR.ls[1] & borrow, carry); + let (d2, carry) = adc(d2, MODULUS_SCALAR.ls[2] & borrow, carry); + let (d3, _) = adc(d3, MODULUS_SCALAR.ls[3] & borrow, carry); + + Scalar{ ls: [d0, d1, d2, d3]} + } + + // returns -self mod q + fn neg(self) -> Self { + // Explanation from zkcrypto repo + // Subtract `self` from `MODULUS` to negate. Ignore the final + // borrow because it cannot underflow; self is guaranteed to + // be in the field. + let (d0, borrow) = sbb(MODULUS_SCALAR.ls[0], self.ls[0], 0); + let (d1, borrow) = sbb(MODULUS_SCALAR.ls[1], self.ls[1], borrow); + let (d2, borrow) = sbb(MODULUS_SCALAR.ls[2], self.ls[2], borrow); + let (d3, _) = sbb(MODULUS_SCALAR.ls[3], self.ls[3], borrow); + + // The mask should be 0 when a==p and 2^65-1 otherwise + // limbs = 0 when self = 0 + let limbs = self.ls[0] | self.ls[1] | self.ls[2] | self.ls[3]; + // p mod p = 0, so this checks whether self is p + let scalar_is_0_mod_p = is_zero_u64(limbs); + // mask = a_is_p - 1. This will give either 1-1 (=0) or 0-1 (wrap around to 2^64-1) + let mask = subtract_1_wrap(scalar_is_0_mod_p); + + Scalar{ ls: [d0 & mask, d1 & mask, d2 & mask, d3 & mask]} + } +} + +impl Scalar { + // returns self + rhs mod q + fn add(self, rhs: Self) -> Self { + let (d0, carry) = adc(self.ls[0], rhs.ls[0], 0); + let (d1, carry) = adc(self.ls[1], rhs.ls[1], carry); + let (d2, carry) = adc(self.ls[2], rhs.ls[2], carry); + let (d3, _) = adc(self.ls[3], rhs.ls[3], carry); + + // Subtract q to ensure the element is always mod q + (Scalar{ls:[d0, d1, d2, d3]}).sub(MODULUS_SCALAR) + } + + + /* + returns t mod q (as Scalar) + + according to zkcrypto repo this is based on Algorithm 14.32 in Handbook of Applied Cryptography + + */ + fn montgomery_reduce( + r0: u64, + r1: u64, + r2: u64, + r3: u64, + r4: u64, + r5: u64, + r6: u64, + r7: u64, + ) -> Self { + let k = wrapping_mul(r0, INV); + let (_, carry) = mac(r0, k, MODULUS_SCALAR.ls[0], 0); + let (r1, carry) = mac(r1, k, MODULUS_SCALAR.ls[1], carry); + let (r2, carry) = mac(r2, k, MODULUS_SCALAR.ls[2], carry); + let (r3, carry) = mac(r3, k, MODULUS_SCALAR.ls[3], carry); + let (r4, carry2) = adc(r4, 0, carry); + + let k = wrapping_mul(r1, INV); + let (_, carry) = mac(r1, k, MODULUS_SCALAR.ls[0], 0); + let (r2, carry) = mac(r2, k, MODULUS_SCALAR.ls[1], carry); + let (r3, carry) = mac(r3, k, MODULUS_SCALAR.ls[2], carry); + let (r4, carry) = mac(r4, k, MODULUS_SCALAR.ls[3], carry); + let (r5, carry2) = adc(r5, carry2, carry); + + let k = wrapping_mul(r2, INV); + let (_, carry) = mac(r2, k, MODULUS_SCALAR.ls[0], 0); + let (r3, carry) = mac(r3, k, MODULUS_SCALAR.ls[1], carry); + let (r4, carry) = mac(r4, k, MODULUS_SCALAR.ls[2], carry); + let (r5, carry) = mac(r5, k, MODULUS_SCALAR.ls[3], carry); + let (r6, carry2) = adc(r6, carry2, carry); + + let k = wrapping_mul(r3, INV); + let (_, carry) = mac(r3, k, MODULUS_SCALAR.ls[0], 0); + let (r4, carry) = mac(r4, k, MODULUS_SCALAR.ls[1], carry); + let (r5, carry) = mac(r5, k, MODULUS_SCALAR.ls[2], carry); + let (r6, carry) = mac(r6, k, MODULUS_SCALAR.ls[3], carry); + let (r7, _) = adc(r7, carry2, carry); + + // Subtract q to ensure the element is always mod q + (Scalar{ ls:[r4, r5, r6, r7]}).sub(MODULUS_SCALAR) + } +} + +impl Scalar { + // returns self * rhs mod q + fn mul(self, rhs: Self) -> Self { + // Schoolbook multiplication + let (r0, carry) = mac(0, self.ls[0], rhs.ls[0], 0); + let (r1, carry) = mac(0, self.ls[0], rhs.ls[1], carry); + let (r2, carry) = mac(0, self.ls[0], rhs.ls[2], carry); + let (r3, r4) = mac(0, self.ls[0], rhs.ls[3], carry); + + let (r1, carry) = mac(r1, self.ls[1], rhs.ls[0], 0); + let (r2, carry) = mac(r2, self.ls[1], rhs.ls[1], carry); + let (r3, carry) = mac(r3, self.ls[1], rhs.ls[2], carry); + let (r4, r5) = mac(r4, self.ls[1], rhs.ls[3], carry); + + let (r2, carry) = mac(r2, self.ls[2], rhs.ls[0], 0); + let (r3, carry) = mac(r3, self.ls[2], rhs.ls[1], carry); + let (r4, carry) = mac(r4, self.ls[2], rhs.ls[2], carry); + let (r5, r6) = mac(r5, self.ls[2], rhs.ls[3], carry); + + let (r3, carry) = mac(r3, self.ls[3], rhs.ls[0], 0); + let (r4, carry) = mac(r4, self.ls[3], rhs.ls[1], carry); + let (r5, carry) = mac(r5, self.ls[3], rhs.ls[2], carry); + let (r6, r7) = mac(r6, self.ls[3], rhs.ls[3], carry); + + ~Scalar::montgomery_reduce(r0, r1, r2, r3, r4, r5, r6, r7) + } + + // returns self ^ 2 mod q + fn square(self) -> Scalar { + let (r1, carry) = mac(0, self.ls[0], self.ls[1], 0); + let (r2, carry) = mac(0, self.ls[0], self.ls[2], carry); + let (r3, r4) = mac(0, self.ls[0], self.ls[3], carry); + + let (r3, carry) = mac(r3, self.ls[1], self.ls[2], 0); + let (r4, r5) = mac(r4, self.ls[1], self.ls[3], carry); + + let (r5, r6) = mac(r5, self.ls[2], self.ls[3], 0); + + let r7 = r6 >> 63; + let r6 = (r6 << 1) | (r5 >> 63); + let r5 = (r5 << 1) | (r4 >> 63); + let r4 = (r4 << 1) | (r3 >> 63); + let r3 = (r3 << 1) | (r2 >> 63); + let r2 = (r2 << 1) | (r1 >> 63); + let r1 = r1 << 1; + + let (r0, carry) = mac(0, self.ls[0], self.ls[0], 0); + let (r1, carry) = adc(0, r1, carry); + let (r2, carry) = mac(r2, self.ls[1], self.ls[1], carry); + let (r3, carry) = adc(0, r3, carry); + let (r4, carry) = mac(r4, self.ls[2], self.ls[2], carry); + let (r5, carry) = adc(0, r5, carry); + let (r6, carry) = mac(r6, self.ls[3], self.ls[3], carry); + let (r7, _) = adc(0, r7, carry); + + ~Scalar::montgomery_reduce(r0, r1, r2, r3, r4, r5, r6, r7) + } + + // returns self + self mod q + fn double(self) -> Scalar { + // zkcrypto comment: TODO: This can be achieved more efficiently with a bitshift. + self.add(self) + } +} + +impl Add for Scalar { + fn add(self, other: Self) -> Self { + self.add(other) + } +} + +impl Subtract for Scalar { + fn subtract(self, other: Self) -> Self { + self.sub(other) + } +} + +impl Multiply for Scalar { + fn multiply(self, other: Self) -> Self { + self.mul(other) + } +} + +impl Eq for Scalar { + fn eq(self, other: Self) -> bool { + self.ct_eq(other).unwrap_as_bool() + } +} + +impl Scalar { + fn from(val: u64) -> Scalar { + Scalar{ ls: [val, 0, 0, 0]} * R2 + } +} + +impl Scalar { + + /// Exponentiates `self` by `by`, where `by` is a + /// little-endian order integer exponent. + /// + /// **This operation is variable time with respect + /// to the exponent.** If the exponent is fixed, + /// this operation is effectively constant time. + pub fn pow_vartime(self, by: [u64; 4]) -> Scalar {//TODO implement when possible, this gives an error when called from sqrt + let mut res = ~Self::one(); + let mut i = 4; + while i > 0 { + let e = by[i -1]; + let mut j = 65; + while j > 0 { + res = res.square(); + + if ((e >> (j-1)) & 1) == 1 { + res *= self; + // res.mul_assign(self); + } + j -= 1; + } + i -= 1; + } + res + } +} + +impl Scalar { +/* +functions +- pow_vartime +- sqrt + +will give Immediate18TooLarge + +(It originally gave the error +error: Internal compiler error: Verification failed: Function anon_11103 return type must match its RET instructions. +Please file an issue on the repository and include the code that triggered this error. + +Sept 12: new error = +error: Internal compiler error: Verification failed: Function one_1 return type must match its RET instructions. +Please file an issue on the repository and include the code that triggered this error. +) +*/ + + /// Computes the square root of this element, if it exists. + pub fn sqrt(self) -> CtOption { + // Tonelli-Shank's algorithm for q mod 16 = 1 + // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) + + // w = self^((t - 1) // 2) + // = self^6104339283789297388802252303364915521546564123189034618274734669823 + let w = self.pow_vartime([ + 0x7fff_2dff_7fff_ffff, + 0x04d0_ec02_a9de_d201, + 0x94ce_bea4_199c_ec04, + 0x0000_0000_39f6_d3a9, + ]); + + // let mut v = S; + // let mut x = self * w; + // let mut b = x * w; + + // // Initialize z as the 2^S root of unity. + // let mut z = ROOT_OF_UNITY; + + // let mut max_v = S; + + // while max_v > 0 { + // let mut k = 1; + // let mut tmp = b.square(); + // let mut j_less_than_v: Choice = ~Choice::from(1u8); + + // let mut j = 2; // j in 2..max_v + // while j <= max_v { + // let tmp_is_one = ~Choice::from_bool(tmp.eq(~Scalar::one())); + // let squared = ~Scalar::conditional_select(tmp, z, tmp_is_one).square(); + // tmp = ~Scalar::conditional_select(squared, tmp, tmp_is_one); + // let new_z = ~Scalar::conditional_select(z, squared, tmp_is_one); + // let j_less_than_v_bool = ~Choice::unwrap_as_bool(j_less_than_v) && j != v; + // j_less_than_v = ~Choice::from_bool(j_less_than_v_bool); + // k = ~u32::conditional_select(j, k, tmp_is_one); + // z = ~Scalar::conditional_select(z, new_z, j_less_than_v); + + // j += 1; + // } + + // let result = x * z; + // x = ~Scalar::conditional_select(result, x, ~Choice::from_bool(b.eq(~Scalar::one()))); + // z = z.square(); + // b *= z; + // v = k; + + // max_v -= 1; + // } + + // ~CtOption::new( + // x, + // (x * x).ct(self), // Only return Some if it's the square root. + // ) + ~CtOption::new(self, ~Choice::from(1)) + } +} diff --git a/bls12_381/src/util.sw b/bls12_381/src/util.sw new file mode 100644 index 0000000..13c9e58 --- /dev/null +++ b/bls12_381/src/util.sw @@ -0,0 +1,99 @@ +library util; + +use utils::choice::{Choice, ConditionallySelectable, CtOption, wrapping_neg}; +use std::{u128::U128}; +use core::ops::{BitwiseXor}; +use core::num::*; +use std::flags::{disable_panic_on_overflow, enable_panic_on_overflow}; + +// If input == 0u64, return 1. Otherwise return 0. +// Done in assembly, because natively comparison becomes a bool. +pub fn is_zero_u64(input: u64) -> u64 { + asm(r1: input, r2) {// set register 1 (r1) to value input, and allocate r2 + eq r2 r1 zero; // r2 = r1 == zero + r2: u64 // return r2 as a u64 + } +} + +// TODO rewrite without if branch. This one is used with variable a and b +// If x >= y: x-y, else max::U128 - (y-x) +pub fn subtract_wrap(x: U128, y: U128) -> U128 { + if y > x { + ~U128::max() - (y - x - U128 { + lower: 1, + upper: 0, + }) + } else { + x - y + } +} + +// ** tailored to input x = 0 or x = 1 ** +// subtract 1 and wrap if necessary. Returns 0 (when x=1) or u64::max (when x=0) +pub fn subtract_1_wrap(x: u64) -> u64 { + /* + Normally, Sway panics when underflow or overflow occurs. + Therefore, to implement this function we need to temporary allow overflow. + After completing the computations, the flag is set back to default + + - See example use of enabling overflow here: https://github.com/FuelLabs/sway/blob/master/sway-lib-std/src/u128.sw + - See definition $of (used in the assembly portion of the code) here: https://github.com/FuelLabs/fuel-specs/blob/master/specs/vm/main.md + */ + + disable_panic_on_overflow(); + let res = asm(underflow, r1: x, r2, r3) { // set register 1 (r1) to value x, and allocate registers underflow, r2, r3 + subi r2 r1 i1; // r2 = r1 - 1 = x - 1 + move underflow of; // move the underflow (which goes into $of automatically) to a variable (named overflow) + or r3 r2 underflow; // if 1-1 then this is (0 | 0 = 0), else if 0-1 this is (0 | u64::max = u64::max) + r3 + }; + enable_panic_on_overflow(); + res +} + +/// Compute a - (b + borrow), returning the result and the new borrow as (result, borrow) +pub fn sbb(a: u64, b: u64, borrow: u64) -> (u64, u64) { + let a_128: U128 = ~U128::from(0, a); + let b_128: U128 = ~U128::from(0, b); + let borrow_128: U128 = ~U128::from(0, borrow >> 63); + + let res: U128 = subtract_wrap(a_128, b_128 + borrow_128); + ( + res.lower, + res.upper, + ) +} + +//returns sum with carry of a and b as (result, carry) +pub fn adc(a: u64, b: u64, carry: u64) -> (u64, u64) { + let a_128: U128 = ~U128::from(0, a); + let b_128: U128 = ~U128::from(0, b); + let carry_128: U128 = ~U128::from(0, carry); + + let sum = a_128 + b_128 + carry_128; + ( + sum.lower, + sum.upper, + ) +} + +//returns the result and new carry of a + b*c + carry as (result, carry) +pub fn mac(a: u64, b: u64, c: u64, carry: u64) -> (u64, u64) { + let a_128: U128 = ~U128::from(0, a); + let b_128: U128 = ~U128::from(0, b); + let c_128: U128 = ~U128::from(0, c); + let carry_128: U128 = ~U128::from(0, carry); + + let res: U128 = a_128 + (b_128 * c_128) + carry_128; + ( + res.lower, + res.upper, + ) +} + +//returns a*b mod 2^64 +pub fn wrapping_mul(a: u64, b: u64) -> u64 { + let a_128: U128 = ~U128::from(0, a); + let b_128: U128 = ~U128::from(0, b); + (a_128 * b_128).lower +} diff --git a/bls12_381/tests/harness.rs b/bls12_381/tests/harness.rs new file mode 100644 index 0000000..120637e --- /dev/null +++ b/bls12_381/tests/harness.rs @@ -0,0 +1,31 @@ +use fuels::{prelude::*, tx::ContractId}; + +// Load abi from json +abigen!(MyContract, "out/debug/bls12-381-abi.json"); + +async fn get_contract_instance() -> (MyContract, ContractId) { + // Launch a local network and deploy the contract + let wallet = launch_provider_and_get_wallet().await; + + let id = Contract::deploy( + "./out/debug/bls12-381.bin", + &wallet, + TxParameters::default(), + StorageConfiguration::with_storage_path(Some( + "./out/debug/bls12-381-storage_slots.json".to_string(), + )), + ) + .await + .unwrap(); + + let instance = MyContract::new(id.to_string(), wallet); + + (instance, id) +} + +#[tokio::test] +async fn can_get_contract_id() { + let (_instance, _id) = get_contract_instance().await; + + // Now you have an instance of your contract you can use to test each function +} diff --git a/testing/tests_bls12_381/.gitignore b/testing/tests_bls12_381/.gitignore new file mode 100644 index 0000000..5e41a4d --- /dev/null +++ b/testing/tests_bls12_381/.gitignore @@ -0,0 +1,5 @@ +out +target + +Forc.lock +Cargo.lock \ No newline at end of file diff --git a/testing/tests_bls12_381/Cargo.toml b/testing/tests_bls12_381/Cargo.toml new file mode 100644 index 0000000..7e9e44b --- /dev/null +++ b/testing/tests_bls12_381/Cargo.toml @@ -0,0 +1,15 @@ +[project] +name = "tests_bls12_381" +version = "0.1.0" +authors = ["Hashcloak"] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +fuels = { version = "0.23", features = ["fuel-core-lib"] } +tokio = { version = "1.12", features = ["rt", "macros"] } + +[[test]] +harness = true +name = "integration_tests" +path = "tests/harness.rs" diff --git a/testing/tests_bls12_381/Forc.toml b/testing/tests_bls12_381/Forc.toml new file mode 100644 index 0000000..11562a7 --- /dev/null +++ b/testing/tests_bls12_381/Forc.toml @@ -0,0 +1,9 @@ +[project] +authors = ["Hashcloak"] +entry = "main.sw" +license = "Apache-2.0" +name = "tests_bls12_381" + +[dependencies] +bls12_381 = { path = "../../bls12_381" } +utils = { path = "../../utils" } diff --git a/testing/tests_bls12_381/src/main.sw b/testing/tests_bls12_381/src/main.sw new file mode 100644 index 0000000..74656cf --- /dev/null +++ b/testing/tests_bls12_381/src/main.sw @@ -0,0 +1,102 @@ +contract; + +use bls12_381::{fp::Fp, fp2::Fp2, scalar::Scalar}; +use utils::choice::{CtOption, Choice}; + +abi BlsTestContract { + // Works + #[storage(read, write)]fn add_fp(a: Fp, b: Fp) -> Fp; + #[storage(read, write)]fn sub_fp(a: Fp, b: Fp) -> Fp; + // Running this one will give Immediate18TooLarge + // #[storage(read, write)]fn lexicographically_largest_fp(a: Fp) -> Choice; + + // works but takes a long time + #[storage(read, write)]fn mul_fp(a: Fp, b: Fp) -> Fp; + + // works if ran by itself + // #[storage(read, write)]fn square_fp(a: Fp) -> Fp; + + // Works + #[storage(read, write)]fn add_fp2(a: Fp2, b: Fp2) -> Fp2; + #[storage(read, write)]fn sub_fp2(a: Fp2, b: Fp2) -> Fp2; + #[storage(read, write)]fn neg_fp2(a: Fp2) -> Fp2; + // Running this one will give Immediate18TooLarge + // #[storage(read, write)]fn lexicographically_largest_fp2(a: Fp2) -> Choice; + + // // not tested, still gives Immediate18TooLarge error + // #[storage(read, write)]fn square_fp2(a: Fp2) -> Fp2; + + #[storage(read, write)]fn mul_fp2(a: Fp2, b: Fp2) -> Fp2; + + #[storage(read, write)]fn add_scalar(a: Scalar, b: Scalar) -> Scalar; + +//This function gives an error + // #[storage(read, write)]fn scalar_sqrt(a: Scalar) -> CtOption; + +// These can't be compiled yet.. + // #[storage(read, write)]fn mul_fp6(a: Fp6, b: Fp6) -> Fp6; + // #[storage(read, write)]fn square_fp6(a: Fp6) -> Fp6; + +} + +impl BlsTestContract for Contract { + #[storage(read, write)]fn add_fp(a: Fp, b: Fp) -> Fp { + a + b + } + + #[storage(read, write)]fn sub_fp(a: Fp, b: Fp) -> Fp { + a - b + } + + // #[storage(read, write)]fn lexicographically_largest_fp(a: Fp) -> Choice { + // a.lexicographically_largest() + // } + + #[storage(read, write)]fn mul_fp(a: Fp, b: Fp) -> Fp { + a * b + } + + // #[storage(read, write)]fn square_fp(a: Fp) -> Fp { + // a.square() + // } + + #[storage(read, write)]fn add_fp2(a: Fp2, b: Fp2) -> Fp2 { + a + b + } + + #[storage(read, write)]fn sub_fp2(a: Fp2, b: Fp2) -> Fp2 { + a - b + } + + #[storage(read, write)]fn neg_fp2(a: Fp2) -> Fp2 { + a.neg() + } + + // #[storage(read, write)]fn lexicographically_largest_fp2(a: Fp2) -> Choice { + // a.lexicographically_largest() + // } + + #[storage(read, write)]fn add_scalar(a: Scalar, b: Scalar) -> Scalar { + a + b + } + + // #[storage(read, write)]fn square_fp2(a: Fp2) -> Fp2 { + // a.square() + // } + + #[storage(read, write)]fn mul_fp2(a: Fp2, b: Fp2) -> Fp2 { + a * b + } + + // #[storage(read, write)]fn scalar_sqrt(a: Scalar) -> CtOption { + // a.sqrt() + // } + + // #[storage(read, write)]fn mul_fp6(a: Fp6, b: Fp6) -> Fp6 { + // a * b + // } + + // #[storage(read, write)]fn square_fp6(a: Fp6) -> Fp6 { + // a.square() + // } +} diff --git a/testing/tests_bls12_381/tests/harness.rs b/testing/tests_bls12_381/tests/harness.rs new file mode 100644 index 0000000..83f305f --- /dev/null +++ b/testing/tests_bls12_381/tests/harness.rs @@ -0,0 +1,5 @@ +mod utils; +mod tests_fp; +mod tests_fp2; +// mod tests_fp6; +mod tests_scalar; \ No newline at end of file diff --git a/testing/tests_bls12_381/tests/tests_fp/mod.rs b/testing/tests_bls12_381/tests/tests_fp/mod.rs new file mode 100644 index 0000000..91b8edd --- /dev/null +++ b/testing/tests_bls12_381/tests/tests_fp/mod.rs @@ -0,0 +1,194 @@ +use crate::utils::{helpers::get_contract_instance, Fp}; +use fuels::{ + prelude::*, + tx::{ConsensusParameters, ContractId}, +}; + +mod success { + use super::*; + + #[tokio::test] //works + async fn test_add_fp() { + let small = Fp{ + ls: [1, 2, 3, 4, 5, 6].to_vec() + }; + let random = Fp{ + ls: [0x3e2528903ca1ef86, 0x270fd67a03bf9e0a, 0xdc70c19599cb699e, 0xebefda8057d5747a, 0xcf20e11f0b1c323, 0xe979cbf960fe51d].to_vec() + }; + let expected_res = Fp{ + ls: [4478030004447473543, 2814704111667093004, 15884408734010272161, 17001047363111187582, 932823543034528552, 1051481384684610851].to_vec() + }; + + let (contract_instance, _id) = get_contract_instance().await; + + let res = contract_instance.add_fp(small, random) + .tx_params(TxParameters::new(None, Some(100_000_000), None)) + .call_params(CallParameters::new(None, None, Some(100_000_000))) + .call().await.unwrap().value; + + assert!(res == expected_res); + } + + #[tokio::test] //works + async fn test_sub_fp() { + let a = Fp { + ls: [10587454305359941416, 4615625447881587853, 9368308553698906485, 9494054596162055604, 377309137954328098, 766262085408033194].to_vec() + }; + + let b = Fp { + ls: [13403040667047958534, 405585388298286396, 7295341050629342949, 1749456428444609784, 1856600841951774635, 296809876162753174].to_vec() + }; + let expected_res = Fp { + ls: [15631157712021534498, 4210040059583301456, 2072967503069563536, 7744598167717445820, 16967452369712105079, 469452209245280019].to_vec() + }; + let (_instance, _id) = get_contract_instance().await; + + let res = _instance.sub_fp(a, b) + .tx_params(TxParameters::new(None, Some(100_000_000), None)) + .call_params(CallParameters::new(None, None, Some(100_000_000))) + .call().await.unwrap().value; + assert!(res == expected_res); + } + + // WORKS, but takes very long + #[tokio::test] + async fn test_mul_fp() { + let a = Fp{ ls:[ + 0x0397_a383_2017_0cd4, + 0x734c_1b2c_9e76_1d30, + 0x5ed2_55ad_9a48_beb5, + 0x095a_3c6b_22a7_fcfc, + 0x2294_ce75_d4e2_6a27, + 0x1333_8bd8_7001_1ebb, + ].to_vec()}; + let b = Fp{ ls:[ + 0xb9c3_c7c5_b119_6af7, + 0x2580_e208_6ce3_35c1, + 0xf49a_ed3d_8a57_ef42, + 0x41f2_81e4_9846_e878, + 0xe076_2346_c384_52ce, + 0x0652_e893_26e5_7dc0, + ].to_vec()}; + let c = Fp{ ls:[ + 0xf96e_f3d7_11ab_5355, + 0xe8d4_59ea_00f1_48dd, + 0x53f7_354a_5f00_fa78, + 0x9e34_a4f3_125c_5f83, + 0x3fbe_0c47_ca74_c19e, + 0x01b0_6a8b_bd4a_dfe4, + ].to_vec()}; + let (_instance, _id) = get_contract_instance().await; + + let res = _instance.mul_fp(a, b) + .tx_params(TxParameters::new(None, Some(100_000_000), None)) + .call_params(CallParameters::new(None, None, Some(100_000_000))) + .call().await.unwrap().value; + assert!(res == c); + } + + // WORKS, but takes very long + /* + #[tokio::test] + async fn test_square_fp() { + let a: Fp = Fp { + ls: [0xd215_d276_8e83_191b,//15138237129114720539 + 0x5085_d80f_8fb2_8261,//5802281256283701857 + 0xce9a_032d_df39_3a56,//14887215013780077142 + 0x3e9c_4fff_2ca0_c4bb,//4511568884102382779 + 0x6436_b6f7_f4d9_5dfb,//7221160228616232443 + 0x1060_6628_ad4a_4d90].to_vec()//1180055427263122832 + }; + + let expected_res: Fp = Fp { + ls: [0x33d9_c42a_3cb3_e235, + 0xdad1_1a09_4c4c_d455, + 0xa2f1_44bd_729a_aeba, + 0xd415_0932_be9f_feac, + 0xe27b_c7c4_7d44_ee50, + 0x14b6_a78d_3ec7_a560].to_vec() + }; + + let (_instance, _id) = get_contract_instance().await; + + let res = _instance.square_fp(a) + .tx_params(TxParameters::new(None, Some(100_000_000), None)) + .call_params(CallParameters::new(None, None, Some(100_000_000))) + .call().await.unwrap().value; + + assert!(res == expected_res); + } + */ + /* + ERROR: Running this one will give Immediate18TooLarge + #[tokio::test] + async fn lexicographically_largest_fp() { + let zero = Fp{ ls: [0,0,0,0,0,0].to_vec()}; + let one = Fp{ ls: [ //=R + 0x7609_0000_0002_fffd, + 0xebf4_000b_c40c_0002, + 0x5f48_9857_53c7_58ba, + 0x77ce_5853_7052_5745, + 0x5c07_1a97_a256_ec6d, + 0x15f6_5ec3_fa80_e493, + ].to_vec()}; + let first = Fp{ ls: [ + 0xa1fa_ffff_fffe_5557, + 0x995b_fff9_76a3_fffe, + 0x03f4_1d24_d174_ceb4, + 0xf654_7998_c199_5dbd, + 0x778a_468f_507a_6034, + 0x0205_5993_1f7f_8103 + ].to_vec()}; + let second = Fp{ ls: [ + 0x1804_0000_0001_5554, + 0x8550_0005_3ab0_0001, + 0x633c_b57c_253c_276f, + 0x6e22_d1ec_31eb_b502, + 0xd391_6126_f2d1_4ca2, + 0x17fb_b857_1a00_6596, + ].to_vec()}; + let third = Fp{ ls: [ + 0x43f5_ffff_fffc_aaae, + 0x32b7_fff2_ed47_fffd, + 0x07e8_3a49_a2e9_9d69, + 0xeca8_f331_8332_bb7a, + 0xef14_8d1e_a0f4_c069, + 0x040a_b326_3eff_0206, + ].to_vec()}; + + let (contract_instance, _id) = get_contract_instance().await; + + let res_zero = contract_instance.lexicographically_largest_fp(zero) + .tx_params(TxParameters::new(None, Some(10_000_000_000_000), None)) + .call_params(CallParameters::new(None, None, Some(10_000_000_000_000))) + .call().await.unwrap().value; + + let res_one = contract_instance.lexicographically_largest_fp(one) + .tx_params(TxParameters::new(None, Some(10_000_000_000_000), None)) + .call_params(CallParameters::new(None, None, Some(10_000_000_000_000))) + .call().await.unwrap().value; + + let res_first = contract_instance.lexicographically_largest_fp(first) + .tx_params(TxParameters::new(None, Some(10_000_000_000_000), None)) + .call_params(CallParameters::new(None, None, Some(10_000_000_000_000))) + .call().await.unwrap().value; + + let res_second = contract_instance.lexicographically_largest_fp(second) + .tx_params(TxParameters::new(None, Some(10_000_000_000_000), None)) + .call_params(CallParameters::new(None, None, Some(10_000_000_000_000))) + .call().await.unwrap().value; + + let res_third = contract_instance.lexicographically_largest_fp(third) + .tx_params(TxParameters::new(None, Some(10_000_000_000_000), None)) + .call_params(CallParameters::new(None, None, Some(10_000_000_000_000))) + .call().await.unwrap().value; + + assert!(res_zero.c == 0); + assert!(res_one.c == 0); + assert!(res_first.c == 0); + assert!(res_second.c == 1); + assert!(res_third.c == 1); + } + */ + +} \ No newline at end of file diff --git a/testing/tests_bls12_381/tests/tests_fp2/mod.rs b/testing/tests_bls12_381/tests/tests_fp2/mod.rs new file mode 100644 index 0000000..077f651 --- /dev/null +++ b/testing/tests_bls12_381/tests/tests_fp2/mod.rs @@ -0,0 +1,391 @@ +use crate::utils::{helpers::get_contract_instance, Fp, Fp2}; +use fuels::{ + prelude::*, + tx::{ConsensusParameters, ContractId}, +}; + +mod success { + use super::*; + + #[tokio::test] + async fn test_add_fp2() { + + let a = Fp2 { + c_0: Fp{ls: [ + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + ].to_vec()}, + c_1: Fp{ls: [ + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + ].to_vec()}, + }; + let b = Fp2 { + c_0: Fp{ls: [ + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + ].to_vec()}, + c_1: Fp{ls: [ + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + ].to_vec()}, + }; + let c = Fp2 { + c_0: Fp{ls: [ + 0x6b82_a9a7_08c1_32d2, + 0x476b_1da3_39ba_5ba4, + 0x848c_0e62_4b91_cd87, + 0x11f9_5955_295a_99ec, + 0xf337_6fce_2255_9f06, + 0x0c3f_e3fa_ce8c_8f43, + ].to_vec()}, + c_1: Fp{ls: [ + 0x6f99_2c12_73ab_5bc5, + 0x3355_1366_17a1_df33, + 0x8b0e_f74c_0aed_aff9, + 0x062f_9246_8ad2_ca12, + 0xe146_9770_738f_d584, + 0x12c3_c3dd_84bc_a26d, + ].to_vec()}, + }; + + let (contract_instance, _id) = get_contract_instance().await; + + let res = contract_instance.add_fp2(a, b) + .tx_params(TxParameters::new(None, Some(100_000_000), None)) + .call_params(CallParameters::new(None, None, Some(100_000_000))) + .call().await.unwrap().value; + + assert!(res.c_0 == c.c_0); + assert!(res.c_1 == c.c_1); + } + + #[tokio::test] + async fn test_sub_fp2() { + + let a = Fp2 { + c_0: Fp{ ls: [ + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + ].to_vec()}, + c_1: Fp{ ls: [ + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + ].to_vec()}, + }; + let b = Fp2 { + c_0: Fp{ ls: [ + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + ].to_vec()}, + c_1: Fp{ ls: [ + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + ].to_vec()}, + }; + let c = Fp2 { + c_0: Fp{ ls: [ + 0xe1c0_86bb_bf1b_5981, + 0x4faf_c3a9_aa70_5d7e, + 0x2734_b5c1_0bb7_e726, + 0xb2bd_7776_af03_7a3e, + 0x1b89_5fb3_98a8_4164, + 0x1730_4aef_6f11_3cec, + ].to_vec()}, + c_1: Fp{ ls: [ + 0x74c3_1c79_9519_1204, + 0x3271_aa54_79fd_ad2b, + 0xc9b4_7157_4915_a30f, + 0x65e4_0313_ec44_b8be, + 0x7487_b238_5b70_67cb, + 0x0952_3b26_d0ad_19a4, + ].to_vec()}, + }; + + let (contract_instance, _id) = get_contract_instance().await; + + let res = contract_instance.sub_fp2(a, b) + .tx_params(TxParameters::new(None, Some(100_000_000), None)) + .call_params(CallParameters::new(None, None, Some(100_000_000))) + .call().await.unwrap().value; + + assert!(res.c_0 == c.c_0); + assert!(res.c_1 == c.c_1); + } + + #[tokio::test] + async fn test_neg_fp2() { + + let a = Fp2 { + c_0: Fp{ls: [ + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + ].to_vec()}, + c_1: Fp{ls: [ + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + ].to_vec()}, + }; + let b = Fp2 { + c_0: Fp{ls: [ + 0xf05c_e7ce_9c11_39d7, + 0x6274_8f57_97e8_a36d, + 0xc4e8_d9df_c664_96df, + 0xb457_88e1_8118_9209, + 0x6949_13d0_8772_930d, + 0x1549_836a_3770_f3cf, + ].to_vec()}, + c_1: Fp{ls: [ + 0x24d0_5bb9_fb9d_491c, + 0xfb1e_a120_c12e_39d0, + 0x7067_879f_c807_c7b1, + 0x60a9_269a_31bb_dab6, + 0x45c2_56bc_fd71_649b, + 0x18f6_9b5d_2b8a_fbde, + ].to_vec()}, + }; + + + let (contract_instance, _id) = get_contract_instance().await; + + let res = contract_instance.neg_fp2(a) + .tx_params(TxParameters::new(None, Some(100_000_000), None)) + .call_params(CallParameters::new(None, None, Some(100_000_000))) + .call().await.unwrap().value; + + assert!(res.c_0 == b.c_0); + assert!(res.c_1 == b.c_1); + } + + #[tokio::test] + async fn test_multiplication() { + let a = Fp2 { + c_0: Fp{ ls:[ + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + ].to_vec()}, + c_1: Fp{ ls:[ + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + ].to_vec()}, + }; + let b = Fp2 { + c_0: Fp{ ls:[ + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + ].to_vec()}, + c_1: Fp{ ls:[ + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + ].to_vec()}, + }; + let c = Fp2 { + c_0: Fp{ ls:[ + 0xf597_483e_27b4_e0f7, + 0x610f_badf_811d_ae5f, + 0x8432_af91_7714_327a, + 0x6a9a_9603_cf88_f09e, + 0xf05a_7bf8_bad0_eb01, + 0x0954_9131_c003_ffae, + ].to_vec()}, + c_1: Fp{ ls:[ + 0x963b_02d0_f93d_37cd, + 0xc95c_e1cd_b30a_73d4, + 0x3087_25fa_3126_f9b8, + 0x56da_3c16_7fab_0d50, + 0x6b50_86b5_f4b6_d6af, + 0x09c3_9f06_2f18_e9f2, + ].to_vec()}, + }; + + let (contract_instance, _id) = get_contract_instance().await; + + let res = contract_instance.mul_fp2(a, b) + .tx_params(TxParameters::new(None, Some(100_000_000), None)) + .call_params(CallParameters::new(None, None, Some(100_000_000))) + .call().await.unwrap().value; + + assert!(res == c); + } + + /* + //ERROR still gives Immediate18TooLarge :( (12 sept) + #[tokio::test] + async fn test_squaring() { + let a = Fp2 { + c_0: Fp{ ls:[ + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + ].to_vec()}, + c_1: Fp{ ls:[ + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + ].to_vec()}, + }; + let b = Fp2 { + c_0: Fp{ ls:[ + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + ].to_vec()}, + c_1: Fp{ ls:[ + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + ].to_vec()}, + }; + let (contract_instance, _id) = get_contract_instance().await; + + let res = contract_instance.square_fp2(a) + .tx_params(TxParameters::new(None, Some(100_000_000), None)) + .call_params(CallParameters::new(None, None, Some(100_000_000))) + .call().await.unwrap().value; + + println!("{}", res.c_0.ls[0]); + println!("{}", res.c_0.ls[1]); + println!("{}", res.c_0.ls[2]); + println!("{}", res.c_0.ls[2]); + println!("{}", res.c_0.ls[4]); + println!("{}", res.c_0.ls[5]); + + println!("{}", res.c_1.ls[0]); + println!("{}", res.c_1.ls[1]); + println!("{}", res.c_1.ls[2]); + println!("{}", res.c_1.ls[2]); + println!("{}", res.c_1.ls[4]); + println!("{}", res.c_1.ls[5]); + + assert!(res.c_0 == b.c_0); + assert!(res.c_1 == b.c_1); + } + */ + /* + //ERROR Immediate18TooLarge + #[tokio::test]//stripped down version from zkcrypto impl + async fn lexicographically_largest_fp2() { + let zero = Fp2 { + c_0 : Fp{ ls: [0,0,0,0,0,0].to_vec()}, + c_1 : Fp{ ls: [0,0,0,0,0,0].to_vec()}, + }; + let one = Fp2 { + c_0: Fp{ ls: [ //=R + 0x7609_0000_0002_fffd, + 0xebf4_000b_c40c_0002, + 0x5f48_9857_53c7_58ba, + 0x77ce_5853_7052_5745, + 0x5c07_1a97_a256_ec6d, + 0x15f6_5ec3_fa80_e493, + ].to_vec()}, + c_1 : Fp{ ls: [0,0,0,0,0,0].to_vec()} + }; + + let first = Fp2 { + c_0: Fp{ ls: [ + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + ].to_vec()}, + c_1 : Fp{ ls: [ + 0xd328_e37c_c2f5_8d41, + 0x948d_f085_8a60_5869, + 0x6032_f9d5_6f93_a573, + 0x2be4_83ef_3fff_dc87, + 0x30ef_61f8_8f48_3c2a, + 0x1333_f55a_3572_5be0].to_vec()} + }; + let (contract_instance, _id) = get_contract_instance().await; + + let res_zero = contract_instance.lexicographically_largest_fp2(zero) + .tx_params(TxParameters::new(None, Some(10_000_000_000_000), None)) + .call_params(CallParameters::new(None, None, Some(10_000_000_000_000))) + .call().await.unwrap().value; + + let res_one = contract_instance.lexicographically_largest_fp2(one) + .tx_params(TxParameters::new(None, Some(10_000_000_000_000), None)) + .call_params(CallParameters::new(None, None, Some(10_000_000_000_000))) + .call().await.unwrap().value; + + let res_first = contract_instance.lexicographically_largest_fp2(first) + .tx_params(TxParameters::new(None, Some(10_000_000_000_000), None)) + .call_params(CallParameters::new(None, None, Some(10_000_000_000_000))) + .call().await.unwrap().value; + + assert!(res_zero.c == 0); + assert!(res_one.c == 0); + assert!(res_first.c == 1); + } + */ +} \ No newline at end of file diff --git a/testing/tests_bls12_381/tests/tests_fp6/mod.rs b/testing/tests_bls12_381/tests/tests_fp6/mod.rs new file mode 100644 index 0000000..988a2a6 --- /dev/null +++ b/testing/tests_bls12_381/tests/tests_fp6/mod.rs @@ -0,0 +1,223 @@ +use crate::utils::{helpers::get_contract_instance, Fp, Fp2, Fp6}; +use fuels::{ + prelude::*, + tx::{ConsensusParameters, ContractId}, +}; + +mod success { + use super::*; + + fn get_a() -> Fp6 { + let a = Fp6 { + c_0: Fp2 { + c_0: Fp{ls: [ + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d, + ].to_vec()}, + c_1: Fp{ls: [ + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1, + ].to_vec()}, + }, + c_1: Fp2 { + c_0: Fp{ls: [ + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8, + ].to_vec()}, + c_1: Fp{ls: [ + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf, + ].to_vec()}, + }, + c_2: Fp2 { + c_0: Fp{ls: [ + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6, + ].to_vec()}, + c_1: Fp{ls: [ + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040, + ].to_vec()}, + }, + }; + + a + } + + fn get_b() -> Fp6 { + let b = Fp6 { + c_0: Fp2 { + c_0: Fp{ ls: [ + 0xf120_cb98_b16f_d84b, + 0x5fb5_10cf_f3de_1d61, + 0x0f21_a5d0_69d8_c251, + 0xaa1f_d62f_34f2_839a, + 0x5a13_3515_7f89_913f, + 0x14a3_fe32_9643_c247, + ].to_vec()}, + c_1: Fp{ ls: [ + 0x3516_cb98_b16c_82f9, + 0x926d_10c2_e126_1d5f, + 0x1709_e01a_0cc2_5fba, + 0x96c8_c960_b825_3f14, + 0x4927_c234_207e_51a9, + 0x18ae_b158_d542_c44e, + ].to_vec()}, + }, + c_1: Fp2 { + c_0: Fp{ ls: [ + 0xbf0d_cb98_b169_82fc, + 0xa679_10b7_1d1a_1d5c, + 0xb7c1_47c2_b8fb_06ff, + 0x1efa_710d_47d2_e7ce, + 0xed20_a79c_7e27_653c, + 0x02b8_5294_dac1_dfba, + ].to_vec()}, + c_1: Fp{ ls: [ + 0x9d52_cb98_b180_82e5, + 0x621d_1111_5176_1d6f, + 0xe798_8260_3b48_af43, + 0x0ad3_1637_a4f4_da37, + 0xaeac_737c_5ac1_cf2e, + 0x006e_7e73_5b48_b824, + ].to_vec()}, + }, + c_2: Fp2 { + c_0: Fp{ ls: [ + 0xe148_cb98_b17d_2d93, + 0x94d5_1104_3ebe_1d6c, + 0xef80_bca9_de32_4cac, + 0xf77c_0969_2827_95b1, + 0x9dc1_009a_fbb6_8f97, + 0x0479_3199_9a47_ba2b, + ].to_vec()}, + c_1: Fp{ ls: [ + 0x253e_cb98_b179_d841, + 0xc78d_10f7_2c06_1d6a, + 0xf768_f6f3_811b_ea15, + 0xe424_fc9a_ab5a_512b, + 0x8cd5_8db9_9cab_5001, + 0x0883_e4bf_d946_bc32, + ].to_vec()}, + }, + }; + + b + } + + /* + //ERROR Immediate18TooLarge + #[tokio::test] + async fn test_fp6() { + let (contract_instance, _id) = get_contract_instance().await; + + let a = Fp6 { + c_0: Fp2 { + c_0: Fp{ls: [ + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d, + ].to_vec()}, + c_1: Fp{ls: [ + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1, + ].to_vec()}, + }, + c_1: Fp2 { + c_0: Fp{ls: [ + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8, + ].to_vec()}, + c_1: Fp{ls: [ + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf, + ].to_vec()}, + }, + c_2: Fp2 { + c_0: Fp{ls: [ + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6, + ].to_vec()}, + c_1: Fp{ls: [ + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040, + ].to_vec()}, + }, + }; + + let res = contract_instance.add_fp2(a, b) + .tx_params(TxParameters::new(None, Some(100_000_000), None, None)) + .call_params(CallParameters::new(None, None, Some(100_000_000))) + .call().await.unwrap().value; + + + assert!(true); + } */ + + /* + //ERROR Immediate18TooLarge + #[tokio::test] + async fn test_square_fp6() { + let (contract_instance, _id) = get_contract_instance().await; + + let res_square = contract_instance.square_fp6(get_a()) + .tx_params(TxParameters::new(None, Some(100_000_000), None)) + .call_params(CallParameters::new(None, None, Some(100_000_000))) + .call().await.unwrap().value; + + let res_expected = contract_instance.mul_fp6(get_a(), get_a()) + .tx_params(TxParameters::new(None, Some(100_000_000), None)) + .call_params(CallParameters::new(None, None, Some(100_000_000))) + .call().await.unwrap().value; + + assert!(res_square == res_expected); + } + */ +} \ No newline at end of file diff --git a/testing/tests_bls12_381/tests/tests_scalar/mod.rs b/testing/tests_bls12_381/tests/tests_scalar/mod.rs new file mode 100644 index 0000000..8e84432 --- /dev/null +++ b/testing/tests_bls12_381/tests/tests_scalar/mod.rs @@ -0,0 +1,109 @@ +use crate::utils::{helpers::get_contract_instance, Scalar}; +use fuels::{ + prelude::*, + tx::{ConsensusParameters, ContractId}, +}; + +mod success { + use super::*; + + #[tokio::test] + async fn test_addition() { + let a: Scalar = Scalar{ ls: [ + 0xffff_ffff_0000_0000, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48, + ].to_vec()}; + + let a_2 = Scalar{ ls: [ + 0xffff_ffff_0000_0000, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48, + ].to_vec()}; + + let expected_res = Scalar{ ls:[ + 0xffff_fffe_ffff_ffff, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48, + ].to_vec()}; + + let (contract_instance, _id) = get_contract_instance().await; + + let res = contract_instance.add_scalar(a, a_2) + .tx_params(TxParameters::new(None, Some(100_000_000), None)) + .call_params(CallParameters::new(None, None, Some(100_000_000))) + .call().await.unwrap().value; + + assert!(res == expected_res); + + let a_3 = Scalar{ ls: [ + 0xffff_ffff_0000_0000, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48, + ].to_vec()}; + + let one = Scalar{ ls: [1,0,0,0].to_vec() }; + let res_2 = contract_instance.add_scalar(a_3, one) + .tx_params(TxParameters::new(None, Some(100_000_000), None)) + .call_params(CallParameters::new(None, None, Some(100_000_000))) + .call().await.unwrap().value; + + assert!(res_2 == Scalar{ ls: [0,0,0,0].to_vec() }); + } + + /*BLOCKED + error: Internal compiler error: Verification failed: Function anon_11103 return type must match its RET instructions. + Please file an issue on the repository and include the code that triggered this error. + */ + // #[tokio::test] + // async fn test_sqrt() { + // let zero = Scalar{ ls: [0,0,0,0].to_vec() }; + // let (contract_instance, _id) = get_contract_instance().await; + // let square_root = contract_instance.scalar_sqrt(zero) + // .tx_params(TxParameters::new(None, Some(100_000_000), None)) + // .call_params(CallParameters::new(None, None, Some(100_000_000))) + // .call().await.unwrap().value; + // assert_eq!(square_root, Scalar{ ls: [0,0,0,0].to_vec() }); + // } + + + /*BLOCKED + error: Internal compiler error: Verification failed: Function anon_11103 return type must match its RET instructions. + Please file an issue on the repository and include the code that triggered this error. + */ + // #[tokio::test] + // async fn test_sqrt() { + // let mut square = Scalar{ ls:[ + // 0x46cd_85a5_f273_077e, + // 0x1d30_c47d_d68f_c735, + // 0x77f6_56f6_0bec_a0eb, + // 0x494a_a01b_df32_468d, + // ].to_vec()}; + + // let mut none_count = 0; + // let (contract_instance, _id) = get_contract_instance().await; + + + // let j = 0; + // while j < 101 { + // let square_root = contract_instance.scalar_sqrt(square) + // .tx_params(TxParameters::new(None, Some(100_000_000), None)) + // .call_params(CallParameters::new(None, None, Some(100_000_000))) + // .call().await.unwrap().value; + // // let square_root = square.sqrt(); + // if square_root.is_none() { + // none_count += 1; + // } else { + // assert_eq!(square_root.unwrap() * square_root.unwrap(), square); + // } + // square -= Scalar::one(); + // j += 1; + // } + + // assert_eq!(49, none_count); + // } +} \ No newline at end of file diff --git a/utils/.gitignore b/utils/.gitignore new file mode 100644 index 0000000..77d3844 --- /dev/null +++ b/utils/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/utils/Cargo.toml b/utils/Cargo.toml new file mode 100644 index 0000000..108d788 --- /dev/null +++ b/utils/Cargo.toml @@ -0,0 +1,15 @@ +[project] +name = "utils" +version = "0.1.0" +authors = ["Hashcloak"] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +fuels = { version = "0.23", features = ["fuel-core-lib"] } +tokio = { version = "1.12", features = ["rt", "macros"] } + +[[test]] +harness = true +name = "integration_tests" +path = "tests/harness.rs" diff --git a/utils/Forc.toml b/utils/Forc.toml new file mode 100644 index 0000000..6cdde65 --- /dev/null +++ b/utils/Forc.toml @@ -0,0 +1,7 @@ +[project] +authors = ["Hashcloak"] +entry = "lib.sw" +license = "Apache-2.0" +name = "utils" + +[dependencies] diff --git a/utils/src/choice.sw b/utils/src/choice.sw new file mode 100644 index 0000000..b74ac5d --- /dev/null +++ b/utils/src/choice.sw @@ -0,0 +1,306 @@ +library choice; + +/* +Copyright (c) 2016-2017 Isis Agora Lovecruft, Henry de Valence. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +use core::num::*; +use std::{option::Option, u128::U128, assert::assert}; +use core::ops::{Eq, BitwiseAnd, BitwiseOr, BitwiseXor}; + +/////////////// IMPORTANT /////////////// + +// All of this is coming from the dalek cryptograhpy project +// see https://github.com/dalek-cryptography/subtle/blob/main/src/lib.rs + +/////////////// IMPORTANT /////////////// + +/// The `Choice` struct represents a choice for use in conditional assignment. +/// +/// It is a wrapper around a `u8`, which should have the value either `1` (true) +/// or `0` (false). +pub struct Choice { c: u8 } + +// Trait for conversion from and to u8 +pub trait From { + fn from(input: u8) -> Self; + fn into(self) -> u8; +} + +// Conversion Choice <-> u8 +impl From for Choice { + fn from(input: u8) -> Self { + Choice { c: input } + } + + fn into(self) -> u8 { + self.c + } +} + +// returns false if a == 1, true if a == 0 +pub fn opposite_choice_value(a: u8) -> bool { + // using assembly to avoid if branch + asm(r1: a, r2) { // setting register 1 (r1) to value a and allocating r2 + eq r2 r1 zero; // r2 = (r1 == zero) + r2: bool // return r2 as a bool + } +} + +// Choice (instead of standard 'bool') is intended to be constant time +impl Choice { + // Get the u8 out of the Choice + pub fn unwrap_u8(self) -> u8 { + self.c + } + + // Unwrap the Choice as a boolean value + pub fn unwrap_as_bool(self) -> bool { + self.c == 1u8 + } + + // Create a Choice instance from a bool. + // true -> Choice { c: 1u8 } + // false -> Choice { c: 0u8 } + pub fn from_bool(b: bool) -> Choice { + // using assembly to avoid if branch + let b_as_u8 = asm(r1: b) { // set register 1 (r1) to value b + r1: u8 //simply read the bool as a u8 + }; + Choice{ c: b_as_u8 } + } +} + +impl Choice { + // return a Choice with the opposite internal value (1u8 or 0u8) + pub fn not(self) -> Choice { + ~Choice::from_bool(opposite_choice_value(self.c)) + } +} + +// Sway natively doesn't (or didn't) implement xor for all primitive types, so this has to be added. +impl BitwiseXor for u8 { + // return (self ^ other) + fn binary_xor(self, other: Self) -> Self { + asm(r1: self, r2: other, r3) { // set register 1 (r1) to value self, r2 to value other and allocate r3 + xor r3 r1 r2; // r3 = r1 ^ r2 + r3: u8 // result is cast to u8 + } + } +} + +// Sway natively doesn't (or didn't) implement and for all primitive types, so this has to be added. +impl BitwiseXor for u32 { + // return (self ^ other) + fn binary_xor(self, other: Self) -> Self { + asm(r1: self, r2: other, r3) { // set register 1 (r1) to value self, r2 to value other and allocate r3 + xor r3 r1 r2; // r3 = r1 ^ r2 + r3: u32 // result is cast to u32 + } + } +} + +// This trait intends to do conditional selection in constant time +pub trait ConditionallySelectable { + // Select a if choice == 1 or select b if choice == 0, in constant time. + fn conditional_select(a: Self, b: Self, choice: Choice) -> Self; +} + +impl ConditionallySelectable for u8 { + // Select a if choice == 1 or select b if choice == 0, in constant time. + fn conditional_select(a: u8, b: u8, choice: Choice) -> u32 { + // If choice == 0, mask = 00...00 + // Else if choice == 1, mask = 11..11 + let mask = wrapping_neg(choice.unwrap_u8()); + // If choosing a: b ^ (a^b) = a + // Else if choosing b: b + b ^ (mask & (a ^b)) + } +} + +impl ConditionallySelectable for u32 { + // Select a if choice == 1 or select b if choice == 0, in constant time. + fn conditional_select(a: u32, b: u32, choice: Choice) -> u32 { + let choice_32: u32 = choice.unwrap_u8(); + // If choice == 0, mask = 00...00 + // Else if choice == 1, mask = 11..11 + let mask = wrapping_neg(choice_32); + // If choosing a: b ^ (a^b) = a + // Else if choosing b: b + b ^ (mask & (a ^ b)) + } +} + +impl ConditionallySelectable for u64 { + // Select a if choice == 1 or select b if choice == 0, in constant time. + fn conditional_select(a: u64, b: u64, choice: Choice) -> u64 { + let choice_64: u64 = choice.unwrap_u8(); + // If choice == 0, mask = 00...00 + // Else if choice == 1, mask = 11..11 + let mask = wrapping_neg(choice_64); + // If choosing a: b ^ (a^b) = a + // Else if choosing b: b + b ^ (mask & (a ^ b)) + } +} + +impl ConditionallySelectable for Choice { + // Select a if choice == 1 or select b if choice == 0, in constant time. + fn conditional_select(a: Self, b: Self, choice: Choice) -> Self { + ~Choice::from(~u8::conditional_select(a.c, b.c, choice)) + } +} + +// Sway natively doesn't (or didn't) implement and for all primitive types, so this has to be added. +impl BitwiseAnd for u8 { + // return (self & other) + fn binary_and(self, other: Self) -> Self { + asm(r1: self, r2: other, r3) {// set register 1 (r1) to value self, r2 to value other and allocate r3 + and r3 r1 r2; // r3 = r1 & r2 + r3: u8 // result is cast to u8 + } + } +} + +impl BitwiseAnd for Choice { + // Returns the choice for the binary 'and' of the inner values of self and other + // Note that we still can't use the `&` operator for this binary_and, but have to use the function name + fn binary_and(self, other: Self) -> Self { + ~Choice::from(self.c & other.c) + } +} + +// Sway natively doesn't (or didn't) implement and for all primitive types, so this has to be added. +impl BitwiseOr for u8 { + // return (self | other) + fn binary_or(self, other: Self) -> Self { + asm(r1: self, r2: other, r3) {// set register 1 (r1) to value self, r2 to value other and allocate r3 + or r3 r1 r2; // r3 = r1 | r2 + r3: u8 // result is cast to u8 + } + } +} + +impl BitwiseOr for Choice { + // Returns the choice for the binary 'or' of the inner values of self and other + // Note that we still can't use the `|` operator for this binary_or, but have to use the function name + fn binary_or(self, other: Self) -> Self { + ~Choice::from(self.c | other.c) + } +} + +// Optional value intended to be in constant time +pub struct CtOption { + value: T, + is_some: Choice, +} + +impl CtOption { + // Create a new optional with a value and whether it contains a value. + // If is_some = false, the value is still stored, but never returned + pub fn new(value: T, is_some: Choice) -> CtOption { + CtOption { + value: value, + is_some: is_some, + } + } + + // Create a new optional, where the is_some value is wrapped in a Choice automatically + pub fn new_from_bool(value: T, is_some: bool) -> CtOption { + let is_some_as_u8 = asm(r1: is_some) { // set register 1 (r1) to value is_some + r1: u8 // simply cast the input to u8 + }; + CtOption {value: value, is_some: Choice{ c: is_some_as_u8 }} + } + + // return whether this optional is none (this doesn't mean it doesn't have a value necessarily, but we don't return it either way) + pub fn is_none(self) -> bool { + // if the function body would reference the is_some function beneath, it would have to go in a separate impl due to restrictions in Sway + !self.is_some.unwrap_as_bool() + } + + // return whether this optional is some + pub fn is_some(self) -> bool { + self.is_some.unwrap_as_bool() + } + + // return the wrapped value, if the optional is_some. Otherwise, revert + pub fn unwrap(self) -> T { + assert(self.is_some.unwrap_as_bool()); + self.value + } + + /* + A function from the original lib we can't implement: + pub fn unwrap_or(self, def: T) -> T + where + T: ConditionallySelectable, + { + T::conditional_select(&def, &self.value, self.is_some) + } + + There is no type restriction possible on generics in Sway + See https://discord.com/channels/732892373507375164/734213700835082330/1007097764242522273 + */ +} + +// An Equal like trait that returns a Choice (instead of a bool) +pub trait ConstantTimeEq { + // returns (self == other), as a choice + fn ct_eq(self, other: Self) -> Choice; +} + +// returns a+b mod 2^64. The result loses the carry. +fn add_wrap_64(a: u64, b :u64) -> u64 { + let a_128: U128 = ~U128::from(0, a); + let b_128: U128 = ~U128::from(0, b); + (a_128 + b_128).lower +} + +// returns -a mod 2^64 +pub fn wrapping_neg(a: u64) -> u64 { + add_wrap_64(~u64::max() - a, 1) +} + +impl ConstantTimeEq for u64 { + fn ct_eq(self, other: u64) -> Choice { + // comments from reference impl + // x == 0 if and only if self == other + let x: u64 = self ^ other; + + // If x == 0, then x and -x are both equal to zero; + // otherwise, one or both will have its high bit set. + let y: u64 = (x | wrapping_neg(x)) >> 63; + + // Result is the opposite of the high bit (now shifted to low). + let res: u8 = y ^ (1u64); + ~Choice::from(res) + } +} diff --git a/utils/src/lib.sw b/utils/src/lib.sw new file mode 100644 index 0000000..602fff6 --- /dev/null +++ b/utils/src/lib.sw @@ -0,0 +1,3 @@ +library utils; + +dep choice; \ No newline at end of file