Skip to content

AES-GCM and ChaCha20-Poly1305: out-of-place encryption from pieces (Rust API) - #1213

Closed
alex wants to merge 1 commit into
mainfrom
claude/compassionate-fermi-rjdmyl-rust-api
Closed

alex wants to merge 1 commit into
mainfrom
claude/compassionate-fermi-rjdmyl-rust-api

Conversation

@alex

@alex alex commented Oct 7, 2026

Copy link
Copy Markdown
Member

Part of #990. This is the Rust API a TLS record layer needs. The verified out-of-place assembly (contracts in #1209) will then replace its internal copy, one target at a time, without changing the API.

Rust only: no Lean, no assembly and no trust change. It is independent of #1209.

API

impl AesGcm {
    pub fn encrypt(&self, nonce: &[u8], aad: &[u8], plaintext: &[&[u8]], out: &mut [u8]) -> Result<[u8; 16], Error>;
}
impl AesGcmEncryptor<'_> {
    pub fn update_into(&mut self, input: &[u8], output: &mut [u8]) -> Result<(), Error>;
}
impl ChaCha20Poly1305 {
    pub fn encrypt(&self, nonce: &[u8; 12], aad: &[u8], plaintext: &[&[u8]], out: &mut [u8]) -> Result<[u8; 16], Error>;
}
  • encrypt encrypts the concatenation of the pieces into out, which must be exactly as long as their total. Otherwise it returns the new Error::InvalidOutputLength.
  • update_into is the streaming form: output.len() == input.len(), and calls may be any length.
  • No partial writes on error: every length is checked before anything is written.

This covers each case of rustls's RecordEncrypter::encrypt in one call:

Case Call Notes
TLS 1.3 key.encrypt(&nonce, &aad, &[chunk…, &[typ]], &mut out[..n]) tag written after out[..n]
TLS 1.2 AES-GCM [chunk…] into &mut out[8..8 + n] explicit nonce written to out[..8]

This includes OutboundPlain::Multiple, which aws-lc-rs's seal_out_of_place_scatter can't take: its provider gathers into out itself.

Implementation, for now

The pieces are gathered into out, which is then encrypted in place with the existing verified functions, on every architecture. That is what rustls-provider (#991) does by hand today, so there is no speedup yet.

The speedup comes from the verified out-of-place functions specified in #1209: vg_aes_gcm_seal_gather, vg_aes_gcm_stream_encrypt_to and vg_chacha20_poly1305_seal_gather. Their implementations follow in stacked PRs. The first is x86-64 AES-GCM's interleaved loops reading the plaintext from the input buffer. They will slot in behind this API.

Tests and benchmarks

  • Unit tests: every split of a message into pieces, including empty ones, matches in-place encryption. Mismatched lengths and an empty nonce fail without writing.
  • Wycheproof: valid AES-GCM and ChaCha20-Poly1305 vectors are also encrypted out of place in two pieces, and AES-GCM's also with update_into.
  • Benchmarks: new groups aes-128-gcm-encrypt-out-of-place and chacha20poly1305-out-of-place. Each encrypts a TLS 1.3-shaped record (plaintext plus the content-type byte) out of place, against aws-lc-rs's seal_out_of_place_scatter (the byte as extra_in) and OpenSSL's EVP Crypter.
  • Local smoke run (this sandbox's CPU), AES-128-GCM at 16 KiB: 1.36 µs here vs 1.33 µs for aws-lc-rs.

Checks

  • cargo fmt --check passes.
  • cargo clippy --all-targets -- -D warnings passes (also in bench/).
  • The aes_gcm and chacha20poly1305 unit tests and their Wycheproof tests pass.
  • ci/check_arch_gates.py, ci/check_variants.py and ci/cpu_tests.py lint pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MmkzVkkRyGFLYBPdnXPeVb


Generated by Claude Code

AesGcm::encrypt and ChaCha20Poly1305::encrypt encrypt the concatenation
of a list of slices into a separate output buffer, and
AesGcmEncryptor::update_into encrypts the next piece of a stream from one
buffer into another (#990). A TLS record layer is handed the plaintext in
buffers it does not own, often in pieces, plus TLS 1.3's content-type
byte, and must write the ciphertext to its own output: this is the API it
needs, the one-call equivalent of aws-lc-rs's seal_out_of_place_scatter
(which also takes several pieces).

For now they gather the pieces into the output and encrypt it there with
the in-place functions, on every architecture; the verified out-of-place
functions (contracts in the Spec PR) replace that copy target by target
without changing the API. Every length is checked before anything is
written, and an output of the wrong length is InvalidOutputLength.

Tests: every split of a message into pieces against in-place encryption,
and the Wycheproof vectors in two pieces (and update_into). Benchmarks:
out-of-place encryption of a TLS 1.3-shaped record (plaintext plus the
content-type byte) against aws-lc-rs's seal_out_of_place_scatter and
OpenSSL's EVP interface.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmkzVkkRyGFLYBPdnXPeVb
@alex

alex commented Oct 7, 2026

Copy link
Copy Markdown
Member Author

Skip this, go directly to the efficient impl

alex commented Oct 7, 2026

Copy link
Copy Markdown
Member Author

Closing as asked. The AES-GCM half of this API is now in #1228, on top of the verified out-of-place x86-64 functions (vg_aes_gcm_encrypt_blocks_to, vg_aes_gcm_stream_encrypt_to). ChaCha20-Poly1305's will come with its own verified implementation.


Generated by Claude Code

@alex alex closed this Oct 7, 2026
@alex
alex deleted the claude/compassionate-fermi-rjdmyl-rust-api branch October 10, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants