Documentation
¶
Overview ¶
Package mlkem768incr implements the INCREMENTAL ML-KEM-768 key encapsulation used by Signal's Sparse Post-Quantum Ratchet (SPQR), as a pure-Go port (no cgo) of the libcrux-ml-kem 0.0.8 `incremental` API that SPQR v1.5.1 depends on. Standard ML-KEM-768 transports the whole encapsulation key at once; the incremental variant splits it into a small header (pk1) and a chunkable encapsulation key (pk2), and encapsulates in two phases (encapsulate1 emits the u-ciphertext from the header alone; encapsulate2 emits the v-ciphertext once the pk2 chunk arrives). This lets the bulky encapsulation key be transported in pieces across SPQR epochs.
The package is built in two layers, each verified independently (ADR 0003):
The FIPS-203 ML-KEM-768 PKE/KEM core — modeled on Go stdlib's FIPS-203 implementation, and verified against (1) the stdlib crypto/mlkem package for end-to-end shared-secret equality and (2) the NIST/ACVP ML-KEM-768 known-answer tests, which pin the intermediate byte encodings (catching a compensating-error pair that an end-to-end-only check could mask).
The libcrux incremental split/serialization layer (pk1/pk2 framing, two-phase encapsulation, the EncapsState byte layout including the issue-1275 endianness handling, and decapsulate_compressed_key) — verified byte-for-byte against known-answer vectors generated from libcrux-ml-kem 0.0.8 itself (the exact revision SPQR v1.5.1 pins).
Byte-exactness with libcrux is the deliverable: SPQR serializes these KEM bytes into its own wire/state, so any divergence breaks mainline interop.
Index ¶
- Constants
- func DecapsulateCompressedKey(dk, ct1, ct2 []byte) ([]byte, error)
- func Encapsulate2(state, pk2 []byte) ([]byte, error)
- func FixEncapsStateEndianness(state []byte) ([]byte, error)
- func ValidatePublicKeyParts(pk1, pk2 []byte) error
- type DecapsulationKey768
- type EncapsulationKey768
- type EncapsulationResult
- type IncrementalKey
Constants ¶
const ( // PublicKey1Size is the header: ρ (32) ‖ H(ek) (32). PublicKey1Size = 64 // PublicKey2Size is the chunked encapsulation key: ByteEncode₁₂(t̂). PublicKey2Size = k * encodingSize12 // 1152 // DecapsulationKeySize is the standard expanded dk: ByteEncode₁₂(ŝ) ‖ ek ‖ // H(ek) ‖ z. DecapsulationKeySize = k*encodingSize12 + EncapsulationKeySize768 + 32 + 32 // 2400 // Ciphertext1Size is Compress₁₀(u). Ciphertext1Size = k * encodingSize10 // 960 // Ciphertext2Size is Compress₄(v). Ciphertext2Size = encodingSize4 // 128 // EncapsStateSize is the saved phase-1 state: r̂ (k polys) ‖ e₂ (1 poly) ‖ // m (32). = 3*512 + 512 + 32 = 2080. EncapsStateSize = k*rawPolyI16Size + rawPolyI16Size + messageSize )
Incremental ML-KEM-768 wire sizes (libcrux 0.0.8, K=3).
const ( SeedSize = 64 // d ‖ z CiphertextSize768 = k*encodingSize10 + encodingSize4 // 1088 EncapsulationKeySize768 = k*encodingSize12 + 32 // 1184 )
Standard ML-KEM-768 byte sizes (FIPS 203).
Variables ¶
This section is empty.
Functions ¶
func DecapsulateCompressedKey ¶
DecapsulateCompressedKey reconstructs the standard ciphertext ct1 ‖ ct2 and runs the ordinary FIPS-203 ML-KEM decaps (with constant-time implicit rejection) against the 2400-byte expanded decapsulation key. libcrux decapsulate_compressed_key.
func Encapsulate2 ¶
Encapsulate2 finishes encapsulation: it parses the EncapsState (applying the libcrux issue-1275 endianness fix if needed), decodes t̂ from pk2, computes v = NTT⁻¹(t̂ᵀ◦r̂) + e₂ + Decompress₁(m), and returns ct2 = Compress₄(v).
func FixEncapsStateEndianness ¶
FixEncapsStateEndianness detects and repairs an EncapsState whose polynomial int16s were serialized with swapped endianness by a libcrux SIMD backend (cryspen/libcrux#1275). It inspects the e₂ region (state[1536:2048]), whose coefficients are CBD η2 samples — all in [-2,2]. Read as little-endian int16, a correct encoding shows only {0, 1, 2, -1, -2}; a byte-swapped one shows the swapped images {0x0100, 0x0200, 0xFEFF}. On the first decisive coefficient we either keep the state (correct) or swap every int16 pair in state[0:len-32] (the trailing 32 random bytes have no endianness and are never swapped). A state of all-ambiguous {0,-1} values, or any unexpected value, is left as-is (matching SPQR's keep-and-warn fallback).
func ValidatePublicKeyParts ¶
ValidatePublicKeyParts checks that a header (pk1) and chunked encaps key (pk2) are consistent: it reconstructs the standard public key (t̂ ‖ ρ), verifies H(ek) matches the header's hash, and checks t̂ is in the valid domain (the ByteDecode₁₂ modulus check). libcrux validate_pk_bytes.
Types ¶
type DecapsulationKey768 ¶
type DecapsulationKey768 struct {
// contains filtered or unexported fields
}
DecapsulationKey768 is an expanded FIPS-203 ML-KEM-768 decapsulation key. It holds the seed (d‖z), ρ, H(ek), and the expanded encryption/decryption keys. The key material is secret; see String for redaction.
func NewDecapsulationKey768 ¶
func NewDecapsulationKey768(seed []byte) (*DecapsulationKey768, error)
NewDecapsulationKey768 expands a decapsulation key from a 64-byte seed (d‖z), per FIPS 203. The seed must be uniformly random and kept secret.
func (*DecapsulationKey768) Bytes ¶
func (dk *DecapsulationKey768) Bytes() []byte
Bytes returns the 64-byte seed form (d‖z) of the decapsulation key (secret).
func (*DecapsulationKey768) Decapsulate ¶
func (dk *DecapsulationKey768) Decapsulate(ciphertext []byte) ([]byte, error)
Decapsulate implements ML-KEM.Decaps (FIPS 203, Algorithm 18) with implicit rejection, constant-time. ciphertext must be CiphertextSize768 bytes.
func (*DecapsulationKey768) EncapsulationKey ¶
func (dk *DecapsulationKey768) EncapsulationKey() *EncapsulationKey768
EncapsulationKey returns the public encapsulation key.
func (DecapsulationKey768) Format ¶
func (dk DecapsulationKey768) Format(f fmt.State, _ rune)
Format implements fmt.Formatter, redacting the secret key material under every verb. Without this, %#v dumps the seed (d), implicit-rejection secret (z), and secret vector ŝ as a raw struct, and %x dumps their bytes. Value receiver so a value copy redacts too. The embedded encryptionKey is public (t̂/Â) but is redacted along with the rest for a single uniform secret-key rendering; use EncapsulationKey() to print the public key. (FIPS-203 secret-leak convention; see curve.PrivateKey.Format.)
func (DecapsulationKey768) String ¶
func (dk DecapsulationKey768) String() string
String redacts the secret key material so a decapsulation key never leaks into logs. Value receiver (matching curve.PrivateKey) so a value copy — not just a pointer — also redacts under %v/%s.
type EncapsulationKey768 ¶
type EncapsulationKey768 struct {
// contains filtered or unexported fields
}
EncapsulationKey768 is an expanded FIPS-203 ML-KEM-768 encapsulation key.
func NewEncapsulationKey768 ¶
func NewEncapsulationKey768(b []byte) (*EncapsulationKey768, error)
NewEncapsulationKey768 parses the standard 1184-byte encapsulation key, checking the modulus (FIPS 203 §7.2 step 2 via polyByteDecode).
func (*EncapsulationKey768) Bytes ¶
func (ek *EncapsulationKey768) Bytes() []byte
Bytes returns the standard 1184-byte ML-KEM-768 encapsulation key encoding.
func (*EncapsulationKey768) EncapsulateInternal ¶
func (ek *EncapsulationKey768) EncapsulateInternal(m *[messageBytes]byte) (sharedKey, ciphertext []byte)
EncapsulateInternal is the derandomized ML-KEM.Encaps (FIPS 203, Algorithm 17) with the 32-byte message m supplied (for KATs). Encapsulate (production) draws m from a CSPRNG.
type EncapsulationResult ¶
type EncapsulationResult struct {
Ciphertext1 []byte // Compress₁₀(u) (Ciphertext1Size)
EncapsState []byte // saved (r̂ ‖ e₂ ‖ m) (EncapsStateSize)
}
EncapsulationResult holds the phase-1 encapsulation outputs.
func Encapsulate1Internal ¶
func Encapsulate1Internal(pk1 []byte, m *[messageBytes]byte) (*EncapsulationResult, error)
Encapsulate1Internal is the derandomized phase-1 encapsulation with the 32-byte message m supplied (for KATs); production callers draw m from a CSPRNG. pk1 is the 64-byte header.
K = G(m‖H(ek))[:32]; the rest is K-PKE.Encrypt's r/e-sampling and the u half:
r̂[i] = NTT(CBD_η1(PRF(r, i))), i∈[0,k) e₁[i] = CBD_η2(PRF(r, k+i)), i∈[0,k) e₂ = CBD_η2(PRF(r, 2k)) u = NTT⁻¹(Âᵀ◦r̂) + e₁; ct1 = Compress₁₀(u)
(r̂, e₂, m) are stashed in the EncapsState for phase 2.
type IncrementalKey ¶
type IncrementalKey struct {
PK1 []byte // header (PublicKey1Size)
PK2 []byte // chunked encaps key (PublicKey2Size)
DK []byte // expanded decapsulation key (DecapsulationKeySize)
}
IncrementalKey is a freshly generated incremental ML-KEM-768 key, split into its header, chunked encapsulation key, and (standard, expanded) decapsulation key. Mirrors SPQR's generate() output (incremental_mlkem768.rs).
func GenerateIncrementalKey ¶
func GenerateIncrementalKey(seed []byte) (*IncrementalKey, error)
GenerateIncrementalKey expands an incremental ML-KEM-768 key from a 64-byte seed (d‖z), per FIPS 203 KeyGen, and serializes it into the incremental split. This is libcrux's KeyPairCompressedBytes::from_seed.