Stylus ML-DSA-65 verifier
MLDSAVerifier is a stateless Arbitrum Stylus contract that verifies an ML-DSA-65 (FIPS 204) signature given a public key, a message hash, and a signature, returning true or false.
Key numbers
| Metric | Value |
|---|---|
| Verification gas | ~374,000 |
| WASM size (raw) | ~20.7 KB |
| WASM size (gzipped) | ~7.8 KB |
| Stylus compressed size limit | 24 KB |
| Public key | 1,952 bytes |
| Signature | 3,309 bytes |
| Message | 32 bytes (keccak256 hash) |
Position in the stack
flowchart LR
A[Smart Account] -->|validateUserOp| B[PQValidatorModule<br/>Solidity]
B -->|verify<br/>pubKey, msgHash, sig| C[MLDSAVerifier<br/>Stylus/WASM]
C -->|bool| B
B -->|VALIDATION_SUCCESS<br/>or VALIDATION_FAILED| A
The contract is stateless — public keys are stored in the Solidity module and passed as calldata on every call.
Verifying against the deployed contract
# Generate ML-DSA test vectors into your shell env
eval $(cargo run --bin gen_test_data --manifest-path ml-dsa-test/Cargo.toml 2>/dev/null)
# Sanity check
echo "${PK_HEX:0:20}..."
# Verify against the deployed contract
cast call $STYLUS_VERIFIER "verify(bytes,bytes32,bytes)(bool)" \
$PK_HEX $MSG_HASH $SIG_HEX --rpc-url http://127.0.0.1:8547
# Estimate verification gas
cast estimate $STYLUS_VERIFIER "verify(bytes,bytes32,bytes)" \
$PK_HEX $MSG_HASH $SIG_HEX --rpc-url http://127.0.0.1:8547
Tests
cargo test --package pq-validator
Four test cases using stylus_test::TestVM:
- Valid signature → returns
true. - Flipped signature byte → returns
false. - Wrong-length public key → reverts with
InvalidPublicKey. - Cross-implementation: a JavaScript signature from
@noble/post-quantumverified by the Rustml-dsacrate, asserting that the off-chain signer and on-chain verifier agree byte-for-byte.
Production limitations
| Limitation | Detail |
|---|---|
| Reactivation required | Auto-deactivates after 365 days or ArbOS upgrades. No built-in monitoring. |
| SDK maturity | stylus-sdk-rs is pre-1.0. See Stylus maturity assessment. |
| WASM size headroom | ~3.3 KB before hitting the 24 KB compressed limit. |
| No tracing | cast run returns OpcodeNotFound. Use cast call for on-chain simulation. |
ml-dsa crate is RC | v0.1.0-rc.7 is not yet stable. Pin the version and re-test before upgrading. |
Related
- ML-DSA library comparison — why
ml-dsa(RustCrypto) was selected - Stylus maturity assessment — SDK issues and mitigations
- Local dev guide — full deployment and testing workflow