Aerie aggregates Falcon-512 or Falcon-1024 signatures into one succinct proof. It is built on the Akita lattice polynomial commitment scheme.
This implementation selects Akita's dev-protocol feature at
e023b6a46143f7b23199ccecb2684192baf7b640,
the full revision pinned in Cargo.toml. The feature name is not a
compatibility guarantee.
Each signature in a batch keeps its own public key, salt and message. One proof covers one parameter set; Falcon-512 and Falcon-1024 signatures cannot be mixed.
The paper, Aerie: Efficient Aggregation of Falcon Signatures, describes the protocol, proves its security and reports the measurements below.
Status: research release. Wire version 1 is frozen: incompatible transcript or encoding changes require a new wire version. Proof interchange requires the same pinned implementation and profile. Neither parameter set has passed the production acceptance criteria of the release policy.
An accepting proof shows that, for every record, a signature exists that:
- satisfies the Falcon verification equation for that record's public key, salt and message;
- meets the Falcon norm bound;
- fits Falcon's canonical compressed signature encoding.
The verifier is given the public keys and messages. The default
prove_aggregate produces per-record 40-byte salts. The wire format and verifier
also support common-salt mode: all signatures use the same salt, and the proof
carries one 40-byte salt expanded per record for binding. The verifier computes
HashToPoint itself; hashing is not proved inside the proof. The full statement is in
specs/falcon.md.
Signatures are never sent to the verifier. Witness omission does not establish zero knowledge; zero knowledge is not claimed.
Aerie supports Falcon v1.2 padded signature format with key-prefixed message
framing (raw mode, empty context), as implemented by fn-dsa 0.3.0. The
HashToPoint input is salt || SHAKE256(pk,64) || 0x00 || 0x00 || message.
Original r || m framing is unsupported. This is not a compatibility claim
for all v1.2 signers or final FIPS 206 (FN-DSA).
The two salt modes require different signers. With per-record salts, each
signature comes from a signer that draws a fresh salt on every signing
attempt, as FN-DSA and the fn-dsa 0.3.0 signer do. Fouque et al. reduce the
unforgeability of this signer to a lattice assumption in the random-oracle
model (ePrint 2024/1769).
In common-salt mode the signer receives the salt and, as Falcon v1.2 does,
retries the norm and encoding checks at that fixed target. It must return the
same signature whenever it is asked again to sign the same salt and message,
either from a cache or by deriving its sampler randomness from a dedicated
secret key, the salt and the message
(specs/falcon.md, Section 10.2). Unforgeability of this
fixed-target signer is an assumption of common-salt mode. No reduction to a
standard lattice assumption is known, and no attack is known.
Aerie includes no common-salt signer. The honest-vectors fixtures in
aerie-falcon-witness replay one salt to fn-dsa from a seeded generator to
build test and benchmark batches; their keys are not production key material.
Aerie requires Rust 1.95.
cargo test --workspace --locked --profile ciCONTRIBUTING.md lists the full checks.
The API is generic over the parameter set P, either
aerie::falcon::Falcon512 or aerie::falcon::Falcon1024. Values built for one
parameter set do not type-check against the other.
This example uses the aerie facade:
# #[cfg(feature = "prover")]
# fn main() {
use aerie::falcon::{
prove_aggregate, verify_aggregate, CanonicalPublicKeyBytes,
CanonicalSignatureBytes, Falcon512, FalconAggregateSetup,
};
use aerie::falcon::{AggregationRecord, AggregationStatement, AggregationWitness};
type P = Falcon512; // or aerie::falcon::Falcon1024
/// Each record is (encoded public key, padded signature, message) for a
/// signature that verifies under Falcon v1.2 padded signature format with
/// key-prefixed message framing (raw mode, empty context), as implemented by
/// fn-dsa 0.3.0. Original `r || m` framing is unsupported; compatibility with
/// all v1.2 signers or final FIPS 206 is not claimed.
fn aggregate(
records: &[(Vec<u8>, Vec<u8>, Vec<u8>)],
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut statement_records = Vec::new();
let mut signatures = Vec::new();
for (public_key, signature, message) in records {
let public_key = CanonicalPublicKeyBytes::<P>::parse(public_key)?;
let record = AggregationRecord::new(public_key, message.clone())?;
statement_records.push(record);
signatures.push(CanonicalSignatureBytes::<P>::parse(signature)?);
}
let statement = AggregationStatement::new(statement_records)?;
let witness = AggregationWitness::new(signatures)?;
// Depends only on the record count; build once and reuse per batch size.
let setup = FalconAggregateSetup::<P>::new(records.len())?;
let proof = prove_aggregate(&setup, &statement, &witness)?;
// The verifier sees the public statement and the proof.
verify_aggregate(setup.verifier(), &statement, &proof)?;
Ok(proof)
}
# }
# #[cfg(not(feature = "prover"))]
# fn main() {}The default facade enables prover and parallel.
prove_aggregate uses per-record salts. To prove a batch in which every
signature uses the same salt, call
prove_aggregate_with_mode(&setup, &statement, &witness, SaltMode::Common);
the verifier reads the mode from the proof. The aerie::falcon facade is the
supported public API. The internals features of aerie-prover and
aerie-falcon exist for the benchmark harness and adversarial tests and are
not a stable API.
For verification only, use aerie with
default-features = false, features = ["verifier"], or depend on
aerie-verifier directly with default features disabled. Both exclude proving,
witness construction, the Falcon signer, producer Akita crates, and benchmark
and profiling dependencies; scalar builds also exclude Rayon. Construct
FalconVerifierSetup::<P>::new(records) and call verify_aggregate through
aerie::falcon or aerie_verifier. Add parallel for parallel verification.
A proof covers 1 to 65,536 records. Batches are padded to a power of two, with a minimum of 8 rows for Falcon-512 and 4 for Falcon-1024, so smaller batches cost the same as the minimum.
Total wire size depends on the padded batch, parameter set and salt mode. For comparison, a padded Falcon-512 signature is 666 bytes and a padded Falcon-1024 signature is 1280 bytes. The benchmark below prints the measured PCS proof bytes and full wire bytes for each batch.
cargo bench --locked -p aerie-bench --features falcon-bench --bench falcon -- 8Append --degree=1024 to select Falcon-1024 or --common-salt to benchmark
common-salt mode. The benchmark proves real Falcon signatures made by the
fn-dsa 0.3.0 signer from test keys. On pull requests that touch the prover, the
Falcon Benchmarks workflow posts head
timings as a comment, with a base comparison when the base provides the same
benchmark schema and package. For traces, see
specs/techniques/profiling.md.
Measured on this release's code on an Apple M4 Max (12 performance and 4 efficiency cores, 64 GiB, macOS 27.0.1) with Rust 1.95.0, using honestly generated batches with one salt per signature. Each timing is the median of seven runs; parallel runs use 16 worker threads. Sizes include salts and headers but not the verifier-supplied keys and messages. The common-salt size keeps one salt in place of the per-record salts. Peak memory is the benchmark process's maximum resident set size. One KiB is 1024 bytes.
Falcon-512:
| Signatures | Prove, 16 threads | Verify, 1 thread | Verify, 16 threads | Aggregate | Common-salt aggregate | Peak memory |
|---|---|---|---|---|---|---|
| 1,024 | 132 ms | 5.5 ms | 4.6 ms | 102 KiB | 62 KiB | 198 MiB |
| 2,048 | 172 ms | 7.5 ms | 5.2 ms | 143 KiB | 63 KiB | 280 MiB |
| 4,096 | 207 ms | 11.1 ms | 6.0 ms | 223 KiB | 63 KiB | 418 MiB |
| 8,192 | 263 ms | 16.9 ms | 6.7 ms | 385 KiB | 65 KiB | 658 MiB |
| 16,384 | 386 ms | 29.0 ms | 8.2 ms | 705 KiB | 65 KiB | 1,126 MiB |
| 32,768 | 645 ms | 53.6 ms | 11.6 ms | 1,346 KiB | 66 KiB | 2,113 MiB |
| 65,536 | 1,071 ms | 101.2 ms | 15.8 ms | 2,627 KiB | 67 KiB | 4,127 MiB |
Falcon-1024:
| Signatures | Prove, 16 threads | Verify, 1 thread | Verify, 16 threads | Aggregate | Common-salt aggregate | Peak memory |
|---|---|---|---|---|---|---|
| 1,024 | 169 ms | 7.2 ms | 5.2 ms | 103 KiB | 63 KiB | 286 MiB |
| 2,048 | 202 ms | 10.2 ms | 5.6 ms | 143 KiB | 63 KiB | 411 MiB |
| 4,096 | 263 ms | 16.1 ms | 6.3 ms | 225 KiB | 65 KiB | 658 MiB |
| 8,192 | 384 ms | 26.9 ms | 7.9 ms | 385 KiB | 65 KiB | 1,126 MiB |
| 16,384 | 633 ms | 49.4 ms | 11.1 ms | 706 KiB | 66 KiB | 2,106 MiB |
| 32,768 | 1,070 ms | 92.1 ms | 15.3 ms | 1,347 KiB | 67 KiB | 4,108 MiB |
| 65,536 | 1,861 ms | 177.0 ms | 23.8 ms | 2,627 KiB | 67 KiB | 7,947 MiB |
For comparison, checking 32,768 Falcon-512 signatures one by one on one core
of the same machine takes 519 ms with PQClean's aarch64 implementation
(0586a824)
and 536 ms with fn-dsa 0.3.0. Aerie's one-thread verification is about 10
times faster than PQClean. Proving the same batch on one thread takes 4.3 s,
about 8 times as long as checking the signatures with PQClean.
See SECURITY.md for the security policy and vulnerability reporting.
The protocol and its security argument are specified in specs/:
falcon.md: the protocol;falcon-projection-theorem.md: the projection soundness theorem;falcon-pcs-extraction.md: commitment extraction;falcon-ledger.mdandfalcon-1024-ledger.md: the soundness error for each parameter set.
The release policy sets the criteria for a
production release, and Section 12.2 of falcon.md lists
the open obligations.
| Crate | Responsibility |
|---|---|
crates/aerie |
Public API (aerie::falcon) |
crates/aerie-bench |
Benchmark harness, build provenance and optional profiling (unpublished) |
crates/aerie-falcon |
Verifier-safe common protocol support, public math, transcript and wire codec |
crates/aerie-verifier |
Verification API backed by common replay |
crates/aerie-prover |
Aggregate proving, setup, forward projection and commitment execution |
crates/aerie-falcon-native |
Falcon encodings and public HashToPoint preprocessing |
crates/aerie-falcon-statement |
Public statements, metadata, shared errors and checked framing |
crates/aerie-falcon-witness |
Validated native arithmetic, witness preparation and fixtures |
crates/aerie-pcs-config |
Akita commitment presets and fold-schedule catalogs |
crates/aerie-keccak, crates/aerie-simd |
Keccak backends and SIMD kernels |
crates/aerie-invariants |
Cross-crate and adversarial tests (unpublished) |
crates/aerie-profiling |
Optional tracing (unpublished) |
Start with the specification index. The paper gives the security proofs and the evaluation.
- Bolboceanu, Bootle, Lyubashevsky, Merino-Gallardo, and Seiler, Orthus: Practical Sublinear Batch-Verification of Lattice Relations from Standard Assumptions, ePrint 2026/398.
- Aardal et al., Aggregating Falcon Signatures with LaBRADOR, ePrint 2024/311.
- Lyubashevsky et al., The LaZer Library: Lattice-Based Zero Knowledge and Succinct Proofs for Quantum-Safe Privacy, ePrint 2024/1846.
- Beullens and Seiler, LaBRADOR: Compact Proofs for R1CS from Module-SIS, ePrint 2022/1341.
- Fouque et al., Falcon: Fast-Fourier Lattice-based Compact Signatures over NTRU, falcon-sign.info.
- Fouque, Gajland, de Groote, Janneck, and Kiltz, A Closer Look at Falcon, ePrint 2024/1769.
- Lazar and Peikert, Deterministic Falcon Signatures, algorand/falcon.
- Dao, Bodaghi, Khajehpour, Vitto, Badakhshan, Georghiades, Liu, Zhang, and Thaler, Akita: A High-Performance Lattice-Based Polynomial Commitment Scheme, ePrint 2026/1983.
- LayerZero-Labs/akita, the pinned lattice multilinear PCS dependency.
Aerie is built on Akita, developed at LayerZero Labs. Part of Quang Dao's work on Aerie was done at LayerZero Labs, which also provided equipment and funded AI model subscriptions and API usage. Part of Alberto Centelles's work on Aerie was done at a16z crypto research, which also funded AI model usage.
We thank the Akita maintainers, Omid Bodaghi, Amirhossein Khajehpour, Taghi Badakhshan and Giuseppe Vitto, for ongoing discussions about Aerie and for designing, implementing or reviewing the Akita changes it depends on. These include:
- committing groups of polynomials with different sizes and coefficient bounds in one batch (30505610, 2322d485);
- one prover backend that holds the setup and serves several parameter sets (#36, #76);
- the Spongefish proof stream that Aerie's opening proof uses (#37);
- schedule planning from declared coefficient bounds, and the prepared verifier (#40, #41, #85).
Aerie is dual-licensed under the MIT License and the
Apache License 2.0, at your option. Unless stated otherwise,
contributions intentionally submitted for inclusion are dual-licensed on the
same terms. The paper in paper/ is licensed under
CC BY 4.0.