Skip to content
Open
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
11 changes: 11 additions & 0 deletions QUALITY_AND_STYLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ yourself whether this function would take 6-months-from-now-you more than 10 min
there comments you could add that would help future you get back up to speed faster about what this code is doing and
which parts were done for a very specific reason and should not be changed on a whim.

## Naming Conventions

All normal rust naming convensions from clippy apply. In addition, some library-specific naming conventions:

* In constants, "LEN" is the length of a value in bytes (typically used for sizing arrays), whereas "SIZE" is a value in
bits (typically used as a security parameter). For example SHA256 could have constants `HASH_SIZE = 256` and
`HASH_LEN = 32`.
* Functions that are part of a stateful streaming api should be named `do_*()`.
* We use "pk" for public key and "sk" for secret key / private key. (some other libraries use "pub" and "priv", but "
pub" is a keyword in rust, and "pubkey / privkey" is verbose :P )

## APIs

Where possible, primitives should expose "one-shot APIs" that simply take data and return a result as a static member
Expand Down
2 changes: 2 additions & 0 deletions alpha_0.1.2_release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* Check the crate release checklist and run claude against the style guide (maybe Francis could cross-check me)
* Run Crucible testing
* Add factories for ML-DSA and ML-KEM (if we are keeping factories, see below)
* After merging the Signer/Verifier, Encrypter/Decrypter split, check if the keygen_from_rng() is still on the right
trait.
* Split the Signature trait into a Signer and a Verifier so that, for example, we can implement the verifier for MTC in
a different struct from the signer; or so that you can get FIPS compliance on old algorithms that are currently only
FIPS-allowed for verification of existing signatures but not for creation of new ones.
Expand Down
92 changes: 92 additions & 0 deletions crypto/core-test-framework/src/fixed_seed_rng.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//! A deterministic fake [RNG] for reproducible tests.

use bouncycastle_core::errors::RNGError;
use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType};
use bouncycastle_core::traits::{RNG, SecurityStrength};

/// A test-only fake [RNG] that produces a fixed, fully deterministic byte stream.
///
/// The stream is the `SEED_LEN`-byte seed repeated indefinitely. A single internal counter is
/// shared across every [RNG] method, so each byte handed out — whether through
/// [RNG::next_bytes_out], [RNG::next_bytes], [RNG::next_int], or [RNG::fill_keymaterial_out] —
/// advances the same stream. Two instances built from the same seed therefore emit identical
/// streams, which is what makes RNG-driven operations reproducible (and comparable against their
/// seed/`m`-driven internal counterparts) in tests.
///
/// This is a deterministic stub for tests only; it is in no way a secure RNG.
pub struct FixedSeedRNG<const SEED_LEN: usize> {
seed: [u8; SEED_LEN],
counter: usize,
}

impl<const SEED_LEN: usize> FixedSeedRNG<SEED_LEN> {
/// Create an instance that emits `seed` repeated indefinitely, starting from its first byte.
pub fn new(seed: [u8; SEED_LEN]) -> Self {
Self { seed, counter: 0 }
}

/// Pull the next byte from the deterministic stream and advance the counter.
fn next_byte(&mut self) -> u8 {
let b = self.seed[self.counter % SEED_LEN];
self.counter += 1;
b
}
}

impl<const SEED_LEN: usize> RNG for FixedSeedRNG<SEED_LEN> {
/// No-op: this fake RNG ignores reseeding, since its stream is fixed by construction.
fn add_seed_keymaterial(
&mut self,
_additional_seed: &dyn KeyMaterialTrait,
) -> Result<(), RNGError> {
Ok(())
}

fn next_int(&mut self) -> Result<u32, RNGError> {
let mut buf = [0u8; 4];
for slot in buf.iter_mut() {
*slot = self.next_byte();
}
Ok(u32::from_le_bytes(buf))
}

fn next_bytes(&mut self, len: usize) -> Result<Vec<u8>, RNGError> {
let mut out = vec![0u8; len];
for slot in out.iter_mut() {
*slot = self.next_byte();
}
Ok(out)
}

fn next_bytes_out(&mut self, out: &mut [u8]) -> Result<usize, RNGError> {
for slot in out.iter_mut() {
*slot = self.next_byte();
}
Ok(out.len())
}

/// Fill `out` to capacity from the stream and mark it as a full-entropy 256-bit seed,
/// mirroring what a real DRBG's `generate_keymaterial_out` produces. A 256-bit security
/// strength is enough for every ML-KEM / ML-DSA parameter set.
fn fill_keymaterial_out(&mut self, out: &mut dyn KeyMaterialTrait) -> Result<usize, RNGError> {
out.allow_hazardous_operations();
let len = {
// mut_ref_to_bytes is infallible here because we just allowed hazardous operations.
let buf = out.mut_ref_to_bytes().unwrap();
for slot in buf.iter_mut() {
*slot = self.seed[self.counter % SEED_LEN];
self.counter += 1;
}
buf.len()
};
out.set_key_len(len)?;
out.set_key_type(KeyType::Seed)?;
out.set_security_strength(SecurityStrength::_256bit)?;
out.drop_hazardous_operations();
Ok(len)
}

fn security_strength(&self) -> SecurityStrength {
SecurityStrength::_256bit
}
}
19 changes: 19 additions & 0 deletions crypto/core-test-framework/src/kem.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::FixedSeedRNG;
use bouncycastle_core::errors::KEMError;
use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey};

Expand Down Expand Up @@ -36,6 +37,24 @@ impl TestFrameworkKEM {
let ss1 = KEMAlg::decaps(&sk, &ct).unwrap();
assert_eq!(ss, ss1);

// Test that encaps_rng is deterministic in its RNG input: two encapsulations against the
// same public key, each fed an RNG that emits identical bytes, must produce the same
// shared secret and ciphertext.
{
let mut rng_a = FixedSeedRNG::new([0x5A; 64]);
let mut rng_b = FixedSeedRNG::new([0x5A; 64]);
let (ss_a, ct_a) = KEMAlg::encaps_rng(&pk, &mut rng_a).unwrap();
let (ss_b, ct_b) = KEMAlg::encaps_rng(&pk, &mut rng_b).unwrap();
assert_eq!(
ss_a, ss_b,
"encaps_rng shared secret must be deterministic given fixed RNG output"
);
assert_eq!(
ct_a, ct_b,
"encaps_rng ciphertext must be deterministic given fixed RNG output"
);
}

// Test non-determinism
if !self.alg_is_deterministic {
let (ss1, ct1) = KEMAlg::encaps(&pk).unwrap();
Expand Down
4 changes: 4 additions & 0 deletions crypto/core-test-framework/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ pub mod kdf;
pub mod kem;
pub mod mac;
pub mod signature;
pub mod symmetric_ciphers;

mod fixed_seed_rng;
pub use fixed_seed_rng::FixedSeedRNG;

pub const DUMMY_SEED_512: &[u8; 512] = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff";

Expand Down
Loading