pub struct KWing { /* private fields */ }kem only.Expand description
A stateful, high-throughput KWing key holder for decapsulation.
KWing pre-computes and heap-caches all three component secret keys and
the composite encapsulation key at construction time. Subsequent calls to
decapsulate reuse the cached material without any
additional key-derivation overhead.
§Key Sizes
| Constant | Bytes | Layout |
|---|---|---|
ENCAPSULATION_KEY_SIZE | 23,120 | X25519(32) ‖ ML-KEM-1024(1568) ‖ FrodoKEM-1344(21520) |
CIPHERTEXT_SIZE | 23,328 | X25519 eph(32) ‖ Salt(32) ‖ ML-KEM CT(1568) ‖ FrodoKEM CT(21696) |
§Security Note
The encapsulation key (public key) returned by get_pub_key
is safe to distribute freely. The underlying secret key material stored in
The underlying X25519 and ML-KEM secret material stored in this struct is
wrapped in zeroize::Zeroizing. FrodoKEM material depends on standard
process memory cleanup.
§Example
use b_wing::KWing;
let secret_seed = [0u8; 128]; // use a real CSPRNG in production
let kwing = KWing::from_seed(&secret_seed).unwrap();
// The public encapsulation key can be shared with any sender.
let ek = kwing.get_pub_key();
assert_eq!(ek.len(), KWing::ENCAPSULATION_KEY_SIZE);Implementations§
Source§impl KWing
impl KWing
Sourcepub const ENCAPSULATION_KEY_SIZE: usize = 23120
pub const ENCAPSULATION_KEY_SIZE: usize = 23120
Byte length of the composite encapsulation (public) key: 23,120 bytes.
Memory layout:
[ X25519 pub (32) | ML-KEM-1024 ek (1568) | FrodoKEM-1344 pk (21520) ]Sourcepub const CIPHERTEXT_SIZE: usize = 23328
pub const CIPHERTEXT_SIZE: usize = 23328
Byte length of the composite ciphertext: 23,328 bytes.
Memory layout:
[ X25519 eph pub (32) | Salt (32) | ML-KEM-1024 ct (1568) | FrodoKEM-1344 ct (21696) ]Sourcepub fn from_seed(secret_seed: &[u8; 128]) -> Result<Self, Error>
pub fn from_seed(secret_seed: &[u8; 128]) -> Result<Self, Error>
Expands a 128-byte secret seed into a fully initialized KWing key holder.
The seed is partitioned deterministically as follows:
| Bytes | Usage |
|---|---|
[0..32] | X25519 static secret |
[32..64] | ML-KEM-1024 keygen parameter d |
[64..96] | ML-KEM-1024 keygen parameter z |
[96..128] | FrodoKEM-1344 keygen seed (fed into ChaCha20) |
§Security Requirements
secret_seedmust be generated by a cryptographically secure random number generator (CSPRNG) such asgetrandom.- Never reuse the same seed for different recipients or sessions.
- The seed should be treated with the same care as a private key.
§Errors
Returns Error::InvalidFormat if an internal slice conversion fails
(this should be impossible given a correctly-sized input).
§Example
use b_wing::KWing;
let mut seed = [0u8; 128];
getrandom::fill(&mut seed).expect("CSPRNG failed");
let kwing = KWing::from_seed(&seed).expect("key generation failed");Sourcepub fn get_pub_key(&self) -> &[u8] ⓘ
pub fn get_pub_key(&self) -> &[u8] ⓘ
Returns a reference to the cached composite encapsulation key.
The returned slice is ENCAPSULATION_KEY_SIZE
bytes long and is safe to distribute publicly. Pass it to
encapsulate on the sender’s side.
Sourcepub fn encapsulate(
encaps_seed: &[u8; 128],
ek: &[u8],
) -> Result<(Vec<u8>, [u8; 64]), Error>
pub fn encapsulate( encaps_seed: &[u8; 128], ek: &[u8], ) -> Result<(Vec<u8>, [u8; 64]), Error>
Encapsulates a fresh shared secret against the recipient’s composite public key.
This is the sender-side operation. It runs all three component KEMs
deterministically from encaps_seed and combines their outputs into a
single composite ciphertext and a 64-byte OKM.
§Seed Layout
| Bytes | Usage |
|---|---|
[0..32] | X25519 ephemeral secret |
[32..64] | ML-KEM-1024 randomness m |
[64..96] | FrodoKEM encapsulation randomness (ChaCha20 seed) |
[96..128] | HKDF salt (transmitted in the ciphertext) |
§Security Requirements
encaps_seedmust be freshly generated from a CSPRNG for every encapsulation. Reusing the seed against the same recipient leaks the X25519 secret key.
§Errors
| Variant | Cause |
|---|---|
Error::InvalidFormat | ek is not exactly ENCAPSULATION_KEY_SIZE bytes |
Error::LowEntropyKey | X25519 DH output is a low-order (all-zero) point |
Error::EncapsulateError | An underlying KEM primitive failed |
§Example
use b_wing::KWing;
let mut encaps_seed = [0u8; 128];
getrandom::fill(&mut encaps_seed).expect("CSPRNG failed");
let (ciphertext, shared_secret) = KWing::encapsulate(&encaps_seed, ek).unwrap();
assert_eq!(ciphertext.len(), KWing::CIPHERTEXT_SIZE);
assert_eq!(shared_secret.len(), 64);Sourcepub fn decapsulate(&self, ct: &[u8]) -> Result<[u8; 64], Error>
pub fn decapsulate(&self, ct: &[u8]) -> Result<[u8; 64], Error>
Decapsulates a composite ciphertext to recover the 64-byte Output Keying Material.
This is the recipient-side operation. It parses the composite ciphertext, runs all three component decapsulations using the cached secret keys, and recomputes the HKDF transcript to produce the OKM.
The OKM is cryptographically bound to the ciphertext and to this specific
KWing instance, so it will not match any other recipient or ciphertext.
§Errors
| Variant | Cause |
|---|---|
Error::InvalidFormat | ct is not exactly CIPHERTEXT_SIZE bytes |
Error::LowEntropyKey | X25519 DH output is a low-order (all-zero) point |
Error::DecapsulateError | An underlying KEM primitive failed |
§Example
use b_wing::KWing;
let okm = kwing.decapsulate(&ct).unwrap();
assert_eq!(okm.len(), 64);
// Derive a 32-byte AES-256 key and 32-byte MAC key from the OKM:
let aes_key = &okm[..32];
let mac_key = &okm[32..];