LPsLux Proposals
Post-Quantum Cryptography
LP-4316

ML-DSA Post-Quantum Digital Signatures

Review

NIST FIPS 204 ML-DSA (CRYSTALS-Dilithium) post-quantum digital signature implementation for Lux Network

Category
Core
Created
2025-11-22

LP-316: ML-DSA Post-Quantum Digital Signatures

Status: Final Type: Standards Track Category: Core Created: 2025-11-22 Updated: 2025-11-22 Authors: Lux Partners Related: LP-303 (Quantum), LP-312 (SLH-DSA), LP-313 (ML-KEM)

Abstract

This LP specifies the integration of ML-DSA (Module-Lattice-Based Digital Signature Algorithm), NIST FIPS 204, into the Lux Network as a quantum-resistant digital signature scheme. ML-DSA provides security against quantum computing attacks while maintaining performance suitable for blockchain consensus and transaction signing.

Motivation

Quantum Threat Timeline

Current blockchain systems rely on ECDSA (secp256k1) which is vulnerable to Shor's algorithm:

  • 2030-2035: NIST estimates quantum computers capable of breaking RSA-2048 and secp256k1
  • Harvest-now-decrypt-later: Adversaries capture encrypted data today for future decryption
  • Validator Security: Long-lived validator keys need quantum protection NOW

Why ML-DSA?

NIST Standardization: FIPS 204 (August 2024)

  • Formally standardized post-quantum signature scheme
  • Extensive cryptanalysis by global community
  • Module-lattice security foundation

Performance Characteristics:

  • Sign: 150-600μs (3 security levels)
  • Verify: 80-150μs
  • ~3x slower than ECDSA but acceptable for blockchain use

Deterministic Signatures:

  • Same message + same key = same signature
  • Eliminates k-value attacks that plague ECDSA
  • Simpler implementation without randomness requirements

Specification

Algorithm Overview

ML-DSA is based on Fiat-Shamir with Aborts construction over module lattices:

  • Security: MLWE (Module Learning With Errors) problem
  • Structure: Ring polynomials mod q = 8380417
  • Parameters: d (dimension), η (noise), γ (challenge weight)

Security Levels

Three parameter sets providing different security/performance trade-offs:

ModeSecurityPublic KeyPrivate KeySignatureSign TimeVerify Time
ML-DSA-44128-bit (NIST-2)1,312 bytes2,528 bytes2,420 bytes~150μs~80μs
ML-DSA-65192-bit (NIST-3)1,952 bytes4,000 bytes3,309 bytes~417μs~108μs
ML-DSA-87256-bit (NIST-5)2,592 bytes4,864 bytes4,627 bytes~600μs~150μs

Lux Default: ML-DSA-65 (192-bit security, balanced performance)

Key Generation

import "github.com/luxfi/crypto/mldsa"

// Generate ML-DSA-65 key pair
sk, err := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
if err != nil {
    return err
}

// Access public key
pk := sk.PublicKey

// Serialize keys
privBytes := sk.Bytes()          // 4,000 bytes
pubBytes := pk.Bytes()            // 1,952 bytes

Signing

// Sign message (deterministic)
message := []byte("Transaction data")
signature, err := sk.Sign(rand.Reader, message, nil)
if err != nil {
    return err
}
// signature is 3,309 bytes for ML-DSA-65

Properties:

  • Deterministic: Same (sk, message) always produces same signature
  • Context Support: Optional context string for domain separation
  • No k-value: Eliminates ECDSA's k-value vulnerability

Verification

// Verify signature
valid := pk.Verify(message, signature, nil)
if !valid {
    return errors.New("invalid signature")
}

Verification checks:

  1. Signature size = 3,309 bytes (for ML-DSA-65)
  2. Polynomial bounds verification
  3. Challenge reconstruction and comparison

Integration Points

P-Chain Validators

Hybrid BLS + ML-DSA:

