C++23 library for post-quantum anonymous tokens. A client gets a token from an issuer, then presents it later without the issuer linking the two steps.
All protocols use UOV signatures and the VOLE-in-the-Head (VOLEitH) proof system from FAEST.
Warning
Experimental and not audited. Do not use in production. APIs and wire formats will change as the specifications evolve.
| Protocol | Use it when | Header |
|---|---|---|
| PomFRIT | You need a blind signature: the issuer signs a message it never sees. | vole_mole/pomfrit.hpp |
| Moussaka | A verifier trusts several issuers and must not learn which one issued a token. | vole_mole/moussaka.hpp |
| Ratatouille | A token carries a hidden credit balance that the issuer updates on each spend. | vole_mole/ratatouille.hpp |
PomFRIT implements Algorithm 2 of ePrint 2026/109, a Fischlin-style blind signature. Moussaka is an Issuer-Hiding Anonymous Token (draft-authors-mole-vole-ihat). Ratatouille is an Anonymous Credit Token (draft-authors-mole-vole-act). Moussaka and Ratatouille were designed for MOLE, but do not depend on it.
You need Meson 1.4 or later, Ninja, Python 3, and Clang 21+ or GCC 16+. Apple Clang 17 is too old. Meson downloads the other dependencies.
Supported CPUs are aarch64 with the crypto extensions and x86_64. On x86_64 the
build uses -march=native, so binaries may not run on other machines.
meson setup build
meson compile -C build
meson test -C build --print-errorlogsOn macOS, install a recent LLVM with Homebrew and select it before setup:
brew install meson ninja llvm@22
export CC=$(brew --prefix llvm@22)/bin/clang
export CXX=$(brew --prefix llvm@22)/bin/clang++Issue and verify a PomFRIT blind signature:
#include <vole_mole/pomfrit.hpp>
#include <vole_mole/uov.hpp>
using namespace vole_mole;
// Issuer: generate a key pair once and publish pk.
const uov::SecretKey sk(uov::generate_secret_key_bytes());
const uov::PublicKey pk(sk.public_key_bytes());
// Client: blind the message. The Issuer never sees msg.
auto [pending, request] = pomfrit::issue_request(pk, msg);
// Issuer: sign the blinded request.
const auto response = pomfrit::issue_response(sk, request);
// Client: unblind. Returns std::nullopt if the response is invalid.
const auto sig = std::move(pending).finalize_issue(pk, response);
if (!sig) return EXIT_FAILURE;
// Verifier: check the signature. It cannot be linked to the issuance.
const bool ok = pomfrit::verify(pk, msg, *sig);msg is a std::span<const uint8_t>. Each public header documents its
functions, including what they return and throw.
| Example | Shows |
|---|---|
keys.cpp |
UOV key generation, encoding, and expansion |
pomfrit.cpp |
Blind signature issuance and verification |
moussaka.cpp |
Issuance from one of several Anchors and redemption, with replay rejection |
ratatouille.cpp |
Issuance, spending credits, and double-spend rejection |
Build as above, then run:
./build/examples/example_pomfritCreate subprojects/vole-mole.wrap in your project:
[wrap-git]
url = https://github.com/cloudflareresearch/vole-mole.git
revision = <commit>
depth = 1Set revision to a commit hash. The API is unstable, so don't track a branch.
Meson clones the repository and its dependencies during meson setup. Then
add to your meson.build:
vole_mole_dep = subproject('vole-mole',
default_options : ['tests=false', 'benchmarks=false', 'examples=false'],
).get_variable('vole_mole_dep')There is no installed package or pkg-config file yet.
This code has not been audited.
The library does not prevent double spending. Moussaka finalize_redeem and
Ratatouille spend_response return a nullifier for each accepted token. Store
it and reject any request that reuses it, for as long as you accept tokens for
those contexts. For Moussaka, one record must cover every Anchor Set you accept,
since a token is valid under any set that contains its Anchor. Expiring
contexts, such as an issuance epoch, bound the storage. PomFRIT has no built-in
nullifier; to make signatures single-use, put one in the signed message.
Client messages carrying proofs are 5–17 KB. On an Apple M4 Pro, every step takes under 2 ms, except Moussaka redemption, which grows with the number of Anchors (about 7 ms for 64). See docs/perf.md for full numbers, the smaller-proof parameter trade-off (4-12KB), and how to run the benchmarks.
BSD 3-Clause, see LICENSE. Meson downloads
faest-arch-opt (MIT) and pqov (CC0 or Apache-2.0) as
subprojects. src/voleith.hpp is adapted from faest-arch-opt and keeps its MIT
notice.
This library is based on a prototype by Tianyu Zhang, written during his Summer 2026 internship at Cloudflare Research. Tianyu also contributed to the circuit and protocol designs.