Skip to content

pipavlo82/ml-dsa-65-ethereum-verification

Repository files navigation

ML-DSA-65 Ethereum Verification (FIPS-204 shape)

Solidity Foundry FIPS-204 ERC-7913 Tests Gas Gas License: MIT

Status: Active development. End-to-end verify() POC + full gas harness. Foundry suite green.
Latest gas (snapshot): test_verify_gas_poc = 68,901,612 gas (logged verify POC ≈ 68,158,524).
PreA milestone: packed A_ntt calldata microbench for compute_w1,499,354 gas (rho0/rho1).
Phase12: ERC-7913 adapters + verifyWithPackedA(...) path (calldata packedA_ntt) + tests & gas microbenches.

This repo is research / standardization work. Not audited. Do not use in production.


Table of Contents


Overview

This repository implements an ML-DSA-65 (FIPS-204 shape) verification pipeline in Solidity, targeting:

  • A clean, auditable on-chain verifier baseline (research + standardization)
  • ERC-7913-style signature verifier adapters (wallets, AA, sequencers, rollups)
  • Reproducible KAT-style tests and deterministic gas snapshots
  • A realistic path to reduce the dominant cost: w = A · z − c · t1

Standards / context


What's implemented

Core crypto pipeline (FIPS-204 shape)

  • Keccak/SHAKE backend (vendored) + thin ML-DSA XOF wrapper
  • NTT/INTT for ML-DSA modulus/degree:
    • q = 8,380,417
    • n = 256
  • Poly / PolyVec / Hint scaffolding for ML-DSA-65 shape:
    • k = 6 (t1)
    • ℓ = 5 (z, h)
  • FIPS-shaped decode for pubkey/signature (t1 / z / c) + KAT coverage
  • Matrix-vector core: w = A · z − c · t1
  • MLDSA65_Verifier_v2 end-to-end verify() POC (decode + checks + compute_w)

ERC-7913 integration

  • MLDSA65_ERC7913Verifier adapter implementing ERC-7913-style verification
  • MLDSA65_ERC7913BoundCommitA (CommitA binding flow) to prevent matrix substitution when using precomputed A
  • Phase12 fast-path:
    • verifyWithPackedA(...) accepts calldata packed A_ntt
    • Adapter tests + bound-commit tests
    • Gas microbench harness

PreA track (performance isolation)

PreA isolates the hot loop and shows what's achievable when A_ntt is supplied efficiently:

  • calldata-friendly packed A_ntt format (loader)
  • microbench: compute w from packed A_ntt in isolation
  • Commitment binding helper (CommitA) to prevent swapping A

PreA (packedA_ntt) — Convention + On-chain proof runner

This repo defines the PreA ABI convention for supplying a precomputed packedA_ntt matrix (NTT-domain) to the verifier fast-path, and provides an on-chain runner to reproduce the ~1.50M gas compute_w cost.

  • PreA (packedA_ntt) convention: docs/preA_packedA_ntt.md
  • On-chain proof runner: script/RunPreAOnChain.s.sol

Reproduce (local anvil)

# terminal 1
anvil

# terminal 2
export RPC_URL=http://127.0.0.1:8545
export PK=<YOUR_ANVIL_PRIVATE_KEY>

forge script script/RunPreAOnChain.s.sol:RunPreAOnChain \
  --rpc-url $RPC_URL \
  --private-key $PK \
  --broadcast -vv

Expected logs

gas_compute_w_fromPacked_A_ntt(rho0) 1499354
gas_compute_w_fromPacked_A_ntt(rho1) 1499354

Gas benchmarks

All numbers are from Foundry tests + .gas-snapshot.

Component Gas Where
verify() POC (snapshot) 68,901,612 MLDSA_VerifyGas_Test:test_verify_gas_poc()
verify() POC (log) ~68,158,524 same test logs
compute_w breakdown see logs MLDSA_VerifyGasBreakdown_Test
PreA compute_w_fromPacked_A_ntt 1,499,354 PreA_ComputeW_GasMicro_Test
ERC-7913 verifyWithPackedA (micro) ~71,796 MLDSA_ERC7913_PackedA_GasMicro.t.sol

