Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions cryptography/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ commonware-parallel.workspace = true
commonware-utils.workspace = true
ecdsa.workspace = true
ed25519-consensus = { workspace = true, default-features = false }
num-rational = { workspace = true, optional = true }
num-traits = { workspace = true, optional = true }
p256 = { workspace = true, features = ["ecdsa"] }
rand.workspace = true
rand_chacha.workspace = true
Expand Down Expand Up @@ -83,6 +85,8 @@ std = [
"ecdsa/std",
"ed25519-consensus/std",
"getrandom/std",
"num-rational",
"num-traits",
"p256/std",
"rand/std",
"rand/std_rng",
Expand Down
4 changes: 2 additions & 2 deletions cryptography/conformance.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ hash = "c0501d4a691d1fccec7c5906e8608228569d24164150edd215838593e3b77512"
n_cases = 65536
hash = "698dac6cf35fcd1557a962e7215da4251d7e42f68a2331b70ff0dd542c4116dd"

["commonware_cryptography::bloomfilter::conformance::FpRateBuckets"]
["commonware_cryptography::bloomfilter::conformance::RationalOptimalBits"]
n_cases = 1024
hash = "723f3a5fd8b147f722d5fa990f39b3ffd6a2f0df46d00dcb1e294647b565eb85"
hash = "62ed87a0f03d0ea9bc55b97389d288ad1dc8b43d01ce3fc3ee946457107423f3"

["commonware_cryptography::bls12381::certificate::multisig::tests::conformance::CodecConformance<Certificate<MinSig>>"]
n_cases = 65536
Expand Down
2 changes: 2 additions & 0 deletions cryptography/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ commonware-codec = { workspace = true, features = ["std"] }
commonware-cryptography = { workspace = true, features = ["std", "arbitrary"] }
commonware-math.workspace = true
commonware-parallel = { workspace = true, features = ["std"] }
commonware-utils = { workspace = true, features = ["std"] }
ed25519-zebra.workspace = true
libfuzzer-sys.workspace = true
num-rational.workspace = true
p256 = { workspace = true, features = ["ecdsa"] }
rand.workspace = true
sha2.workspace = true
Expand Down
27 changes: 18 additions & 9 deletions cryptography/fuzz/fuzz_targets/bloomfilter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
use arbitrary::Arbitrary;
use commonware_codec::{Decode, Encode, EncodeSize};
use commonware_cryptography::{sha256::Sha256, BloomFilter};
use commonware_utils::rational::BigRationalExt;
use libfuzzer_sys::fuzz_target;
use num_rational::BigRational;
use std::{
collections::HashSet,
num::{NonZeroU16, NonZeroU8, NonZeroUsize},
Expand All @@ -26,7 +28,8 @@ enum Constructor {
},
WithRate {
expected_items: NonZeroU16,
fp_rate: f64,
fp_numerator: u32,
fp_denominator: u32,
},
}

Expand All @@ -43,11 +46,13 @@ impl<'a> Arbitrary<'a> for Constructor {
Ok(Constructor::New { hashers, bits })
} else {
let expected_items = u.arbitrary::<NonZeroU16>()?;
// Generate f64 in range (0.0, 1.0) exclusive
let fp_rate = u.int_in_range(1u32..=u32::MAX - 1)? as f64 / u32::MAX as f64;
// Generate FP rate as rational: numerator in [1, denominator-1] to ensure (0, 1)
let fp_denominator = u.int_in_range(2u32..=10_000)?;
let fp_numerator = u.int_in_range(1u32..=fp_denominator - 1)?;
Ok(Constructor::WithRate {
expected_items,
fp_rate,
fp_numerator,
fp_denominator,
})
}
}
Expand Down Expand Up @@ -77,11 +82,15 @@ fn fuzz(input: FuzzInput) {
Constructor::New { hashers, bits } => BloomFilter::<Sha256>::new(hashers, bits.into()),
Constructor::WithRate {
expected_items,
fp_rate,
} => BloomFilter::<Sha256>::with_rate(
NonZeroUsize::new(expected_items.get() as usize).unwrap(),
fp_rate,
),
fp_numerator,
fp_denominator,
} => {
let fp_rate = BigRational::from_frac_u64(fp_numerator as u64, fp_denominator as u64);
BloomFilter::<Sha256>::with_rate(
NonZeroUsize::new(expected_items.get() as usize).unwrap(),
fp_rate,
)
}
};