type ValidatorSignature struct {
    BLS     []byte  // 96 bytes - current
    MLDSA   []byte  // 3,309 bytes - quantum-safe
    Mode    uint8   // ML-DSA mode (44/65/87)
}

Verification: Both signatures must be valid

  • Classical: BLS threshold verification
  • Quantum: ML-DSA individual verification
  • Transition: Gradually increase ML-DSA weight in consensus

Transaction Signing

Address Format:

lux1mldsa<mode><bech32-encoded-pubkey-hash>

Example: lux1mldsa65qpr3zvr8j5y5jxm9d8qgtnpwjx7h9k2v

Transaction Structure:

type MLDSATransaction struct {
    ChainID     ids.ID
    Nonce       uint64
    To          common.Address
    Value       *big.Int
    Data        []byte
    Signature   []byte  // 3,309 bytes (ML-DSA-65)
    PublicKey   []byte  // 1,952 bytes
    Mode        uint8   // 65
}

EVM Precompile

Address: 0x0200000000000000000000000000000000000006

Interface:

interface IMLDSA {
    /// @notice Verify ML-DSA signature
    /// @param publicKey 1,952 bytes for ML-DSA-65
    /// @param message Arbitrary length message
    /// @param signature 3,309 bytes for ML-DSA-65
    /// @return valid True if signature is valid
    function verify(
        bytes calldata publicKey,
        bytes calldata message,
        bytes calldata signature
    ) external view returns (bool valid);
}

Gas Cost:

  • Base: 100,000 gas
  • Per byte: 10 gas per message byte

Example Usage:

contract SecureVault {
    address constant MLDSA = 0x0200000000000000000000000000000000000006;

    function withdraw(
        bytes calldata pubKey,
        bytes calldata message,
        bytes calldata signature
    ) external {
        (bool success, bytes memory result) = MLDSA.staticcall(
            abi.encode(pubKey, message, signature)
        );
        require(success && abi.decode(result, (bool)), "Invalid signature");

        // Process withdrawal
    }
}

Implementation

Core Cryptographic Library

GitHub: github.com/luxfi/crypto/mldsa Local Path: ~/work/lux/crypto/mldsa/

Key Files:

Dependencies:

  • github.com/cloudflare/circl v1.6.1 (FIPS 204 compliant)

API:

package mldsa

type Mode int
const (
    MLDSA44 Mode = iota  // 128-bit security
    MLDSA65              // 192-bit security (default)
    MLDSA87              // 256-bit security
)

type PrivateKey struct { /* ... */ }
type PublicKey struct { /* ... */ }

// Key generation
func GenerateKey(rand io.Reader, mode Mode) (*PrivateKey, error)

// Signing
func (sk *PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error)

// Verification
func (pk *PublicKey) Verify(message, signature []byte, opts crypto.SignerOpts) bool

// Serialization
func PrivateKeyFromBytes(mode Mode, data []byte) (*PrivateKey, error)
func PublicKeyFromBytes(data []byte, mode Mode) (*PublicKey, error)

EVM Precompile

GitHub: github.com/luxfi/evm/precompile/contracts/mldsa Local Path: ~/work/lux/evm/precompile/contracts/mldsa/ Precompile Address: 0x0200000000000000000000000000000000000006

Files:

Integration:

// Register in precompile registry
func init() {
    precompile.Register(&MLDSAPrecompile{})
}

Solidity Smart Contracts

Local Path: ~/work/lux/standard/src/

Example Usage: See IMLDSA.sol for interface and library examples

// Using ML-DSA precompile in your contracts
import "~/work/lux/evm/precompile/contracts/mldsa/IMLDSA.sol";

contract SecureVault is MLDSAVerifier {
    function withdraw(
        bytes calldata publicKey,
        bytes calldata message,
        bytes calldata signature
    ) external {
        // Automatically reverts if signature is invalid
        verifyMLDSASignature(publicKey, message, signature);

        // Process withdrawal
        payable(msg.sender).transfer(address(this).balance);
    }
}

