From 8fae56b57c997bec5e9c6215bfc4a3bea15a96d8 Mon Sep 17 00:00:00 2001 From: Vaibhav Pathak Date: Fri, 21 Mar 2025 17:09:24 +0530 Subject: [PATCH] init: bigint, bigfield --- src/algebra/bigfield/bigint.rs | 843 +++++++++++++++++++++++++++++++++ src/algebra/bigfield/mod.rs | 711 +++++++++++++++++++++++++++ src/algebra/mod.rs | 1 + src/lib.rs | 2 + 4 files changed, 1557 insertions(+) create mode 100644 src/algebra/bigfield/bigint.rs create mode 100644 src/algebra/bigfield/mod.rs diff --git a/src/algebra/bigfield/bigint.rs b/src/algebra/bigfield/bigint.rs new file mode 100644 index 00000000..672bbe02 --- /dev/null +++ b/src/algebra/bigfield/bigint.rs @@ -0,0 +1,843 @@ +pub type Word = u64; +pub type DWord = u128; +pub const BYTES: usize = 8; // Number of bytes in each 'Word' + +#[derive(Debug, Clone, Copy)] +pub struct BigInt([Word; N]); + +impl BigInt { + pub const BITS: u32 = N as u32 * Word::BITS; + pub const ONE: BigInt = BigInt::::from_u64(1); + pub const ZERO: BigInt = BigInt([0 as Word; N]); + + pub const fn from_u32(value: u32) -> Self { + let mut limbs = [0 as Word; N]; + limbs[0] = value as Word; + Self(limbs) + } + + pub const fn from_u64(value: u64) -> Self { + let mut limbs = [0 as Word; N]; + limbs[0] = value as Word; + Self(limbs) + } + + pub const fn pow_of_2(pos: u32) -> Self { + assert!((pos as usize) < N * (Word::BITS as usize)); + let mut limbs = [0 as Word; N]; + let limb = pos / Word::BITS; + + limbs[limb as usize] = 1 << (pos % Word::BITS); + + Self(limbs) + } + + pub const fn from_le_bytes(bytes: [u8; N * 8]) -> Self { + let mut limbs = [0 as Word; N]; + let mut i = 0; + + while i < N { + let mut j = i * 8; + let mut value = 0 as Word; + let mut mag = 1; + while j < 8 * (i + 1) { + value += mag * (bytes[j] as u64); + mag = mag << 8; + j += 1; + } + limbs[i] = value; + i += 1; + } + + Self(limbs) + } + + pub const fn from_be_bytes(bytes: [u8; N * 8]) -> Self { + let mut limbs = [0 as Word; N]; + let mut i = 0; + + while i < N { + let mut j = i * 8; + let mut value = 0 as Word; + let mut mag = 1; + while j < 8 * (i + 1) { + value += mag * (bytes[(N * 8 - 1) - j] as u64); + mag = mag << 8; + j += 1; + } + limbs[i] = value; + i += 1; + } + + Self(limbs) + } + + pub const fn to_le_bytes(&self) -> [u8; N * BYTES] { + let mut bytes = [0u8; N * BYTES]; + let mut i = 0; + while i < N { + let mut j = 0; + while j < BYTES { + bytes[8 * i + j] = ((self.0[i] >> 8 * j) & ((1 << 8) - 1)) as u8; + j += 1; + } + i += 1; + } + + bytes + } + + pub const fn to_be_bytes(&self) -> [u8; N * BYTES] { + let mut bytes = [0u8; N * BYTES]; + let mut i = 0; + while i < N { + let mut j = 0; + while j < BYTES { + bytes[8 * i + j] = ((self.0[N - 1 - i] >> 8 * (BYTES - j - 1)) & ((1 << 8) - 1)) as u8; + j += 1; + } + i += 1; + } + + bytes + } + + pub const fn to_words(&self) -> [Word; N] { self.0 } + + pub const fn eq(&self, rhs: &Self) -> bool { + let mut i = 0; + while i < N { + if self.0[i] != rhs.0[i] { + return false; + } + i += 1; + } + true + } + + pub const fn neq(&self, rhs: &Self) -> bool { !self.eq(rhs) } + + pub const fn gt(&self, rhs: &Self) -> bool { + let mut i = N; + while i > 0 && self.0[i - 1] == rhs.0[i - 1] { + i -= 1; + } + + if i > 0 { + return self.0[i - 1] > rhs.0[i - 1]; + } + false + } + + pub const fn lt(&self, rhs: &Self) -> bool { + let mut i = N; + while i > 0 && self.0[i - 1] == rhs.0[i - 1] { + i -= 1; + } + + if i > 0 { + return self.0[i - 1] < rhs.0[i - 1]; + } + false + } + + pub const fn carrying_add(&self, rhs: &Self, mut carry: bool) -> (Self, bool) { + let mut i = 0; + let mut res = [0 as Word; N]; + while i < N { + let (sum, _carry) = self.0[i].carrying_add(rhs.0[i], carry); + res[i] = sum; + carry = _carry; + i += 1; + } + + (Self(res), carry) + } + + pub const fn borrowing_sub(&self, rhs: &Self, mut borrow: bool) -> (Self, bool) { + let mut res = [0 as Word; N]; + let mut i = 0; + while i < N { + let (sum, _borrow) = self.0[i].borrowing_sub(rhs.0[i], borrow); + res[i] = sum; + borrow = _borrow; + i += 1; + } + + (Self(res), borrow) + } + + pub const fn widening_mul(&self, rhs: &Self) -> (Self, Self) + where [(); N * 2]: { + let mut w = [0 as Word; N * 2]; + let mut i = 0; + while i < N { + let mut carry = 0 as Word; + let mut j = 0; + while j < N { + let (mul, carry0) = self.0[j].carrying_mul(rhs.0[i], carry); + let (sum, carry1) = w[i + j].carrying_add(mul, false); + w[i + j] = sum; + carry = carry0 + carry1 as u64; + j += 1; + } + w[i + N] = carry; + i += 1; + } + + i = 0; + let mut res = [0 as Word; N]; + let mut carry = [0 as Word; N]; + while i < N { + res[i] = w[i]; + i += 1; + } + while i < 2 * N { + carry[i - N] = w[i]; + i += 1; + } + + (Self(res), Self(carry)) + } + + pub const fn bitwise_or(&self, rhs: &Self) -> Self { + let mut limbs = [0 as Word; N]; + + let mut i = 0; + while i < N { + limbs[i] = self.0[i] | rhs.0[i]; + i += 1; + } + + Self(limbs) + } + + pub const fn bitwise_and(&self, rhs: &Self) -> Self { + let mut limbs = [0 as Word; N]; + + let mut i = 0; + while i < N { + limbs[i] = self.0[i] & rhs.0[i]; + i += 1; + } + + Self(limbs) + } + + pub const fn shr(&self, shift: u32) -> (Self, Self) { + assert!((shift as usize) < N * Word::BITS as usize); + + let shiftq = (shift / Word::BITS) as usize; + let shiftr = (shift % Word::BITS) as usize; + let mut limbs = [0 as Word; N]; + let mut i = N; + let mut borrow = 0 as Word; + + while i > shiftq { + if shiftr != 0 { + limbs[i - 1 - shiftq] = (self.0[i - 1] >> shiftr) | borrow; + borrow = (self.0[i - 1] & ((1 << shiftr) - 1)) << (Word::BITS - shiftr as u32); + } else { + limbs[i - 1 - shiftq] = self.0[i - 1]; + } + i -= 1; + } + + let mut borrow_limbs = [0 as Word; N]; + + while i > 0 { + if shiftr != 0 { + borrow_limbs[N - 1 + i - shiftq] = (self.0[i - 1] >> shiftr) | borrow; + borrow = (self.0[i - 1] & ((1 << shiftr) - 1)) << (Word::BITS - shiftr as u32); + } else { + borrow_limbs[N - 1 + i - shiftq] = self.0[i - 1]; + } + + i -= 1; + } + borrow_limbs[N - 1 - shiftq] = borrow; + + (Self(limbs), Self(borrow_limbs)) + } + + pub const fn shl(&self, shift: u32) -> (Self, Self) { + assert!((shift as usize) < N * Word::BITS as usize); + + let shiftq = (shift / Word::BITS) as usize; + let shiftr = shift % Word::BITS; + let mut limbs = [0 as Word; N]; + let mut i = shiftq; + let mut carry = 0 as Word; + + while i < N { + if shiftr != 0 { + limbs[i] = (self.0[i - shiftq] << shiftr) | carry; + carry = (self.0[i - shiftq] & !((1 << (Word::BITS - shiftr)) - 1)) >> Word::BITS - shiftr; + } else { + limbs[i] = self.0[i - shiftq]; + } + i += 1; + } + + let mut carry_limbs = [0 as Word; N]; + let mut j = N - shiftq; + while j < N { + if shiftr != 0 { + carry_limbs[j + shiftq - N] = (self.0[j] << shiftr) | carry; + carry = (self.0[j] & !((1 << (Word::BITS - shiftr)) - 1)) >> Word::BITS - shiftr; + } else { + carry_limbs[j + shiftq - N] = self.0[j]; + } + j += 1; + } + carry_limbs[shiftq] = carry; + + (Self(limbs), Self(carry_limbs)) + } + + pub const fn add(&self, rhs: &Self) -> Self { self.carrying_add(rhs, false).0 } + + pub const fn sub(&self, rhs: &Self) -> Self { self.borrowing_sub(rhs, false).0 } + + #[inline] + pub const fn is_even(&self) -> bool { (self.0[0] & 1) == 0 } + + pub const fn carrying_mul_word(&self, rhs: Word, mut carry: Word) -> (Self, Word) { + let mut w = [0 as Word; N]; + let mut i = 0; + + while i < N { + let (mul, carry0) = self.0[i].carrying_mul(rhs, carry); + let (sum, carry1) = w[i].carrying_add(mul, false); + w[i] = sum; + carry = carry0 + carry1 as u64; + i += 1; + } + + (Self(w), carry) + } +} + +impl From for BigInt { + fn from(value: u64) -> Self { Self::from_u64(value) } +} + +impl From for BigInt { + fn from(value: u32) -> Self { Self::from_u32(value) } +} + +pub const fn calculate_mu(m: &BigInt) -> BigInt<{ 2 * N }> +where [(); 2 * N]: { + assert!(m.0[N - 1] > 1); + + let mut q = [0 as Word; 2 * N]; + + let mut x = BigInt::<{ 2 * N }>::ZERO; + let mut y_words = [0 as Word; 2 * N]; + let mut i = 0; + while i < N { + y_words[i] = m.0[i]; + i += 1; + } + + let y = BigInt(y_words); + + let b = (1 as DWord) << Word::BITS; + let c = b / y.0[N - 1] as DWord; + q[N] = c as Word; + + while (q[N] as DWord).checked_mul((y.0[N - 1] as DWord) * b + (y.0[N - 2] as DWord)).is_none() { + q[N] -= 1; + } + + let mut x1 = y.shl(N as u32 * Word::BITS).0; + let mut x2 = x1.carrying_mul_word(q[N], 0).0; + x = x.sub(&x2); + + i = 2 * N - 1; + while i >= N { + q[i - N] = match x.0[i] == y.0[N - 1] { + true => (b - 1) as Word, + false => ((x.0[i] as DWord * b + x.0[i - 1] as DWord) / y.0[N - 1] as DWord) as Word, + }; + + while BigInt::from_u64(q[i - N]) + .widening_mul(&BigInt([y.0[N - 2], y.0[N - 1], 0, 0])) + .0 + .gt(&BigInt([x.0[i - 2], x.0[i - 1], x.0[i], 0])) + { + q[i - N] -= 1; + } + + x1 = y.shl((i - N) as u32 * Word::BITS).0; + x2 = x1.carrying_mul_word(q[i - N], 0).0; + x = match x.lt(&x2) { + true => { + q[i - N] -= 1; + x.add(&x1).sub(&x2) + }, + false => x.sub(&x2), + }; + + i -= 1; + } + + BigInt(q) +} + +#[derive(Debug, Clone, Copy)] +pub struct Concat(T, T); + +impl Concat> { + pub const ONE: Self = Self(BigInt::::ONE, BigInt::::ZERO); + pub const ZERO: Self = Self(BigInt::::ZERO, BigInt::::ZERO); + + pub const fn new(lo: &BigInt, hi: &BigInt) -> Self { Self(*lo, *hi) } + + pub const fn from_words(words: [Word; 2 * N]) -> Self { + let mut a = [0 as Word; N]; + let mut b = [0 as Word; N]; + + let mut i = 0; + while i < N { + a[i] = words[i]; + i += 1; + } + + while i < 2 * N { + b[i - N] = words[i]; + i += 1; + } + + Self(BigInt(a), BigInt(b)) + } + + pub const fn from_le_bytes(bytes: [u8; N * 2 * BYTES]) -> Self + where [(); N * BYTES]: { + let mut a = [0u8; N * BYTES]; + let mut b = [0u8; N * BYTES]; + + let mut i = 0; + while i < N * BYTES { + a[i] = bytes[i]; + i += 1; + } + + while i < 2 * N * BYTES { + b[i - N * BYTES] = bytes[i]; + i += 1; + } + + Self(BigInt::::from_le_bytes(a), BigInt::::from_le_bytes(b)) + } + + pub const fn gt(&self, rhs: &Self) -> bool { + if self.1.neq(&rhs.1) { + return self.1.gt(&rhs.1); + } + self.0.gt(&rhs.0) + } + + pub const fn lt(&self, rhs: &Self) -> bool { + if self.1.neq(&rhs.1) { + return self.1.lt(&rhs.1); + } + self.0.lt(&rhs.0) + } + + pub const fn bitwise_and(&self, rhs: &Self) -> Self { + let r1 = self.0.bitwise_and(&rhs.0); + let r2 = self.1.bitwise_and(&rhs.1); + + Self(r1, r2) + } + + pub const fn carrying_add(&self, rhs: &Self, carry: bool) -> (Self, bool) { + let (r1, c1) = self.0.carrying_add(&rhs.0, carry); + let (r2, c2) = self.1.carrying_add(&rhs.1, c1); + + (Self(r1, r2), c2) + } + + pub const fn add(&self, rhs: &Self) -> Self { self.carrying_add(rhs, false).0 } + + pub const fn borrowing_sub(&self, rhs: &Self, borrow: bool) -> (Self, bool) { + let (r1, c1) = self.0.borrowing_sub(&rhs.0, borrow); + let (r2, c2) = self.1.borrowing_sub(&rhs.1, c1); + + (Self(r1, r2), c2) + } + + pub const fn sub(&self, rhs: &Self) -> Self { self.borrowing_sub(rhs, false).0 } + + pub const fn carrying_mul_word(&self, rhs: Word, carry: Word) -> (Self, Word) { + let (r1, c1) = self.0.carrying_mul_word(rhs, carry); + let (r2, c2) = self.1.carrying_mul_word(rhs, c1); + + (Self(r1, r2), c2) + } + + pub const fn mul_word(&self, rhs: Word) -> Self { self.carrying_mul_word(rhs, 0).0 } + + pub const fn mul_half(&self, rhs: &BigInt) -> Self + where [(); N * 2]: { + let (r1, c1) = self.0.widening_mul(rhs); + let (r2, _) = self.1.widening_mul(rhs); + + Self(r1, r2.carrying_add(&c1, false).0) + } + + pub const fn widening_mul(&self, rhs: &Self) -> (Self, Self) + where [(); N * 2]: { + let mut w = [BigInt::::ZERO; 4]; + let x = [self.0, self.1]; + let y = [rhs.0, rhs.1]; + let mut i = 0; + while i < 2 { + let mut j = 0; + let mut carry = BigInt::::ZERO; + while j < 2 { + let (r0, c0) = x[j].widening_mul(&y[i]); + let (r1, c1) = w[i + j].carrying_add(&carry, false); + let (r2, c2) = r1.carrying_add(&r0, false); + w[i + j] = r2; + carry = c0.carrying_add(&BigInt::from_u32(c1 as u32 + c2 as u32), false).0; + j += 1; + } + w[i + 2] = carry; + i += 1; + } + + (Self(w[0], w[1]), Self(w[2], w[3])) + } + + pub const fn shl(&self, shift: u32) -> Self { + assert!(shift < 2 * N as u32 * Word::BITS); + + if shift < N as u32 * Word::BITS { + let (r1, c1) = self.0.shl(shift); + let r2 = self.1.shl(shift).0.bitwise_or(&c1); + + Self(r1, r2) + } else { + let shiftr = shift % (N as u32 * Word::BITS); + let r2 = self.0.shl(shiftr).0; + + Self(BigInt::ZERO, r2) + } + } + + pub const fn shr(&self, shift: u32) -> Self { + assert!(shift < 2 * N as u32 * Word::BITS); + + if shift < N as u32 * Word::BITS { + let (r2, c2) = self.1.shr(shift); + let r1 = self.0.shr(shift).0.bitwise_or(&c2); + + Self(r1, r2) + } else { + let shiftr = shift % (N as u32 * Word::BITS); + let r1 = self.1.shr(shiftr).0; + + Self(r1, BigInt::ZERO) + } + } + + pub const fn split(self) -> (BigInt, BigInt) { (self.0, self.1) } + + pub const fn to_le_bytes(&self) -> [u8; N * 2 * BYTES] + where [(); N * BYTES]: { + let a: [u8; N * BYTES] = self.0.to_le_bytes(); + let b: [u8; N * BYTES] = self.1.to_le_bytes(); + + let mut c = [0u8; N * 2 * BYTES]; + + let mut i = 0; + while i < N * BYTES { + c[i] = a[i]; + i += 1; + } + + while i < 2 * N * BYTES { + c[i] = b[i - N * BYTES]; + i += 1; + } + + c + } + + pub const fn to_words(self) -> [Word; N * 2] + where [(); N * 2]: { + let a = self.0.to_words(); + let b = self.1.to_words(); + + let mut c = [0 as Word; N * 2]; + + let mut i = 0; + while i < N { + c[i] = a[i]; + i += 1; + } + while i < 2 * N { + c[i] = b[i - N]; + i += 1; + } + + c + } +} + +#[cfg(test)] +mod tests { + use crypto_bigint::{Encoding, Limb, U256, U512}; + use hex_literal::hex; + use rand::Rng; + + use super::*; + + #[test] + fn test_from() { + let mut rng = rand::thread_rng(); + for _ in 0..100000 { + let bytes: [u8; 32] = + (0..32).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + let actual1 = BigInt::<4>::from_le_bytes(bytes).0; + let expected1 = U256::from_le_bytes(bytes).to_words(); + assert_eq!(actual1, expected1); + + let actual2 = BigInt::<4>::from_be_bytes(bytes).0; + let expected2 = U256::from_be_bytes(bytes).to_words(); + assert_eq!(actual2, expected2); + + let a = rng.gen(); + let expected = U256::from_u32(a).to_words(); + let actual = BigInt::<4>::from_u32(a).0; + assert_eq!(actual, expected); + } + } + + #[test] + fn test_add_sub() { + let mut rng = rand::thread_rng(); + for _ in 0..10000 { + let x_bytes: [u8; 32] = + (0..32).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + let y_bytes: [u8; 32] = + (0..32).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + let x = BigInt::<4>::from_le_bytes(x_bytes); + let y = BigInt::<4>::from_le_bytes(y_bytes); + + let (z1, c1) = x.carrying_add(&y, false); + let (z2, c2) = x.borrowing_sub(&y, false); + + let actual1 = z1.to_le_bytes(); + let actual2 = z2.to_le_bytes(); + let actual3 = (c1 as Word).to_le_bytes(); + let actual4 = (c2 as Word).wrapping_neg().to_le_bytes(); + + let p = U256::from_le_bytes(x_bytes); + let q = U256::from_le_bytes(y_bytes); + + let (r1, s1) = p.adc(&q, Limb::ZERO); + let (r2, s2) = p.sbb(&q, Limb::ZERO); + + let expected1 = r1.to_le_bytes(); + let expected2 = r2.to_le_bytes(); + let expected3 = s1.to_le_bytes(); + let expected4 = s2.to_le_bytes(); + + assert_eq!(actual1, expected1); + assert_eq!(actual2, expected2); + assert_eq!(actual3, expected3); + assert_eq!(actual4, expected4); + } + } + + #[test] + fn test_mul() { + let mut rng = rand::thread_rng(); + for _i in 0..10000 { + let x_bytes: [u8; 32] = + (0..32).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + let y_bytes: [u8; 32] = + (0..32).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + let x = BigInt::<4>::from_le_bytes(x_bytes); + let y = BigInt::<4>::from_le_bytes(y_bytes); + + let (z1, c1) = x.widening_mul(&y); + + let p = U256::from_le_bytes(x_bytes); + let q = U256::from_le_bytes(y_bytes); + + let (r1, s1) = p.widening_mul(&q).split(); + + let actual1 = z1.0; + let actual2 = c1.0; + + let expected1 = r1.to_words(); + let expected2 = s1.to_words(); + + assert_eq!(actual1, expected1); + assert_eq!(actual2, expected2); + } + } + + #[test] + fn test_bitwise() { + let mut rng = rand::thread_rng(); + for _i in 0..1000 { + let bytes: [u8; 32] = + (0..32).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + let p = BigInt::<4>::from_le_bytes(bytes); + let a = U256::from_le_bytes(bytes); + + for shift in 0..=255 { + let (p1, _) = p.shl(shift); + let a1 = a.shl(shift); + + let (p2, _) = p.shr(shift); + let a2 = a.shr(shift); + + let actual1 = p1.to_words(); + let actual2 = p2.to_words(); + + let expected1 = a1.to_words(); + let expected2 = a2.to_words(); + + assert_eq!(actual1, expected1); + assert_eq!(actual2, expected2); + } + } + } + + #[test] + fn test_concat() { + let mut rng = rand::thread_rng(); + for _i in 0..1000 { + let bytes: [u8; 64] = + (0..64).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + let u = BigInt::<4>::from_le_bytes(bytes[..32].try_into().unwrap()); + let v = BigInt::<4>::from_le_bytes(bytes[32..].try_into().unwrap()); + let p = Concat::new(&u, &v); + let a = U512::from_le_bytes(bytes); + + for shift in 0..=511 { + let p1 = p.shl(shift); + let p2 = p.shr(shift); + + let a1 = a.shl(shift); + let a2 = a.shr(shift); + + let actual1 = p1.to_words(); + let actual2 = p2.to_words(); + + let expected1 = a1.to_words(); + let expected2 = a2.to_words(); + + assert_eq!(actual1, expected1); + assert_eq!(actual2, expected2); + } + } + } + + #[test] + fn test_concat_mul() { + let mut rng = rand::thread_rng(); + for _i in 0..10000 { + let x_bytes: [u8; 64] = + (0..64).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + let y_bytes: [u8; 64] = + (0..64).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + let mut z_bytes = [0; 64]; + z_bytes[..32].copy_from_slice(&y_bytes[..32]); + + let p = Concat::from_le_bytes(x_bytes); + let q = Concat::from_le_bytes(y_bytes); + let r = BigInt::<4>::from_le_bytes(z_bytes[..32].try_into().unwrap()); + + let (s0, s1) = p.widening_mul(&q); + let t = p.mul_half(&r); + + let a = U512::from_le_bytes(x_bytes); + let b = U512::from_le_bytes(y_bytes); + let c = U512::from_le_bytes(z_bytes); + + let (c0, c1) = a.widening_mul(&b).split(); + let (d0, _d1) = a.widening_mul(&c).split(); + + let actual1 = s0.to_words(); + let actual2 = s1.to_words(); + let actual3 = t.to_words(); + + let expected1 = c0.to_words(); + let expected2 = c1.to_words(); + let expected3 = d0.to_words(); + + assert_eq!(actual1, expected1); + assert_eq!(actual2, expected2); + assert_eq!(actual3, expected3); + } + } + + #[test] + fn test_concat_ops() { + let mut rng = rand::thread_rng(); + for _i in 0..10000 { + let mut x_bytes: [u8; 64] = + (0..64).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + let mut y_bytes: [u8; 64] = + (0..64).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + x_bytes[63] = 128; + y_bytes[63] = 64; + + let p = Concat::>::from_le_bytes(x_bytes); + let q = Concat::>::from_le_bytes(y_bytes); + + let r1 = p.sub(&q); + let r2 = p.add(&q); + + let a = U512::from_le_bytes(x_bytes); + let b = U512::from_le_bytes(y_bytes); + + let c1 = a - b; + let c2 = a + b; + + let actual1 = r1.to_words(); + let actual2 = r2.to_words(); + + let expected1 = c1.to_words(); + let expected2 = c2.to_words(); + + assert_eq!(actual1, expected1); + assert_eq!(actual2, expected2); + } + } + + #[test] + fn test_mu() { + let mu = calculate_mu(&BigInt::<4>::from_be_bytes(hex!( + "1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed" + ))); + + let actual = mu.to_le_bytes(); + let expected = [ + 0x1B, 0x13, 0x2C, 0x0A, 0xA3, 0xE5, 0x9C, 0xED, 0xA7, 0x29, 0x63, 0x08, 0x5D, 0x21, 0x06, + 0x21, 0xEB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x0F, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ]; + + assert_eq!(actual, expected); + } +} diff --git a/src/algebra/bigfield/mod.rs b/src/algebra/bigfield/mod.rs new file mode 100644 index 00000000..a5dc0d73 --- /dev/null +++ b/src/algebra/bigfield/mod.rs @@ -0,0 +1,711 @@ +pub mod bigint; + +use std::{ + marker::PhantomData, + ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, +}; + +use bigint::{BigInt, BYTES}; + +#[macro_export] +macro_rules! create_modulus { + ($name:ident, $type:ty, $modulus:expr) => { + use crate::algebra::bigfield::bigint::*; + + #[derive(Debug)] + pub struct $name; + + impl Modulus<$type> for $name { + const M: $type = <$type>::from_be_bytes($modulus); + + fn reduce(xt: ($type, $type)) -> $type { + const MU: Concat<$type> = Concat::from_le_bytes(calculate_mu(&<$name>::M).to_le_bytes()); + + let x = Concat::new(&xt.0, &xt.1); + + let q1 = x.shr(<$type>::BITS - Word::BITS); + + let (q2_r, q2_c) = q1.widening_mul(&MU); + let q3 = Concat::new(&q2_r.split().1, &q2_c.split().0).shr(Word::BITS); + + // mask = (1 << 320) - 1; + let mask = Concat::new(&BigInt::ZERO, &BigInt::pow_of_2(Word::BITS)).sub(&Concat::ONE); + + let r1 = x.bitwise_and(&mask); + + const M_PAD: Concat<$type> = Concat::<$type>::new(&<$name>::M, &<$type>::ZERO); + let mut r2 = q3.mul_half(&Self::M); + r2 = r2.bitwise_and(&mask); + + let mut r = match r1.lt(&r2) { + true => { + let t = r1.carrying_add(&mask, true); + t.0.sub(&r2) + }, + false => r1.sub(&r2), + }; + + let mut i = 0; + while i < 2 && r.gt(&M_PAD) { + r = r.sub(&M_PAD); + i += 1; + } + + let (lo, _hi) = r.split(); + lo + } + } + }; +} + +#[macro_export] +macro_rules! create_special_modulus_type_1 { + ($name:ident, $type:ty, $t:expr, $c:expr) => { + use crate::algebra::bigfield::bigint::*; + + pub struct $name; + impl Modulus<$type> for $name { + const M: $type = { + const BT: $type = <$type>::pow_of_2($t); + const C: $type = <$type>::from_u64($c); + + BT.sub(&C) + }; + + fn reduce(xt: ($type, $type)) -> $type { + let x = Concat::<$type>::new(&xt.0, &xt.1); + let mut q0 = x.shr($t); + let r0 = x.sub(&q0.shl($t)); + let mut r = r0; + + while q0.gt(&Concat::<$type>::ZERO) { + let q0_c = q0.mul_word($c); + let q1 = q0_c.shr($t); + + let r1 = q0_c.sub(&q1.shl($t)); + + let r2 = r.add(&r1); + r = r2; + + q0 = q1; + } + + const M_PAD: Concat<$type> = Concat::<$type>::new(&<$name>::M, &<$type>::ZERO); + while r.gt(&M_PAD) { + r = r.sub(&M_PAD); + } + + let (lo, _hi) = r.split(); + lo + } + } + }; +} + +#[macro_export] +macro_rules! create_special_modulus_type_2 { + ($name:ident, $type:ty, $t:expr, $c:expr) => { + pub struct $name; + impl Modulus<$type> for $name { + const M: $type = { + const BT: $type = <$type>::pow_of_2($t); + const C: $type = <$type>::from_be_bytes($c); + + BT.add(&C) + }; + + fn reduce(xt: ($type, $type)) -> $type { + const C: $type = <$type>::from_be_bytes($c); + + let x = Concat::<$type>::new(&xt.0, &xt.1); + let mut q0 = x.shr($t); + let r0 = x.sub(&q0.shl($t)); + let mut rp = r0; + let mut rn = Concat::ZERO; + + let mut flag = true; + + while q0.gt(&Concat::<$type>::ZERO) { + let q0_c = q0.mul_half(&C); + let q1 = q0_c.shr($t); + + let r1 = q0_c.sub(&q1.shl($t)); + + match flag { + true => rn = rn.add(&r1), + false => rp = rp.add(&r1), + }; + + q0 = q1; + flag = !flag; + } + + const M_PAD: Concat<$type> = Concat::<$type>::new(&<$name>::M, &<$type>::ZERO); + + let mut r = match rp.lt(&rn) { + true => rp.add(&M_PAD).sub(&rn), + false => rp.sub(&rn), + }; + + let mut i = 0; + while i < 2 && !r.lt(&M_PAD) { + r = r.sub(&M_PAD); + i += 1; + } + + let (lo, _hi) = r.split(); + lo + } + } + }; +} + +pub trait Modulus { + const M: T; + fn reduce(x: (T, T)) -> T; +} + +pub struct PrimeField> { + pub(crate) value: T, + phantom: PhantomData

