Chris Pollett >
Students >
Molina
( Print View)
[Bio]
[Blog]
[CS 297 Proposal]
[Deliverable 1]
[Deliverable 2]
[Deliverable 3]
[Deliverable 4]
[McEliece System]
[Regev System]
[RSA System]
[CS 297 Report-PDF]
[CS 298 Proposal]
[CS298 Slides-PDF]
[CS298 Report-PDF]
|
Implementing the McEliece Cryptosystem
Michaela Molina (michaela.molina@sjsu.edu)
Purpose:
The purpose of this deliverable was to implement the public key system from [McEliece1978]. The system is a candidate for post-quantum cryptography.
Code:
main.rs
// main.rs
mod text;
mod GF_2m_arithmetic;
mod GF_2mt_arithmetic;
mod matrix;
mod encrypt_decrypt;
mod test;
mod system;
use GF_2m_arithmetic as f1;
use GF_2mt_arithmetic as f2;
mod util;
fn main() {
// Example use
let mut message = String::from("!@#$%^&*()_+-=1234567890kjfkjhg");
let (public_key, private_key) = system::create_system(32, 2);
let iv = system::initialization_vector(&public_key);
let ciphertext = system::encrypt(message.clone(), &public_key, &iv);
let plaintext = system::decrypt(&ciphertext, &private_key, &iv);
if message == plaintext {
println!("True");
} else {
println!("False");
}
}
GF_2m_arithmetic.rs
// GF_2m_arithmetic.rs
// Used as "f1" for "field 1"
// This file contains:
// - arithmetic operations add, multiply, reduce, inverse, power, and gcd for elements of GF(2^m)
// - method to generate a irreducible polynomial of degree m over GF(2)
// Explanation:
// - elements of GF(2^m) are polynomials of degree m-1 over GF(2).
// - since these polynomials can be represented by binary strings,
// they can be represented by by binary u64s, with higher order
// coefficients corresponding to higher order bits.
// - elements of GF(2^m) are represented by the names a, b, or u,
// and the irreducible polynomial is represented by f
use crate::GF_2mt_arithmetic as f2;
use std::process;
use rand::{thread_rng, Rng};
use peroxide::fuga::*;
use itertools::Itertools;
// Returns gcd of two polynomials a and b
pub fn gcd(a:u64, b:u64) -> u64 {
if a == 0 || b == 0 {
return 0;
}
let mut u = a.clone();
let mut v = b.clone();
let mut g1 = 1 as u64;
let mut g2 = 0 as u64;
let mut h1 = 0 as u64;
let mut h2 = 1 as u64;
while u != 0 {
if v == 0 {
println!("Error in f1 gcd: b = {}", string(b));
process::exit(1);
}
let mut j:i64 = deg(u) as i64 - deg(v) as i64;
if j < 0 {
swap(&mut u, &mut v);
swap(&mut g1, &mut g2);
swap(&mut h1, &mut h2);
j *= -1;
}
let mut p1 = v.clone();
let mut p2 = g2.clone();
let mut p3 = h2.clone();
shift_left_i(&mut p1, j as u64);
shift_left_i(&mut p2, j as u64);
shift_left_i(&mut p3, j as u64);
u = add(u, p1);
g1 = add(g1, p2);
h1 = add(h1, p3);
}
let mut d = v.clone();
d
}
// Returns the inverse of a modulo f
pub fn inverse(a:u64, f:u64) -> u64 {
if a == 0 as u64 {
println!("Error in f1::inverse - a was 0");
process::exit(1);
}
let mut u = a.clone();
let mut v = f.clone();
let mut g1 = 1;
let mut g2 = 0;
while u != 1 {
if u == 0 {
println!("Error in f1: polynomial was not irreducible");
println!("a = {:#066b}, f = {:#066b}", a, f);
process::exit(1);
}
let mut j:i64 = deg(u) as i64 - deg(v) as i64;
if j < 0 as i64 {
swap(&mut u, &mut v);
swap(&mut g1, &mut g2);
j *= -1 as i64
}
let mut p1 = v.clone();
let mut p2 = g2.clone();
shift_left_i(&mut p1, j as u64);
shift_left_i(&mut p2, j as u64);
u = add(u, p1);
g1 = add(g1, p2);
}
return g1;
}
// Right-to-left shift-and-add field multiplication
// Multiplies two polynomials a, b while keeping result less than degree of f
// Precondition: a, b in GF(2^m)
pub fn multiply(a:u64, b:u64, f:u64) -> u64 {
let mut m = deg(f);
let mut b = b.clone();
let mut c = 0;
if a%2 == 1 {
c = b;
}
for i in 1..m {
shift_left(&mut b);
if deg(b) == m {
b = add(b, rz(f));
b = add(b, 2_u64.pow(m as u32) as u64);
}
if bit(a, i) == 1 {
c = add(c, b);
}
}
c
}
// Reduces polynomial u modulo f
pub fn reduce(u:&mut u64, f:u64) {
let m = deg(f);
let n = deg(*u);
let mut rz = rz(f);
shift_left_i(&mut rz, 63-m);
for i in (m..64).rev() {
if 2_u64.pow(i as u32) & (*u) != 0 {
*u = add(*u, rz);
*u = add(*u, 2_u64.pow(i as u32));
}
shift_right(&mut rz);
}
}
// Return u^i modulo f for u a polynomial in GF(2^m)
pub fn pow(u:u64, i:u64, f:u64) -> u64 {
let mut p = 1;
for i in 0..i {
p = multiply(p,u,f);
}
p
}
// Adds two polynomials a and b over GF(2) - same as bitwise XOR
pub fn add(a:u64, b:u64) -> u64 {
a ^ b
}
// ========== helper methods ==========
// Returns the vector representation of polynomial u
// The low order bits go into high order vector index
pub fn vec(u:u64, f:u64) -> Vec<f64> {
let mut vec = vec![0 as f64; deg(f) as usize];
let mut u = u.clone();
for i in (0..vec.len()).rev() {
if u%2 == 0 {
vec[i] = 0 as f64;
} else {
vec[i] = 1 as f64;
}
shift_right(&mut u);
}
vec
}
// Swaps two polynomial references
fn swap(a:&mut u64, b:&mut u64) {
let mut temp = *a;
*a = *b;
*b = temp;
}
// Returns true if coefficient at position bit is 1 in polynomial a, false otherwise
fn bit(a:u64, bit:u64) -> u64 {
if a & (1 << bit) != 0 {
return 1;
} else {
return 0;
}
}
// Returns poynomial f, except with highest order coefficient removed
// Used with the following facts in multiply, reduce:
// f(z) = z^m + r(z)
// z^m mod f(z) = r(z)
pub fn rz(f:u64) -> u64 {
let mut x = 1;
shift_left_i(&mut x, deg(f));
add(f, x)
}
// Returns degree of polynomial a
pub fn deg(a:u64) -> u64 {
if a == 0 {
return 0;
}
return ((a as f64).log2().floor()+1 as f64) as u64 - 1;
}
// Returns polynomial a as binary string
pub fn string(a:u64) -> String {
return format!("{:#066b}", a);
}
// Returns polynomial a as a text string: {0,1}x^m-1 + ... + {0,1}x + {0,1}
pub fn poly_string(a:u64) -> String {
let mut result = String::from("");
let mut deg = deg(a) as i64;
while deg >= 0 {
if a & 2_u64.pow(deg as u32) != 0 {
if deg != 0 {
result.push_str(&format!("1x^{} + ", deg));
} else {
result.push_str(&format!("1x^{}", deg));
}
} else {
if deg != 0 {
result.push_str(&format!("0x^{} + ", deg));
} else {
result.push_str(&format!("0x^{}", deg));
}
}
deg -= 1;
}
result
}
// Divide polynomial a by x
pub fn shift_right(a:&mut u64) -> u64 {
*a = *a >> 1;
a.clone()
}
// Multiply polynomial a by x
pub fn shift_left(a:&mut u64) -> u64 {
*a = *a << 1;
a.clone()
}
// Divide polynomial a by x^i
pub fn shift_right_i(a:&mut u64, i:u64) -> u64 {
for i in 0..i {
shift_right(a);
}
a.clone()
}
// Multiply polynomial a by x^i
pub fn shift_left_i(a:&mut u64, i:u64) -> u64 {
for i in 0..i {
shift_left(a);
}
a.clone()
}
// ========== polynomial methods ==========
// Ben-Or Irreducibility Test
pub fn is_irreducible(f:u64) -> bool {
let mut m = deg(f);
let mut i = 0;
let mut rhs = 2 as u64; // rhs = x
rhs = multiply(rhs, rhs, f); // rhs = rhs^2 mod f
i += 1;
while i <= (m as f64/2 as f64).floor() as u64 {
let mut rhs_plus_x = add(rhs,2);
let mut gcd = gcd(f, rhs_plus_x);
if gcd != 1 {
return false;
}
rhs = multiply(rhs, rhs, f); // rhs = rhs^2 mod f
i += 1;
}
return true;
}
pub fn get_random_polynomial(m:u64) -> u64 {
let mut f = 2_u64.pow(m as u32); // degree m
for index in 0..m {
let mut coeff:u64 = rand::thread_rng().gen_range(0..2); // choose 0 or 1
if coeff == 1 {
f = add(f, 2_u64.pow(index as u32));
}
}
f
}
pub fn get_irreducible_polynomial(m:u64) -> u64 {
let mut f = get_random_polynomial(m);
// test random polynomial
let mut count = 0;
while !is_irreducible(f) {
f = get_random_polynomial(m);
count += 1;
}
f
}
// Returns length random elements of GF(2^m) in a shuffled vector
// If one of them is a root, polynomial g was not irreducible
pub fn create_L(g:&Vec<u64>, f:u64, length:usize) -> Vec<u64> {
let mut L = Vec::new();
for i in 0..2_u64.pow(deg(f) as u32) {
if f2::compute(g, f, i) == 0 {
println!("Error in choosing L: g(x) is not irreducible over GF(2^{})", deg(f));
process::exit(1);
}
L.push(i);
}
for element in L.clone() {
if element != 0 {
let mut inverse = inverse(element, f);
println!("inverse of {} mod {} = {}\n", string(element), string(f), string(inverse));
}
}
L.shuffle(&mut thread_rng());
return (&L[0..length]).to_vec();
}
GF_2mt_arithmetic.rs
// GF_2mt_arithmetic.rs
// Used as "f2" for "field 2"
// This file contains:
// - arithmetic operations add, multiply, reduce, inverse, square root, and gcd for elements of GF(2^mt)
// - method to generate an irreducible polynomial of degree t over GF(2^m)
// Explanation:
// - elements of GF(2^mt) are polynomials of degree t-1 over GF(2^m).
// - they are represented as vectors of u64s, with the u64 being the coefficient in GF(2^m),
// and low order indices corresponding to low order coefficients
// - elements of GF(2^mt) are represented by the names a or b, with the irreducible polynomial
// represented by g, and the irreducible polynomial for the field GF(2^m) represented by f
// - for visualization, the higher order coefficients are on the left so that a multiply by x
// is a shift left
use crate::GF_2m_arithmetic as f1;
use std::process;
use std::mem;
use rand::{thread_rng, Rng};
use peroxide::fuga::*;
use itertools::Itertools;
// Returns gcd of a and b
pub fn gcd(a:&Vec<u64>, b:&Vec<u64>, f:u64) -> Vec<u64> {
if is_zero(&a) || is_zero(&b) {
return vec![0];
}
let mut u = a.clone();
let mut v = b.clone();
let mut g1 = vec![1 as u64];
let mut g2 = vec![0 as u64];
let mut h1 = vec![0 as u64];
let mut h2 = vec![1 as u64];
let mut t = deg(&b);
let mut count = 0;
while !is_zero(&u) {
if count > 5000 {
println!("Error: max iterations in f2 gcd");
process::exit(1);
}
if is_zero(&v) {
println!("Error in f2 gcd: g = {}", string(&b));
process::exit(1);
}
let mut j:i64 = deg(&u) as i64 - deg(&v) as i64;
if j<0 as i64 {
mem::swap(&mut u, &mut v);
mem::swap(&mut g1, &mut g2);
mem::swap(&mut h1, &mut h2);
j *= -1 as i64;
}
let mut p1 = v.clone();
let mut p2 = g2.clone();
let mut p3 = h2.clone();
shift_left_i(&mut p1, j as usize);
shift_left_i(&mut p2, j as usize);
shift_left_i(&mut p3, j as usize);
let mut u_coeff = u[u.len()-1];
let mut v_coeff = v[v.len()-1];
let mut coeff = f1::multiply(f1::inverse(v_coeff, f), u_coeff, f);
p1 = multiply_by_constant(&p1, coeff, f);
p2 = multiply_by_constant(&p2, coeff, f);
p3 = multiply_by_constant(&p3, coeff, f);
let mut len1 = u.len();
u = add(&u, &p1);
let mut len2 = u.len();
g1 = add(&g1, &p2);
h1 = add(&h1, &p3);
count += 1;
}
let mut d = v;
d
}
// Returns (b(x), a(x)) such that v(x)*b(x) = a(x) mod g(x)
// Pass in (v(x), g(x)) and use extended euclidean algorithm
// Used in decode
pub fn get_bx_ax(vx:&Vec<u64>, gx:&Vec<u64>, f:u64) -> (Vec<u64>, Vec<u64>) {
let mut u = vx.clone();
let mut v = gx.clone();
let mut g1 = vec![1 as u64];
let mut g2 = vec![0 as u64];
let mut h1 = vec![0 as u64];
let mut h2 = vec![1 as u64];
let mut t = deg(&gx);
let mut u_bound = ((t as f64)/(2 as f64)).floor() as u64;
let mut g1_bound = ((t as f64 - 1 as f64)/(2 as f64)).floor() as u64;
let mut count = 0;
while !is_zero(&u) {
if count > 100 {
println!("Error: max iterations in f2 get_bx_ax");
process::exit(1);
}
if is_zero(&v) {
println!("Error in f2 get_bx_ax: g = {}", string(&gx));
process::exit(1);
}
let mut j:i64 = deg(&u) as i64 - deg(&v) as i64;
if j<0 as i64 {
mem::swap(&mut u, &mut v);
mem::swap(&mut g1, &mut g2);
mem::swap(&mut h1, &mut h2);
j *= -1 as i64;
}
let mut p1 = v.clone();
let mut p2 = g2.clone();
let mut p3 = h2.clone();
shift_left_i(&mut p1, j as usize);
shift_left_i(&mut p2, j as usize);
shift_left_i(&mut p3, j as usize);
let mut u_coeff = u[u.len()-1];
let mut v_coeff = v[v.len()-1];
let mut coeff = f1::multiply(f1::inverse(v_coeff, f), u_coeff, f);
p1 = multiply_by_constant(&p1, coeff, f);
p2 = multiply_by_constant(&p2, coeff, f);
p3 = multiply_by_constant(&p3, coeff, f);
u = add(&u, &p1);
g1 = add(&g1, &p2);
h1 = add(&h1, &p3);
if deg(&u) > u_bound || deg(&g1) > g1_bound {
if deg(&v) <= u_bound && deg(&g2) <= g1_bound { // check prev
break;
}
}
count += 1;
}
return (g2, v);
}
// Returns the inverse of a modulo g
// Coefficient arithmetic done in GF(2^m) using f
pub fn inverse(a:&Vec<u64>, g:&Vec<u64>, f:u64) -> Vec<u64> {
let mut u = a.clone();
let mut v = g.clone();
let mut g1 = vec![1 as u64];
let mut g2 = vec![0 as u64];
let mut count = 0;
while !is_constant(&u) {
if count > 100 {
println!("Error: max iterations in f2 inverse");
process::exit(1);
}
if is_zero(&u) || is_zero(&v) {
println!("Error in f2: g = {}", string(&g));
process::exit(1);
}
let mut j:i64 = deg(&u) as i64 - deg(&v) as i64;
if j<0 as i64 {
mem::swap(&mut u, &mut v);
mem::swap(&mut g1, &mut g2);
j *= -1 as i64;
}
let mut p1 = v.clone();
let mut p2 = g2.clone();
shift_left_i(&mut p1, j as usize);
shift_left_i(&mut p2, j as usize);
let mut u_coeff = u[u.len()-1];
let mut v_coeff = v[v.len()-1];
let mut p1_coeff = f1::multiply(f1::inverse(v_coeff, f), u_coeff, f);
p1 = multiply_by_constant(&p1, p1_coeff, f);
p2 = multiply_by_constant(&p2, p1_coeff, f);
u = add(&u, &p1);
g1 = add(&g1, &p2);
count += 1;
}
let mut inv = f1::inverse(u[0], f);
g1 = multiply_by_constant(&g1, inv, f);
return g1;
}
// Reduces polynomial a modulo polynomial g, coefficients reduced modulo f
pub fn reduce(a:&mut Vec<u64>, g:&Vec<u64>, f:u64) {
let mut deg_a = deg(&a) as usize;
let mut deg_g = deg(&g) as usize;
if deg_a < deg_g {
return;
}
let mut rz = g.clone();
rz.pop();
shift_left_i(&mut rz, deg_a - deg_g);
for i in (deg_g..deg_a+1).rev() {
if a[i] != 0 {
let mut to_add = multiply_by_constant(&rz, a[i], f);
*a = add(&a, &to_add);
a.pop();
} else {
a.pop();
}
shift_right(&mut rz);
}
}
// Returns a^2 modulo g - do not reduce because
// this operation is used in decoding and you don't
// reduce it there
pub fn square(a:&Vec<u64>, g:&Vec<u64>, f:u64) -> Vec<u64> {
let mut b = Vec::new();
let mut i = 0;
while i < a.len() {
b.push(f1::multiply(a[i],a[i],f));
b.push(0 as u64);
i += 1;
}
b.pop();
b
}
// Returns square root of a modulo g, coefficients reduced modulo f
pub fn sqrt(a:&Vec<u64>, g:&Vec<u64>, f:u64) -> Vec<u64> {
let mut t = deg(&g);
let mut m = f1::deg(f);
let mut n = t*m-1;
let mut result = a.clone();
for i in 0..n {
result = square(&result, &g, f);
reduce(&mut result, &g, f);
}
result
}
// Adds two polynomials a and b in GF(2^tm)
pub fn add(a:&Vec<u64>, b:&Vec<u64>) -> Vec<u64> {
let mut c = Vec::new();
let mut i = 0;
while i < a.len() && i < b.len() {
c.push(f1::add(a[i], b[i]));
i += 1;
}
while i < a.len() {
c.push(a[i]);
i += 1;
}
while i < b.len() {
c.push(b[i]);
i += 1;
}
while c[c.len()-1] == 0 && c.len() > 1 {
c.pop();
}
c
}
// Plugs x, an element of GF(2^m), into g(x)
// Coefficients are reduced modulo f
pub fn compute(g:&Vec<u64>, f:u64, x:u64) -> u64 {
let mut sum = 0;
let mut power = deg(g) as usize;
while power > 0 {
sum = f1::add(f1::multiply(g[power], f1::pow(x, power as u64, f), f), sum);
power -= 1;
}
sum = f1::add(sum, g[power]); // add constant term
sum
}
// ========== helper methods ==========
// Multiplies polynomial a by constant c and reduces coefficients modulo f
pub fn multiply_by_constant(a:&Vec<u64>, c:u64, f:u64) -> Vec<u64> {
let mut b = a.clone();
for i in 0..a.len() {
b[i] = f1::multiply(b[i], c, f);
}
b
}
// Returns true if polynomial is the constant 1, false otherwise
pub fn is_one(a:&Vec<u64>) -> bool {
return a.len() == 1 && a[a.len()-1] == 1;
}
// Returns true is polynomial a is only a constant value
pub fn is_constant(a:&Vec<u64>) -> bool {
if a.len() == 1 && a[0] != 0 {
return true;
}
return false;
}
// Returns true is polynomial a is zero, false otherwise
pub fn is_zero(a:&Vec<u64>) -> bool {
for i in 0..a.len() {
if a[i] != 0 {
return false;
}
}
return true;
}
// Multiplies polynomial a by x
pub fn shift_left(a:&mut Vec<u64>) {
a.insert(0,0 as u64);
}
// Divides polynomial a by x
pub fn shift_right(a:&mut Vec<u64>) {
a.remove(0);
}
// Multiplies polynomial a by x^i
pub fn shift_left_i(a:&mut Vec<u64>, i:usize) {
let mut i = i;
while i > 0 {
shift_left(a);
i -= 1;
}
}
// Returns degree of polynomial g
pub fn deg(g:&Vec<u64>) -> u64 {
return g.len() as u64-1;
}
// Returns polynomial a as a string: {GF(2^m)}x^{t-1} + ... + {GF(2^m)}x + {GF(2^m)}
pub fn string(a:&Vec<u64>) -> String {
let mut result = String::from("");
let mut deg = deg(&a) as i64;
for u in a.iter().rev() {
result.push_str(&format!("x^{}: {:#066b}", deg, u));
if deg != 0 {
result.push_str("\n");
}
deg -= 1;
}
result
}
// ========== polynomial methods ==========
// Returns random polynomial of degree t over GF(2^m)
pub fn get_random_polynomial(t:u64, f:u64) -> Vec<u64> {
let mut m = f1::deg(f);
let mut g = vec![1 as u64];
shift_left_i(&mut g, t as usize);
for index in 0..t {
let mut constant = rand::thread_rng().gen_range(1..2_u64.pow(m as u32));
g[index as usize] = constant;
}
g
}
// Returns irreducible polynomial of degree t over GF(2^m)
pub fn get_irreducible_polynomial(t:u64, f:u64) -> Vec<u64> {
let mut g = get_random_polynomial(t, f);
let mut count = 0;
while !is_irreducible(&g, f) {
g = get_random_polynomial(t, f);
count += 1;
}
println!("{} tries to find irreducible g", count);
g
}
// Ben-Or irreducibility test
pub fn is_irreducible(g:&Vec<u64>, f:u64) -> bool {
let mut t = deg(&g);
let mut m = f1::deg(f);
let mut rhs = vec![0,1]; // rhs = x
let t_over_2 = (t as f64/2 as f64).floor() as u64;
let mut j = 0;
for i in 0..m { // square rhs m times
rhs = square(&rhs, &g, f);
reduce(&mut rhs, &g, f);
}
j += 1;
while j <= t_over_2 {
let mut rhs_plus_x = add(&mut rhs, &vec![0 as u64, 1 as u64]);
let mut gcd = gcd(&g, &rhs_plus_x, f);
if !is_one(&gcd) {
return false;
}
for i in 0..m { // square rhs m times
rhs = square(&rhs, &g, f);
reduce(&mut rhs, &g, f);
}
j += 1;
}
return true;
}
matrix.rs
// matrix.rs
// This file contains:
// - method to create generator matrix for a binary irreducible Goppa code
// using an irreducible polynomial g
// - method to generate the matrices S and P for the private key,
// used to scramble the generator matrix
// - method used to convert matrix of elements of GF(2^m) to binary matrix
// - method to multiply two matrices using field multiplication from GF(2^m)
// Explanation:
// - the generator matrix for the code is found by taking the transpose of the nullspace
// of the parity check matrix for the code
// - the parity check matrix is H = xyz where x,y,and z are matrices formed
// using g, f, and L
use crate::GF_2m_arithmetic as f1;
use crate::GF_2mt_arithmetic as f2;
use peroxide::fuga::*;
use std::process;
use rand_chacha::ChaCha20Rng;
use rand_chacha::ChaCha8Rng;
// Returns the generator matrix for the binary irreducible Goppa code corresponding
// to the irreducible polynomial g
// Returns (generator, height, width)
pub fn generator(g:&Vec<u64>, f:u64, L:&Vec<u64>) -> (Matrix, u64, u64) {
let t = f2::deg(g);
let n = L.len();
let m = f1::deg(f);
let x = x(g, t);
let y = y(L, t, n as u64, f);
let z = z(&g, &L, n as u64, f);
let xy = multiply(&x, &y, t as usize, t as usize, t as usize, n, f);
let h = multiply(&xy, &z, t as usize, n, n, n, f);
let mut h = binary_matrix(&h, t as usize, n, m as usize, f);
let (generator, dimension, length) = nullspace_t(&mut h, t*m as u64, n as u64);
return (generator, dimension, length);
}
// Append two matrices together
pub fn append(a:&Matrix, b:&Matrix, h1:u64, w1:u64, h2:u64, w2:u64) -> Matrix {
let mut m = zeros(h1 as usize, (w1+w2) as usize);
let mut r1 = 0 as usize;
let mut c1 = 0 as usize;
while r1 < h1 as usize {
c1 = 0;
while c1 < w1 as usize {
m[(r1,c1)] = a[(r1,c1)];
c1 += 1;
}
r1 += 1;
}
let mut r2 = 0 as usize;
while r2 < h2 as usize {
let mut c2 = 0 as usize;
while c2 < w2 as usize {
m[(r2, c1+c2)] = b[(r2,c2)];
c2 += 1;
}
r2 += 1;
}
m
}
// Returns the invertible matrix S
pub fn dense_nonsingular_matrix(k:u64) -> Matrix {
let mut m = zeros(k as usize, k as usize);
for i in 0..k {
for j in 0..k {
if i == j {
m[(i as usize, j as usize)] = 1 as f64;
}
}
}
let mut density = k;
let mut min_density = ((k as f64*k as f64)/2 as f64).floor() as u64;
let mut no_density_increase = 0;
while density < min_density {
for row in 0..k {
let mut first_density = density;
let mut random_row = rand::thread_rng().gen_range(0..k);
if row != random_row {
let mut possible = true;
for i in 0..k {
if m[(row as usize, i as usize)] == 1.0 && m[(random_row as usize, i as usize)] == 1.0 {
possible = false;
break;
}
}
if possible {
for i in 0..k {
let mut before = m[(row as usize, i as usize)];
m[(row as usize, i as usize)] = m[(row as usize, i as usize)] + m[(random_row as usize, i as usize)];
let mut after = m[(row as usize, i as usize)];
if after > before {
density += 1;
}
}
}
}
let mut last_density = density;
if first_density == last_density {
no_density_increase += 1;
} else {
no_density_increase = 0;
}
}
if no_density_increase > k*3 {
break;
}
}
m
}
// Returns random permutation matrix P for use in private key
pub fn permutation_matrix(n:u64) -> Matrix {
let mut ones: Vec<u64> = (0..n).collect();
ones.shuffle(&mut thread_rng());
let mut m = zeros(n as usize,n as usize);
let mut i:usize = 0;
for i in 0..n {
m[(i as usize,ones[i as usize] as usize)] = 1 as f64;
}
m
}
// Convert a matrix of elements of GF(2^m) to a binary matrix
// by converting each element of GF(2^m) to a vertical binary string where
// the top number is the high order bit
pub fn binary_matrix(h:&Matrix, t:usize, d:usize, m:usize, f:u64) -> Matrix {
let mut b = zeros(t*m, d);
for c in 0..d {
for r in 0..t {
let mut vec = f1::vec(h[(r,c)] as u64, f); // high order to low order
for i in 0..m {
b[(r*m+i, c)] = vec[i];
}
}
}
b
}
// Multiplies two matrices together
// Since the elements of the matrix are elements of GF(2^m), regular
// multiplication cannot be used and field multiplication in GF(2^m) must be used
pub fn multiply(x:&Matrix, y:&Matrix, xrows:usize, xcols:usize, yrows:usize, ycols:usize, f:u64) -> Matrix {
if xcols != yrows {
println!("matrix multiplication error");
process::exit(1);
}
let mut z = zeros(xrows, ycols);
for r in 0..xrows {
for c in 0..ycols {
for t in 0..xcols {
z[(r,c)] = f1::add(z[(r,c)] as u64, f1::multiply(x[(r,t)] as u64, y[(t,c)] as u64, f)) as f64;
}
}
}
z
}
// Return z in (xyz = parity check matrix)
// It is a diagonal matrix where the elements are
// the inverse of g(l) modulo g(x) for l in L
pub fn z(g:&Vec<u64>, L:&Vec<u64>, d:u64, f:u64) -> Matrix {
let mut z = zeros(d as usize, d as usize);
for r in 0..d {
for c in 0..d {
if r == c {
z[(r as usize, c as usize)] = f1::inverse(f2::compute(&g, f, L[r as usize]), f) as f64;
}
}
}
z
}
// Returns y in (xyz = parity check matrix)
// It contains the elements of L raised to powers and reduced modulo f
pub fn y(L:&Vec<u64>, t:u64, n:u64, f:u64) -> Matrix {
let mut L = L.clone();
let mut y = zeros(t as usize, n as usize);
for c in 0..n {
y[(0,c as usize)] = 1 as f64;
}
for r in 1..t {
for c in 0..n {
y[(r as usize,c as usize)] = f1::pow(L[c as usize],r,f) as f64;
}
}
y
}
// Returns x in (xyz = parity check matrix)
// It is a lower triangular matrix made
// using the coefficients of irreducible polynomial g(x)
pub fn x(g:&Vec<u64>, t:u64) -> Matrix {
let mut g = g.clone();
g.remove(0);
let mut x = zeros(t as usize, t as usize);
for r in (0..t).rev() {
for c in 0..t {
x[(r as usize, c as usize)] = g[c as usize] as f64;
}
g.remove(0);
g.push(0);
}
x
}
// Modified rref, used in last decryption step
pub fn rref_for_decrypt(M:&mut Matrix, m:u64, n:u64, rows_to_include:u64, columns_to_include:u64) {
let mut lead:usize = 0;
let mut rowCount:usize = rows_to_include as usize;
let mut columnCount:usize = columns_to_include as usize;
for r in 0..rowCount {
if columnCount <= lead {
return
}
let mut i = r;
while M[(i, lead)] == 0 as f64{
i = i+1;
if rowCount == i {
i = r;
lead = lead + 1;
if columnCount == lead {
return
}
}
}
if i != r {
unsafe {
M.swap(i, r, Row);
}
}
for i in 0..rowCount {
if i != r && M[(i, lead)] != 0 as f64 {
for c in 0..(n as usize) {
M[(i,c)] = f1::add(M[(i,c)] as u64, M[(r,c)] as u64) as f64;
}
}
}
lead = lead + 1;
}
}
// Puts a matrix into reduced row echelon form modulo 2
// Modified from wikipedia rref
pub fn rref_mod_2(M:&mut Matrix, m:u64, n:u64) {
let mut lead:usize = 0;
let mut rowCount:usize = m as usize;
let mut columnCount:usize = n as usize;
for r in 0..rowCount {
if columnCount <= lead {
return
}
let mut i = r;
while M[(i, lead)] == 0 as f64{
i = i+1;
if rowCount == i {
i = r;
lead = lead + 1;
if columnCount == lead {
return
}
}
}
if i != r {
unsafe {
M.swap(i, r, Row);
}
}
for i in 0..rowCount {
if i != r && M[(i, lead)] != 0 as f64 {
// add row r to row i
for c in 0..columnCount {
M[(i,c)] = f1::add(M[(i,c)] as u64, M[(r,c)] as u64) as f64;
}
}
}
lead = lead + 1;
}
}
// Reduces an element of a matrix modulo 2
pub fn mod2(a:f64) -> f64 {
let result = (a.round() as i64).rem_euclid(2) as f64;
result
}
// Reduces all the elements of a matrix modulo 2
pub fn mod2_matrix(m:&Matrix, rows:usize, cols:usize) -> Matrix {
let mut result = m.clone();
for row in 0..rows {
for col in 0..cols {
result[(row, col)] = mod2(result[(row,col)]);
}
}
result
}
// Returns true if row is all zeros, false otherwise
pub fn row_is_zero(M:&Matrix, i:u64) -> bool {
let mut row = M.row(i as usize);
for e in row {
if e != 0 as f64 {
return false;
}
}
return true;
}
// Check if a column is all zeros except a 1 at position col
pub fn is_col(vec:&Vec<f64>, col:usize) -> bool {
for i in 0..vec.len() {
if i == col {
if vec[i] != 1 as f64 {
return false;
}
} else {
if vec[i] != 0 as f64 {
return false;
}
}
}
return true;
}
// Find column to swap with in swap_columns()
pub fn find_col(M:&Matrix, rows:usize, cols:usize, i:usize) -> (bool, usize) {
for c in 0..cols {
let mut vec = M.col(c);
if is_col(&vec, i) {
return (true,c );
}
}
return (false,0);
}
// Returns the transpose of the nullspace of matrix M along with its height, width
// Arithmetic done modulo 2
pub fn nullspace_t(M:&mut Matrix, rows:u64, cols:u64) -> (Matrix, u64, u64) {
let mut swap = Vec::new();
rref_mod_2(M, rows, cols);
// swap columns
for row in 0..rows {
for col in 0..cols {
if row == col && M[(row as usize, col as usize)] != 1 as f64 {
let (mut is_col, index) = find_col(&M, rows as usize, cols as usize, col as usize);
if is_col {
unsafe {
M.swap(row as usize, index as usize, Col);
swap.push((row as usize, index as usize));
}
} else {
}
}
}
}
// end swap columns
// find where identity matrix ends
let mut last_nonzero_row = rows-1;
for row in (0..rows).rev() {
if !row_is_zero(&M, row) {
last_nonzero_row = row;
break;
}
}
let mut first_column = last_nonzero_row+1;
// if rref form is an identity matrix, there is no nullspace
if (cols - first_column) < 1 {
println!("Error: Nullspace is zero or too small");
process::exit(1);
}
// nullspace has one row for each column and one column for each column after the identity matrix
let mut nullspace = zeros(cols as usize, cols as usize-first_column as usize);
for row in 0..rows {
for col in 0..(cols as usize - first_column as usize) {
nullspace[(row as usize, col as usize)] = M[(row as usize, col as usize +first_column as usize)];
}
}
// fill in identity matrix
let mut row = first_column as usize;
let mut col = 0 as usize;
while col < (cols as usize - first_column as usize) {
nullspace[(row, col)] = 1 as f64;
col += 1;
row += 1;
}
// swap rows back in reverse order
if swap.len() != 0 {
for &(r1, r2) in swap.iter().rev() {
unsafe {
nullspace.swap(r1,r2,Row);
}
}
}
// end swap rows back
let mut nullspace = nullspace.transpose();
return (nullspace, cols as u64 - first_column as u64, cols as u64);
}
encrypt_decrypt.rs
// encrypt_decrypt.rs
// This file contains:
// - methods to encrypt and decrypt a message
// - includes Patterson's error correcting algorithm, method to
// compute the syndrome, and method to compute error vector
// Explanation:
// - the irreduicible polynomial for GF(2^mt) is represented by g
// - the irreducible polynomial for GF(2^m) is represented by f
// - the public key is the scambled generator matrix
// - the private key is S, G, P, L, g, and f
use crate::GF_2m_arithmetic as f1;
use crate::GF_2mt_arithmetic as f2;
use crate::matrix;
use peroxide::fuga::*;
use itertools::Itertools;
// Returns encrypted message
// generator is the public key
pub fn encrypt(message:&Matrix, generator:&Matrix, length:usize, t:u64) -> Matrix {
let mut mG = matrix::mod2_matrix(&(message.clone() * generator.clone()), 1 as usize, length as usize);
let mut error_vector = choose_error_vector(length as u64, t);
let mut y = matrix::mod2_matrix(&(mG.clone() + error_vector.clone()), 1 as usize, length as usize);
y
}
// Returns encrypted message
// For use in testing
pub fn encrypt_with_error_vector(message:&Matrix, generator:&Matrix, length:usize, t:u64, error_vector:&Matrix) -> Matrix {
let mut mG = matrix::mod2_matrix(&(message.clone() * generator.clone()), 1 as usize, length as usize);
let mut y = matrix::mod2_matrix(&(mG.clone() + error_vector.clone()), 1 as usize, length as usize);
y
}
// Returns decrypted message
// y is the received, encrypted message
pub fn decrypt(y:&Matrix, S:&Matrix, G:&Matrix, P:&Matrix, g:&Vec<u64>, f:u64,
L:&Vec<u64>, length:u64, dimension:u64) -> Matrix {
let mut yP_inv = y.clone()*P.inv();
let mut error_vector = error_vector(&yP_inv, &g, f, &L);
let mut mSG = matrix::mod2_matrix(&(yP_inv + error_vector), 1 as usize, length as usize);
let mut find_mS = matrix::append(&G.transpose(), &mSG.transpose(), length, dimension, length, 1 as u64);
matrix::rref_for_decrypt(&mut find_mS, length, dimension+1, length, dimension);
let mut mS = zeros(1 as usize, dimension as usize);
let mut col = dimension as usize;
for row in 0..dimension {
mS[(0 as usize,row as usize)] = find_mS[(row as usize, col as usize)];
}
let mut S_inv = matrix::mod2_matrix(&(S.inv()), dimension as usize, dimension as usize);
let mut decoded = matrix::mod2_matrix(&(mS.clone() * S_inv), 1 as usize, dimension as usize);
decoded
}
// Returns error vector which contains moved errors
pub fn error_vector(yP_inv:&Matrix, g:&Vec<u64>, f:u64, L:&Vec<u64>) -> Matrix {
let mut syndrome = syndrome(yP_inv, g, f, L);
let mut sigma = sigma(&syndrome, g, f);
let mut error_vector = zeros(1 as usize, L.len() as usize);
for i in 0..L.len() {
let mut result = f2::compute(&sigma, f, L[i]);
if result == 0 as u64 {
error_vector[(0,i)] = 1 as f64;
} else {
error_vector[(0,i)] = 0 as f64;
}
}
error_vector
}
// Returns polynomial to correct errors
// Uses Patterson's error correcting algorithm
pub fn sigma(syndrome:&Vec<u64>, g:&Vec<u64>, f:u64) -> Vec<u64> {
let syndrome_inv = f2::inverse(syndrome, g, f);
// low order to high order
let mut syndrome_inv_plus_x = f2::add(&syndrome_inv, &vec![0 as u64, 1 as u64]);
let mut sqrt = f2::sqrt(&syndrome_inv_plus_x, &g, f);
let (mut bx, mut ax) = f2::get_bx_ax(&sqrt, &g, f);
let mut bx2 = f2::square(&bx, &g, f);
let mut ax2 = f2::square(&ax, &g, f);
f2::shift_left(&mut bx2); // multiply b(x) by x
let mut sigma = f2::add(&ax2, &bx2);
sigma
}
// Returns syndrome which is a polynomial in GF(2^tm)
pub fn syndrome(yP_inv:&Matrix, g:&Vec<u64>, f:u64, L:&Vec<u64>) -> Vec<u64> {
let mut sum = vec![0 as u64];
for i in 0..L.len() {
let mut denom = vec![L[i], 1]; // low order to high order
if yP_inv[(0,i)] == 1 as f64 {
let mut inverse = f2::inverse(&denom, &g, f);
sum = f2::add(&sum, &inverse);
}
}
sum
}
// Returns vector of all Choose(width, group_size) combinations of possible error vector
// Use only for debugging
pub fn all_possible_error_vectors(width:u64, group_size:u64) -> Vec<Matrix> {
let it = (0..width).combinations(group_size as usize);
let mut vectors = Vec::new();
for element in it {
let mut vec = zeros(1 as usize, width as usize);
for index in element {
vec[(0 as usize,index as usize)] = 1 as f64;
}
vectors.push(vec);
}
vectors
}
// Returns error vector with t errors
// Must contain t errors or else it crashes
pub fn choose_error_vector(length:u64, t:u64) -> Matrix {
let mut error_vector = zeros(1,length as usize);
let mut indices = Vec::new();
let mut count = 0;
while count < t {
let mut index = rand::thread_rng().gen_range(0..length);
if indices.contains(&index) {
continue;
} else {
indices.push(index);
error_vector[(0,index as usize)] = 1 as f64;
count += 1;
}
}
error_vector
}
system.rs
// system.rs
// This file contains:
// - method to create a cryptosystem with the specified parameters
// - structs that hold the private and public keys
// - implementation of cipher block chaining
// - method to encrypt and decrypt a string
// Explanation:
// - we use this file for creating systems
// - we also use it for testing the system on text
use std::process;
use peroxide::fuga::*;
use crate::GF_2m_arithmetic as f1;
use crate::GF_2mt_arithmetic as f2;
use crate::matrix;
use crate::encrypt_decrypt;
use crate::test;
use crate::util;
// Struct used to hold the public key
pub struct public_key {
pub generator: Matrix,
pub length: u64,
pub dimension: u64,
pub t: u64
}
// Struct used to hold the private key
pub struct private_key {
pub S: Matrix,
pub G: Matrix,
pub P: Matrix,
pub L: Vec<u64>,
pub g: Vec<u64>,
pub f: u64,
pub length: u64,
pub dimension: u64
}
// Returns tuple containing public_key and private_key
pub fn create_system(n:u64, t:u64) -> (public_key, private_key) {
let m = (n as f64).log2();
if m.fract() != 0.0 {
println!("Error: n must be a power of 2");
process::exit(1);
}
let mut m = m as u64;
let mut length = 2_u64.pow(m as u32); // set n = 2^m always
test::check_precondition(length, t, m);
let mut f = f1::get_irreducible_polynomial(m);
let mut g = f2::get_irreducible_polynomial(t, f);
let mut L = f1::create_L(&g, f, length as usize);
let (mut G, mut dimension, mut length) = matrix::generator(&g, f, &L);
let mut S = matrix::dense_nonsingular_matrix(dimension as u64);
let mut P = matrix::permutation_matrix(length as u64);
let mut generator = matrix::mod2_matrix(&(S.clone() * G.clone() * P.clone()), dimension as usize, length as usize);
let mut public_key = public_key {
generator: generator,
length: length,
dimension: dimension,
t: t
};
let mut private_key = private_key {
S: S,
G: G,
P: P,
L: L,
g: g,
f: f,
length: length,
dimension: dimension
};
(public_key, private_key)
}
// Returns initialization vector for cipher block chaining
pub fn initialization_vector(public_key:&public_key) -> Matrix {
let mut dimension = public_key.dimension;
let mut iv = zeros(1 as usize, dimension as usize);
for i in 0..dimension {
let mut value:u64 = rand::thread_rng().gen_range(0..2);
iv[(0,i as usize)] = value as f64;
}
iv
}
// Encrypts a String using cipher block chaining
pub fn encrypt(message:String, public_key:&public_key, iv:&Matrix) -> Vec<u64> {
let mut plaintext = util::string_to_vec_of_u64s(message);
let dimension = public_key.dimension;
let length = public_key.length;
// make sure vector of 1 and 0 is divisible by dimension
while plaintext.len() % dimension as usize != 0 {
plaintext.push(0);
}
let mut prev_ciphertext = iv.clone();
let mut ciphertext = Vec::new(); // vector of 1 x length matrices
// split plaintext into 1 x dimension matrices
let blocks = vec_into_matrix_blocks(&plaintext, dimension);
let mut count = 1;
let mut blocks_len = blocks.len();
for block in blocks { // iterate through matrices
let x = xor_matrix(&block, &prev_ciphertext, dimension); // xor with previous ciphertext
let mut curr_ciphertext = encrypt_decrypt::encrypt(&x,
&public_key.generator,
public_key.length as usize,
public_key.t);
prev_ciphertext = curr_ciphertext.clone();
ciphertext.push(curr_ciphertext.clone()); // ciphertext is a vector of encrypted matrices
count += 1;
}
let mut ciphertext = matrix_blocks_into_vec(&ciphertext, length);
ciphertext
}
// Decrypts a String using cipher block chaining
pub fn decrypt(ciphertext:&Vec<u64>, private_key:&private_key, iv:&Matrix) -> String {
let mut ciphertext = vec_into_matrix_blocks(&ciphertext, private_key.length);
let mut plaintext = Vec::new();
let mut prev_ciphertext = iv.clone();
let mut count = 1;
let mut ciphertext_len = ciphertext.len();
for block in ciphertext {
let mut plaintext_block = encrypt_decrypt::decrypt(&block,
&private_key.S,
&private_key.G,
&private_key.P,
&private_key.g,
private_key.f,
&private_key.L,
private_key.length,
private_key.dimension);
let mut plaintext_block = xor_matrix(&plaintext_block, &prev_ciphertext, private_key.dimension);
plaintext.push(plaintext_block);
prev_ciphertext = block.clone();
count += 1;
}
let mut plaintext = matrix_blocks_into_vec(&plaintext, private_key.dimension);
while plaintext.len() % 8 as usize != 0 {
plaintext.pop();
}
let mut plaintext = util::vec_of_u64s_to_string(&plaintext);
plaintext
}
// Converts matrix to vector
pub fn matrix(a:&Vec<u64>, dimension:u64) -> Matrix {
let mut m = zeros(1 as usize, dimension as usize);
for i in 0..dimension {
m[(0,i as usize)] = a[i as usize] as f64;
}
m
}
// Converts vector of 1 x width matrices to one long vector
pub fn matrix_blocks_into_vec(a:&Vec<Matrix>, width:u64) -> Vec<u64> {
let mut v = Vec::new();
for i in 0..a.len() {
for j in 0..width as usize {
v.push(a[i][(0,j)] as u64);
}
}
v
}
// Takes in vector of length that is divisible by dimension
// Returns vector of matrices of length equal to dimension
// Precondition: a.len()%dimension == 0
pub fn vec_into_matrix_blocks(a:&Vec<u64>, dimension:u64) -> Vec<Matrix> {
let mut blocks = Vec::new();
let mut i = 0;
while i < a.len() {
let mut block = Vec::new();
while block.len() < dimension as usize {
block.push(a[i]);
i += 1;
}
blocks.push(matrix(&block, dimension));
}
blocks
}
// Takes in two 1 x dimension matrices
// Returns 1 x dimension matrix that represents their xor
pub fn xor_matrix(a:&Matrix, b:&Matrix, dimension:u64) -> Matrix {
let mut c = zeros(1 as usize, dimension as usize);
for i in 0..dimension as usize {
if a[(0,i)] == 1.0 && b[(0,i)] == 1.0 {
c[(0,i)] = 0 as f64;
} else if a[(0,i)] == 1.0 || b[(0,i)] == 1.0 {
c[(0,i)] = 1.0 as f64;
} else {
c[(0,i)] = 0.0 as f64;
}
}
c
}
test.rs
// test.rs
// This file contains:
// - method to test one instance of the system
// - method to unit test all messages and all error vectors
// - method to run unit tests on specified input parameters
// Explanation:
// - this file is used to unit test the system
use rand::{thread_rng, Rng};
use peroxide::fuga::*;
use std::process;
use itertools::Itertools;
use rand::seq::SliceRandom;
use crate::GF_2m_arithmetic as f1;
use crate::GF_2mt_arithmetic as f2;
use crate::matrix;
use crate::encrypt_decrypt;
use std::time::Instant;
use std::time::Duration;
use get_size::GetSize;
use crate::system;
pub fn run_some_tests() {
let (mut n, mut t) = (16, 2);
let (correct, incorrect, total) = test(n, t, 1);
let (mut n, mut t) = (16, 3);
let (correct, incorrect, total) = test(n, t, 2);
let (mut n, mut t) = (32, 2);
let (correct, incorrect, total) = test(n, t, 3);
let (mut n, mut t) = (32, 3);
let (correct, incorrect, total) = test(n, t, 4);
let (mut n, mut t) = (64, 2);
let (correct, incorrect, total) = test(n, t, 5);
}
// Runs unit tests on a system with the specified parameters
pub fn test(n:u64, t:u64, test_number:u64) -> (u64, u64, u64) {
let (public_key, private_key) = system::create_system(n, t);
let (correct, incorrect, total) = run_unit_tests(&public_key, &private_key);
println!("test {} results:\n {}/{} correct, {}/{} incorrect\n", test_number, correct, total, incorrect, total);
return (correct, incorrect, total);
}
// Runs encryption and decryption on a single instance
pub fn run_single_test() {
let (public_key, private_key) = system::create_system(8, 2);
let mut length = public_key.length;
let mut dimension = public_key.dimension;
let dimension = public_key.dimension;
let length = public_key.length;
let t = public_key.t;
let generator = public_key.generator.clone();
let S = private_key.S.clone();
let G = private_key.G.clone();
let P = private_key.P.clone();
let g = private_key.g.clone();
let f = private_key.f;
let L = private_key.L.clone();
let mut message = zeros(1,2);
message[(0,0)] = 0.0;
message[(0,1)] = 1.0;
let mut y = encrypt_decrypt::encrypt(&message, &generator, length as usize, t);
let mut decrypted = encrypt_decrypt::decrypt(&y, &S, &G, &P, &g, f, &L, length, dimension);
println!("decrypted = {:?}", decrypted);
}
// Runs encryption and decryption on multiple messages and multiple error vectors
pub fn run_unit_tests(public_key:&system::public_key, private_key:&system::private_key) -> (u64, u64, u64) {
let dimension = public_key.dimension;
let length = public_key.length;
let t = public_key.t;
let generator = public_key.generator.clone();
let S = private_key.S.clone();
let G = private_key.G.clone();
let P = private_key.P.clone();
let g = private_key.g.clone();
let f = private_key.f;
let L = private_key.L.clone();
let messages = get_random_messages(dimension, 100);
let mut number_of_error_vectors = 10;
let mut error_vectors = Vec::new();
for i in 0..number_of_error_vectors {
error_vectors.push(encrypt_decrypt::choose_error_vector(length, t));
}
let mut correct_count = 0;
let mut incorrect_count = 0;
let mut count = 0;
for message in &messages {
for error_vector in &error_vectors {
let mut y = encrypt_decrypt::encrypt_with_error_vector(&message, &generator, length as usize, t, error_vector);
let mut decrypted = encrypt_decrypt::decrypt(&y, &S, &G, &P, &g, f, &L, length, dimension);
if is_equal(&message, &decrypted, dimension as u64) {
correct_count += 1;
} else {
incorrect_count += 1;
}
count += 1;
}
}
(correct_count, incorrect_count, messages.len() as u64 * number_of_error_vectors as u64)
}
// Checks the precondition that 2^m > mt
pub fn check_precondition(length:u64, t:u64, m:u64) {
if length <= t*m {
println!("Error: length must be > t*m; length = {}, t*m = {}", length, t*m);
process::exit(1);
}
}
// check if two vectors are equal
pub fn is_equal(a:&Matrix, b:&Matrix, width:u64) -> bool {
for i in 0..width as usize {
if a[(0,i)] != b[(0,i)] {
return false;
}
}
return true;
}
// returns vector of all Choose(width, group_size) combinations of possible error vector
pub fn all_possible_error_vectors(width:u64, group_size:u64) -> Vec<Matrix> {
let it = (0..width).combinations(group_size as usize);
let mut vectors = Vec::new();
for element in it {
let mut vec = zeros(1 as usize, width as usize);
for index in element {
vec[(0 as usize,index as usize)] = 1 as f64;
}
vectors.push(vec);
}
vectors
}
// Returns random messages with length elements
pub fn get_random_messages(length:u64, number:u64) -> Vec<Matrix> {
let mut messages = Vec::new();
for i in 0..number {
let mut message = zeros(1, length as usize);
for j in 0..length {
message[(0,j as usize)] = rand::thread_rng().gen_range(0..2) as f64;
}
messages.push(message);
}
messages
}
// returns vector of all possible binary vectors of width width
pub fn all_possible_messages(width:u64) -> Vec<Matrix> {
let mut vectors = Vec::new();
for i in 0..2_u64.pow(width as u32) {
let mut vec = f1::vec(i, 2_u64.pow(width as u32));
let mut matrix = zeros(1 as usize, width as usize);
for j in 0..vec.len() {
matrix[(0 as usize, j as usize)] = vec[j as usize];
}
vectors.push(matrix);
}
vectors
}
util.rs
// util.rs
// This file contains:
// - method to convert a string to a binary vector
// - method to convert a binary vector to a string
// Explanation:
// - these methods are used in cipher block chaining
// convert string to vector of 0 and 1 of type u64
pub fn string_to_vec_of_u64s(s:String) -> Vec<u64> {
let mut string_of_1_and_0 = String::from("");
for character in s.clone().into_bytes() { // character is number in ascii
let mut character_as_binary_string = format!("{:b}", character);
while character_as_binary_string.len() < 8 {
let mut new_str = String::from("0");
new_str.push_str(&character_as_binary_string);
character_as_binary_string = new_str;
}
string_of_1_and_0.push_str(&character_as_binary_string);
}
let mut vec_of_u64s = Vec::new();
for ch in string_of_1_and_0.chars() {
if ch == '1' {
vec_of_u64s.push(1);
} else if ch == '0' {
vec_of_u64s.push(0);
}
}
if s.len()*8 != vec_of_u64s.len() {
use std::process;
println!("Error in util.rs: translating string to vector did not work");
process::exit(1);
}
vec_of_u64s
}
// Converts a vector of u64 ones and zeros to a String of ones and zeros
pub fn vec_of_u64s_to_string(v:&Vec<u64>) -> String {
let mut vec_of_chars = Vec::new();
let mut binary_string = String::from("");
for i in 0..v.len() {
if v[i] == 0 {
binary_string += "0";
} else if v[i] == 1 {
binary_string += "1";
}
if binary_string.len() == 8 {
vec_of_chars.push(binary_string.clone());
binary_string = String::from("");
}
}
let mut characters = Vec::new();
for i in 0..vec_of_chars.len() {
let bin = vec_of_chars[i].clone();
characters.push(isize::from_str_radix(&bin, 2).unwrap() as u8);
}
while characters[characters.len()-1] == 0 { // get rid of nulls
characters.pop();
}
let mut characters = &characters[..];
let st = std::str::from_utf8(&characters).unwrap();
st.to_string()
}
Cargo.toml
[package]
name = "mceliece"
version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8.5"
peroxide = "0.30"
itertools = "0.10.3"
Sources:
[1] Hankerson, Darrel, Alfred J. Menezes, and Scott Vanstone. Guide to elliptic curve cryptography. Springer Science & Business Media, 2006.
[2] Stallings, William. Cryptography and Network Security Principles and Practice. Sixth edition. Boston: Pearson, 2014.
|