AI Initiative logoAI Initiative
Reading room / Manthan

HPKE Library Survey for HERALD Reference Implementations

Compares HPKE library support across languages for encrypted whisper attestations, with point-in-time recommendations and interoperability constraints.

Research report / proposal; not independently verified publication
This is the retained source text, not a new validation of its claims. Numerical results and comparative claims require the accompanying source review before public reuse.

Read the connected case study or article →

worker-opus-2026-05-05-R202-par · 2026-05-05

Provenance, authorship and editorial notes

HPKE Library Survey for HERALD Reference Implementations

Id

manthan-herald-protocol-research-hpke-survey

Project

Manthan

Source path

manthan/herald-protocol/research/hpke-survey.md

Source sha256

6365a6adfbfab0d5c8302c73108b27757a4cb7844b956a52d368086af4c55083

Local copy sha256

6365a6adfbfab0d5c8302c73108b27757a4cb7844b956a52d368086af4c55083

Repo head

3a94084024a3434cef331fecac91c2c3b43f0313

Date

2026-05-05

Date basis

Explicit document metadata

Author

worker-opus-2026-05-05-R202-par

Author basis

Worker

Last source commit

8b75d1b0157c20a0a463f0e31008ef9f73099beb

Last source commit date

2026-06-08T07:40:14Z

Document status

Research report / proposal; not independently verified publication

Summary

Compares HPKE library support across languages for encrypted whisper attestations, with point-in-time recommendations and interoperability constraints.

Topics
  • Trust and collective intelligence research
  • 1. HERALD context — what we need from an HPKE library
  • 2. TypeScript / JavaScript
  • 2.1 `@hpke/core` (dajiaji/hpke-js) — primary candidate
Theme

Trust and collective intelligence research

Priority

deep

Related ids
  • manthan-herald-protocol-research-confession-alignment
  • manthan-herald-protocol-research-dkim-analogy
  • manthan-herald-protocol-research-patina-formula
Evidence concerns
  • Recovered source text is preserved verbatim. Assertions, comparisons, commands and results belong to its original context; they were not re-executed or externally re-verified during this corpus review.
  • Research/design document, not proof of deployed capability, peer review, novelty or improved outcomes. External citations and dated maturity claims need independent verification before public reuse.
Word count

3665

Bytes

26890

Source relative

herald-protocol/research/hpke-survey.md

Remote mirror

manthan/herald-protocol/research/hpke-survey.md

Full text preserved

True

HPKE Library Survey for HERALD Reference Implementations

Task: R-202 — Survey HPKE library availability across TS/Python/Go/Rust
Worker: worker-opus-2026-05-05-R202-par
Date prepared: 2026-05-05
Last verified: 2026-05-05 (web sources are point-in-time; re-check before commit)


1. HERALD context — what we need from an HPKE library

HERALD's whisper attestation [EXT §7.3] is the only HPKE-binding primitive in the protocol substrate. The relevant normative requirements:

"Encryption MUST use HPKE per RFC 9180 with a registered KEM/KDF/AEAD suite. The default suite is hpke-x25519-hkdf-sha256-aes128gcm. The HPKE Additional Authenticated Data (AAD) MUST equal the envelope id of the envelope that carries the whisper. The AAD binding is what prevents cut-and-paste of an encrypted blob into a different envelope." — EXT.md line 532
"Recipients MUST verify the AAD on decrypt and reject ciphertexts whose AAD does not equal the carrying envelope's id." — EXT.md line 534

The threat model is explicit about why this matters:

"An encrypted blob with no authenticated binding to the envelope it sits inside can be cut-and-pasted by a malicious conduit into a different envelope. Defence. Specify HPKE (RFC 9180) with envelope id (or canonical body hash) as AAD." — THREATS.md §4.12 (lines 270–274)

From these, the minimum capability bar for any candidate library is:

CapabilityRequired?Why
RFC 9180 conformance, base modeMUSTEXT §7.3 default suite is RFC-9180-registered
DHKEM(X25519, HKDF-SHA256) KEMMUSTDefault suite KEM
HKDF-SHA256 KDFMUSTDefault suite KDF
AES-128-GCM AEADMUSTDefault suite AEAD
ChaCha20Poly1305 AEADSHOULDCommon alternative; fallback for hw without AES-NI
AAD passed per-Seal/Open callMUSTEXT §7.3 binds AAD = envelope id. Libraries that fix AAD = "" or only expose info are insufficient
Sender/Recipient context APISHOULDAllows future extension to multi-message whispers; default whisper is single-shot
Stable release ≥ 1.0 or ≥ 0.x with adoptionSHOULDHERALD reference impl should not bind itself to alpha-quality crypto
Maintained (commit < 12 months old)SHOULDCrypto rot is real

A library that exposes only the RFC-9180 single-shot Seal(pkR, info, "", pt) (i.e. AAD hard-coded to empty) fails the EXT §7.3 requirement and is unsuitable. The info parameter is not AAD — it is mixed into the HPKE key schedule (RFC 9180 §5.1) and does not protect each ciphertext under a unique tag the way AAD does.


2. TypeScript / JavaScript

2.1 @hpke/core (dajiaji/hpke-js) — primary candidate

``typescript
override async seal(
data: ArrayBuffer,
aad: ArrayBuffer = EMPTY.buffer as ArrayBuffer,
): Promise<ArrayBuffer>
``

2.2 panva/hpke — alternative

2.3 TypeScript recommendation

For T-114 (HPKE-bound whisper attestation) and the existing T-003 TS reference library:

Use @hpke/core 1.7.5 or newer, with explicit subpackage imports (@hpke/dhkem-x25519 for the default suite). Do not use the umbrella hpke-js package (it pulls all suites; tree-shaking is the point of @hpke/core).

Rationale: maintained, modular, audited-by-test-vectors, AAD on seal/open confirmed, post-CVE patched, runs everywhere HERALD needs to run. Track panva/hpke as a contender if dependency footprint or Web-Crypto-purity becomes load-bearing later.


3. Python

3.1 cryptography (pyca) — gold standard, but insufficient for HERALD whisper

``python
Suite.encrypt(plaintext, public_key, info=b"")
Suite.decrypt(ciphertext, private_key, info=b"")
``

3.2 pyhpke (dajiaji) — primary candidate

``python
seal(pt: bytes, aad: bytes = b'') -> bytes
open(ct: bytes, aad: bytes = b'') -> bytes
``
(sender/recipient context methods, both stateful and supporting per-message AAD)

3.3 hpke-py (ctz / Joseph Birr-Pixton) — alternative

3.4 Python recommendation

For T-004 (Python reference library) and any future Python HERALD whisper code:

Use pyhpke 0.6.4+ as primary. It is the only well-maintained Python HPKE library that exposes per-message AAD on seal/open per EXT §7.3.

Secondary: ride alongside cryptography for everything else (Ed25519 sigs, hashing, key serialization), and file an upstream pyca issue requesting AAD-bearing HPKE so we can migrate to the standard library long-term.

hpke-py is a credible fallback if pyhpke becomes unmaintained, but its 18-month release gap is itself a concern.


4. Go

4.1 Go 1.26 standard library: crypto/hpke — strongly preferred

```go
// Single-shot (no per-message AAD parameter; uses empty AAD internally)
func Seal(pk PublicKey, kdf KDF, aead AEAD, info, plaintext []byte) ([]byte, error)
func Open(k PrivateKey, kdf KDF, aead AEAD, info, ciphertext []byte) ([]byte, error)

// Streaming (AAD on each Seal/Open)
func NewSender(pk PublicKey, kdf KDF, aead AEAD, info []byte) (enc []byte, s Sender, err error)
func (s
Sender) Seal(aad, plaintext []byte) ([]byte, error)
func (r *Recipient) Open(aad, ciphertext []byte) ([]byte, error)
```

4.2 cloudflare/circl/hpke — pre-stdlib incumbent

4.3 Other Go HPKE libraries (briefly)

4.4 Go recommendation

For future T-103 (Go reference library, projected) and any conduit/Herald written in Go:

Use crypto/hpke from Go 1.26+ standard library. Pin Go MSRV to 1.26.2 or later. Use the Sender/Recipient streaming API (not the single-shot Seal/Open) so envelope id can be passed as AAD per EXT §7.3.

