nx_chacha20_poly1305.nx
buildroot/runtime/nx_chacha20_poly1305.nx
about
nx_chacha20_poly1305.nx -- AEAD per RFC 8439 §2.8.
Phase 0b §A.2 of the Nishi TLS 1.3 stack per
docs/NISHI_TLS13_GAP_AUDIT.md. Composes the already-shipped
ChaCha20 stream cipher and Poly1305 universal hash into the
Authenticated Encryption with Associated Data primitive that
TLS 1.3, QUIC, WireGuard, Signal, and Noise all use.
Construction:
1. otk = ChaCha20(key, counter=0, nonce)[0..32]
-- the per-message Poly1305 key, fresh because nonce is fresh
2. ciphertext = ChaCha20(key, counter=1, nonce, plaintext)
-- counter starts at 1 because counter 0 was burnt on the OTK
3. mac_data = aad || pad16(aad) || ct || pad16(ct) || u64_le(|aad|) || u64_le(|ct|)
4. tag = Poly1305(otk, mac_data)
Decrypt is symmetric: recompute tag, constant-time compare, then
XOR ciphertext with the same keystream. Tag check FIRST, decrypt
AFTER -- this matters because returning plaintext when the tag
failed would let an attacker probe for valid ciphertexts byte-by-byte
(the classic CBC-padding-oracle attack pattern, ported to AEAD by
careless implementations).
What it does today:
- encrypt + tag in one call
- decrypt + verify in one call; constant-time tag compare
- sealed AEAD verdict (OK / TAG_MISMATCH)
What it doesn't do yet:
- chunked / streaming encryption (one-shot only; matches
TLS 1.3 record-layer usage which is bounded by 2^14 + 256)
- extended XChaCha20-Poly1305 (24-byte nonce variant; Signal
+ libsodium use this; not yet in TLS 1.3)
KAT verified:
- RFC 8439 §2.8.2 worked example (114-byte Sunscreen vector +
12-byte AAD; both ciphertext and tag match spec)
- internal round-trip on random-ish data
- tampered-tag rejection
- tampered-ciphertext rejection
dependencies 3 imports · 16 importers
diagram shows first 10 each side; +0 more imports, +6 more importers in the complete lists below.
imports: nx_syscalls.nxnx_chacha20.nxnx_poly1305.nx
imported by: nx_aead_bench2.nxnx_chacha20_poly1305_test.nxnx_connect_seal.nxnx_dev_api_build_probe.nxnx_doc_crypto.nxnx_dr_encrypt.nxnx_dr_encrypt_gate.nxnx_e2e.nxnx_https_get_complete.nxnx_https_get_stream.nxnx_login_e2e_probe.nxnx_password_vault.nxnx_pe_perf_probe.nxnx_profile_backup.nxnx_tls13_record.nxnx_tls13_record_test.nx
structs
| none |
consts
| 78 | const NX_AEAD_TAG_BYTES: i64 = 16 |
| 79 | const NX_AEAD_NONCE_BYTES: i64 = 12 |
| 80 | const NX_AEAD_KEY_BYTES: i64 = 32 |
| 83 | const NX_AEAD_VERDICT_UNKNOWN: i64 = 0 |
| 84 | const NX_AEAD_VERDICT_OK: i64 = 1 |
| 85 | const NX_AEAD_VERDICT_TAG_MISMATCH: i64 = 2 |
| 86 | const NX_AEAD_VERDICT_N: i64 = 3 |
| 205 | const NX_CP_MACBUF_CAP: i64 = 17000 |
functions
| 90 | func aead_derive_otk(key: *u8, nonce: *u8, otk_out: *u8) -> i64 called by 2: nx_chacha20_poly1305_encryptnx_chacha20_poly1305_decrypt calls 2: sys_mmapchacha20_block |
| 102 | func aead_put_u64_le(buf: *u8, off: i64, v: i64) -> i64 called by 1: aead_build_mac_data |
| 119 | func aead_build_mac_data( |
| 168 | func nx_chacha20_poly1305_encrypt( |
| 200 | func nx_cp_mac_ms() -> i64 { return G_CP_MAC_MS } called by 1: nx_https_req_complete |
| 201 | func nx_cp_poly_ms() -> i64 { return G_CP_POLY_MS } called by 1: nx_https_req_complete |
| 202 | func nx_cp_cha_ms() -> i64 { return G_CP_CHA_MS } called by 1: nx_https_req_complete |
| 211 | func nx_cp_calls() -> i64 { return G_CP_CALLS } called by 1: nx_https_req_complete |
| 212 | func nx_cp_scratch() -> i64 { return G_CP_SCRATCH } called by 1: nx_https_req_complete |
| 214 | func nx_chacha20_poly1305_decrypt( |
| 255 | func nx_aead_verdict_is_valid(v: i64) -> i64 called by 1: main |