diff --git a/QUALITY_AND_STYLE.md b/QUALITY_AND_STYLE.md index 94036d0..581582c 100644 --- a/QUALITY_AND_STYLE.md +++ b/QUALITY_AND_STYLE.md @@ -1,75 +1,128 @@ This document lists general quality and style guidelines used across the library. Hint: ask an AI to help review your PR against this style guide. - # Architecture The Bounce Castle Rust project should be broken up into individual modular crates named `bouncycastle_*`. -The project aims to be completely self-contained with zero external dependencies in the runtime code. External dependencies are ok in test or benchmarking code. +The project aims to be completely self-contained with zero external dependencies in the runtime code. External +dependencies are ok in test or benchmarking code. lib.rs for all crates needs to contain: `#![forbid(missing_docs)]`, `#![no_std]`. - All primitives must be accompanied by a CLI in `/cli`. - # Quality ## Tests -All crates must have tests in `src/tests`. Part of writing code that treats future maintainers as malicious is that all functions that form part of the public interface should have their expected behaviour fully constrained with tests. In other words, any behaviour change of the library that could cause a change in a calling application should also cause a test in bc-rust to fail. An excellent tool for achieving this is `cargo mutants` which must be run on every crate and each failed mutant must be investigated. We do not require `cargo mutants` to be clean because it's reasonably common, especially in low-level crypto code, that there are multiple correct ways to write the same code; for example where swapping an OR for an XOR results in functionally equivalent code. +All crates must have tests in `src/tests`. Part of writing code that treats future maintainers as malicious is that all +functions that form part of the public interface should have their expected behaviour fully constrained with tests. In +other words, any behaviour change of the library that could cause a change in a calling application should also cause a +test in bc-rust to fail. An excellent tool for achieving this is `cargo mutants` which must be run on every crate and +each failed mutant must be investigated. We do not require `cargo mutants` to be clean because it's reasonably common, +especially in low-level crypto code, that there are multiple correct ways to write the same code; for example where +swapping an OR for an XOR results in functionally equivalent code. -Where the behaviour of a function is critical to test but cannot be tested from outside the crate because it is on a private function, in-line tests in the source file should be used. - -All traits in `bouncycastle-core` must have corresponding tests in `bouncycastle-core-test-framework` that exercise all behaviours and error conditions that are comment to all implementations of that trait. +Where the behaviour of a function is critical to test but cannot be tested from outside the crate because it is on a +private function, in-line tests in the source file should be used. +All traits in `bouncycastle-core` must have corresponding tests in `bouncycastle-core-test-framework` that exercise all +behaviours and error conditions that are comment to all implementations of that trait. ## Performance Benchmarks -Any crate that contains an algorithm were runtime matters must have cargo-compatible performance benchmarks in a `/benches` folder. +Any crate that contains an algorithm were runtime matters must have cargo-compatible performance benchmarks in a +`/benches` folder. -The benches must cover all algorithms. If there are multiple variants of an algorithm with different performance characteristics (such as with pre-expanded keys), then these must each be benchmarked separately. Separate benchmarks should not be written for different APIs for accessing the same underlying implementation; such as one-shot and streaming APIs that use the same core algorithm implementation. +The benches must cover all algorithms. If there are multiple variants of an algorithm with different performance +characteristics (such as with pre-expanded keys), then these must each be benchmarked separately. Separate benchmarks +should not be written for different APIs for accessing the same underlying implementation; such as one-shot and +streaming APIs that use the same core algorithm implementation. ## Stack Usage Benchmarks -Bouncy Castle Rust cares about the peak stack memory usage of its algorithms. Crates should be accompanied by a memory usage test harness in `/mem_usage_benches`. +Bouncy Castle Rust cares about the peak stack memory usage of its algorithms. Crates should be accompanied by a memory +usage test harness in `/mem_usage_benches`. # Style -Part of writing code that treats future maintainers as malicious is good inline comments. Anything even remotely tricky, or where naive modification would put it out of alignment with, for example, sample code in an RFC or FIPS spec should be commented line-by-line with the corresponding lines from the spec. This also helps with code review and certification. Any deviations from the spec should be noted and explained / justified. A good rule-of-thumb is to ask yourself whether this function would take 6-months-from-now-you more than 10 minutes to understand thoroughly, and are 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. +Part of writing code that treats future maintainers as malicious is good inline comments. Anything even remotely tricky, +or where naive modification would put it out of alignment with, for example, sample code in an RFC or FIPS spec should +be commented line-by-line with the corresponding lines from the spec. This also helps with code review and +certification. Any deviations from the spec should be noted and explained / justified. A good rule-of-thumb is to ask +yourself whether this function would take 6-months-from-now-you more than 10 minutes to understand thoroughly, and are +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 function that does not require object instantiation. +Where possible, primitives should expose "one-shot APIs" that simply take data and return a result as a static member +function that does not require object instantiation. -Other version of Bouncy Castle have a design pattern where stateful objects follow a pattern of new() -> init() -> do_update() -> do_final(), and then optionally reset() that sets the object back to an unitialized state. Instead, bc-rust does not have init() functions (moving this logic into new() or from() as appropriate), and consequently it also does not have reset(). Also, we take advantage of the rust borrow checker's syntax so that all do_final() functions are actually final, in other words they must take ownership of self `do_final(self, ...)` so that no subsequent calls can be made to this object (as opposed to the usual pattern of taking a ref to self as in `do_update(&self, ...)`). These tricks go a long way to reducing fallibility since now in general there is no (or very very little) object state to track and return errors about. +Other version of Bouncy Castle have a design pattern where stateful objects follow a pattern of new() -> init() -> +do_update() -> do_final(), and then optionally reset() that sets the object back to an unitialized state. Instead, +bc-rust does not have init() functions (moving this logic into new() or from() as appropriate), and consequently it also +does not have reset(). Also, we take advantage of the rust borrow checker's syntax so that all do_final() functions are +actually final, in other words they must take ownership of self `do_final(self, ...)` so that no subsequent calls can be +made to this object (as opposed to the usual pattern of taking a ref to self as in `do_update(&self, ...)`). These +tricks go a long way to reducing fallibility since now in general there is no (or very very little) object state to +track and return errors about. Any struct that holds sensitive data must impl the `core::Secret` trait and all associated super-traits. ## Fallibility -As much as humanly possible, Result and unwrap() should be used for "Bad input data" type things and not "Programmer didn't read the docs" type things. +As much as humanly possible, Result and unwrap() should be used for "Bad input data" type things and not "Programmer +didn't read the docs" type things. -`.unwrap()` causes system crashes. The use of `.unwrap()` should always be preceeded by testing that we're in a state where we know the call will succeed, or else there should be an inline comment explaining why the `.unwrap()` will always succeed. +`.unwrap()` causes system crashes. The use of `.unwrap()` should always be preceeded by testing that we're in a state +where we know the call will succeed, or else there should be an inline comment explaining why the `.unwrap()` will +always succeed. -Also, we want to avoid forcing users of the library from needing excessive amounts of `.unwrap()`. To this end, any function that returns a `Result` should be inspected closely to ensure that +Also, we want to avoid forcing users of the library from needing excessive amounts of `.unwrap()`. To this end, any +function that returns a `Result` should be inspected closely to ensure that -Therefore, public APIs should aim to avoid the use of Result if it is not strictly needed. This generally means that returning a `Result` is only used for instances where bad data was handed in to the function, or where something unrecoverable happened, like the internal RNG failed to initialize. `Result` must never be thrown out of convenience to the maintainer of bc-rust -- instead, get creative about how to check for and resolve error conditions within the function so that valid input will always produce valid output. Also, the rust language has a lot of features for turning runtime error conditions into compile-time error conditions. For example, if you find yourself taking in a reference to bytes `in: &[u8]` and then checking its length `if in.len() != LEN { return Err() }`, stop and instead change the function signature to `in: &[u8; LEN]` so that it is simply impossible for the caller to hand you data of the wrong length (this also has a small performance benefit since you don't need to do that if-check). In other contexts it might be possible to use rust typing system to track state change of an object instead of carrying a member variable that tracks it. +Therefore, public APIs should aim to avoid the use of Result if it is not strictly needed. This generally means that +returning a `Result` is only used for instances where bad data was handed in to the function, or where something +unrecoverable happened, like the internal RNG failed to initialize. `Result` must never be thrown out of convenience to +the maintainer of bc-rust -- instead, get creative about how to check for and resolve error conditions within the +function so that valid input will always produce valid output. Also, the rust language has a lot of features for turning +runtime error conditions into compile-time error conditions. For example, if you find yourself taking in a reference to +bytes `in: &[u8]` and then checking its length `if in.len() != LEN { return Err() }`, stop and instead change the +function signature to `in: &[u8; LEN]` so that it is simply impossible for the caller to hand you data of the wrong +length (this also has a small performance benefit since you don't need to do that if-check). In other contexts it might +be possible to use rust typing system to track state change of an object instead of carrying a member variable that +tracks it. -Use `./dev_scripts/quality_stats.sh` to see the fallibility metrics for the crate you're working on and try to get those numbers down. +Use `./dev_scripts/quality_stats.sh` to see the fallibility metrics for the crate you're working on and try to get those +numbers down. # Docs ## Usage Examples -The crate docs needs a section "Usage Examples" with sample code for all the major usage patterns of the primitives in the crate. +The crate docs needs a section "Usage Examples" with sample code for all the major usage patterns of the primitives in +the crate. ## Memory Usage -The crate docs needs a section "Memory Usage" with a table of the stack memory usage of each algorithm or primitive in the crate. +The crate docs needs a section "Memory Usage" with a table of the stack memory usage of each algorithm or primitive in +the crate. ## Security Considerations -Most crates should have a "Security Considerations" section that documents any footguns where the user of this crate could undermine their own security; for example where providing a seed or a nonce that is not truly random would completely undermine the algorithm. \ No newline at end of file +Most crates should have a "Security Considerations" section that documents any footguns where the user of this crate +could undermine their own security; for example where providing a seed or a nonce that is not truly random would +completely undermine the algorithm. \ No newline at end of file diff --git a/crypto/core-test-framework/src/lib.rs b/crypto/core-test-framework/src/lib.rs index 92f7215..20ae770 100644 --- a/crypto/core-test-framework/src/lib.rs +++ b/crypto/core-test-framework/src/lib.rs @@ -14,6 +14,7 @@ pub mod kdf; pub mod kem; pub mod mac; pub mod signature; +mod symmetric_ciphers; 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"; diff --git a/crypto/core-test-framework/src/symmetric_ciphers.rs b/crypto/core-test-framework/src/symmetric_ciphers.rs new file mode 100644 index 0000000..4d16ca3 --- /dev/null +++ b/crypto/core-test-framework/src/symmetric_ciphers.rs @@ -0,0 +1,361 @@ +use crate::{DUMMY_SEED_512, DUMMY_SEED_1024}; +use bouncycastle_core::errors::SymmetricCipherError; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::traits::{ + AEADCipher, BlockCipher, SecurityStrength, StreamCipher, SymmetricCipher, +}; + +pub struct TestFrameworkSymmetricCipher { + // Put any config options here +} + +impl TestFrameworkSymmetricCipher { + pub fn new() -> Self { + Self {} + } + + /// Test all the members of trait Hash against the given input-output pair. + /// This gives good baseline test coverage, but is not exhaustive. + pub fn test< + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + C: SymmetricCipher, + >( + &self, + ) { + let msg = b"The quick brown fox jumps over the lazy dog"; + + let key = KeyMaterial::::from_bytes_as_type( + &DUMMY_SEED_512[..KEY_LEN], + KeyType::SymmetricCipherKey, + ) + .unwrap(); + + // one-shot API + let mut ct = [0u8; 1024]; + let (iv, ct_bytes_written) = C::encrypt_out(&key, msg, &mut ct).unwrap(); + assert_ne!(ct_bytes_written, 0); + + let mut pt = [0u8; 1024]; + let pt_bytes_written = C::decrypt_out(&key, iv, &ct[..ct_bytes_written], &mut pt).unwrap(); + assert_ne!(pt_bytes_written, 0); + assert_eq!(msg, &pt[..pt_bytes_written]); + + // todo -- add tests for encrypt() / decrypt() wrapped in a #[cfg(std)] + + // messing with the ciphertext does not give back the same plaintext (or failing to decrypt is also ok) + ct[17] ^= 0xFF; + match C::decrypt_out(&key, iv, &ct[..ct_bytes_written], &mut pt) { + Ok(bytes_written) => { + // so it decrypted something, but it had better not match the original plaintext + assert_eq!(bytes_written, pt_bytes_written); + assert_ne!(&pt[..bytes_written], msg); + } + Err(SymmetricCipherError::DecryptionFailed) => { /* also ok */ } + _ => panic!("Unexpected error"), + }; + + // error case: KeyMaterial of wrong type + let mac_key = + KeyMaterial::::from_bytes_as_type(&DUMMY_SEED_512[..KEY_LEN], KeyType::MACKey) + .unwrap(); + match C::encrypt_out(&mac_key, msg, &mut ct) { + Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } + _ => panic!("Unexpected error"), + }; + + // error case: security strengths too weak and too strong + let mut key = KeyMaterial::::from_bytes_as_type( + &DUMMY_SEED_512[..KEY_LEN], + KeyType::SymmetricCipherKey, + ) + .unwrap(); + key.allow_hazardous_operations(); + + let security_strengths = [ + SecurityStrength::None, + SecurityStrength::_112bit, + SecurityStrength::_128bit, + SecurityStrength::_192bit, + SecurityStrength::_256bit, + ]; + for ss in security_strengths.iter() { + key.set_security_strength(ss.clone()).unwrap(); + + match C::encrypt_out(&mac_key, msg, &mut ct) { + Ok(_) => { + if ss >= &C::MAX_SECURITY_STRENGTH { /* good */ + } else { + panic!("Should have been a strong enough key"); + } + } + Err(SymmetricCipherError::KeyMaterialError(_)) => { + if ss < &C::MAX_SECURITY_STRENGTH { /* good */ + } else { + panic!("Should not have accepted a key weaker than algorithm"); + } + } + _ => panic!("Unexpected error"), + }; + } + } +} + +pub struct TestFrameworkBlockCipher { + // Put any config options here +} + +impl TestFrameworkBlockCipher { + pub fn new() -> Self { + Self {} + } + + pub fn test< + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + const BLOCK_LEN: usize, + C: BlockCipher, + >( + &self, + ) { + let key = KeyMaterial::::from_bytes_as_type( + &DUMMY_SEED_512[..KEY_LEN], + KeyType::SymmetricCipherKey, + ) + .unwrap(); + + // to test blocks, we'll chunk our dummy seed + let (mut encryptor, iv) = C::do_encrypt_init(&key).unwrap(); + let mut decryptor = C::do_decrypt_init(&key, &iv).unwrap(); + + for msg_chunk in DUMMY_SEED_512.as_chunks::().0.iter() { + let ct = encryptor.do_encrypt_block(msg_chunk).unwrap(); + let pt = decryptor.do_decrypt_block(&ct).unwrap(); + assert_eq!(msg_chunk, &pt); + } + + // do it again using the _out versions + + let (mut encryptor, iv) = C::do_encrypt_init(&key).unwrap(); + let mut decryptor = C::do_decrypt_init(&key, &iv).unwrap(); + + let mut ct = [0u8; BLOCK_LEN]; + let mut pt = [0u8; BLOCK_LEN]; + for msg_chunk in DUMMY_SEED_1024.as_chunks::().0.iter() { + let ct_bytes_written = encryptor.do_encrypt_block_out(msg_chunk, &mut ct).unwrap(); + assert_eq!(ct_bytes_written, BLOCK_LEN); + + let pt_bytes_written = decryptor.do_decrypt_block_out(&ct, &mut pt).unwrap(); + assert_eq!(pt_bytes_written, BLOCK_LEN); + + assert_eq!(msg_chunk, &pt); + } + + // test that the iv is random (ie not the same on two runs) + let (_encryptor, iv1) = C::do_encrypt_init(&key).unwrap(); + let (_encryptor, iv2) = C::do_encrypt_init(&key).unwrap(); + assert_ne!(iv1, iv2); + + // error case: KeyMaterial of wrong type + let mac_key = + KeyMaterial::::from_bytes_as_type(&DUMMY_SEED_512[..KEY_LEN], KeyType::MACKey) + .unwrap(); + match C::do_encrypt_init(&mac_key) { + Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } + _ => panic!("Unexpected error"), + }; + + // error case: security strengths too weak and too strong + let mut key = KeyMaterial::::from_bytes_as_type( + &DUMMY_SEED_512[..KEY_LEN], + KeyType::SymmetricCipherKey, + ) + .unwrap(); + key.allow_hazardous_operations(); + + let security_strengths = [ + SecurityStrength::None, + SecurityStrength::_112bit, + SecurityStrength::_128bit, + SecurityStrength::_192bit, + SecurityStrength::_256bit, + ]; + for ss in security_strengths.iter() { + key.set_security_strength(ss.clone()).unwrap(); + + match C::do_encrypt_init(&key) { + Ok(_) => { + if ss >= &C::MAX_SECURITY_STRENGTH { /* good */ + } else { + panic!("Should have been a strong enough key"); + } + } + Err(SymmetricCipherError::KeyMaterialError(_)) => { + if ss < &C::MAX_SECURITY_STRENGTH { /* good */ + } else { + panic!("Should not have accepted a key weaker than algorithm"); + } + } + _ => panic!("Unexpected error"), + }; + } + } +} + +pub struct TestFrameworkAEADCipher { + // Put any config options here +} + +impl TestFrameworkAEADCipher { + pub fn new() -> Self { + Self {} + } + + /// Test all the members of trait Hash against the given input-output pair. + /// This gives good baseline test coverage, but is not exhaustive. + pub fn test< + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + C: AEADCipher, + >( + &self, + ) { + let msg = b"The quick brown fox jumps over the lazy dog"; + let aad = b"some associated data"; + + let key = KeyMaterial::::from_bytes_as_type( + &DUMMY_SEED_512[..KEY_LEN], + KeyType::SymmetricCipherKey, + ) + .unwrap(); + + // one-shot API + let mut ct = [0u8; 1024]; + let (nonce, ct_bytes_written, tag) = C::aead_encrypt_out(&key, aad, msg, &mut ct).unwrap(); + if nonce.len() != 0 { + assert_ne!(nonce, [0u8; NONCE_LEN]); + } + assert_ne!(ct_bytes_written, 0); + assert_ne!(tag, [0u8; TAG_LEN]); + + let mut pt = [0u8; 1024]; + let pt_bytes_written = + C::aead_decrypt_out(&key, &nonce, aad, &ct[..ct_bytes_written], &tag, &mut pt).unwrap(); + assert_ne!(pt_bytes_written, 0); + assert_eq!(msg, &pt[..pt_bytes_written]); + + // todo -- add tests for aead_encrypt() / aead_decrypt() wrapped in a #[cfg(std)] + + // messing with the ciphertext does not give back the same plaintext (or failing to decrypt is also ok) + ct[17] ^= 0xFF; + match C::aead_decrypt_out(&key, &nonce, aad, &ct[..ct_bytes_written], &tag, &mut pt) { + Ok(bytes_written) => { + // so it decrypted something, but it had better not match the original plaintext + assert_eq!(bytes_written, pt_bytes_written); + assert_ne!(&pt[..bytes_written], msg); + } + Err(SymmetricCipherError::DecryptionFailed) => { /* also ok */ } + _ => panic!("Unexpected error"), + }; + + // messing with the aad causes the aead_decrypt to fail + match C::aead_decrypt_out( + &key, + &nonce, + b"not the right associated data", + &ct[..ct_bytes_written], + &tag, + &mut pt, + ) { + Err(SymmetricCipherError::AEADTagCheckFailed) => { /* good */ } + _ => panic!("Expected TagCheckFailed error"), + }; + + // messing with the tag causes the aead_decrypt to fail + match C::aead_decrypt_out( + &key, + &nonce, + aad, + &ct[..ct_bytes_written], + &[3u8; TAG_LEN], + &mut pt, + ) { + Err(SymmetricCipherError::AEADTagCheckFailed) => { /* good */ } + _ => panic!("Expected TagCheckFailed error"), + }; + + // multiple invocations give different nonces + let (nonce1, _ct_bytes_written, _tag) = + C::aead_encrypt_out(&key, aad, msg, &mut ct).unwrap(); + let (nonce2, _ct_bytes_written, _tag) = + C::aead_encrypt_out(&key, aad, msg, &mut ct).unwrap(); + assert_ne!(nonce1, nonce2); + + // error case: KeyMaterial of wrong type + let mac_key = + KeyMaterial::::from_bytes_as_type(&DUMMY_SEED_512[..KEY_LEN], KeyType::MACKey) + .unwrap(); + match C::encrypt_out(&mac_key, msg, &mut ct) { + Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } + _ => panic!("Unexpected error"), + }; + + // error case: security strengths too weak and too strong + let mut key = KeyMaterial::::from_bytes_as_type( + &DUMMY_SEED_512[..KEY_LEN], + KeyType::SymmetricCipherKey, + ) + .unwrap(); + key.allow_hazardous_operations(); + + let security_strengths = [ + SecurityStrength::None, + SecurityStrength::_112bit, + SecurityStrength::_128bit, + SecurityStrength::_192bit, + SecurityStrength::_256bit, + ]; + for ss in security_strengths.iter() { + key.set_security_strength(ss.clone()).unwrap(); + + match C::encrypt_out(&mac_key, msg, &mut ct) { + Ok(_) => { + if ss >= &C::MAX_SECURITY_STRENGTH { /* good */ + } else { + panic!("Should have been a strong enough key"); + } + } + Err(SymmetricCipherError::KeyMaterialError(_)) => { + if ss < &C::MAX_SECURITY_STRENGTH { /* good */ + } else { + panic!("Should not have accepted a key weaker than algorithm"); + } + } + _ => panic!("Unexpected error"), + }; + } + } +} + +pub struct TestFrameworkStreamCipher { + // Put any config options here +} + +impl TestFrameworkStreamCipher { + pub fn new() -> Self { + Self {} + } + + /// Test all the members of trait Hash against the given input-output pair. + /// This gives good baseline test coverage, but is not exhaustive. + pub fn test< + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + C: StreamCipher, + >( + &self, + ) { + todo!() + } +} diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index de80d9a..f382d0f 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -80,10 +80,29 @@ pub enum SignatureError { RNGError(RNGError), } +#[derive(Debug)] +pub enum SymmetricCipherError { + GenericError(&'static str), + AEADTagCheckFailed, + DecryptionFailed, + KeyMaterialError(KeyMaterialError), + RNGError(RNGError), + StateError(&'static str), +} +/*** Promotion functions ***/ +impl From for SymmetricCipherError { + fn from(e: KeyMaterialError) -> SymmetricCipherError { + Self::KeyMaterialError(e) + } +} +impl From for SymmetricCipherError { + fn from(e: RNGError) -> SymmetricCipherError { + Self::RNGError(e) + } +} -/*** Promotion functions ***/ impl From for HashError { fn from(e: KeyMaterialError) -> HashError { Self::KeyMaterialError(e) @@ -115,7 +134,9 @@ impl From for KEMError { } impl From for KEMError { - fn from(e: RNGError) -> KEMError { Self::RNGError(e) } + fn from(e: RNGError) -> KEMError { + Self::RNGError(e) + } } impl From for MACError { @@ -143,5 +164,7 @@ impl From for SignatureError { } impl From for SignatureError { - fn from(e: RNGError) -> SignatureError { Self::RNGError(e) } + fn from(e: RNGError) -> SignatureError { + Self::RNGError(e) + } } diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 27dd844..b945b59 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -1,9 +1,11 @@ //! Provides simplified abstracted APIs over classes of cryptigraphic primitives, such as Hash, KDF, etc. -use core::marker::Sized; -use core::fmt::{Debug, Display}; -use crate::errors::{HashError, KDFError, KEMError, MACError, RNGError, SignatureError}; +use crate::errors::{ + HashError, KDFError, KEMError, MACError, RNGError, SignatureError, SymmetricCipherError, +}; use crate::key_material::KeyMaterialTrait; +use core::fmt::{Debug, Display}; +use core::marker::Sized; // Imports needed for docs #[allow(unused_imports)] @@ -17,7 +19,207 @@ pub trait Algorithm { const MAX_SECURITY_STRENGTH: SecurityStrength; } -pub trait Hash : Default { +/// The basic one-shat encrypt and decrypt that all types of symmetric ciphers must implement. +/// These are meant to be simple, easy to use, secure, and fool-proof APIs, but they may result in +/// ciphertexts that are incompatible with other implementations as ciphers in more complex modes, such +/// as AEADs or stream ciphers may need to stick extra data either at the beginning or end of the ciphertext. +/// See the documentation of the underlying implementation for more details. +pub trait SymmetricCipher: + Algorithm + Secret +{ + #[cfg(std)] + /// A one-shot API to encrypt some plaintext with the given key. + /// This function returns the ciphertext as a Vec, and therefore is only available when compiling with std. + /// Returns a tuple containing the initialization data and the ciphertext. + fn encrypt( + key: &KeyMaterial, + plaintext: &[u8], + ) -> Result<([u8; INIT_DATA_LEN], Vec), SymmetricCipherError>; + /// A one-shot API to encrypt some plaintext with the given key. + /// This function takes a reference to the output buffer for the ciphertext, and is therefore available in no_std. + /// See the documentation for the underlying implementation for details on providing a ciphertext buffer of sufficient size; + /// typically the ciphertext is the same length as the plaintext, but some ciphers may have an expansion factor or require + /// extra space for a nonce or tag. + /// Returns a tuple containing the initialization data and the number of bytes written to the ciphertext buffer. + fn encrypt_out( + key: &KeyMaterial, + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result<([u8; INIT_DATA_LEN], usize), SymmetricCipherError>; + #[cfg(std)] + /// A one-shot API to decrypt some ciphertext with the given key. + /// This function returns the ciphertext as a Vec, and therefore is only available when compiling with std. + fn decrypt( + &self, + key: &KeyMaterial, + ciphertext: &[u8], + ) -> Result, SymmetricCipherError>; + /// A one-shot API to decrypt some ciphertext with the given key. + /// This function takes a reference to the output buffer for the plaintext, and is therefore available in no_std. + /// See the documentation for the underlying implementation for details on providing a plaintext buffer of sufficient size; + /// typically the ciphertext is the same length as the plaintext, but some ciphers may have an expansion factor or require + /// extra space for a nonce or tag. + /// Returns a tuple containing the initialization data and the number of bytes written to the plaintext buffer. + fn decrypt_out( + key: &KeyMaterial, + init_data: [u8; INIT_DATA_LEN], + ciphertext: &[u8], + plaintext: &mut [u8], + ) -> Result; +} + +/// The basic functions of a block cipher. +/// This trait allows for a block cipher to generate initialization data, such as an Initialization Vector (IV) or Counter (CTR) +/// which is not technically part of the ciphertext, but must be transmitted along with the ciphertext in order for the +/// recipient to perform successful decryption. The length of the initialization data is specified by the implementing struct +/// via the `INIT_DATA_SIZE` constant. +/// In order for these one-shot APIs to be usable securely in all contexts, the init data will be generated +/// securely by the block cipher implementation and returned along with the ciphertext, and there is no API for the +/// user to provide the init data. If you require this functionality, see the documentation for the underlying implementation. +pub trait BlockCipher: + SymmetricCipher + Sized +{ + /// Constructor that begins a flow of the streaming API for encrypting one block at a time. + /// Allows for the implementation to return init data such as an IV which is generated prior to encrypting the first block. + fn do_encrypt_init( + key: &KeyMaterial, + ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError>; + /// Encrypts a single block of plaintext. + fn do_encrypt_block( + &mut self, + plaintext: &[u8; BLOCK_LEN], + ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>; + /// Encrypts a single block of plaintext and writes the ciphertext to the provided buffer. + fn do_encrypt_block_out( + &mut self, + plaintext: &[u8; BLOCK_LEN], + ciphertext: &mut [u8; BLOCK_LEN], + ) -> Result; + /// Constructor that begins a flow of the streaming API for decryption one block at a time. + fn do_decrypt_init( + key: &KeyMaterial, + init_data: &[u8; INIT_DATA_LEN], + ) -> Result; + /// Decrypts a single block of ciphertext. + fn do_decrypt_block( + &mut self, + ciphertext: &[u8; BLOCK_LEN], + ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>; + /// Decrypts a single block of ciphertext and writes the plaintext to the provided buffer. + fn do_decrypt_block_out( + &mut self, + ciphertext: &[u8; BLOCK_LEN], + plaintext: &mut [u8; BLOCK_LEN], + ) -> Result; +} + +/// The basic functions of an Authenticated Encryption with Addititional Data cipher. +pub trait AEADCipher: + SymmetricCipher + Sized +{ + #[cfg(std)] + /// A one-shot API to encrypt some plaintext with the given key. + /// A distinguishing feature of AEAD ciphers is the ability to provide additional authenticated data (AAD) + /// that is not encrypted but is protected by the authentication tag; ie it can be sent along with the ciphertext + /// and any tampering with it will result in the decryption operation failing the tag check. + /// This function returns the ciphertext as a Vec, and therefore is only available when compiling with std. + /// Returns a tuple containing the ciphertext and the tag. + fn aead_encrypt( + key: &KeyMaterial, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + plaintext: &[u8], + ) -> Result<(Vec, [u8; TAG_LEN]), SymmetricCipherError>; + /// A one-shot API to encrypt some plaintext with the given key. + /// A distinguishing feature of AEAD ciphers is the ability to provide additional authenticated data (AAD) + /// that is not encrypted but is protected by the authentication tag; ie it can be sent along with the ciphertext + /// and any tampering with it will result in the decryption operation failing the tag check. + /// Returns a tuple containing the randomly-generated nonce, number of bytes written to the ciphertext buffer, and the tag. + /// If you need a deterministic mode where you feed in the nonce, use the streaming API of [BlockCipher] + /// or [StreamCipher] as appropriate and feed the nonce into the IV field. + fn aead_encrypt_out( + key: &KeyMaterial, + aad: &[u8], + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result<([u8; NONCE_LEN], usize, [u8; TAG_LEN]), SymmetricCipherError>; + /// All AEAD ciphers will also be either a [BlockCipher] or a [StreamCipher], and so will already + /// have a streaming API. + /// This allows you to finish either style of streaming API flow with AEAD specific do_final() + /// that computes and returns the authentication tag. + fn do_aead_encrypt_final(self) -> Result<[u8; TAG_LEN], SymmetricCipherError>; + #[cfg(std)] + /// A one-shot API to decrypt some ciphertext with the given key. + /// This function returns the ciphertext as a Vec, and therefore is only available when compiling with std. + fn aead_decrypt( + key: &KeyMaterial, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + ciphertext: &[u8], + tag: &[u8; TAG_LEN], + ) -> Result, SymmetricCipherError>; + /// A one-shot API to decrypt some ciphertext with the given key. + /// This function takes a reference to the output buffer for the plaintext, and is therefore available in no_std. + /// See the documentation for the underlying implementation for details on providing a plaintext buffer of sufficient size; + /// typically the ciphertext is the same length as the plaintext, but some ciphers may have an expansion factor or require + /// extra space for a nonce or tag. + /// Returns a tuple containing the initialization data and the number of bytes written to the plaintext buffer. + fn aead_decrypt_out( + key: &KeyMaterial, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + ciphertext: &[u8], + tag: &[u8; TAG_LEN], + plaintext: &mut [u8], + ) -> Result; + /// All AEAD ciphers will also be either a [BlockCipher] or a [StreamCipher], and so will already + /// have a streaming API. + /// This allows you to finish either style of streaming API flow with AEAD specific do_final() + /// that computes and returns the authentication tag. + fn do_aead_decrypt_final(self, tag: &[u8; TAG_LEN]) -> Result<(), SymmetricCipherError>; +} + +/// The basic functions of a stream cipher, which differ from those of a block cipher only in that +/// a stream cipher is assumed to have no underlying block size tied to the implementation, and so the caller gets to specify +/// the block size for the streaming APIs. +pub trait StreamCipher: + SymmetricCipher + Sized +{ + /// Constructor that begins a flow of the streaming API for encrypting one block at a time. + /// Allows for the implementation to return init data such as an IV which is generated prior to encrypting the first block. + fn do_stream_encrypt_init( + key: &KeyMaterial, + ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError>; + /// Encrypts a single block of plaintext. + fn do_stream_encrypt_block( + &mut self, + plaintext: &[u8; BLOCK_LEN], + ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>; + /// Encrypts a single block of plaintext and writes the ciphertext to the provided buffer. + fn do_stream_encrypt_block_out( + &mut self, + plaintext: &[u8; BLOCK_LEN], + ciphertext: &mut [u8; BLOCK_LEN], + ) -> Result<(), SymmetricCipherError>; + /// Constructor that begins a flow of the streaming API for decryption one block at a time. + fn do_stream_decrypt_init( + key: &KeyMaterial, + init_data: &[u8; INIT_DATA_LEN], + ) -> Result; + /// Decrypts a single block of ciphertext. + fn do_stream_decrypt_block( + &mut self, + ciphertext: &[u8; BLOCK_LEN], + ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>; + /// Decrypts a single block of ciphertext and writes the plaintext to the provided buffer. + fn do_stream_decrypt_block_out( + &mut self, + ciphertext: &[u8; BLOCK_LEN], + plaintext: &mut [u8; BLOCK_LEN], + ) -> Result<(), SymmetricCipherError>; +} + +pub trait Hash: Default { /// The size of the internal block in bits -- needed by functions such as HMAC to compute security parameters. fn block_bitlen(&self) -> usize; @@ -36,12 +238,10 @@ pub trait Hash : Default { /// Provide a chunk of data to be absorbed into the hashes. /// `data` can be of any length, including zero bytes. /// do_update() is intended to be used as part of a streaming interface, and so may by called multiple times. - // fn do_update(&mut self, data: &[u8]) -> Result<(), HashError>; fn do_update(&mut self, data: &[u8]); /// Finish absorbing input and produce the hashes output. /// Consumes self, so this must be the final call to this object. - // fn do_final(self) -> Result, HashError>; fn do_final(self) -> Vec; /// Finish absorbing input and produce the hashes output. @@ -84,7 +284,7 @@ pub trait HashAlgParams: Algorithm { /// A Key Derivation Function (KDF) is a function that takes in one or more input key and some unstructured /// additional input, and uses them to produces a derived key. -pub trait KDF : Default { +pub trait KDF: Default { /// Implementations of this function are capable of deriving an output key from an input key, /// assuming that they have been properly initialized. /// @@ -186,11 +386,12 @@ pub trait KEM< const SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, ->: Sized { +>: Sized +{ /// Generate a keypair. /// Error condition: Basically only on RNG failures fn keygen() -> Result<(PK, SK), KEMError>; - + /// Performs an encapsulation against the given public key. /// Returns the ciphertext and derived shared secret. fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; @@ -204,7 +405,9 @@ pub trait KEM< // todo: that automatically call the encode and from_bytes() ? /// A public key for a KEM algorithm, often denoted "pk". -pub trait KEMPublicKey : PartialEq + Eq + Clone + Debug + Display + Sized { +pub trait KEMPublicKey: + PartialEq + Eq + Clone + Debug + Display + Sized +{ /// Write it out to bytes in its standard encoding. fn encode(&self) -> [u8; PK_LEN]; /// Write it out to bytes in its standard encoding. @@ -214,7 +417,7 @@ pub trait KEMPublicKey : PartialEq + Eq + Clone + Debug + D } /// A private key for a KEM algorithm, often denoted "sk" (for "secret key"). -pub trait KEMPrivateKey : PartialEq + Eq + Clone + Secret + Sized { +pub trait KEMPrivateKey: PartialEq + Eq + Clone + Secret + Sized { /// Write it out to bytes in its standard encoding. fn encode(&self) -> [u8; SK_LEN]; /// Write it out to bytes in its standard encoding. @@ -223,7 +426,6 @@ pub trait KEMPrivateKey : PartialEq + Eq + Clone + Secret + fn from_bytes(bytes: &[u8]) -> Result; } - /// A Message Authentication Code algorithm is a keyed hash function that behaves somewhat like a symmetric signature function. /// A MAC algorithm takes in a key and some data, and produces a MAC (message authentication code) that /// can be used to verify the integrity of data. @@ -293,7 +495,7 @@ pub trait MAC: Sized { /// Depending on the underlying MAC implementation, NIST may require that the library enforce /// a minimum length on the mac output value. See documentation for the underlying implementation /// to see conditions under which it throws [MACError::InvalidLength]. - fn mac_out(self, data: &[u8],out: &mut [u8]) -> Result; + fn mac_out(self, data: &[u8], out: &mut [u8]) -> Result; /// One-shot API that verifies a MAC for the provided data. /// `data` can be of any length, including zero bytes. @@ -381,11 +583,14 @@ impl SecurityStrength { /// be used by applications that intend to submit to FIPS certification as it more closely aligns with the /// requirements of SP 800-90A. /// Note: this interface produces bytes. If you want a [KeyMaterialTrait], then use [KeyMaterial::from_rng]. -pub trait RNG : Default { +pub trait RNG: Default { // TODO: add back once we figure out streaming interaction with entropy sources. // fn add_seed_bytes(&mut self, additional_seed: &[u8]) -> Result<(), RNGError>; - fn add_seed_keymaterial(&mut self, additional_seed: impl KeyMaterialTrait) -> Result<(), RNGError>; + fn add_seed_keymaterial( + &mut self, + additional_seed: impl KeyMaterialTrait, + ) -> Result<(), RNGError>; fn next_int(&mut self) -> Result; /// Returns the number of requested bytes. @@ -403,9 +608,9 @@ pub trait RNG : Default { /// A trait that forces an object to implement a zeroizing Drop() as well as Debug and Display that /// will not log the sensitive contents, even in error or crash-dump scenarios. #[allow(drop_bounds)] // Since rust auto-implements Drop, there's a lint that explicitly bounding on Drop is useless. - // I disagree because I want to force things that are secrets to manually implement Drop that zeroizes the data. - // So I'm turning off this lint. -pub trait Secret : Drop + Debug + Display {} +// I disagree because I want to force things that are secrets to manually implement Drop that zeroizes the data. +// So I'm turning off this lint. +pub trait Secret: Drop + Debug + Display {} /// Pre-Hashed Signature is an extension to [Signature] that adds functionality specific to signature /// primatives that can operate on a pre-hashed message instead of the full message. @@ -415,8 +620,9 @@ pub trait PHSignature< const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - const PH_LEN: usize>: - Signature{ + const PH_LEN: usize, +>: Signature +{ /// Produce a signature for the provided pre-hashed message and context. /// /// `ctx` accepts a zero-length byte array. @@ -441,12 +647,26 @@ pub trait PHSignature< /// Not all signature primitives will support a context value, so you may need to consult the /// documentation for the underlying primitive for how it handles a ctx in that case, for example, it /// might throw an error, ignore the provided ctx value, or append the ctx to the msg in a non-standard way. - fn sign_ph(sk: &SK, ph: &[u8; PH_LEN], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError>; + fn sign_ph( + sk: &SK, + ph: &[u8; PH_LEN], + ctx: Option<&[u8]>, + ) -> Result<[u8; SIG_LEN], SignatureError>; /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer. - fn sign_ph_out(sk: &SK, ph: &[u8; PH_LEN], ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN]) -> Result; + fn sign_ph_out( + sk: &SK, + ph: &[u8; PH_LEN], + ctx: Option<&[u8]>, + output: &mut [u8; SIG_LEN], + ) -> Result; /// On success, returns Ok(()) /// On failure, returns Err([SignatureError::SignatureVerificationFailed]); may also return other types of [SignatureError] as appropriate (such as for invalid-length inputs). - fn verify_ph(pk: &PK, ph: &[u8; PH_LEN], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError>; + fn verify_ph( + pk: &PK, + ph: &[u8; PH_LEN], + ctx: Option<&[u8]>, + sig: &[u8], + ) -> Result<(), SignatureError>; } /// A digital signature algorithm is defined as a set of three operations: @@ -469,8 +689,9 @@ pub trait Signature< SK: SignaturePrivateKey, const PK_LEN: usize, const SK_LEN: usize, - const SIG_LEN: usize ->: Sized { + const SIG_LEN: usize, +>: Sized +{ /// Generate a keypair. /// Error condition: Basically only on RNG failures fn keygen() -> Result<(PK, SK), SignatureError>; @@ -501,7 +722,12 @@ pub trait Signature< fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError>; /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer. - fn sign_out(sk: &SK, msg: &[u8], ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN]) -> Result; + fn sign_out( + sk: &SK, + msg: &[u8], + ctx: Option<&[u8]>, + output: &mut [u8; SIG_LEN], + ) -> Result; /* streaming signing API */ /// Initialize a signer for streaming mode with the provided private key. @@ -539,7 +765,9 @@ pub trait Signature< // todo: that automatically call the encode and from_bytes() ? /// A public key for a signature algorithm, often denoted "pk". -pub trait SignaturePublicKey : PartialEq + Eq + Clone + Debug + Display + Sized { +pub trait SignaturePublicKey: + PartialEq + Eq + Clone + Debug + Display + Sized +{ /// Write it out to bytes in its standard encoding. fn encode(&self) -> [u8; PK_LEN]; /// Write it out to bytes in its standard encoding. @@ -549,7 +777,9 @@ pub trait SignaturePublicKey : PartialEq + Eq + Clone + Deb } /// A private key for a signature algorithm, often denoted "sk" (for "secret key"). -pub trait SignaturePrivateKey : PartialEq + Eq + Clone + Secret + Sized { +pub trait SignaturePrivateKey: + PartialEq + Eq + Clone + Secret + Sized +{ /// Write it out to bytes in its standard encoding. fn encode(&self) -> [u8; SK_LEN]; /// Write it out to bytes in its standard encoding. @@ -558,7 +788,6 @@ pub trait SignaturePrivateKey : PartialEq + Eq + Clone + Se fn from_bytes(bytes: &[u8]) -> Result; } - /// Extensible Output Functions (XOFs) are similar to hash functions, except that they can produce output of arbitrary length. /// The naming used for the functions of this trait are borrowed from the SHA3-style sponge constructions that split XOF operation /// into two phases: an absorb phase in which an arbitrary amount of input is provided to the XOF, @@ -577,7 +806,7 @@ pub trait SignaturePrivateKey : PartialEq + Eq + Clone + Se /// to break anonymity-preserving technology. /// Applications that require the arbitrary-length output of an XOF, but also care about these /// distinguishing attacks should consider adding a cryptographic salt to diversify the inputs. -pub trait XOF : Default { +pub trait XOF: Default { /// A static one-shot API that digests the input data and produces `result_len` bytes of output. fn hash_xof(self, data: &[u8], result_len: usize) -> Vec;