mirror of
https://github.com/vacp2p/mdsecheck.git
synced 2026-01-09 05:18:01 -05:00
Comments, docs and new names for some functions
This commit is contained in:
29
README.md
29
README.md
@@ -1,3 +1,28 @@
|
||||
# mdsecheck
|
||||
The crate provides tools for generating random square [MDS matrices](https://en.wikipedia.org/wiki/MDS_matrix) over prime finite fields and applying the MDSECheck method to check such matrices for unconditional security as the components of affine permutation boxes of [partial substitution-permutation networks (P-SPNs)](https://eprint.iacr.org/2020/500.pdf), which are widespread designs of the modern symmetric ciphers and hash functions. The used data types of field elements and polynomials are provided by the crates [ark-ff](https://docs.rs/ark-ff) and [ark-poly](https://docs.rs/ark-poly). The auxiliary tools in the crate modules are accessible as well.
|
||||
|
||||
Work in Progress
|
||||
The round unconditional P-SPN security level of the square MDS matrix M is defined as l rounds, where l is a positive integer, if and only if M simultaneously satisfies the following conditions:
|
||||
1. The [minimal polynomials](https://en.wikipedia.org/wiki/Minimal_polynomial_(linear_algebra)) of M, M<sup>2</sup>, ..., M<sup>l</sup> have maximum degree and are irreducible.
|
||||
2. The minimal polynomial of M<sup>l + 1</sup> is not of maximum degree or not irreducible.
|
||||
|
||||
Theorem 8 in the paper ["Proving Resistance Against Infinitely Long Subspace Trails: How to Choose the Linear Layer"](https://eprint.iacr.org/2020/500.pdf) by L. Grassi, C. Rechberger and M. Schofnegger ensures that if the round unconditional P-SPN security level of the matrix is l, then "there is no infinitely long [subspace trail](https://eprint.iacr.org/2020/500.pdf) with/without active S-boxes of period less than or equal to l" regardless of the structure of the P-SPN affine permutation box using this matrix, but does not provide the same guarantees for larger periods. This independence from the P-SPN affine permutation boxes is the reason for using the term "unconditional security". Once an MDS matrix with the round unconditional P-SPN security level l has been chosen, it can protect any P-SPN with at most l rounds from the ["attacks based on infinitely long truncated differentials with probability 1"](https://eprint.iacr.org/2020/500.pdf).
|
||||
|
||||
To check whether the round unconditional P-SPN security level of the specified matrix is no less than the specified bound, the crate provides the implementation of the MDSECheck method, whose name is derived from the words "MDS", "security", "elaborated" and "check". An incomplete description of this novel method can be found in [this report](https://notes.status.im/CVMoa6EcTmS2D4VPBCsH2w), while a reference to a more detailed article dedicated to MDSECheck is planned to be included in the next version of the crate.
|
||||
|
||||
# Example
|
||||
```rust
|
||||
use ark_bn254::Fr;
|
||||
use mdsecheck::{random_cauchy, security_level};
|
||||
use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng};
|
||||
|
||||
// Generating pseudorandom 5 x 5 MDS matrices over the BN254 scalar field until
|
||||
// a matrix with the round unconditional P-SPN security level 25 is obtained
|
||||
let mut r = ChaCha8Rng::seed_from_u64(123456);
|
||||
loop {
|
||||
// The field is large enough to generate 5 x 5 Cauchy matrices
|
||||
let m = random_cauchy::<Fr>(5, &mut r).unwrap();
|
||||
if security_level(&m, 25) == Some(25) {
|
||||
println!("{:?}", m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
42
src/lib.rs
42
src/lib.rs
@@ -1,3 +1,5 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
use ark_ff::PrimeField;
|
||||
use ark_poly::{polynomial::univariate::DensePolynomial, DenseUVPolynomial};
|
||||
use indexmap::IndexSet;
|
||||
@@ -7,8 +9,14 @@ pub mod mat;
|
||||
pub mod num;
|
||||
pub mod poly;
|
||||
|
||||
/// Creates a random Cauchy square MDS matrix, which has the order specified by the first
|
||||
/// argument and the entries, which are determined by both the order and the source of
|
||||
/// randomness specified by the second argument. If the first argument is 0 or the field
|
||||
/// does not have enough elements for the specified matrix order, then None is returned.
|
||||
pub fn random_cauchy<F: PrimeField>(n: u32, r: &mut (impl Rng + ?Sized)) -> Option<Vec<Vec<F>>> {
|
||||
if (n == 0) || (F::MODULUS < F::BigInt::from(n as u64 * 2)) {
|
||||
// The first argument is 0 or the field does not have
|
||||
// enough elements for the specified matrix order
|
||||
return None;
|
||||
}
|
||||
let n = n as usize;
|
||||
@@ -20,6 +28,8 @@ pub fn random_cauchy<F: PrimeField>(n: u32, r: &mut (impl Rng + ?Sized)) -> Opti
|
||||
for y in d.iter().skip(n) {
|
||||
let mut v = Vec::with_capacity(n);
|
||||
for x in d.iter().take(n) {
|
||||
// Since all elements in IndexSet are distinct, for
|
||||
// each pair of them the difference is invertible
|
||||
v.push((*x - y).inverse().unwrap());
|
||||
}
|
||||
m.push(v);
|
||||
@@ -27,11 +37,23 @@ pub fn random_cauchy<F: PrimeField>(n: u32, r: &mut (impl Rng + ?Sized)) -> Opti
|
||||
Some(m)
|
||||
}
|
||||
|
||||
pub fn secure_rounds<F: PrimeField>(a: &[impl AsRef<[F]>], l: u32) -> Option<u32> {
|
||||
/// Computes the largest positive number, which exceeds neither the round unconditional
|
||||
/// P-SPN security level of the specified MDS matrix, nor the second argument, by means
|
||||
/// of the MDSECheck method. The matrix is not checked for being MDS, so it should be
|
||||
/// generated properly, e.g. using the tools the crate provides. If the matrix is not
|
||||
/// unconditionally P-SPN secure, then None is returned.
|
||||
pub fn security_level<F: PrimeField>(a: &[impl AsRef<[F]>], l: u32) -> Option<u32> {
|
||||
let n = a.len();
|
||||
if (n < 2) || (l == 0) {
|
||||
// The first argument is not a matrix, which can
|
||||
// be used in P-SPN, or the second argument is 0
|
||||
return None;
|
||||
}
|
||||
// Using the Krylov’s method fragment, which successfully
|
||||
// computes the minimal polynomial of the matrix, provided
|
||||
// that the polynomial is of maximum degree and irreducible,
|
||||
// fails if the polynomial is not of maximum degree and may
|
||||
// fail in other cases
|
||||
let mut m = Vec::<Vec<F>>::with_capacity(n);
|
||||
m.push(vec![F::ONE; n]);
|
||||
for i in 0..n - 1 {
|
||||
@@ -43,10 +65,16 @@ pub fn secure_rounds<F: PrimeField>(a: &[impl AsRef<[F]>], l: u32) -> Option<u32
|
||||
(m[y][x], m[x][y]) = (m[x][y], m[y][x]);
|
||||
}
|
||||
}
|
||||
let mut s = mat::unique_solution(&m, &b)?;
|
||||
// If the function returns at this point, then the Krylov’s method fragment
|
||||
// has failed. Consequently, the matrix is not unconditionally P-SPN secure
|
||||
let mut s = mat::system_solution(&m, &b)?;
|
||||
s.iter_mut().for_each(|e| *e = -*e);
|
||||
s.push(F::ONE);
|
||||
let c = DensePolynomial::<F>::from_coefficients_vec(s);
|
||||
// Checking the irreducibility of the minimal polynomial, which has been found using
|
||||
// the Krylov’s method fragment, by means of Algorithm 2.2.9 in the book "Prime Numbers -
|
||||
// A Computational Perspective (2nd edn.)" by R. Crandall and C. Pomerance. Some values
|
||||
// computed in this step will be used in the next one
|
||||
let f = num::prime_divisors(n as u32)
|
||||
.into_iter()
|
||||
.map(|e| n / e as usize)
|
||||
@@ -57,18 +85,28 @@ pub fn secure_rounds<F: PrimeField>(a: &[impl AsRef<[F]>], l: u32) -> Option<u32
|
||||
for d in 1..=n / 2 {
|
||||
r = poly::power_modulo(&r, F::characteristic(), &c)?;
|
||||
if !poly::coprimality(&(&r - &x), &c) {
|
||||
// The minimal polynomial is not irreducible
|
||||
return None;
|
||||
}
|
||||
if f.contains(&d) {
|
||||
// Saving a value useful for the next step
|
||||
y.push(r.clone());
|
||||
}
|
||||
}
|
||||
// Checking that the minimal polynomials for the higher powers of the matrix also are
|
||||
// of maximum degree and irreducible. This is done by checking that the higher powers
|
||||
// of a root of the characteristic polynomial of the matrix do not belong to nontrivial
|
||||
// subfields of the splitting field of the characteristic polynomial. Since the minimal
|
||||
// polynomial of the matrix is of maximum degree, it equals the characteristic polynomial
|
||||
let (mut g, mut h) = (x.clone(), y.clone());
|
||||
for i in 2..=l {
|
||||
g = poly::reduced_modulo(&(&g * &x), &c)?;
|
||||
for (v, u) in h.iter_mut().zip(y.iter()) {
|
||||
*v = poly::reduced_modulo(&(&*v * u), &c)?;
|
||||
if *v == g {
|
||||
// For the current power of the matrix the minimal polynomial is not
|
||||
// of maximum degree or not irreducible, so the round unconditional
|
||||
// P-SPN security level of the matrix equals the previous exponent
|
||||
return Some(i - 1);
|
||||
}
|
||||
}
|
||||
|
||||
15
src/mat.rs
15
src/mat.rs
@@ -1,5 +1,9 @@
|
||||
//! Provides auxiliary tools for working with matrices.
|
||||
|
||||
use ark_ff::Field;
|
||||
|
||||
/// Computes the matrix product of the arguments. If the arguments are
|
||||
/// not matrices for which the product is defined, then None is returned.
|
||||
pub fn product_matrix<F: Field>(
|
||||
a: &[impl AsRef<[F]>],
|
||||
b: &[impl AsRef<[F]>],
|
||||
@@ -26,6 +30,8 @@ pub fn product_matrix<F: Field>(
|
||||
Some(m)
|
||||
}
|
||||
|
||||
/// Computes the product of the specified matrix and column vector. If the arguments
|
||||
/// are not a matrix-vector pair for which the product is defined, then None is returned.
|
||||
pub fn product_vector<F: Field>(a: &[impl AsRef<[F]>], b: &[F]) -> Option<Vec<F>> {
|
||||
let k = b.len();
|
||||
if (k == 0) || a.is_empty() {
|
||||
@@ -43,7 +49,11 @@ pub fn product_vector<F: Field>(a: &[impl AsRef<[F]>], b: &[F]) -> Option<Vec<F>
|
||||
Some(v)
|
||||
}
|
||||
|
||||
pub fn unique_solution<F: Field>(a: &[impl AsRef<[F]>], b: &[F]) -> Option<Vec<F>> {
|
||||
/// Computes such a column vector that the product of the specified nonsingular
|
||||
/// matrix and it is equal to the specified column vector by means of the Gaussian
|
||||
/// elimination method. If the first argument is not a nonsingular matrix of the
|
||||
/// height of the specified vector, then None is returned.
|
||||
pub fn system_solution<F: Field>(a: &[impl AsRef<[F]>], b: &[F]) -> Option<Vec<F>> {
|
||||
let n = a.len();
|
||||
if n == 0 {
|
||||
// The matrix is empty
|
||||
@@ -62,6 +72,7 @@ pub fn unique_solution<F: Field>(a: &[impl AsRef<[F]>], b: &[F]) -> Option<Vec<F
|
||||
}
|
||||
m.push([r, &[*v][..]].concat());
|
||||
}
|
||||
// Obtaining the row echelon form of the augmented matrix
|
||||
for r in 0..n {
|
||||
if m[r][r] == F::ZERO {
|
||||
if let Some(p) = (r + 1..n).find(|y| m[*y][r] != F::ZERO) {
|
||||
@@ -84,6 +95,8 @@ pub fn unique_solution<F: Field>(a: &[impl AsRef<[F]>], b: &[F]) -> Option<Vec<F
|
||||
}
|
||||
}
|
||||
}
|
||||
// Transforming the rightmost column of the row
|
||||
// echelon matrix into the sought column vector
|
||||
for r in (1..n).rev() {
|
||||
for y in 0..r {
|
||||
m[y][n] = m[y][n] - m[y][r] * m[r][n];
|
||||
|
||||
13
src/num.rs
13
src/num.rs
@@ -1,3 +1,7 @@
|
||||
//! Provides auxiliary tools for working with numbers.
|
||||
|
||||
/// Computes the ascending list of prime numbers that divide
|
||||
/// the argument by means of the wheel factorization method.
|
||||
pub fn prime_divisors(mut n: u32) -> Vec<u32> {
|
||||
if n < 2 {
|
||||
return vec![];
|
||||
@@ -26,6 +30,10 @@ pub fn prime_divisors(mut n: u32) -> Vec<u32> {
|
||||
extract(d, &mut n, &mut p);
|
||||
b = sqrt(n);
|
||||
}
|
||||
// A trial divisor is either 5 or 6k + 1 or 6k + 5 for
|
||||
// some positive integer k. Therefore, the difference
|
||||
// between the (j + 1)-th and the j-th trial divisor
|
||||
// is 2, if j is odd, and 4 otherwise
|
||||
(d, s) = (d + s, s ^ 6);
|
||||
}
|
||||
if n > 1 {
|
||||
@@ -34,6 +42,11 @@ pub fn prime_divisors(mut n: u32) -> Vec<u32> {
|
||||
p
|
||||
}
|
||||
|
||||
/// Creates an iterator over the bits of the argument's big-endian
|
||||
/// binary representation with leading zeroes removed. The argument
|
||||
/// is an unsigned integer represented by its 64-bit chunks stored
|
||||
/// in the little-endian order. If the argument is 0, an empty
|
||||
/// iterator is returned.
|
||||
pub fn reversed_bits(n: &[u64]) -> impl Iterator<Item = bool> + '_ {
|
||||
n.iter()
|
||||
.rev()
|
||||
|
||||
15
src/poly.rs
15
src/poly.rs
@@ -1,9 +1,12 @@
|
||||
//! Provides auxiliary tools for working with polynomials.
|
||||
|
||||
use ark_ff::{PrimeField, Zero};
|
||||
use ark_poly::{
|
||||
polynomial::univariate::{DenseOrSparsePolynomial, DensePolynomial},
|
||||
DenseUVPolynomial, Polynomial,
|
||||
};
|
||||
|
||||
/// Checks whether the arguments are coprime polynomials by means of the Euclidean method.
|
||||
pub fn coprimality<F: PrimeField>(a: &DensePolynomial<F>, b: &DensePolynomial<F>) -> bool {
|
||||
let mut a = match reduced_modulo(a, b) {
|
||||
Some(r) => r,
|
||||
@@ -19,10 +22,16 @@ pub fn coprimality<F: PrimeField>(a: &DensePolynomial<F>, b: &DensePolynomial<F>
|
||||
a.degree() == 0
|
||||
}
|
||||
|
||||
/// Creates a polynomial from the specified big-endian slice of coefficients.
|
||||
pub fn new<F: PrimeField>(c: &[impl Into<F> + Clone]) -> DensePolynomial<F> {
|
||||
DensePolynomial::from_coefficients_vec(c.iter().rev().map(|e| e.clone().into()).collect())
|
||||
}
|
||||
|
||||
/// Computes the first argument raised to the power of the second argument
|
||||
/// modulo the third one by means of the left-to-right binary exponentiation
|
||||
/// method. The second argument is an unsigned integer represented by its
|
||||
/// 64-bit chunks stored in the little-endian order. If the modulus is 0,
|
||||
/// then None is returned.
|
||||
pub fn power_modulo<F: PrimeField>(
|
||||
p: &DensePolynomial<F>,
|
||||
e: &[u64],
|
||||
@@ -39,12 +48,14 @@ pub fn power_modulo<F: PrimeField>(
|
||||
Some(r)
|
||||
}
|
||||
|
||||
/// Computes the first argument modulo the second.
|
||||
/// If the modulus is 0, then None is returned.
|
||||
pub fn reduced_modulo<F: PrimeField>(
|
||||
p: &DensePolynomial<F>,
|
||||
m: &DensePolynomial<F>,
|
||||
) -> Option<DensePolynomial<F>> {
|
||||
// This check is to avoid a "divide_with_q_and_r" panic, since
|
||||
// for some reason "divide_with_q_and_r" never returns None
|
||||
// This check is performed to avoid panics of "divide_with_q_and_r",
|
||||
// because for some reason "divide_with_q_and_r" never returns None
|
||||
if m.is_zero() {
|
||||
return None;
|
||||
}
|
||||
|
||||
13
tests/lib.rs
13
tests/lib.rs
@@ -1,5 +1,5 @@
|
||||
use ark_bn254::Fr;
|
||||
use mdsecheck::{mat, random_cauchy, secure_rounds};
|
||||
use mdsecheck::{mat, random_cauchy, security_level};
|
||||
use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng};
|
||||
|
||||
#[test]
|
||||
@@ -8,17 +8,22 @@ fn test_random_cauchy() {
|
||||
let b = [Fr::from(1), Fr::from(2), Fr::from(3), Fr::from(4)];
|
||||
for (_, n) in (0..100).zip((1..=b.len()).cycle()) {
|
||||
let m = random_cauchy::<Fr>(n as u32, &mut r).unwrap();
|
||||
mat::unique_solution(&m, &b[..n]).unwrap();
|
||||
// Checking the generated matrix for nonsingularity
|
||||
mat::system_solution(&m, &b[..n]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_secure_rounds() {
|
||||
fn test_security_level() {
|
||||
let mut r = ChaCha8Rng::seed_from_u64(456);
|
||||
for (i, n) in (0..12).zip((2..=7).cycle()) {
|
||||
let m = random_cauchy::<Fr>(n, &mut r).unwrap();
|
||||
let s = secure_rounds(&m, 25);
|
||||
let s = security_level(&m, 25);
|
||||
match i {
|
||||
// The expected output has been computed by means of a SageMath script, which uses
|
||||
// the built-in tools for working with matrices and polynomials over finite fields.
|
||||
// In particular, the methods for computing the characteristic polynomial of a matrix
|
||||
// and checking the irreducibility of a polynomial have been used
|
||||
0 | 7 | 9 => assert_eq!(s, Some(25)),
|
||||
_ => assert_eq!(s, None),
|
||||
}
|
||||
|
||||
22
tests/mat.rs
22
tests/mat.rs
@@ -1,5 +1,5 @@
|
||||
use ark_bn254::Fr;
|
||||
use mdsecheck::mat::{product_matrix, product_vector, unique_solution};
|
||||
use mdsecheck::mat::{product_matrix, product_vector, system_solution};
|
||||
|
||||
#[test]
|
||||
fn test_product_matrix() {
|
||||
@@ -156,31 +156,31 @@ fn test_product_vector() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unique_solution() {
|
||||
fn test_system_solution() {
|
||||
assert_eq!(
|
||||
unique_solution(&[] as &[&[Fr]], &[Fr::from(1), Fr::from(2), Fr::from(3)]),
|
||||
system_solution(&[] as &[&[Fr]], &[Fr::from(1), Fr::from(2), Fr::from(3)]),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
unique_solution(&[vec![]], &[Fr::from(3), Fr::from(2), Fr::from(1)]),
|
||||
system_solution(&[vec![]], &[Fr::from(3), Fr::from(2), Fr::from(1)]),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
unique_solution(
|
||||
system_solution(
|
||||
&[[Fr::from(2), Fr::from(1)], [Fr::from(1), Fr::from(0)]],
|
||||
&[]
|
||||
),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
unique_solution(
|
||||
system_solution(
|
||||
&[vec![Fr::from(3), Fr::from(4)], vec![Fr::from(5)]],
|
||||
&[Fr::from(6), Fr::from(7)]
|
||||
),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
unique_solution(
|
||||
system_solution(
|
||||
&[
|
||||
[Fr::from(1), Fr::from(2), Fr::from(3)],
|
||||
[Fr::from(4), Fr::from(5), Fr::from(6)]
|
||||
@@ -190,7 +190,7 @@ fn test_unique_solution() {
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
unique_solution(
|
||||
system_solution(
|
||||
&[
|
||||
[Fr::from(1), Fr::from(2)],
|
||||
[Fr::from(2), Fr::from(3)],
|
||||
@@ -201,7 +201,7 @@ fn test_unique_solution() {
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
unique_solution(
|
||||
system_solution(
|
||||
&[
|
||||
[Fr::from(1), Fr::from(2), Fr::from(3)],
|
||||
[Fr::from(4), Fr::from(5), Fr::from(6)],
|
||||
@@ -212,7 +212,7 @@ fn test_unique_solution() {
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
unique_solution(
|
||||
system_solution(
|
||||
&[
|
||||
[Fr::from(1), Fr::from(2), Fr::from(3)],
|
||||
[Fr::from(4), Fr::from(5), Fr::from(6)],
|
||||
@@ -223,7 +223,7 @@ fn test_unique_solution() {
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
unique_solution(
|
||||
system_solution(
|
||||
&[
|
||||
[Fr::from(1), Fr::from(2), Fr::from(4)],
|
||||
[Fr::from(1), Fr::from(3), Fr::from(9)],
|
||||
|
||||
Reference in New Issue
Block a user