Key point: end-to-end verifier is dominated by compute_w (matrix-vector core).
PreA demonstrates what the hot loop can look like when A_ntt is supplied efficiently.


Benchmark datasets (gas-per-secure-bit)

This repo is primarily about a clean, FIPS-204-shaped ML-DSA-65 verifier in Solidity (correctness/KATs/structure + gas engineering).

For cross-scheme comparisons and normalized reporting (e.g. gas per effective security bit under explicit assumptions), see:

It contains reproducible datasets (data/results.csv / .jsonl), runners, and provenance tracking (repo, commit, bench_name, chain_profile).

Current default normalization uses a proxy security metric:

gas_per_secure_bit = gas_verify / lambda_eff

(where lambda_eff is recorded explicitly and may evolve into other metric types, including VRF/min-entropy under stated threat models).

Note: vendor implementations remain under upstream licenses; this repo focuses on benchmark artifacts + provenance.

Gas-per-secure-bit benchmarking (external dataset repo)

For normalized cross-scheme comparisons and interoperability vectors (AA/UserOp JSON schema), see:


Standardization track

This repository is structured as a standardization-friendly reference implementation for PQ signature verification on EVM:

1) Common verifier surface (ERC-7913)

We treat ERC-7913 as the canonical wallet / AA / rollup-facing interface for PQ verifiers.

Goal: a verifier that can be plugged into tooling in the same way OpenZeppelin patterns are reused for classical primitives.

2) Canonical calldata / ABI shapes (draft)

We are converging on stable calldata layouts for:

  • pubkey / signature (FIPS-204 shape)
  • optional precomputed inputs (e.g., packedA_ntt) for performance paths

This enables apples-to-apples gas comparisons across ML-DSA / Dilithium / Falcon, and makes it feasible to build shared tooling.

3) Canonical JSON KAT schema (draft)

Vectors are tracked in a KAT-like JSON format to support:

  • deterministic decoding tests
  • cross-implementation conformance checks
  • reproducible CI for correctness

4) Reproducible benchmarking discipline

Gas is measured via:

  • dedicated gas harness tests
  • .gas-snapshot snapshots
  • logged per-component breakdown (decode vs compute_w)

This ensures changes are measurable and comparable over time.

5) Alignment across PQ algorithms

The long-term intent is a shared "PQ verifier kit" mindset:

  • same high-level interface (ERC-7913)
  • compatible vector formats and harnesses
  • comparable performance datasets (gas / calldata size / constraints)

Related discussion (ecosystem context):

Surfaces / interfaces: ERC-7913 adapters are the app-facing surface for ML-DSA-65 verification on EVM. EIP-7932 is a candidate protocol-facing surface (precompile-style); the goal is compatible ABI shapes and a shared JSON KAT schema across both surfaces.


Milestones (Phases)

This repo has been developed as an iterative set of "phases" with reproducible tests + gas snapshots.

Phase Branch / PR What landed Tests Key gas numbers
Phase 5 feature/mldsa-ntt-opt (baseline snapshot commit be08d42) Baseline verify() POC with NTT assembly path; established .gas-snapshot discipline green test_verify_gas_poc81,630,615
Phase 7 feature/mldsa-ntt-opt-phase7 → wired back into feature/mldsa-ntt-opt _compute_w hot-loop optimization via inline assembly pointer-walk; fixed memory layout bug (row pointers) 72/72 green (then higher as suite grew) test_verify_gas_poc80,000,775; matrixvec cores logged
Phase 10 feature/mldsa-ntt-opt-phase11-preA (history includes Phase10 commits) Switched critical NTT mul to native EVM mulmod (dropped Barrett attempt); large gas win green verify() POC dropped materially vs ~80M-era baseline
Phase 11 (PreA) feature/mldsa-ntt-opt-phase11-preA Introduced packed A_ntt calldata format + PreA compute_w microbench; ExpandA gas micro harness; removed dump-only test 85/85 (later 86/86) green test_verify_gas_poc68,901,612 (log ≈ 68,158,524) ; PreA compute_w_fromPacked_A_ntt1,499,354
Phase 11 (CommitA) feature/mldsa-ntt-opt-phase11-preA Added CommitA binding flow to prevent matrix substitution when accepting precomputed A green binding overhead measured via micro benches
Phase 12 (PackedA + ERC-7913) feature/mldsa-ntt-opt-phase12-erc7913-packedA / PR #27 Added verifyWithPackedA(...) fast-path (calldata packedA_ntt) in ERC-7913 adapters, tests + gas microbenches 89/89 green adapter microbench ~71,796 gas (ok), ~71,691 (mismatch); PreA microbench unchanged