cloudflare/circl/hpke is acceptable for code that needs to support Go versions older than 1.26 (e.g. interop testing or transient backports), but new code should not introduce CIRCL purely for HPKE.


5. Rust

5.1 hpke (rozbb/rust-hpke) — primary candidate

``rust
AeadCtxS::seal(msg, aad)
AeadCtxR::open(&ciphertext, aad)
// also non-allocating in-place variants:
AeadCtxS::seal_in_place_detached(&mut msg, aad)
AeadCtxR::open_in_place_detached(&mut ciphertext, aad, &auth_tag)
``

5.2 hpke-rs (cryspen, formerly franziskuskiefer) — alternative

``rust
fn seal(&mut self, pk_r: &HpkePublicKey, info: &[u8], aad: &[u8],
plaintext: &[u8], psk: Option<&[u8]>, psk_id: Option<&[u8]>,
sk_s: Option<&HpkePrivateKey>) -> Result<(Vec<u8>, Vec<u8>), HpkeError>
``

5.3 Other Rust HPKE crates (briefly)

5.4 Rust recommendation

For any future Rust HERALD reference impl:

Use hpke (rozbb/rust-hpke) ≥ 0.13 as the primary choice for a HERALD-Rust reference implementation. Re-evaluate at 0.14 stable release.
Use hpke-rs (cryspen) if formal verification of the cryptographic core (via the libcrux backend) is a hard requirement — but factor in the MPL-2.0 license and the more verbose API.

Both libraries pass our minimum AAD-on-seal/open bar. rozbb/hpke has more downloads and dual MIT/Apache licensing; cryspen/hpke-rs has a more rigorous backend story.


6. Side-by-side comparison

LibraryLangLicenseLatestStarsLast activityRFC 9180AAD on seal/openDefault-suite supportAuditRecommended?
@hpke/core (dajiaji)TSMIT1.9.0 (2026-04)1162026-04FullYesYesTest-vectors only; CVE-2025-64767 fixed in 1.7.5Yes for T-003/T-114; pin ≥1.7.5
panva/hpkeTSMIT1.1.0 (2026-03)302026-05YesLikely (unverified)YesNone statedWatch list
pyhpke (dajiaji)PythonMIT0.6.4 (2025-12)132026-05FullYesYesTest-vectors onlyYes for T-004
cryptography (pyca)PythonApache-2.0/BSD47.0.0 (2026-04)n/a2026-04YesNO (single-shot, no AAD param)YesIndustry-standardNo for whisper; use for Ed25519 etc.
hpke-py (ctz)PythonApache-2.00.3.2 (2024-10)82024-10FullLikely (unverified)YesNoneFallback only; stale
crypto/hpke Go stdlibGoBSDGo 1.26 (2026-02)n/an/aFull base modeYes (streaming API)YesGo security processYes for future T-103
cloudflare/circl/hpkeGoBSD-3v1.6.3 (2026-01)1,6552026-05draft-07 (≈RFC 9180)YesYesCloudflare-deployedAcceptable for legacy Go
hpke (rozbb)RustApache-2.0/MIT0.13.0 (2025-02), 0.14.0-pre.2862026-04FullYesYesCloudflare internal review (v0.8)Yes for future Rust impl
hpke-rs (cryspen)RustMPL-2.00.6.1 (2026-03)492026-05FullYesYeslibcrux backend = formally verifiedYes if formal-verif required

7. Per-task recommendations

HERALD taskLanguageLibraryPin
T-003 (TS reference lib core)TS@hpke/core + @hpke/dhkem-x25519≥ 1.7.5 (post CVE-2025-64767)
T-004 (Python reference lib core)Pythonpyhpke≥ 0.6.4
T-114 (HPKE-bound whisper, TS impl)TS@hpke/core (single-shot pattern with AAD = envelope id)≥ 1.7.5
T-103 (future Go reference lib)Gocrypto/hpke stdlibGo ≥ 1.26.2
Future Rust referenceRusthpke (rozbb)0.13.x track now; migrate to 0.14 stable when released

8. Gotchas and pitfalls (canonical list)

These are footguns every HERALD implementer using HPKE must understand.

8.1 info is not AAD

Most HPKE libraries expose two distinct context-binding strings: the info parameter (mixed into key-schedule per RFC 9180 §5.1) and the aad parameter (passed to AEAD per §5.2). They serve different purposes:

EXT §7.3 says AAD = envelope id. Putting envelope id in info is not equivalent: it would prevent decryption if info were wrong, but does not provide per-message tagging that distinguishes between "this ciphertext was minted for envelope X" and "this ciphertext was minted for envelope Y." Verifiers would have to derive a separate context per envelope, multiplying setup cost, and the EXT §7.3 requirement is specifically the AAD wire-binding — pyca's info-only API will be flagged non-conformant by any reviewer who reads RFC 9180.

Action: When picking a Python library, refuse anything that doesn't surface AAD per call. Same for any other language.

8.2 Single-shot APIs that bury AAD

Several libraries (pyca/cryptography, Go stdlib's Seal/Open top-level functions) offer a single-shot API that does not expose the AEAD AAD — it's hard-coded to empty. Do not use these for HERALD whisper. Always use the streaming/context API even for single messages, so envelope id can be passed.

8.3 Concurrent seal on a single context

CVE-2025-64767 in @hpke/core ≤1.7.4 was a race: concurrent seal() calls on a single sender context could reuse nonces. Two protections:

1. Pin to fixed version (≥1.7.5).
2. For HERALD whisper, never call seal more than once on a single context — whisper is one-shot per envelope. The default usage pattern is safe.

A library-agnostic rule: do not share an HPKE sender or recipient context across asynchronous tasks unless the library documents internal locking.

8.4 Export interface as a side-channel

HPKE's export interface (Sender.Export/Recipient.Export) lets callers derive arbitrary keys from the same sender/recipient setup. Several libraries surface Export-Only as a "no-AEAD" mode. HERALD whisper does not use export. If a future extension does, it MUST domain-separate via the exporter context string and MUST NOT reuse keys across exports and seals — the library does not enforce this for you.

8.5 Ciphersuite negotiation downgrade

EXT §7.3 lets whisper.kem carry the suite identifier. A naïve verifier that trusts the field could be forced to a weaker suite. Mitigation:

This is a HERALD-protocol-level concern, not a library bug, but the library's API ergonomics affect how easy it is to lock down.

8.6 Recipient public key freshness

EXT §7.3 says the recipient X25519 public key is "typically published in the recipient's birth attestation or in a Herald-issued endpoint cert." The library doesn't validate freshness. HERALD verifiers MUST reject whispers encrypted to revoked or molted keys (a separate concern from HPKE itself but a system-level requirement).

8.7 Pre-RFC drafts vs. RFC 9180

cisco/go-hpke and earlier cloudflare/circl/hpke versions implement drafts of HPKE that predate RFC 9180. The wire formats can differ in subtle ways (KEM/KDF/AEAD codepoint values, label strings). HERALD MUST require RFC 9180 conformance specifically; implementers MUST verify against RFC 9180 §A test vectors.

8.8 ML-KEM and post-quantum suites

All four reference libraries now ship ML-KEM and X-Wing variants. EXT §7.3's default suite is purely classical (X25519/AES-128-GCM). HERALD v0.1 MUST stick to the classical default; PQ migration is a separate task. Library API surfaces that aggressively prefer PQ suites (e.g., the hpke-js umbrella) should be configured explicitly to the X25519 suite.

8.9 License compatibility

Most libraries are MIT or Apache-2.0 / MIT dual-licensed. cryspen/hpke-rs is MPL-2.0, which is file-level copyleft. If HERALD reference impls vendor or statically link hpke-rs, ensure the consuming code's license either matches or declares the MPL files unchanged (MPL §3.3). Most projects can comply trivially; flag for reviewers.

8.10 Audit status across the board

No HPKE library surveyed has a published third-party formal audit at this date (verify on re-check). The Cloudflare review of rust-hpke v0.8 is a credible internal-review prior, not a public audit. RFC 9180's small surface and the libraries' habit of building on already-audited primitives (X25519, HKDF, AES-GCM) is a comfort, but HERALD should be honest in its threat model: the HPKE binding is "best-available unaudited RFC-9180 implementation," not "audited cryptographic core."


9. References

Specs

TypeScript

Python

Go

Rust

Test vectors


End of survey.

Continue exploring