, +} + +impl>> Clone for PrimeField, P> { + fn clone(&self) -> Self { Self { value: self.value.clone(), phantom: PhantomData } } +} + +impl>> Copy for PrimeField, P> {} + +impl>> PrimeField, P> { + const ONE: PrimeField, P> = Self { value: BigInt::from_u64(1), phantom: PhantomData }; + const ZERO: PrimeField, P> = + Self { value: BigInt::from_u64(0), phantom: PhantomData }; + + pub const fn to_le_bytes(self) -> [u8; N * BYTES] { self.value.to_le_bytes() } + + pub const fn to_be_bytes(self) -> [u8; N * BYTES] { self.value.to_be_bytes() } + + pub const fn eq(self, other: &Self) -> bool { self.value.eq(&other.value) } + + pub const fn is_even(self) -> bool { (self.value.to_words()[0] & 1) == 0 } + + pub const fn is_odd(self) -> bool { !self.is_even() } + + pub fn pow(self, exp: &BigInt) -> Self + where [(); N * 2]: { + if exp.eq(&BigInt::::ZERO) { + Self::ONE + } else if exp.is_even() { + let a = self.pow(&exp.shr(1).0); + a * a + } else { + let a = self.pow(&exp.shr(1).0); + a * a * self + } + } + + pub fn inv(self) -> Option + where [(); N * 2]: { + if self.eq(&Self::ZERO) { + return None; + } + + Some(self.pow(&P::M.sub(&BigInt::from_u64(2)))) + } + + pub fn euler_criterion(&self) -> bool + where [(); N * 2]: { + self.pow(&(P::M.sub(&BigInt::::ONE)).shr(1).0).eq(&Self::ONE) + } + + /// Computes the square root of a field element using the [Tonelli-Shanks algorithm](https://en.wikipedia.org/wiki/Tonelli–Shanks_algorithm). + pub fn sqrt(&self) -> Option<(Self, Self)> + where [(); N * 2]: { + if self.eq(&Self::ZERO) { + return Some((Self::ZERO, Self::ZERO)); + } + + if !self.euler_criterion() { + return None; + } + + // First define the Q and S values for the prime number P. + let mut q = P::M.sub(&BigInt::ONE); + let mut s = 0; + while q.is_even() { + q = q.shr(1).0; + s += 1; + } + + // Find a z that is not a quadratic residue + let mut z = Self::from(2u32); + while z.euler_criterion() { + z += Self::ONE; + } + let mut m = s; + let mut c = z.pow(&q); + let mut t = self.pow(&q); + let mut r = self.pow(&(q.add(&BigInt::ONE)).shr(1).0); + loop { + if t.eq(&Self::ONE) { + if (-r).value.lt(&r.value) { + return Some((-r, r)); + } else { + return Some((r, -r)); + } + } + // Repeatedly square to find a t^2^i = 1 + let mut i = 1; + let mut t_pow = t.pow(&BigInt::from(2u32)); + while !t_pow.eq(&Self::ONE) { + t_pow = t_pow.pow(&BigInt::from(2u32)); + i += 1; + } + let b = c.pow(&BigInt::pow_of_2(m - i - 1)); + m = i; + c = b.pow(&BigInt::from(2u32)); + t *= c; + r *= b; + } + } +} + +impl>> From> for PrimeField, P> { + fn from(value: BigInt) -> Self { + let mut reduced_value = value; + if !value.lt(&P::M) { + let value_padded = (value, BigInt::::ZERO); + reduced_value = P::reduce(value_padded); + } + + Self { value: reduced_value, phantom: PhantomData } + } +} + +impl>> From<(BigInt, BigInt)> + for PrimeField, P> +{ + fn from(value: (BigInt, BigInt)) -> Self { + Self { value: P::reduce(value), phantom: PhantomData } + } +} + +impl>> From for PrimeField, P> { + fn from(value: u64) -> Self { Self::from(BigInt::from_u64(value)) } +} + +impl>> From for PrimeField, P> { + fn from(value: u32) -> Self { Self::from(BigInt::from_u32(value)) } +} + +impl>> Add for PrimeField, P> { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + let (r, c) = self.value.carrying_add(&rhs.value, false); + if c || !r.lt(&P::M) { + return Self { value: r.sub(&P::M), phantom: PhantomData }; + } + Self { value: r, phantom: PhantomData } + } +} + +impl>> AddAssign for PrimeField, P> { + fn add_assign(&mut self, rhs: Self) { *self = *self + rhs; } +} + +impl>> Sub for PrimeField, P> { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + let mut r = self.value; + if r.lt(&rhs.value) { + let r0 = r.add(&P::M); + r = r0; + } + let r1 = r.sub(&rhs.value); + Self { value: r1, phantom: PhantomData } + } +} + +impl>> SubAssign for PrimeField, P> { + fn sub_assign(&mut self, rhs: Self) { *self = *self - rhs; } +} + +impl>> Mul for PrimeField, P> +where [(); N * 2]: +{ + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + let (u, v) = self.value.widening_mul(&rhs.value); + let x = P::reduce((u, v)); + Self { value: x, phantom: PhantomData } + } +} + +impl>> MulAssign for PrimeField, P> +where [(); N * 2]: +{ + fn mul_assign(&mut self, rhs: Self) { *self = *self * rhs; } +} + +impl>> Neg for PrimeField, P> { + type Output = Self; + + fn neg(self) -> Self::Output { Self { value: P::M.sub(&self.value), phantom: PhantomData } } +} + +#[cfg(test)] +mod tests { + use crypto_bigint::{ + impl_modulus, + modular::{ConstMontyForm, ConstMontyParams, Retrieve}, + Encoding, U256, U512, + }; + use hex_literal::hex; + use rand::Rng; + use rstest::rstest; + + use super::*; + + create_special_modulus_type_1!(P0, BigInt<4>, 255, 19); + create_modulus!( + L0, + BigInt<4>, + hex!("1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed") + ); + + impl_modulus!(P1, U256, "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed"); + impl_modulus!( + P2, + U512, + "0000000000000000000000000000000000000000000000000000000000000000\ + 7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" + ); + + impl_modulus!(L1, U256, "1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed"); + impl_modulus!( + L2, + U512, + "0000000000000000000000000000000000000000000000000000000000000000\ + 1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed" + ); + + type F0 = PrimeField, P0>; + type F1 = ConstMontyForm; + type F2 = ConstMontyForm; + + type F3 = PrimeField, L0>; + type F4 = ConstMontyForm; + type F5 = ConstMontyForm; + + #[test] + fn test_reduction_random() { + let mut rng = rand::thread_rng(); + for _i in 0..10000 { + let bytes: [u8; 32] = + (0..32).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + let a1 = BigInt::<4>::from_le_bytes(bytes); + + let a2 = U256::from_le_bytes(bytes); + + let actual1 = F0::from(a1).to_le_bytes(); + let actual2 = F3::from(a1).to_le_bytes(); + + let expected1 = F1::new(&a2).retrieve().to_le_bytes(); + let expected2 = F4::new(&a2).retrieve().to_le_bytes(); + + assert_eq!(actual1, expected1); + assert_eq!(actual2, expected2); + } + } + #[test] + fn test_concat_reduction() { + let mut rng = rand::thread_rng(); + for _i in 0..10000 { + let bytes: [u8; 64] = + (0..64).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + let u = BigInt::<4>::from_le_bytes(bytes[..32].try_into().unwrap()); + let v = BigInt::<4>::from_le_bytes(bytes[32..].try_into().unwrap()); + + let actual1 = F0::from((u, v)).to_le_bytes(); + let actual2 = F3::from((u, v)).to_le_bytes(); + + let expected1: [u8; 32] = + F2::new(&U512::from_le_bytes(bytes)).retrieve().to_le_bytes()[..32].try_into().unwrap(); + + let expected2: [u8; 32] = + F5::new(&U512::from_le_bytes(bytes)).retrieve().to_le_bytes()[..32].try_into().unwrap(); + + assert_eq!(actual1, expected1); + assert_eq!(actual2, expected2); + } + } + + #[test] + fn test_add_sub_mul() { + let mut rng = rand::thread_rng(); + for _i in 0..10000 { + let bytes: [u8; 64] = + (0..64).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + let u = F0::from(BigInt::<4>::from_le_bytes(bytes[..32].try_into().unwrap())); + let v = F0::from(BigInt::<4>::from_le_bytes(bytes[32..].try_into().unwrap())); + + let u1 = F3::from(BigInt::<4>::from_le_bytes(bytes[..32].try_into().unwrap())); + let v1 = F3::from(BigInt::<4>::from_le_bytes(bytes[32..].try_into().unwrap())); + + let w1 = u * v; + let w2 = u + v; + let w3 = u - v; + + let mut w4 = u; + w4 += v; + w4 *= v; + w4 -= v; + + let x1 = u1 * v1; + let x2 = u1 + v1; + let x3 = u1 - v1; + + let mut x4 = u1; + x4 += v1; + x4 *= v1; + x4 -= v1; + + let p = F1::new(&U256::from_le_bytes(bytes[..32].try_into().unwrap())); + let q = F1::new(&U256::from_le_bytes(bytes[32..].try_into().unwrap())); + + let p1 = F4::new(&U256::from_le_bytes(bytes[..32].try_into().unwrap())); + let q1 = F4::new(&U256::from_le_bytes(bytes[32..].try_into().unwrap())); + + let r1 = p * q; + let r2 = p + q; + let r3 = p - q; + + let mut r4 = p; + r4 += q; + r4 *= q; + r4 -= q; + + let t1 = p1 * q1; + let t2 = p1 + q1; + let t3 = p1 - q1; + + let mut t4 = p1; + t4 += q1; + t4 *= q1; + t4 -= q1; + + let actual1 = w1.to_le_bytes(); + let actual2 = w2.to_le_bytes(); + let actual3 = w3.to_le_bytes(); + let actual4 = w4.to_le_bytes(); + + let actual5 = x1.to_le_bytes(); + let actual6 = x2.to_le_bytes(); + let actual7 = x3.to_le_bytes(); + let actual8 = x4.to_le_bytes(); + + let expected1 = r1.retrieve().to_le_bytes(); + let expected2 = r2.retrieve().to_le_bytes(); + let expected3 = r3.retrieve().to_le_bytes(); + let expected4 = r4.retrieve().to_le_bytes(); + + let expected5 = t1.retrieve().to_le_bytes(); + let expected6 = t2.retrieve().to_le_bytes(); + let expected7 = t3.retrieve().to_le_bytes(); + let expected8 = t4.retrieve().to_le_bytes(); + + assert_eq!(actual1, expected1); + assert_eq!(actual2, expected2); + assert_eq!(actual3, expected3); + assert_eq!(actual4, expected4); + + assert_eq!(actual5, expected5); + assert_eq!(actual6, expected6); + assert_eq!(actual7, expected7); + assert_eq!(actual8, expected8); + } + } + + #[test] + fn test_pow() { + let mut rng = rand::thread_rng(); + + for _i in 0..1000 { + let bytes: [u8; 64] = + (0..64).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + let base1 = F0::from(BigInt::<4>::from_le_bytes(bytes[..32].try_into().unwrap())); + let exp1 = BigInt::<4>::from_le_bytes(bytes[32..].try_into().unwrap()); + let exp1a = BigInt::<4>::from_u64(_i); + + let res1 = base1.pow(&exp1); + let res1a = base1.pow(&exp1a); + + let base2 = F1::new(&U256::from_le_bytes(bytes[..32].try_into().unwrap())); + let exp2 = U256::from_le_bytes(bytes[32..].try_into().unwrap()); + let exp2a = U256::from_u64(_i); + + let res2 = base2.pow(&exp2); + let res2a = base2.pow(&exp2a); + + let base3 = F3::from(BigInt::<4>::from_le_bytes(bytes[..32].try_into().unwrap())); + let exp3 = BigInt::<4>::from_le_bytes(bytes[32..].try_into().unwrap()); + let exp3a = BigInt::<4>::from_u64(_i); + + let res3 = base1.pow(&exp1); + let res3a = base1.pow(&exp1a); + + let base4 = F4::new(&U256::from_le_bytes(bytes[..32].try_into().unwrap())); + let exp4 = U256::from_le_bytes(bytes[32..].try_into().unwrap()); + let exp4a = U256::from_u64(_i); + + let res4 = base2.pow(&exp2); + let res4a = base2.pow(&exp2a); + + let actual1 = base1.to_le_bytes(); + let actual2 = exp1.to_le_bytes(); + let actual3 = exp1a.to_le_bytes(); + let actual4 = res1.to_le_bytes(); + let actual5 = res1a.to_le_bytes(); + + let actual6 = base3.to_le_bytes(); + let actual7 = exp3.to_le_bytes(); + let actual8 = exp3a.to_le_bytes(); + let actual9 = res3.to_le_bytes(); + let actual10 = res3a.to_le_bytes(); + + let expected1 = base2.retrieve().to_le_bytes(); + let expected2 = exp2.to_le_bytes(); + let expected3 = exp2a.to_le_bytes(); + let expected4 = res2.retrieve().to_le_bytes(); + let expected5 = res2a.retrieve().to_le_bytes(); + + let expected6 = base4.retrieve().to_le_bytes(); + let expected7 = exp4.to_le_bytes(); + let expected8 = exp4a.to_le_bytes(); + let expected9 = res4.retrieve().to_le_bytes(); + let expected10 = res4a.retrieve().to_le_bytes(); + + assert_eq!(actual1, expected1); + assert_eq!(actual2, expected2); + assert_eq!(actual3, expected3); + assert_eq!(actual4, expected4); + assert_eq!(actual5, expected5); + + assert_eq!(actual6, expected6); + assert_eq!(actual7, expected7); + assert_eq!(actual8, expected8); + assert_eq!(actual9, expected9); + assert_eq!(actual10, expected10); + } + } + + #[test] + fn test_inv() { + const P_2: U256 = + U256::from_be_hex("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb"); + const L_2: U256 = + U256::from_be_hex("1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3eb"); + + let mut rng = rand::thread_rng(); + for _i in 0..1000 { + let bytes: [u8; 32] = + (0..32).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + + // by off chance :) + if bytes == [0u8; 32] { + continue; + } + + let base1 = F0::from(BigInt::<4>::from_le_bytes(bytes)); + let base2 = F1::new(&U256::from_le_bytes(bytes)); + + let base3 = F3::from(BigInt::<4>::from_le_bytes(bytes)); + let base4 = F4::new(&U256::from_le_bytes(bytes)); + + let inv1 = base1.inv().unwrap(); + let inv2 = base2.pow(&P_2); + let inv3 = base2.inv().unwrap(); + + let inv4 = base3.inv().unwrap(); + let inv5 = base4.pow(&L_2); + let inv6 = base4.inv().unwrap(); + + let actual1 = inv1.to_le_bytes(); + let actual2 = inv4.to_le_bytes(); + + let expected1a = inv2.retrieve().to_le_bytes(); + let expected1b = inv3.retrieve().to_le_bytes(); + + let expected2a = inv5.retrieve().to_le_bytes(); + let expected2b = inv6.retrieve().to_le_bytes(); + + assert_eq!(actual1, expected1a); + assert_eq!(actual1, expected1b); + + assert_eq!(actual2, expected2a); + assert_eq!(actual2, expected2b); + } + } + + /// Find the square root of an element of the `F1` + /// It uses the algorithm given in Section 5.1.1 of [RFC8032] utilizing the special case of + /// `P = 5 (mod 8)`. To read more, see: (https://en.wikipedia.org/wiki/Quadratic_residue#Prime_or_prime_power_modulus) + pub fn sqrt(x: &F1) -> Option { + const TWO: U256 = U256::from_u8(2u8); + const THREE: U256 = U256::from_u8(3u8); + + let mut exp = (P1::MODULUS.get() + THREE).shr(3); + let y1 = x.pow(&exp); + + if y1 * y1 == *x { + return Some(y1); + } + + exp = (P1::MODULUS.get() - U256::ONE).shr(2); + let z = F1::new(&TWO).pow(&exp); + let y2 = y1 * z; + if y2 * y2 == *x { + return Some(y2); + } else { + return None; + } + } + + #[test] + fn test_sqrt() { + let mut rng = rand::thread_rng(); + for _i in 0..1000 { + let bytes: [u8; 32] = + (0..32).map(|_| rng.gen_range(0..=255)).collect::>().try_into().unwrap(); + let p = F0::from(BigInt::from_le_bytes(bytes)); + + let r = p.sqrt(); + + let a = F1::new(&U256::from_le_bytes(bytes)); + + let c = sqrt(&a); + + match (r, c) { + (Some(x), Some(y)) => { + let y1 = match y.neg().retrieve() < y.retrieve() { + true => y, + false => y.neg(), + }; + assert_eq!(x.1.to_le_bytes(), y1.retrieve().to_le_bytes()); + }, + (None, None) => assert!(true), + + // written separately to help debug + (None, _) => assert!(false), + (_, None) => assert!(false), + } + } + } +} diff --git a/src/algebra/mod.rs b/src/algebra/mod.rs index b7353807..c3f4a2f8 100644 --- a/src/algebra/mod.rs +++ b/src/algebra/mod.rs @@ -2,6 +2,7 @@ //! Defines algebraic types: //! - [`group::Group`]: Group //! - [`field::Field`]: Field +pub mod bigfield; pub mod field; pub mod group; diff --git a/src/lib.rs b/src/lib.rs index 7acc5a38..3d2912c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,8 @@ #![allow(incomplete_features)] #![feature(effects)] +#![feature(bigint_helper_methods)] +#![feature(const_bigint_helper_methods)] #![feature(const_trait_impl)] #![feature(const_mut_refs)] #![feature(const_for)]