let cfg = (bf.hashers(), bf.bits().try_into().unwrap());
Expand Down
14 changes: 11 additions & 3 deletions cryptography/src/bloomfilter/benches/contains.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
use commonware_cryptography::{blake3::Blake3, sha256::Sha256, BloomFilter, Hasher};
use commonware_utils::rational::BigRationalExt;
use criterion::{criterion_group, Criterion};
use num_rational::BigRational;
use rand::{rngs::StdRng, RngCore, SeedableRng};
use std::{collections::HashSet, hint::black_box, num::NonZeroUsize};

const ITEM_SIZES: [usize; 3] = [32, 2048, 4096];
const NUM_ITEMS: usize = 10000;
const FP_RATES: [f64; 2] = [0.1, 0.001];

fn fp_rates() -> [(BigRational, &'static str); 2] {
[
(BigRational::from_frac_u64(1, 10), "10%"),
(BigRational::from_frac_u64(1, 1000), "0.1%"),
]
}

fn run_contains_bench<H: Hasher>(c: &mut Criterion, hasher: &str, query_inserted: bool) {
let query_type = if query_inserted {
Expand All @@ -14,7 +22,7 @@ fn run_contains_bench<H: Hasher>(c: &mut Criterion, hasher: &str, query_inserted
"negative"
};
for item_size in ITEM_SIZES {
for fp_rate in FP_RATES {
for (fp_rate, fp_label) in fp_rates() {
// Create and populate the bloom filter
let mut rng = StdRng::seed_from_u64(42);
let mut bf =
Expand Down Expand Up @@ -52,7 +60,7 @@ fn run_contains_bench<H: Hasher>(c: &mut Criterion, hasher: &str, query_inserted
module_path!(),
hasher,
item_size,
fp_rate,
fp_label,
query_type
),
|b| {
Expand Down
16 changes: 12 additions & 4 deletions cryptography/src/bloomfilter/benches/insert.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
use commonware_cryptography::{blake3::Blake3, sha256::Sha256, BloomFilter, Hasher};
use commonware_utils::rational::BigRationalExt;
use criterion::{criterion_group, BatchSize, Criterion};
use num_rational::BigRational;
use rand::{rngs::StdRng, RngCore, SeedableRng};
use std::num::NonZeroUsize;

const ITEM_SIZES: [usize; 3] = [32, 2048, 4096];
const NUM_ITEMS: usize = 10000;
const FP_RATES: [f64; 2] = [0.1, 0.001];

fn fp_rates() -> [(BigRational, &'static str); 2] {
[
(BigRational::from_frac_u64(1, 10), "10%"),
(BigRational::from_frac_u64(1, 1000), "0.1%"),
]
}

fn run_insert_bench<H: Hasher>(c: &mut Criterion, hasher: &str) {
for item_size in ITEM_SIZES {
for fp_rate in FP_RATES {
for (fp_rate, fp_label) in fp_rates() {
// Pre-generate items to insert
let mut rng = StdRng::seed_from_u64(42);
let items: Vec<Vec<u8>> = (0..NUM_ITEMS)
Expand All @@ -26,15 +34,15 @@ fn run_insert_bench<H: Hasher>(c: &mut Criterion, hasher: &str) {
module_path!(),
hasher,
item_size,
fp_rate
fp_label
),
|b| {
let mut idx = 0;
b.iter_batched(
|| {
BloomFilter::<H>::with_rate(
NonZeroUsize::new(NUM_ITEMS).unwrap(),
fp_rate,
fp_rate.clone(),
)
},
|mut bf| {
Expand Down
41 changes: 20 additions & 21 deletions cryptography/src/bloomfilter/conformance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,63 +3,62 @@
use super::{BloomFilter, Sha256};
use commonware_codec::conformance::CodecConformance;
use commonware_conformance::Conformance;
use commonware_utils::rational::BigRationalExt;
use core::num::NonZeroUsize;
use num_rational::BigRational;

commonware_conformance::conformance_tests! {
CodecConformance<BloomFilter>,
FpRateBuckets => 1024,
RationalOptimalBits => 1024,
}

/// Conformance test for FP rate bucket calculations and with_rate constructor.
/// Conformance test for rational-based optimal_bits and with_rate.
/// Verifies that optimal_bits, optimal_hashers, and with_rate produce stable
/// outputs across all four FP rate buckets for various expected_items values.
struct FpRateBuckets;
/// outputs for various expected_items values and FP rates expressed as rationals.
struct RationalOptimalBits;

impl Conformance for FpRateBuckets {
impl Conformance for RationalOptimalBits {
async fn commit(seed: u64) -> Vec<u8> {
let mut log = Vec::new();

// Use seed to vary expected_items (1 to 1M range)
let expected_items = ((seed % 1_000_000) + 1) as usize;

// Test all four FP rate buckets with representative values
// Test FP rates as rationals: 1/10000, 1/1000, 1/100, 1/10
let fp_rates = [
0.0001, // FP_1E4 bucket (~0.01%)
0.001, // FP_1E3 bucket (~0.1%)
0.01, // FP_1E2 bucket (~1%)
0.1, // FP_1E1 bucket (~10%)
BigRational::from_frac_u64(1, 10_000), // 0.01%
BigRational::from_frac_u64(1, 1_000), // 0.1%
BigRational::from_frac_u64(1, 100), // 1%
BigRational::from_frac_u64(1, 10), // 10%
];

for &fp_rate in &fp_rates {
for fp_rate in &fp_rates {
// Test individual functions
let bits = BloomFilter::<Sha256>::optimal_bits(expected_items, fp_rate);
let hashers = BloomFilter::<Sha256>::optimal_hashers(expected_items, bits);

log.extend((expected_items as u64).to_le_bytes());
log.extend(fp_rate.to_le_bytes());
log.extend((bits as u64).to_le_bytes());
log.extend(hashers.to_le_bytes());

// Test with_rate constructor produces same results
let filter = BloomFilter::<Sha256>::with_rate(
NonZeroUsize::new(expected_items).unwrap(),
fp_rate,
fp_rate.clone(),
);
log.extend((filter.bits().get() as u64).to_le_bytes());
log.extend(filter.hashers().get().to_le_bytes());
}

// Also test bucket boundaries to catch rounding changes
// Test some boundary values
let boundary_rates = [
0.00014, // Just below FP_1E4/FP_1E3 boundary
0.00016, // Just above FP_1E4/FP_1E3 boundary
0.00104, // Just below FP_1E3/FP_1E2 boundary
0.00106, // Just above FP_1E3/FP_1E2 boundary
0.01004, // Just below FP_1E2/FP_1E1 boundary
0.01006, // Just above FP_1E2/FP_1E1 boundary
BigRational::from_frac_u64(1, 7_000), // Between 0.01% and 0.1%
BigRational::from_frac_u64(1, 500), // Between 0.1% and 1%
BigRational::from_frac_u64(1, 50), // Between 1% and 10%
BigRational::from_frac_u64(3, 100), // 3%
];

for &fp_rate in &boundary_rates {
for fp_rate in &boundary_rates {
let bits = BloomFilter::<Sha256>::optimal_bits(expected_items, fp_rate);
log.extend((bits as u64).to_le_bytes());
}
Expand Down
Loading
Loading