Skip to content

Commit

Permalink
add proptests for (de)compress
Browse files Browse the repository at this point in the history
  • Loading branch information
aszepieniec committed Feb 14, 2024
1 parent fe991d8 commit abcf6c7
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 36 deletions.
10 changes: 10 additions & 0 deletions falcon-rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,13 @@ hex = "0.4.3"
sha3 = "0.10.8"
num-complex = "0.4.4"
num = "0.4.1"

[dev-dependencies]
proptest = "1.4.0"
proptest-derive = "0.4.0"

[profile.test.package.proptest]
opt-level = 3

[profile.test.package.rand_chacha]
opt-level = 3
7 changes: 7 additions & 0 deletions falcon-rust/proptest-regressions/encoding.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc af9d7e8e65decd95ddaced228a64a43c10b9aaa96a429adaa3576ead86ad186c # shrinks to v = []
96 changes: 63 additions & 33 deletions falcon-rust/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub(crate) fn compress_slow(v: &[i16], slen: usize) -> Option<Vec<u8>> {
Some(bitvector.to_bytes())
}

/// Take as input a list of integers v and a bytelength slen, and
/// Take as input a list of integers v and a byte length slen, and
/// return a bytestring of length slen that encode/compress v.
/// If this is not possible, return False.
///
Expand All @@ -52,17 +52,23 @@ pub(crate) fn compress_slow(v: &[i16], slen: usize) -> Option<Vec<u8>> {
pub(crate) fn compress(v: &[i16], slen: usize) -> Option<Vec<u8>> {
// encode each coefficient separately; join later
let lengths_and_coefficients = v.iter().map(|c| compress_coefficient(*c)).collect_vec();
if lengths_and_coefficients
let total_length = lengths_and_coefficients
.iter()
.map(|(l, _c)| *l)
.sum::<usize>()
> slen
{
.sum::<usize>();

// if we can't fit all coefficients in the allotted bytes
if total_length > slen * 8 {
return None;
}

// no coefficients are given
if v.is_empty() {
return None;
}

// join all but one coefficients assuming enough space
let mut bytes = vec![0u8; slen / 8];
let mut bytes = vec![0u8; slen];
let mut counter = 0;
for (length, coefficient) in lengths_and_coefficients.iter().take(v.len() - 1) {
let (cdiv8, cmod8) = counter.div_mod_floor(&8);
Expand Down Expand Up @@ -104,11 +110,7 @@ fn compress_coefficient(coeff: i16) -> (usize, u8) {
/// This is a deprecated decompress routine used now only for testing
/// compatibility with the new, faster implementation (below).
#[allow(dead_code)]
pub(crate) fn decompress_slow(x: &[u8], bitlength: usize, n: usize) -> Option<Vec<i16>> {
if x.len() * 8 != bitlength {
return None;
}

pub(crate) fn decompress_slow(x: &[u8], n: usize) -> Option<Vec<i16>> {
let bitvector = BitVec::from_bytes(x);
let mut index = 0;
let mut result = Vec::with_capacity(n);
Expand Down Expand Up @@ -151,11 +153,7 @@ pub(crate) fn decompress_slow(x: &[u8], bitlength: usize, n: usize) -> Option<Ve
/// Algorithm 18 p. 48 of the specification [1].
///
/// [1]: https://falcon-sign.info/falcon.pdf
pub(crate) fn decompress(x: &[u8], bitlength: usize, n: usize) -> Option<Vec<i16>> {
if x.len() * 8 != bitlength {
return None;
}

pub(crate) fn decompress(x: &[u8], n: usize) -> Option<Vec<i16>> {
let bitvector = BitVec::from_bytes(x);
let mut index = 0;
let mut result = Vec::with_capacity(n);
Expand Down Expand Up @@ -276,14 +274,53 @@ mod test {

use crate::{
encoding::{compress, compress_slow, decompress, decompress_slow},
falcon::FalconVariant,
field::Q,
};
use bit_vec::BitVec;
use itertools::Itertools;
use rand::{thread_rng, Rng};
use rand_distr::{num_traits::ToPrimitive, Distribution, Normal};

use proptest::prelude::*;

fn short_elements(n: usize) -> Vec<i16> {
let sigma = 1.5 * (Q.to_f64().unwrap()).sqrt();
let distribution = Normal::<f64>::new(0.0, sigma).unwrap();
let mut rng = thread_rng();
(0..n)
.map(|_| {
(distribution.sample(&mut rng) + 0.5)
.floor()
.to_i32()
.unwrap() as i16
})
.collect::<Vec<_>>()
}
proptest! {
#[test]
fn compress_does_not_crash(v in (0..2000usize).prop_map(short_elements)) {
compress(&v, 2*v.len());
}
}
proptest! {
#[test]
fn decompress_recovers(v in (0..2000usize).prop_map(short_elements)) {
let slen = 2 * v.len();
let n = v.len();
if let Some(compressed) = compress(&v, slen) {
let recovered = decompress(&compressed, n).unwrap();
prop_assert_eq!(v, recovered.clone());
let recompressed = compress(&recovered, slen).unwrap();
prop_assert_eq!(compressed, recompressed);
}
}
}

#[test]
fn compress_empty_vec_does_not_crash() {
compress(&[], 0);
}

#[test]
fn test_compress_decompress() {
let num_iterations = 1000;
Expand Down Expand Up @@ -315,12 +352,7 @@ mod test {
.try_into()
.unwrap();
if let Some(compressed) = compress(&initial, slen * 8) {
if let Some(decompressed) = decompress(
&compressed,
(FalconVariant::Falcon512.parameters().sig_bytelen - SALT_LEN - HEAD_LEN)
* 8,
N,
) {
if let Some(decompressed) = decompress(&compressed, N) {
assert_eq!(initial.to_vec(), decompressed);
num_successes_512 += 1;
}
Expand All @@ -344,7 +376,7 @@ mod test {
.try_into()
.unwrap();
if let Some(compressed) = compress(&initial, slen * 8) {
if let Some(decompressed) = decompress(&compressed, slen * 8, N) {
if let Some(decompressed) = decompress(&compressed, N) {
assert_eq!(initial.to_vec(), decompressed);
num_successes_1024 += 1;
}
Expand Down Expand Up @@ -372,7 +404,7 @@ mod test {
.collect::<Vec<_>>();
let slen = 2 * n * 8;
let compressed = compress_slow(&initial, slen).unwrap();
let compressed_fast = compress(&initial, slen).unwrap();
let compressed_fast = compress(&initial, slen / 8).unwrap();
assert_eq!(
compressed,
compressed_fast,
Expand Down Expand Up @@ -403,8 +435,8 @@ mod test {
let slen = 2 * n * 8;
let compressed = compress(&initial, slen).unwrap();

let decompressed = decompress(&compressed, slen, n);
let decompressed_fast = decompress_slow(&compressed, slen, n);
let decompressed = decompress(&compressed, n);
let decompressed_fast = decompress_slow(&compressed, n);

assert_eq!(decompressed, decompressed_fast);
}
Expand All @@ -431,10 +463,8 @@ mod test {
let slen = 2 * n * 8;
let compressed = compress(&initial, slen).unwrap();

assert!(decompress(&compressed, slen, n + 1).is_none());
assert!(decompress(&compressed, slen + 1, n).is_none());
// assert!(decompress(&compressed, slen, n - 1).is_none()); // should work
assert!(decompress(&compressed, slen - 1, n).is_none());
assert!(decompress(&compressed, n + 1).is_none());
// assert!(decompress(&compressed, n - 1).is_none()); // should work

// flip last set bit -- should cause failure
let mut compressed_bitvec = BitVec::from_bytes(&compressed);
Expand All @@ -444,7 +474,7 @@ mod test {
}
compressed_bitvec.set(index - 1, false);
let last_bit_flipped = compressed_bitvec.to_bytes();
assert!(decompress(&last_bit_flipped, slen, n).is_none());
assert!(decompress(&last_bit_flipped, n).is_none());

// try random string -- might fail, but if not must re-encode to the same
// let random = (0..compressed.len()).map(|_| rng.gen::<u8>()).collect_vec();
Expand All @@ -460,7 +490,7 @@ mod test {
for i in 0..num_trailing_zeros {
random[len - 1 - i] = 0;
}
if let Some(decompressed) = decompress(&random, slen, n) {
if let Some(decompressed) = decompress(&random, n) {
let recompressed = compress(&decompressed, slen).unwrap();
assert_eq!(
random,
Expand Down
6 changes: 3 additions & 3 deletions falcon-rust/src/falcon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ pub fn verify<const N: usize>(m: &[u8], sig: &Signature<N>, pk: &PublicKey<N>) -
let r_cat_m = [sig.r.to_vec(), m.to_vec()].concat();
let c = hash_to_point(&r_cat_m, n);

let s2 = match decompress(&sig.s, (params.sig_bytelen - 41) * 8, n) {
let s2 = match decompress(&sig.s, n) {
Some(success) => success,
None => {
return false;
Expand Down Expand Up @@ -1233,7 +1233,7 @@ mod test {
r: nonce,
s: compress(
&sigvec,
(FalconVariant::Falcon1024.parameters().sig_bytelen - 41) * 8,
FalconVariant::Falcon1024.parameters().sig_bytelen - 41,
)
.unwrap(),
};
Expand All @@ -1250,7 +1250,7 @@ mod test {
r: nonce,
s: compress(
&sigvec,
(FalconVariant::Falcon512.parameters().sig_bytelen - 41) * 8,
FalconVariant::Falcon512.parameters().sig_bytelen - 41,
)
.unwrap(),
};
Expand Down

0 comments on commit abcf6c7

Please sign in to comment.