Testing:

# Test Solidity contracts using ML-DSA
cd ~/work/lux/standard
forge test --match-path test/**/*MLDSA*.t.sol

Test Results

Core Implementation: 11/11 PASSING ✅

✓ SignVerify                 (0.00s)
✓ InvalidSignature           (0.00s)
✓ WrongMessage               (0.00s)
✓ EmptyMessage               (0.00s)
✓ LargeMessage               (0.00s)
✓ PrivateKeyFromBytes        (0.00s)
✓ PublicKeyFromBytes         (0.00s)
✓ InvalidMode                (0.00s)
✓ InvalidKeySize             (0.00s)
✓ GetPublicKeySize           (0.00s)
✓ GetSignatureSize           (0.00s)

Performance Benchmarks (Apple M1 Max)

BenchmarkMLDSA_Sign_65         2,400 ops    417,000 ns/op
BenchmarkMLDSA_Verify_65       9,259 ops    108,000 ns/op
BenchmarkMLDSA_KeyGen_65       8,000 ops    125,000 ns/op

Migration Path

Phase 1: Validator Support (Q1 2026)

  • Add ML-DSA public keys to validator registration
  • Hybrid BLS + ML-DSA signing in consensus
  • Gradual weight shift from BLS to ML-DSA

Phase 2: Transaction Support (Q2 2026)

  • Deploy ML-DSA precompile to C-Chain
  • Enable ML-DSA transaction signing
  • Wallet integration for quantum-safe addresses

Phase 3: Full Transition (Q3 2026)

  • ML-DSA becomes primary signature scheme
  • BLS maintained for backwards compatibility
  • New validators require ML-DSA keys

Security Considerations

Quantum Resistance

Lattice Security: Based on MLWE problem

  • No known quantum algorithms break lattice problems efficiently
  • NIST analyzed security for 6+ years before standardization
  • Conservative parameter selection (128/192/256-bit security)

Long-term Security:

  • Public keys captured today remain secure post-quantum
  • Deterministic signatures prevent timing attacks
  • Constant-time implementation in CIRCL library

Side-Channel Resistance

Constant-Time Operations:

  • All arithmetic operations run in constant time
  • No secret-dependent branches
  • No secret-dependent memory access

Implementation Quality:

  • CIRCL library used by Cloudflare in production
  • Formal verification of critical components
  • Regular security audits

Key Management

Validator Keys:

  • Generate fresh ML-DSA keys for each validator
  • Store private keys in HSM when available
  • Use separate keys for consensus vs transaction signing

Backup & Recovery:

  • ML-DSA private keys are 4,000 bytes (65% larger than ECDSA)
  • Use seed-based key derivation (BIP-39 compatible)
  • Encrypt backups with quantum-safe encryption (AES-256)

Backwards Compatibility

Hybrid Period (2026-2027):

  • All validators support BOTH BLS and ML-DSA
  • Transactions can use either ECDSA or ML-DSA
  • Consensus requires both signature types to be valid

Legacy Support:

  • ECDSA addresses continue to function
  • Cross-chain messaging supports both signature types
  • Gradual deprecation of ECDSA over 2-3 years

Rationale

Why ML-DSA over alternatives?

vs SLH-DSA (LP-312):

  • ML-DSA is 10-100x faster (417μs vs 40ms sign time)
  • Smaller signatures (3,309 bytes vs 17,088-49,856 bytes)
  • Trade-off: Lattice security assumptions vs hash-based conservative security

vs Classical Multivariate:

  • ML-DSA has stronger security analysis
  • NIST standardized (vs academic proposals)
  • Better performance and key sizes

vs Falcon (NIST finalist):

  • ML-DSA has simpler implementation (no floating point)
  • More conservative security parameters
  • Deterministic signing (Falcon requires random seeds)

Why ML-DSA-65 as default?

192-bit Security (NIST Level 3):

  • Exceeds Bitcoin's 128-bit security
  • Margin for future cryptanalysis advances
  • Matches high-value financial applications

