A Faster, Lighter, PQC-Ready Go JOSE Library (github.com/lestrrat-go/jwx/v4)
TL;DR:
A Faster, Lighter, PQC-Ready Go JOSE Library (github.com/lestrrat-go/jwx/v4)
TL;DR:
- Go module github.com/lestrrat-go/jwx now has a v4. Please read the announcement page for details.
- It’s lighter, faster, and Post-Quantum ready.
- You can secure your JWTs using PQC or Hybrid HPKE today.
- Some of this was only possible because of (or, “I wouldn’t have done it without”) Coding Agents.
A Bluesky Post Pushes Me Over the Edge
I have been developing and maintaining a Go library for JOSE standards called [github.com/lestrrat-go/jwx](https://github.com/lestrrat-go/jwx) (JWX) for over 10 years now.
If you’re not in the JOSE corner of the Go ecosystem: JOSE is the family of standards behind JWT, JWS, JWE, and JWK — the acronyms you end up touching the moment you do anything with OAuth, OIDC, service-to-service auth, or signed webhooks.
JWX is the library I built because the alternatives didn’t quite do what I needed, and then kept maintaining because people started relying on it.
Throughout those ten years, I’d always been interested in how to incorporate post-quantum cryptography (PQC) into JWX — ever since it started popping up on my radar. For most of that time it was pure curiosity and not a priority.
That changed recently. I have become more and more convinced that I need to be ready to ship PQC with JWX sooner rather than later. Obviously I’m not an expert on quantum computing, and I may very well be prey to fear mongering. I have no insider knowledge. I don’t know when — or whether — a cryptographically relevant quantum computer is actually going to exist.
But at the same time, the quiet, coordinated efforts by major players like Google and Cloudflare to roll PQC into their production stacks have not gone unnoticed. And of course, it’s hard to ignore the fact that NIST has clearly marked RSA, ECDSA, and EdDSA as scheduled to be deprecated and disallowed within the next 3 to 8 years.
Then Filippo Valsorda posted on Bluesky that shit is going to hit the fan sooner than we thought, and then I went “Okay, it’s time to get off of my ass”.
[embed]
After exactly 14 days later, voila, I just released github.com/lestrrat-go/jwx/v4, a major release that can bring you PQC today.
The v4 Release at a Glance
V4 comes with a fairly substantial list of changes, so if you are thinking of migrating see MIGRATION.md and [github.com/jwx-go/jwxmigrate](https://github.com/jwx-go/jwxmigrate) for a mostly-mechanical upgrade path.
However, if I must pick a handful of highlights, it will be these three:
- Lighter: the core module’s direct dependencies are noticeably smaller than v3. Use of generics allowed us to remove features/extra modules that require
reflect. Moving optional features out into companion modules under[github.com/jwx-go](https://github.com/jwx-go) further shortened the list. - Faster: generics removed a fair amount of the reflection and runtime type-assertion that v3 was full of. As a result, we are sometime 300% faster than v3, and exceed or is about the same level as other libraries in this area. See benchmarks for details.
- PQC-ready: ML-DSA, ML-KEM, and hybrid HPKE all work today as opt-in companion modules.
The Companion Module System
As stated above, v4 uses a companion module system to extend features. In v3, every feature a lived in the main module, sometimes gated by a build tag. In v4 we improve the these non-core features live outside of the core module, allowing for liberal use of non-stdlib modules — those who want the feature can opt-in to add new dependencies, those who do not, they can simply not use it.
All companion modules have the same major version number as the main JWX version that they work with. So all companion modules have a v4 version; should we release a JWX v5, the companion modules will be v5.
Features moved out of core if they matched either of the following: a) The feature relies on a module outside of standard library, or b) The module implements a standard that has not been finalized yet.
So for example, ML-DSA PQC signing is implemented outside of core, because crypto/mldsa does not live in JWX code becase it depends on filippo.io/mldsa until it’s implemented in Go stdlib, and ML-KEM PQC key management is also outside of core depsite Go shipping with crypto/mlkem because the RFC itself is still in draft stage.
You can see a full list of official companion modules in the docs or in the jwx-go GitHub organization.
This whole system gave us the freedom to extend to experimental features while keeping the core JWX module light, as well as paving the way for third party modules.
Without this I wouldn’t have been able to perform the necessary refactoring for v4. Of course, this architecture comes with its problems, but more on that later.
Post-Quantum Is Today, Not Tomorrow
Harvest now, decrypt later is something we need to mitigate today. Obviously we won’t be able to just switch from classical cryptography to PQC in one fell swoop; But that means we need to be ready now. That’s why I experdited the JWX v4 release.
In JWX v4 and its companion modules, the following are supported:
- PQC Signatures via ML-DSA (44,65,87)
- PQC Key Management via ML-KEM (768,1024)
- Composite Signatures for ML-DSA + Classical Crypto signatures
- Hybrid PQ HPKE for ML-KEM + Classical Crypto encryption
While PQC signing with ML-DSA is almost across the line, crypto/mldsa is not in core, so it’s a companion module, not baked into JWX. The three remaining items are companion modules and marked experimental because their underlying specification drafts are still moving.
So I do not recommend you switching to using these anytime soon, but you can always start to experiment today. Also, knowing how businesses work, I think it’s worth it to get this code past security reviews now than later.
Maintaining a Modular Architecture with AI Agents
Splitting JWX into core module and a fleet of companion modules may sound east and obvious thing to do, given the requirements and feature sets.
In practice, it’s a real hassle trying to do the dance between 13 companion modules and try not to miss a beat. Keeping each module’s repository settings, CI workflows, etc in sync is not something I recommend a mere mortal to do.
In fact, this is exactly the reason I was hesitant to do anything close to a modular split of JWX.
But it’s 2026, and we have Coding Agents. I no longer need to type the same command 13 times across multiple repositories (all while making sure that I do something a little different for those couple of repositories that don’t quite follow the rule, etc…)
I started out by laying out the rules to my coding agent, such as the expected repo setup, conventions, workflows, etc. Then I had my coding agent create templates to create settings files and workflows from those rules, along with a mechanism to apply them in bulk.
Once I had the setup, I created some new skills to execute bulk commands across all repositories. Now I can just “apply the same changes to all companion modules” or “update repository settings for each from the templates”.
And of course, once that was done, I just let my coding agents loose and had them code my vision within 14 days, across 14 repositories, producing approximately 900 commits and 300 pull requests.
With github.com/lestrrat-go/jwx/v4 I can say that we achieved something pretty special. Please try it out! Don’t forget you can migrate code semi-automatically using github.com/jwx-go/jwxmigrate@latest
메타데이터
- post_id
- f2bc712921b0
- slug
- a-faster-lighter-pqc-ready-go-jose-library-github-com-lestrrat-go-jwx-v4-f2bc712921b0
- url
- https://medium.com/@lestrrat/a-faster-lighter-pqc-ready-go-jose-library-github-com-lestrrat-go-jwx-v4-f2bc712921b0
- canonical_url
- https://medium.com/@lestrrat/a-faster-lighter-pqc-ready-go-jose-library-github-com-lestrrat-go-jwx-v4-f2bc712921b0
- author_url
- https://medium.com/@lestrrat
- status
- ok
- fetched_at
- 2026-06-17 17:30:55