This commit is contained in:
Pascal Engélibert 2026-02-20 16:02:58 +01:00
commit 610cc4784a
5 changed files with 39 additions and 310 deletions

92
Cargo.lock generated
View file

@ -10,46 +10,15 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "cfg-if"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "getrandom"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
@ -63,15 +32,14 @@ dependencies = [
"num-traits",
"rand",
"rand_core",
"rayon",
"zeroize",
]
[[package]]
name = "libc"
version = "0.2.175"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "num-traits"
@ -93,18 +61,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.101"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
@ -139,31 +107,11 @@ dependencies = [
"getrandom",
]
[[package]]
name = "rayon"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "syn"
version = "2.0.106"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
@ -172,9 +120,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.18"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "wasi"
@ -184,18 +132,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "zerocopy"
version = "0.8.26"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.26"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
dependencies = [
"proc-macro2",
"quote",
@ -204,6 +152,6 @@ dependencies = [
[[package]]
name = "zeroize"
version = "1.8.1"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"

View file

@ -1,7 +1,15 @@
[package]
name = "gwrizienn"
version = "0.1.0"
authors = ["tuxmain <tuxmain@zettascript.org>"]
edition = "2024"
license = "AGPL-3.0-only"
description = "Easy and fast modular arithmetic, polynomial quotient rings"
categories = ["mathematics", "no-std"]
keywords = ["arithmetic", "polynomial", "ntt", "lattice-crypto"]
homepage = "https://git.zoai.re/tuxmain/gwrizienn"
repository = "https://git.zoai.re/tuxmain/gwrizienn"
documentation = "https://docs.rs/gwrizienn"
[dependencies]
num-traits = "0.2"
@ -9,9 +17,6 @@ rand = { version = "0.8", optional = true }
rand_core = { version = "0.6", optional = true }
zeroize = { version = "1", optional = true, default-features = false }
[dev-dependencies]
rayon = "1.8"
[features]
default = ["rand", "zeroize"]

View file

@ -2,8 +2,6 @@
Modular arithmetic, polynomial quotient rings in Rust.
Early development, really.
Goals:
* Easy to use, obvious when you look at the documentation and type system
* Fast, most possible work is done at compile time
@ -13,12 +11,20 @@ Non-goals:
* Generic (it's generic but only for primitive types)
* Complete (it's simple because it's not a complete implementation of modern algebra)
* Dynamic (vector dimensions and moduli are strongly typed)
* Footgun-free (if you give incoherent arguments to the macros, it may produce unsafe code)
Supported:
* ring Zq
* ring Zq/(x^N+1) with additive operations
* ring Zq/(x^N+1) with multiplicative operations if q=p or q=2p with p prime and 2N divides p-1
* vectors and matrices of the above rings
* lift between different rings (see example ntwe) (lifting may lack some features or be unsafe)
**Warning**: There are some TODOs in the code. The code has not been audited nor proven. Please don't use it in production yet.
## Why
When implementing Dilithium and other similar lattice-based schemes, I found no crate that was both easy and fast, so I made one with the exact set of features I needed.
## Name
@ -28,7 +34,7 @@ Pronounce _grizienn_. It's Breton for "root", because we use roots of unity to c
[Support me via LiberaPay](https://liberapay.com/tuxmain/donate)
GNU AGPL v3, CopyLeft 2025 Pascal Engélibert [(why copyleft?)](https://txmn.tk/blog/why-copyleft/)
GNU AGPL v3, CopyLeft 2025-2026 Pascal Engélibert [(why copyleft?)](https://txmn.tk/blog/why-copyleft/)
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

View file

@ -8,6 +8,8 @@
#![allow(clippy::tabs_in_doc_comments)]
//! Modular and polynomial arithmetic.
//!
//! See the examples in the repo.
pub mod matrix;
pub mod ntt;

View file

@ -1,232 +0,0 @@
crate::ring!(Z7681, u16, u32, i32, 7681);
/// Element of (Z/2Z)[x]/(x^N+1)
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct R7681<const N: usize>(pub [Z7681; N]);
impl<const N: usize> crate::poly::Poly<u16, N> for R7681<N> {
type Element = Z7681;
fn modulus() -> u16 {
7681
}
fn new(v: [Z7681; N]) -> Self {
Self(v)
}
}
impl<const N: usize> R7681<N> {
pub const fn one() -> Self {
let mut x = Self([Z7681(0); N]);
x.0[0].0 = 1;
x
}
pub fn is_one(&self) -> bool {
self.0[0].0 == 1 && self.0.iter().skip(1).all(|x| x.0 == 0)
}
pub const fn zetas() -> [u16; N] {
let mut zetas = [0; N];
let mut i: usize = 1;
let mut z = 62;
while i < N {
zetas[crate::poly::bitrev(i, N)] = z;
z = (z as u32 * 62 % 7681) as u16;
i += 1;
}
zetas
}
}
impl<const N: usize> std::ops::Add for R7681<N> {
type Output = Self;
fn add(mut self, rhs: Self) -> Self {
self.0
.iter_mut()
.zip(rhs.0.iter())
.for_each(|(x, y)| *x += *y);
self
}
}
impl<const N: usize> std::ops::Add<&Self> for R7681<N> {
type Output = Self;
fn add(mut self, rhs: &Self) -> Self {
self.0
.iter_mut()
.zip(rhs.0.iter())
.for_each(|(x, y)| *x += *y);
self
}
}
impl<const N: usize> std::ops::AddAssign for R7681<N> {
fn add_assign(&mut self, rhs: Self) {
self.0
.iter_mut()
.zip(rhs.0.iter())
.for_each(|(x, y)| *x += *y);
}
}
impl<const N: usize> std::ops::AddAssign<&Self> for R7681<N> {
fn add_assign(&mut self, rhs: &Self) {
self.0
.iter_mut()
.zip(rhs.0.iter())
.for_each(|(x, y)| *x += *y);
}
}
impl<const N: usize> std::ops::Sub for R7681<N> {
type Output = Self;
fn sub(mut self, rhs: Self) -> Self {
self.0
.iter_mut()
.zip(rhs.0.iter())
.for_each(|(x, y)| *x -= *y);
self
}
}
impl<const N: usize> std::ops::Sub<&Self> for R7681<N> {
type Output = Self;
fn sub(mut self, rhs: &Self) -> Self {
self.0
.iter_mut()
.zip(rhs.0.iter())
.for_each(|(x, y)| *x -= *y);
self
}
}
impl<const N: usize> std::ops::SubAssign for R7681<N> {
fn sub_assign(&mut self, rhs: Self) {
self.0
.iter_mut()
.zip(rhs.0.iter())
.for_each(|(x, y)| *x -= *y);
}
}
impl<const N: usize> std::ops::SubAssign<&Self> for R7681<N> {
fn sub_assign(&mut self, rhs: &Self) {
self.0
.iter_mut()
.zip(rhs.0.iter())
.for_each(|(x, y)| *x -= *y);
}
}
impl<const N: usize> num_traits::Zero for R7681<N> {
fn zero() -> Self {
Self([Z7681(0); N])
}
fn is_zero(&self) -> bool {
self.0.iter().all(num_traits::Zero::is_zero)
}
}
/*impl num_traits::One for R7681<N> {
fn one() -> Self {
let mut x = Self([$Zq(0); $N]);
x.0[0].0 = 1;
x
}
fn is_one(&self) -> bool {
self.0[0].0 == 1 && self.0.iter().skip(1).all(|x| x.0 == 0)
}
}*/
impl<const N: usize> crate::ntt::Ntt for R7681<N> {
type Output = crate::ntt::NttDomain<R7681<N>>;
fn ntt(mut self) -> crate::ntt::NttDomain<R7681<N>> {
let mut m = 0;
let mut len = N / 2;
while len >= 1 {
for start in (0..N).step_by(2 * len) {
m += 1;
let z = Z7681(R7681::<N>::zetas()[m]);
for j in start..start + len {
let t = z * self.0[j + len];
self.0[j + len] = self.0[j] - t;
self.0[j] += t;
}
}
len /= 2;
}
crate::ntt::NttDomain(self)
}
}
impl<const N: usize> crate::ntt::NttInv for crate::ntt::NttDomain<R7681<N>> {
type Output = R7681<N>;
fn ntt_inv(mut self) -> R7681<N> {
let mut m = N;
let mut len = 1;
while len < N {
for start in (0..N).step_by(2 * len) {
m -= 1;
let z = Z7681(7681 - R7681::<N>::zetas()[m]);
for j in start..start + len {
let t = self.0.0[j];
self.0.0[j] += self.0.0[j + len];
self.0.0[j + len] = z * (t - self.0.0[j + len]);
}
}
len *= 2;
}
self.0.0.iter_mut().for_each(|wj| *wj *= Z7681(N as u16).inverse());
self.0
}
}
impl<const N: usize> std::ops::Mul for crate::ntt::NttDomain<R7681<N>> {
type Output = Self;
fn mul(mut self, rhs: Self) -> Self {
self.0
.0
.iter_mut()
.zip(rhs.0.0.iter())
.for_each(|(x, y)| *x *= *y);
self
}
}
impl<const N: usize> std::ops::Mul<&Self> for crate::ntt::NttDomain<R7681<N>> {
type Output = Self;
fn mul(mut self, rhs: &Self) -> Self {
self.0
.0
.iter_mut()
.zip(rhs.0.0.iter())
.for_each(|(x, y)| *x *= *y);
self
}
}
impl<const N: usize> std::ops::MulAssign for crate::ntt::NttDomain<R7681<N>> {
fn mul_assign(&mut self, rhs: Self) {
self.0
.0
.iter_mut()
.zip(rhs.0.0.iter())
.for_each(|(x, y)| *x *= *y);
}
}
impl<const N: usize> std::ops::MulAssign<&Self> for crate::ntt::NttDomain<R7681<N>> {
fn mul_assign(&mut self, rhs: &Self) {
self.0
.0
.iter_mut()
.zip(rhs.0.0.iter())
.for_each(|(x, y)| *x *= *y);
}
}
impl<const N: usize> num_traits::Inv for crate::ntt::NttDomain<R7681<N>> {
type Output = Self;
fn inv(mut self) -> Self {
self.0.0.iter_mut().for_each(|x| *x = x.inverse());
self
}
}