Performance Balance:

  • 417μs sign time acceptable for transaction throughput
  • 108μs verify time suitable for consensus
  • 3,309 byte signatures fit in typical network packets

Reference Implementation

Complete Example

package main

import (
    "crypto/rand"
    "fmt"

    "github.com/luxfi/crypto/mldsa"
)

func main() {
    // Generate validator key pair
    validatorKey, err := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
    if err != nil {
        panic(err)
    }

    // Sign consensus message
    blockHash := []byte("block_hash_data_here")
    signature, err := validatorKey.Sign(rand.Reader, blockHash, nil)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Signature size: %d bytes\n", len(signature))
    // Output: Signature size: 3309 bytes

    // Verify signature
    valid := validatorKey.PublicKey.Verify(blockHash, signature, nil)
    fmt.Printf("Signature valid: %v\n", valid)
    // Output: Signature valid: true

    // Serialize for storage
    pubKeyBytes := validatorKey.PublicKey.Bytes()
    privKeyBytes := validatorKey.Bytes()

    fmt.Printf("Public key: %d bytes\n", len(pubKeyBytes))
    // Output: Public key: 1952 bytes

    fmt.Printf("Private key: %d bytes\n", len(privKeyBytes))
    // Output: Private key: 4000 bytes
}

Test Cases

Unit Tests

  1. Cryptographic Primitives

    • Test key generation
    • Verify signature creation
    • Test signature verification
  2. Post-Quantum Security

    • Verify NIST compliance
    • Test parameter validation
    • Validate security levels
  3. Performance Benchmarks

    • Measure key generation time
    • Benchmark signing operations
    • Test verification throughput

Integration Tests

  1. Hybrid Signature Schemes

    • Test classical-PQ combinations
    • Verify fallback mechanisms
    • Test key rotation
  2. Network Integration

    • Test consensus with PQ signatures
    • Verify cross-chain compatibility
    • Test upgrade transitions

Copyright and related rights waived via CC0.

References

  • [LP-200](./lp-4200-post-quantum-cryptography-suite-for-lux-network.md - Parent specification
  • [LP-317](./lp-4317-slh-dsa-stateless-hash-based-digital-signatures.md - Alternative PQC signature scheme
  • [LP-318](./lp-4318-ml-kem-post-quantum-key-encapsulation.md - Complementary key exchange
  • [LP-311](./lp-4316-ml-dsa-post-quantum-digital-signatures.md - EVM precompile implementation
  • [LP-201](./lp-4201-hybrid-classical-quantum-cryptography-transitions.md - Migration strategy

Standards and Specifications

  1. FIPS 204: Module-Lattice-Based Digital Signature Standard
  2. CRYSTALS-Dilithium: Specification v3.1
  3. CIRCL Library: Cloudflare Cryptographic Library

Implementation Files

  1. Implementation: crypto/mldsa/
  2. Precompile: evm/precompile/contracts/mldsa/

Appendix A: Key Size Comparison

SchemePublic KeyPrivate KeySignatureSecurity
ECDSA (secp256k1)33 bytes32 bytes65 bytes128-bit (classical)
BLS12-38196 bytes32 bytes96 bytes128-bit (classical)
ML-DSA-441,312 bytes2,528 bytes2,420 bytes128-bit (quantum)
ML-DSA-651,952 bytes4,000 bytes3,309 bytes192-bit (quantum)
ML-DSA-872,592 bytes4,864 bytes4,627 bytes256-bit (quantum)

Size Trade-off: 60x larger keys, 50x larger signatures for quantum resistance

Appendix B: Performance Comparison

OperationECDSABLSML-DSA-65Slowdown
Key Generation~30μs~100μs~125μs4x vs ECDSA
Sign~88μs~1,200μs~417μs5x vs ECDSA
Verify~88μs~2,500μs~108μs1.2x vs ECDSA

Performance Trade-off: 1-5x slower operations for quantum resistance