ascon
A pure OCaml implementation of the final NIST SP 800-232 (August 2025) Ascon standard. One 320-bit permutation, one sponge, four standardized constructions — and nothing underneath the OCaml standard library.
Ascon.Aead128— authenticated encryption with associated data.Ascon.Hash256— a fixed 256-bit hash.Ascon.Xof128— an extendable-output function.Ascon.Cxof128— a customized extendable-output function.
The core keeps the five-word state in Int64, has no runtime
dependency beyond the standard library, and does not link Unix or C. It
is meant for ordinary Unix applications and MirageOS-style unikernels
alike.
This is the standardized little-endian design. It is not Ascon v1.2, and it is not byte-for-byte compatible with the older NIST lightweight-cryptography submission. Ciphertexts and digests will not match a v1.2 implementation.
| Release | 0.1.0 |
|---|---|
| OCaml | 4.14 or newer |
| Runtime dependencies | none — no C stubs, no Unix |
| Official vectors passing | 4,228 |
| Independent audit | none |
| License | ISC |
Quick start
opam install ascon
Add the library to the relevant dune stanza:
(libraries ascon)
Hashing is one call and cannot fail:
let digest = Ascon.Hash256.digest_string "message"
(* [digest] is exactly 32 bytes. *)
Authenticated encryption validates its key and nonce up front, and reports authentication failure as a typed error rather than an exception:
let key = Result.get_ok (Ascon.Aead128.Key.of_bytes key_bytes) in
let nonce = Result.get_ok (Ascon.Aead128.Nonce.of_bytes nonce_bytes) in
let ciphertext, tag =
Ascon.Aead128.encrypt ~key ~nonce
~associated_data:(Bytes.of_string "record header")
~plaintext:(Bytes.of_string "secret payload")
in
match Ascon.Aead128.decrypt ~key ~nonce ~associated_data ~ciphertext ~tag with
| Ok plaintext -> use plaintext
| Error `Authentication_failure -> reject_record ()
| Error `Invalid_tag_length -> reject_malformed_record ()
Decryption buffers the whole candidate plaintext and returns it only after the full 128-bit tag verifies, so unauthenticated bytes never reach the caller. Continue with usage for the XOF lifecycle and the incremental interfaces.
What the API commits to
- Typed lifecycles, not conventions
-
Xof128andCxof128give absorbing and squeezing states distinct types. Once a state starts producing output, the compiler refuses to feed it more message bytes. - Validated key material
-
Keys and nonces are abstract 16-byte values built by a checked
constructor. A wrong length is an
Error, never a silent truncation. - One-shot AEAD
- There is no streaming decrypt, deliberately: an incremental API would have to hand back plaintext before the tag is known.
- Persistent hash and XOF contexts
- Each update copies the small state and buffer, so branching and reuse behave the way the types suggest, at the cost of allocation.
One permutation, four constructions
Every algorithm above is the same 320-bit sponge in the diagram, with a different initial value, rate, and round count:
| Construction | Rate | Rounds | Initial value |
|---|---|---|---|
Aead128 | 16 bytes | p[8] per block, p[12] at the ends | 0x00001000808c0001 |
Hash256 | 8 bytes | p[12] | 0x0000080100cc0002 |
Xof128 | 8 bytes | p[12] | 0x0000080000cc0003 |
Cxof128 | 8 bytes | p[12] | 0x0000080000cc0004 |
The permutation itself is not part of the installed public API. See the generated API reference for the exported surface, or development for the module layout underneath it.
Not audited. This library has had no independent cryptographic review and is not a FIPS-validated or NIST-validated module. Passing the official vectors is evidence of conformance, not of security. Read the security notes before deploying it.