From 3dda3528f00b536afb27865c3733df413779b336 Mon Sep 17 00:00:00 2001 From: Sam Hadow Date: Fri, 23 May 2025 23:55:32 +0200 Subject: [PATCH] deep lll --- src/deep_lll.rs | 151 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + src/matrix.rs | 37 +++++++----- 3 files changed, 175 insertions(+), 14 deletions(-) create mode 100644 src/deep_lll.rs diff --git a/src/deep_lll.rs b/src/deep_lll.rs new file mode 100644 index 0000000..3a6da05 --- /dev/null +++ b/src/deep_lll.rs @@ -0,0 +1,151 @@ +use crate::matrix::Matrix; +use rug::{Integer, Rational}; + +/// Perform DeepLLL reduction on a given lattice basis represented by Matrix. +/// 1/4 < delta < 1. +pub fn deep_lll(mut mat: Matrix, delta: Rational) -> Option { + let n = mat.n; + let (mut mu, mut b_star_sq) = gramm_schmidt(&mat); + let mut k = 2; + + while k <= n { + size_reduce(&mut mat, &mut mu, &mut b_star_sq, k); + let mut c = norm_sq(&mat, k); + let mut i = 1; + while i < k { + if c >= delta.clone() * b_star_sq[i - 1].clone() { + let mu_ki = mu[k - 1][i - 1].clone(); + c -= mu_ki.clone() * mu_ki * b_star_sq[i - 1].clone(); + i += 1; + } else { + deep_insert(&mut mat, i, k); + let (new_mu, new_bs) = gramm_schmidt(&mat); + mu = new_mu; + b_star_sq = new_bs; + k = if i > 2 { i } else { 2 } - 1; + break; + } + } + + k += 1; + } + + Some(mat) +} + +/// Compute Gram–Schmidt coefficients and squared norms of orthogonal vectors b*_i as Rationals. +fn gramm_schmidt(mat: &Matrix) -> (Vec>, Vec) { + let n = mat.n; + let m = mat.m; + let mut mu = vec![vec![Rational::from((0, 1)); n]; n]; + let mut b_star = vec![vec![Rational::from((0, 1)); m]; n]; + let mut b_star_sq = vec![Rational::from((0, 1)); n]; + + for i in 0..n { + for j in 0..m { + b_star[i][j] = Rational::from(mat[(i, j)].clone()); + } + for j in 0..i { + let mut numer = Rational::from((0, 1)); + for k in 0..m { + let prod = b_star[j][k].clone() * Rational::from(mat[(i, k)].clone()); + numer += prod; + } + mu[i][j] = numer.clone() / b_star_sq[j].clone(); + for k in 0..m { + let tmp = mu[i][j].clone() * b_star[j][k].clone(); + b_star[i][k] -= tmp; + } + } + let mut sum = Rational::from((0, 1)); + for k in 0..m { + let sq = b_star[i][k].clone() * b_star[i][k].clone(); + sum += sq; + } + b_star_sq[i] = sum; + } + + (mu, b_star_sq) +} + +/// Size-reduce column k in-place +fn size_reduce(mat: &mut Matrix, mu: &mut [Vec], b_star_sq: &mut [Rational], k: usize) { + let mut updated = true; + while updated { + updated = false; + for j in (1..k).rev() { + let mu_kj = mu[k - 1][j - 1].clone(); + let q = mu_kj.round(); + if q != 0 { + for row in 0..mat.n { + let t = mat[(k - 1, row)].clone(); + let prod = &mat[(j - 1, row)] * q.clone(); + mat[(k - 1, row)] = (t - prod).round_ref().into(); + } + updated = true; + } + } + if updated { + let (new_mu, new_bs) = gramm_schmidt(mat); + mu.clone_from_slice(&new_mu); + b_star_sq.clone_from_slice(&new_bs); + } + } +} + +/// Deep insertion: move column k into position i (1-based), shifting intermediate columns right. +fn deep_insert(mat: &mut Matrix, i: usize, k: usize) { + let col = mat.columns.remove(k - 1); + mat.columns.insert(i - 1, col); +} + +/// Compute squared Euclidean norm of column k as a Rational. +fn norm_sq(mat: &Matrix, k: usize) -> Rational { + let mut sum = Rational::from((0, 1)); + for row in 0..mat.n { + let v = mat[(k - 1, row)].clone(); + sum += Rational::from(v.clone() * v); + } + sum +} + +#[cfg(test)] +mod deep_lll_tests { + use super::*; + use rug::{Integer, Rational}; + #[test] + fn test_identity_basis() { + let values = vec![ + Integer::from(1), + Integer::from(0), + Integer::from(0), + Integer::from(1), + ]; + let mat = Matrix::new(2, 2, values.clone()).unwrap(); + let delta = Rational::from((3, 4)); + let reduced = deep_lll(mat.clone(), delta).unwrap(); + for col in 0..2 { + for row in 0..2 { + assert_eq!(mat[(col, row)], reduced[(col, row)]); + } + } + } + + #[test] + fn test_determinant_preservation() { + let values = vec![ + Integer::from(4), + Integer::from(1), + Integer::from(1), + Integer::from(3), + ]; + let mat = Matrix::new(2, 2, values).unwrap(); + let delta = Rational::from((51, 100)); + let reduced = deep_lll(mat.clone(), delta).unwrap(); + let det_orig = + mat[(0, 0)].clone() * mat[(1, 1)].clone() - mat[(1, 0)].clone() * mat[(0, 1)].clone(); + let det_red = reduced[(0, 0)].clone() * reduced[(1, 1)].clone() + - reduced[(1, 0)].clone() * reduced[(0, 1)].clone(); + assert_eq!(det_orig, det_red); + } +} diff --git a/src/main.rs b/src/main.rs index cdc084f..db1ad1e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod agcd; mod bkz; +mod deep_lll; mod file; mod lll; mod matrix; diff --git a/src/matrix.rs b/src/matrix.rs index f43bd36..f1183c2 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -1,7 +1,7 @@ +use crate::vector::IntVector; use rug::ops::Pow; use rug::Integer; use std::ops::{Index, IndexMut}; -use crate::vector::IntVector; macro_rules! int { ($x:expr) => { @@ -9,11 +9,11 @@ macro_rules! int { }; } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct Matrix { - pub n: usize, // rows - pub m: usize, // columns - columns: Vec, + pub n: usize, // number of columns + pub m: usize, // number of rows + pub columns: Vec, } impl Matrix { @@ -43,11 +43,11 @@ impl Matrix { } for i in 1..n { - for j in 0..n { + for (j, column) in columns.iter_mut().enumerate().take(n) { if i == j { - columns[j].push(ciphertexts[0].clone()); + column.push(ciphertexts[0].clone()); } else { - columns[j].push(int!(0)); + column.push(int!(0)); } } } @@ -118,10 +118,14 @@ mod tests { assert_eq!(m2[(0, 2)], int!(3)); assert_eq!(m2[(1, 0)], int!(4)); - let result = panic::catch_unwind(|| { let _ = m2[(2, 0)]; }); + let result = panic::catch_unwind(|| { + let _ = m2[(2, 0)]; + }); assert!(result.is_err(), "Expected panic on m2[(2, 0)]"); - let result2 = panic::catch_unwind(|| { let _ = m2[(0, 3)]; }); + let result2 = panic::catch_unwind(|| { + let _ = m2[(0, 3)]; + }); assert!(result2.is_err(), "Expected panic on m2[(0, 3)]"); } @@ -138,9 +142,15 @@ mod tests { // 2nd column = [ciphertexts[1], ciphertexts[0], 0] = [8,5,0] // 3rd column = [ciphertexts[2], 0, ciphertexts[0]] = [12,0,5] let expected_flat = vec![ - int!(8), int!(8), int!(12), - int!(0), int!(5), int!(0), - int!(0), int!(0), int!(5), + int!(8), + int!(8), + int!(12), + int!(0), + int!(5), + int!(0), + int!(0), + int!(0), + int!(5), ]; let mut actual_flat = Vec::with_capacity(9); @@ -153,4 +163,3 @@ mod tests { assert_eq!(actual_flat, expected_flat); } } -