From 57377e14e501c28fc93523dd84eebf536b756550 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Sun, 1 Sep 2019 14:35:34 +0200 Subject: [PATCH] add poseidon --- .gitignore | 3 + Cargo.toml | 14 +++ README.md | 9 ++ src/lib.rs | 249 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 275 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5ab5518 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "poseidon-rs" +version = "0.0.1" +authors = ["arnaucube "] +edition = "2018" +license = "GPL-3.0" +description = "Poseidon hash implementation" + +[dependencies] +num = "0.2.0" +num-bigint = "0.2.2" +num-traits = "0.2.8" +blake2 = "0.8" +rustc-hex = "1.0.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..0a7014b --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# poseidon-rs +Poseidon hash implementation in Rust + +https://eprint.iacr.org/2019/458.pdf + +Compatible with the Poseidon Go implementation from https://github.com/iden3/go-iden3-crypto + +## Warning +Do not use in production diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8bc31d9 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,249 @@ +extern crate num; +extern crate num_bigint; +extern crate num_traits; + +use blake2::digest::{Input, VariableOutput}; +use blake2::VarBlake2b; + +use num_bigint::{BigInt, Sign}; +use num_traits::{One, Zero}; + +const SEED: &str = "poseidon"; +const NROUNDSF: usize = 8; +const NROUNDSP: usize = 57; +const T: usize = 6; + +pub struct Constants { + r: BigInt, + c: Vec, + m: Vec>, +} + +pub fn generate_constants() -> Constants { + let r: BigInt = BigInt::parse_bytes( + b"21888242871839275222246405745257275088548364400416034343698204186575808495617", + 10, + ) + .unwrap(); + let c = get_pseudo_random(&r, format!("{}{}", SEED, "_constants"), NROUNDSF + NROUNDSP); + let m = get_mds(&r); + Constants { r: r, c: c, m: m } +} + +pub fn get_pseudo_random(r: &BigInt, seed: String, n: usize) -> Vec { + let mut hasher = VarBlake2b::new(32).unwrap(); + hasher.input(seed.as_bytes()); + let mut h = hasher.vec_result(); + + let mut res: Vec = Vec::new(); + while res.len() < n { + let new_n: BigInt = modulus(&BigInt::from_bytes_le(Sign::Plus, &h), &r); + res.push(new_n); + let mut hasher = VarBlake2b::new(32).unwrap(); + hasher.input(h); + h = hasher.vec_result(); + } + res +} + +pub fn nonce_to_string(n: usize) -> String { + let mut r = format!("{}", n); + while r.len() < 4 { + r = format!("0{}", r); + } + r +} +pub fn get_mds(r: &BigInt) -> Vec> { + let mut nonce = 0; + let mut cauchy_matrix = get_pseudo_random( + r, + format!("{}_matrix_{}", SEED, nonce_to_string(nonce)), + T * 2, + ); + while !check_all_different(&cauchy_matrix) { + nonce = nonce + 1; + cauchy_matrix = get_pseudo_random( + r, + format!("{}_matrix_{}", SEED, nonce_to_string(nonce)), + T * 2, + ); + } + let mut m: Vec> = Vec::new(); + for i in 0..T { + let mut mi: Vec = Vec::new(); + for j in 0..T { + mi.push(modinv( + &modulus(&(&cauchy_matrix[i] - &cauchy_matrix[T + j]), &r), + &r, + )); + } + m.push(mi); + } + m +} + +pub fn check_all_different(v: &Vec) -> bool { + let zero: BigInt = Zero::zero(); + for i in 0..v.len() { + if v[i] == zero { + return false; + } + for j in i + 1..v.len() { + if v[i] == v[j] { + return false; + } + } + } + true +} + +pub fn modulus(a: &BigInt, m: &BigInt) -> BigInt { + ((a % m) + m) % m +} + +pub fn modinv(a: &BigInt, q: &BigInt) -> BigInt { + let mut mn = (q.clone(), a.clone()); + let mut xy: (BigInt, BigInt) = (Zero::zero(), One::one()); + + let big_zero: BigInt = Zero::zero(); + while mn.1 != big_zero { + xy = (xy.1.clone(), xy.0 - (mn.0.clone() / mn.1.clone()) * xy.1); + mn = (mn.1.clone(), modulus(&mn.0, &mn.1)); + } + + while xy.0 < Zero::zero() { + xy.0 = modulus(&xy.0, q); + } + xy.0 +} + +pub fn check_bigint_in_field(a: &BigInt, q: &BigInt) -> bool { + if a >= q { + return false; + } + true +} + +pub fn check_bigint_array_in_field(arr: &Vec, q: &BigInt) -> bool { + for a in arr { + if !check_bigint_in_field(a, &q) { + return false; + } + } + true +} + +pub struct Poseidon { + constants: Constants, +} +impl Poseidon { + pub fn new() -> Poseidon { + Poseidon { + constants: generate_constants(), + } + } + pub fn ark(&self, state: &Vec, c: &BigInt) -> Vec { + let mut new_state: Vec = state.clone(); + for i in 0..state.len() { + new_state[i] = modulus(&(&state[i] + c), &self.constants.r); + } + + new_state + } + + pub fn cubic(&self, a: &BigInt) -> BigInt { + modulus(&(a * a * a * a * a), &self.constants.r) + } + + pub fn sbox(&self, state: &Vec, i: usize) -> Vec { + let mut new_state: Vec = state.clone(); + if i < NROUNDSF / 2 || i >= NROUNDSF / 2 + NROUNDSP { + for j in 0..T { + new_state[j] = self.cubic(&state[j]); + } + } else { + new_state[0] = self.cubic(&state[0]); + } + new_state + } + + pub fn mix(&self, state: &Vec, m: &Vec>) -> Vec { + let mut new_state: Vec = Vec::new(); + for i in 0..state.len() { + new_state.push(Zero::zero()); + for j in 0..state.len() { + new_state[i] = modulus( + &(&new_state[i] + modulus(&(&m[i][j] * &state[j]), &self.constants.r)), + &self.constants.r, + ) + } + } + new_state + } + + pub fn poseidon_hash(&self, inp: Vec) -> Result { + if inp.len() == 0 || inp.len() > T { + return Err("Wrong inputs length".to_string()); + } + // check if arr elements are inside the finite field over R + if !check_bigint_array_in_field(&inp, &self.constants.r) { + return Err("elements not inside the finite field over R".to_string()); + } + + let mut state = inp.clone(); + for _ in inp.len()..T { + state.push(Zero::zero()); + } + + for i in 0..(NROUNDSF + NROUNDSP) { + state = self.ark(&state, &self.constants.c[i]); + state = self.sbox(&state, i); + state = self.mix(&state, &self.constants.m); + } + + Ok(state[0].clone()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rustc_hex::ToHex; + + #[test] + fn test_blake2_version() { + let mut hasher = VarBlake2b::new(32).unwrap(); + hasher.input(b"poseidon_constants"); + let h = hasher.vec_result(); + assert_eq!( + h.to_hex(), + "e57ba154fb2c47811dc1a2369b27e25a44915b4e4ece4eb8ec74850cb78e01b1" + ); + } + + #[test] + fn test_poseidon_hash() { + let b1: BigInt = BigInt::parse_bytes(b"1", 10).unwrap(); + let b2: BigInt = BigInt::parse_bytes(b"2", 10).unwrap(); + let mut big_arr: Vec = Vec::new(); + big_arr.push(b1.clone()); + big_arr.push(b2.clone()); + let poseidon = Poseidon::new(); + let h = poseidon.poseidon_hash(big_arr).unwrap(); + assert_eq!( + h.to_string(), + "12242166908188651009877250812424843524687801523336557272219921456462821518061" + ); + + let b3: BigInt = BigInt::parse_bytes(b"3", 10).unwrap(); + let b4: BigInt = BigInt::parse_bytes(b"4", 10).unwrap(); + let mut big_arr34: Vec = Vec::new(); + big_arr34.push(b3.clone()); + big_arr34.push(b4.clone()); + let h34 = poseidon.poseidon_hash(big_arr34).unwrap(); + assert_eq!( + h34.to_string(), + "17185195740979599334254027721507328033796809509313949281114643312710535000993" + ); + } +}