Notes:

  • All gas numbers are sourced from Foundry logs and/or .gas-snapshot in the corresponding phase.
  • "verify() POC" is a research baseline that is still dominated by compute_w = A·z − c·t1; PreA isolates the hot loop to track achievable improvements when A_ntt is supplied efficiently.

Build & test

Build

forge build

Run all tests

forge test -vv

Focused runs (recommended)

# verify() POC gas
forge test --match-test test_verify_gas_poc -vv

# breakdown (decode + compute_w)
forge test --match-contract MLDSA_VerifyGasBreakdown_Test -vv

# PreA packed A_ntt microbench
forge test --match-contract PreA_ComputeW_GasMicro_Test -vv

# MatrixVec correctness + gas
forge test --match-contract MLDSA_MatrixVec_Test -vv
forge test --match-contract MLDSA_MatrixVecGas_Test -vv

# ERC-7913 packedA adapter tests
forge test --match-contract MLDSA_ERC7913_PackedA_Test -vv

# Update .gas-snapshot
forge snapshot

Repo layout

Typical structure (names may evolve, but intent stays stable):

  • contracts/
    • verifier/MLDSA65_Verifier_v2.sol, ERC-7913 adapters, bound-commit flow
    • ntt/ — NTT/INTT + zetas tables (ML-DSA q = 8,380,417)
    • vendored SHAKE/Keccak backend
    • poly/ — Poly / PolyVec / Hint helpers (FIPS shape)
  • test/
    • decode tests + JSON KAT tests
    • gas harnesses (verify POC, breakdown, matrixvec gas, PreA micro)
    • ERC-7913 adapter tests
  • test_vectors/
    • JSON KAT-style vectors (pubkey / signature / message-hash)
  • .gas-snapshot
  • foundry.toml
  • README.md, PQ-NOTES.MD

Design notes

Why ERC-7913

ERC-7913 generalizes signature verification beyond "address-only" signatures (EOA / ERC-1271), which is important for PQ keys.

OpenZeppelin interface docs mention the expected magic values:

Why CommitA binding (when using packed A)

If we accept a precomputed A_ntt from calldata, we must prevent an attacker from swapping the matrix. CommitA binding is a lightweight "bind A to rho / context" mechanism for the fast-path.


Roadmap

Short-term (next practical steps):

  1. Wire PreA fast-path into MLDSA65_Verifier_v2

    • guarded by CommitA binding
    • keep legacy path for reference correctness
  2. Tighten end-to-end FIPS-204 conformance

    • challenge derivation, sampling details, KAT equality checks
    • keep the repo FIPS-shaped and test-vector-driven
  3. Push down compute_w gas

    • inner-loop reductions (fewer loads/stores)
    • unrolling / batching
    • minimize memory roundtrips in NTT domain
  4. Standardization packaging

    • ERC-7913 "drop-in verifier" shape
    • canonical JSON KAT pipeline
    • reproducible benches + "gas per secure bit" methodology (separate workstream)

Competitors / related PQ/EVM work

PQ signature Solidity implementations / research

Standardization / ecosystem direction


Security

  • Not audited. Research-quality code.
  • Main risk surfaces:
    • calldata-supplied A_ntt path (must be bound/committed)
    • decode correctness (FIPS packing/unpacking)
    • domain separation (hash/XOF wiring)
  • This repo is designed to be test-vector-first and gas-bench reproducible.

License

MIT (see LICENSE).
Vendored Keccak/SHAKE files retain their original headers and license terms.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published