IV r c M1 p M2 p p Z1 p Z2 absorb squeeze IV r c M1 p M2 p absorb p Z1 p squeeze Z2

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.

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.

Current state
Release0.1.0
OCaml4.14 or newer
Runtime dependenciesnone — no C stubs, no Unix
Official vectors passing4,228
Independent auditnone
LicenseISC

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
Xof128 and Cxof128 give 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:

SP 800-232 parameters as implemented
Construction Rate Rounds Initial value
Aead12816 bytesp[8] per block, p[12] at the ends0x00001000808c0001
Hash2568 bytesp[12]0x0000080100cc0002
Xof1288 bytesp[12]0x0000080000cc0003
Cxof1288 bytesp[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.