use crate::prelude::*;
use proptest::prelude::*;
use rustywallet_keys::private_key::PrivateKey;
use std::collections::HashSet;
use std::time::Instant;
#[test]
fn property_performance_threshold() {
let batch_size = 10_000;
let start = Instant::now();
let keys = BatchGenerator::new()
.count(batch_size)
.parallel()
.generate_vec()
.unwrap();
let elapsed = start.elapsed();
assert_eq!(keys.len(), batch_size);
let rate = keys.len() as f64 / elapsed.as_secs_f64();
assert!(
rate >= 500.0,
"Performance below minimum threshold: {:.0} keys/sec (expected >= 500)",
rate
);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn property_key_uniqueness(batch_size in 10usize..500) {
let keys = BatchGenerator::new()
.count(batch_size)
.generate_vec()
.unwrap();
let hex_keys: HashSet<_> = keys.iter().map(|k| k.to_hex()).collect();
prop_assert_eq!(
hex_keys.len(),
keys.len(),
"Generated keys should all be unique"
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn property_batch_size_compliance(batch_size in 1usize..1000) {
let keys = BatchGenerator::new()
.count(batch_size)
.generate_vec()
.unwrap();
prop_assert_eq!(
keys.len(),
batch_size,
"Should generate exactly {} keys",
batch_size
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn property_ec_point_addition(start_value in 1u64..1000000) {
let hex = format!("{:064x}", start_value);
let base = PrivateKey::from_hex(&hex).unwrap();
let scanner = KeyScanner::new(base.clone())
.direction(ScanDirection::Forward);
let keys: Vec<_> = scanner.scan_range(5)
.map(|r| r.unwrap())
.collect();
for (i, key) in keys.iter().enumerate() {
let expected_hex = format!("{:064x}", start_value + i as u64);
prop_assert_eq!(
key.to_hex(),
expected_hex,
"Key {} should be base + {}", i, i
);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn property_bidirectional_scanning(
start_value in 100u64..1000000,
steps in 1usize..50
) {
let hex = format!("{:064x}", start_value);
let base = PrivateKey::from_hex(&hex).unwrap();
let forward_scanner = KeyScanner::new(base.clone())
.direction(ScanDirection::Forward);
let forward_keys: Vec<_> = forward_scanner.scan_range(steps + 1)
.map(|r| r.unwrap())
.collect();
let last_forward = forward_keys.last().unwrap().clone();
let backward_scanner = KeyScanner::new(last_forward)
.direction(ScanDirection::Backward);
let backward_keys: Vec<_> = backward_scanner.scan_range(steps + 1)
.map(|r| r.unwrap())
.collect();
let last_backward = backward_keys.last().unwrap();
prop_assert_eq!(
base.to_hex(),
last_backward.to_hex(),
"Forward {} then backward {} should return to original",
steps, steps
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn property_iterator_incremental(batch_size in 10usize..200) {
let mut stream = BatchGenerator::new()
.count(batch_size)
.generate()
.unwrap();
let mut count = 0;
while let Some(result) = stream.next() {
prop_assert!(result.is_ok(), "Each key should be valid");
count += 1;
}
prop_assert_eq!(count, batch_size, "Should yield exactly {} keys", batch_size);
}
}
#[test]
fn property_thread_independence() {
let keys1 = BatchGenerator::new()
.count(1000)
.parallel()
.generate_vec()
.unwrap();
let keys2 = BatchGenerator::new()
.count(1000)
.parallel()
.generate_vec()
.unwrap();
let set1: HashSet<_> = keys1.iter().map(|k| k.to_hex()).collect();
let set2: HashSet<_> = keys2.iter().map(|k| k.to_hex()).collect();
let intersection: Vec<_> = set1.intersection(&set2).collect();
assert!(
intersection.is_empty(),
"Parallel runs should produce independent keys"
);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn property_format_export_consistency(batch_size in 1usize..50) {
let keys = BatchGenerator::new()
.count(batch_size)
.generate_vec()
.unwrap();
for key in keys {
let hex = key.to_hex();
prop_assert_eq!(hex.len(), 64, "Hex should be 64 chars");
let bytes = key.to_bytes();
prop_assert_eq!(bytes.len(), 32, "Bytes should be 32 bytes");
let recovered = PrivateKey::from_hex(&hex).unwrap();
prop_assert_eq!(key.clone(), recovered, "Hex round-trip should preserve key");
let recovered = PrivateKey::from_bytes(bytes).unwrap();
prop_assert_eq!(key, recovered, "Bytes round-trip should preserve key");
}
}
}
#[test]
fn property_configuration_validation() {
assert!(BatchConfig::default().validate().is_ok());
assert!(BatchConfig::fast().validate().is_ok());
assert!(BatchConfig::balanced().validate().is_ok());
assert!(BatchConfig::memory_efficient().validate().is_ok());
let config = BatchConfig::default().with_batch_size(0);
assert!(config.validate().is_err());
let config = BatchConfig::default().with_chunk_size(0);
assert!(config.validate().is_err());
let config = BatchConfig::default().with_thread_count(Some(0));
assert!(config.validate().is_err());
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn property_cryptographic_safety(batch_size in 1usize..100) {
let keys = BatchGenerator::new()
.count(batch_size)
.generate_vec()
.unwrap();
for key in keys {
let bytes = key.to_bytes();
prop_assert!(
PrivateKey::is_valid(&bytes),
"All generated keys should be valid"
);
let pubkey = key.public_key();
let compressed = pubkey.to_compressed();
prop_assert_eq!(compressed.len(), 33, "Public key should be 33 bytes compressed");
}
}
}
#[test]
fn property_stream_memory_efficiency() {
let stream = BatchGenerator::new()
.count(1_000_000)
.chunk_size(100)
.generate()
.unwrap();
let keys: Vec<_> = stream.take(10).collect();
assert_eq!(keys.len(), 10);
assert!(keys.iter().all(|r| r.is_ok()));
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn property_memory_bounded_streaming(
chunk_size in 10usize..100,
take_count in 1usize..50
) {
let stream = BatchGenerator::new()
.count(10_000) .chunk_size(chunk_size)
.generate()
.unwrap();
let keys: Vec<_> = stream.take(take_count).collect();
prop_assert_eq!(keys.len(), take_count);
prop_assert!(keys.iter().all(|r| r.is_ok()));
}
}
#[test]
fn property_parallel_ordering_determinism() {
let keys = BatchGenerator::new()
.count(100)
.parallel()
.deterministic()
.generate_vec()
.unwrap();
assert_eq!(keys.len(), 100);
let hex_keys: HashSet<_> = keys.iter().map(|k| k.to_hex()).collect();
assert_eq!(hex_keys.len(), 100);
}
#[test]
fn property_fast_key_correctness() {
use crate::fast_gen::FastKeyGenerator;
let keys = FastKeyGenerator::new(1000)
.parallel(true)
.generate();
assert_eq!(keys.len(), 1000);
let hex_keys: HashSet<_> = keys.iter().map(|k| k.to_hex()).collect();
assert_eq!(hex_keys.len(), 1000);
for key in &keys {
assert!(PrivateKey::is_valid(&key.to_bytes()));
let _ = key.public_key();
}
}
#[test]
fn property_fast_fallback_reliability() {
use crate::fast_gen::FastKeyGenerator;
let keys_seq = FastKeyGenerator::new(100)
.parallel(false)
.generate();
let keys_par = FastKeyGenerator::new(100)
.parallel(true)
.generate();
assert_eq!(keys_seq.len(), 100);
assert_eq!(keys_par.len(), 100);
let hex_seq: HashSet<_> = keys_seq.iter().map(|k| k.to_hex()).collect();
let hex_par: HashSet<_> = keys_par.iter().map(|k| k.to_hex()).collect();
assert_eq!(hex_seq.len(), 100);
assert_eq!(hex_par.len(), 100);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn property_security_preservation(batch_size in 1usize..50) {
let batch_keys = BatchGenerator::new()
.count(batch_size)
.generate_vec()
.unwrap();
for key in batch_keys {
let individual = PrivateKey::random();
prop_assert_eq!(key.to_hex().len(), individual.to_hex().len());
prop_assert_eq!(key.to_bytes().len(), individual.to_bytes().len());
let batch_pub = key.public_key();
let ind_pub = individual.public_key();
prop_assert_eq!(batch_pub.to_compressed().len(), ind_pub.to_compressed().len());
}
}
}
#[test]
fn property_partial_failure_handling() {
let result = BatchConfig::default()
.with_batch_size(0)
.validate();
assert!(result.is_err());
let result = BatchGenerator::with_config(
BatchConfig::default().with_batch_size(0)
).generate_vec();
assert!(result.is_err());
}
#[test]
fn property_progress_accuracy() {
let base = PrivateKey::from_hex(
"0000000000000000000000000000000000000000000000000000000000000100"
).unwrap();
let scanner = KeyScanner::new(base);
let keys: Vec<_> = scanner.scan_range(100)
.map(|r| r.unwrap())
.collect();
assert_eq!(keys.len(), 100);
for (i, key) in keys.iter().enumerate() {
let expected = format!("{:064x}", 0x100 + i);
assert_eq!(key.to_hex(), expected);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn property_batch_address_consistency_p2pkh(batch_size in 1usize..50) {
use crate::address::{BatchAddressGenerator, BatchAddressType};
use rustywallet_address::{Network, P2PKHAddress};
let generator = BatchAddressGenerator::new(BatchAddressType::P2PKH, Network::BitcoinMainnet);
let addresses = generator.generate_vec(batch_size).unwrap();
for (key, addr) in addresses {
let pubkey = key.public_key();
let derived_addr = P2PKHAddress::from_public_key(&pubkey, Network::BitcoinMainnet)
.unwrap()
.to_string();
prop_assert_eq!(
addr,
derived_addr,
"Batch-generated P2PKH address should match manually derived address"
);
}
}
#[test]
fn property_batch_address_consistency_p2wpkh(batch_size in 1usize..50) {
use crate::address::{BatchAddressGenerator, BatchAddressType};
use rustywallet_address::{Network, P2WPKHAddress};
let generator = BatchAddressGenerator::new(BatchAddressType::P2WPKH, Network::BitcoinMainnet);
let addresses = generator.generate_vec(batch_size).unwrap();
for (key, addr) in addresses {
let pubkey = key.public_key();
let derived_addr = P2WPKHAddress::from_public_key(&pubkey, Network::BitcoinMainnet)
.unwrap()
.to_string();
prop_assert_eq!(
addr,
derived_addr,
"Batch-generated P2WPKH address should match manually derived address"
);
}
}
#[test]
fn property_batch_address_consistency_p2tr(batch_size in 1usize..50) {
use crate::address::{BatchAddressGenerator, BatchAddressType};
use rustywallet_address::{Network, P2TRAddress};
let generator = BatchAddressGenerator::new(BatchAddressType::P2TR, Network::BitcoinMainnet);
let addresses = generator.generate_vec(batch_size).unwrap();
for (key, addr) in addresses {
let pubkey = key.public_key();
let derived_addr = P2TRAddress::from_public_key(&pubkey, Network::BitcoinMainnet)
.unwrap()
.to_string();
prop_assert_eq!(
addr,
derived_addr,
"Batch-generated P2TR address should match manually derived address"
);
}
}
#[test]
fn property_batch_address_consistency_stream(batch_size in 1usize..50) {
use crate::address::{BatchAddressGenerator, BatchAddressType};
use rustywallet_address::{Network, P2WPKHAddress};
let generator = BatchAddressGenerator::new(BatchAddressType::P2WPKH, Network::BitcoinMainnet);
let stream = generator.generate_stream(batch_size);
for (key, addr) in stream {
let pubkey = key.public_key();
let derived_addr = P2WPKHAddress::from_public_key(&pubkey, Network::BitcoinMainnet)
.unwrap()
.to_string();
prop_assert_eq!(
addr,
derived_addr,
"Streamed address should match manually derived address"
);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn property_batch_address_consistency_testnet(batch_size in 1usize..30) {
use crate::address::{BatchAddressGenerator, BatchAddressType};
use rustywallet_address::{Network, P2WPKHAddress, P2TRAddress};
let generator = BatchAddressGenerator::new(BatchAddressType::P2WPKH, Network::BitcoinTestnet);
let addresses = generator.generate_vec(batch_size).unwrap();
for (key, addr) in addresses {
let pubkey = key.public_key();
let derived_addr = P2WPKHAddress::from_public_key(&pubkey, Network::BitcoinTestnet)
.unwrap()
.to_string();
prop_assert!(addr.starts_with("tb1q"), "Testnet P2WPKH should start with tb1q");
prop_assert_eq!(addr, derived_addr);
}
let generator = BatchAddressGenerator::new(BatchAddressType::P2TR, Network::BitcoinTestnet);
let addresses = generator.generate_vec(batch_size).unwrap();
for (key, addr) in addresses {
let pubkey = key.public_key();
let derived_addr = P2TRAddress::from_public_key(&pubkey, Network::BitcoinTestnet)
.unwrap()
.to_string();
prop_assert!(addr.starts_with("tb1p"), "Testnet P2TR should start with tb1p");
prop_assert_eq!(addr, derived_addr);
}
}
}