PQC Signature Verification Guide for Dogenals v2.0

How to verify Falcon-512 and ML-DSA-44 signatures in Dogenals inscriptions

Overview

Dogenals v2.0 requires quantum-safe signatures for new inscriptions. This guide provides step-by-step instructions for developers implementing signature verification in indexers, wallets, and tools.

Supported Algorithms

  • Key Size: 897 bytes (public), 1281 bytes (secret)
  • Signature Size: 666-752 bytes
  • Security Level: NIST Category 5 (highest)
  • Performance: Fast signing/verification

ML-DSA-44 (Alternative)

  • Key Size: 1312 bytes (public), 2560 bytes (secret)
  • Signature Size: 2420 bytes
  • Security Level: NIST Category 4
  • Performance: Slower but more conservative

Hybrid Mode

  • Combined: Falcon-512 + ECDSA
  • Total Size: ~730-820 bytes
  • Use Case: Transition period during quantum migration

Python Implementation

Dependencies

pip install pycryptodome dilithium  # For ML-DSA
# Note: Falcon-512 requires custom implementation or libfalcon

Falcon-512 Verification

import hashlib
import falcon512  # Custom implementation needed
 
def verify_falcon512_signature(pubkey_hex: str, signature_hex: str, message: bytes) -> bool:
    """Verify a Falcon-512 signature.
 
    Args:
        pubkey_hex: Public key as hex string (897 bytes)
        signature_hex: Signature as hex string (666-752 bytes)
        message: Message bytes to verify against
 
    Returns:
        bool: True if signature is valid
    """
    try:
        pubkey = bytes.fromhex(pubkey_hex)
        signature = bytes.fromhex(signature_hex)
 
        # Verify key size
        if len(pubkey) != 897:
            return False
        if not (666 <= len(signature) <= 752):
            return False
 
        # Hash message to get digest
        digest = hashlib.sha256(message).digest()
 
        # Verify signature
        return falcon512.verify(pubkey, signature, digest)
 
    except Exception:
        return False

ML-DSA-44 Verification

from dilithium import Dilithium2  # ML-DSA-44 equivalent
 
def verify_mldsa44_signature(pubkey_hex: str, signature_hex: str, message: bytes) -> bool:
    """Verify an ML-DSA-44 signature.
 
    Args:
        pubkey_hex: Public key as hex string (1312 bytes)
        signature_hex: Signature as hex string (2420 bytes)
        message: Message bytes to verify against
 
    Returns:
        bool: True if signature is valid
    """
    try:
        pubkey = bytes.fromhex(pubkey_hex)
        signature = bytes.fromhex(signature_hex)
 
        # Verify sizes
        if len(pubkey) != 1312:
            return False
        if len(signature) != 2420:
            return False
 
        # Create verifier instance
        verifier = Dilithium2(public_key=pubkey)
 
        # Verify signature
        return verifier.verify(message, signature)
 
    except Exception:
        return False

ECDSA Verification (Legacy)

import ecdsa
import hashlib
 
def verify_ecdsa_signature(pubkey_hex: str, signature_hex: str, message: bytes) -> bool:
    """Verify an ECDSA signature (legacy, quantum-vulnerable).
 
    Args:
        pubkey_hex: Compressed public key as hex string (66 chars)
        signature_hex: DER signature as hex string
        message: Message bytes to verify against
 
    Returns:
        bool: True if signature is valid
    """
    try:
        # Decode public key
        pubkey_bytes = bytes.fromhex(pubkey_hex)
        vk = ecdsa.VerifyingKey.from_string(pubkey_bytes, curve=ecdsa.SECP256k1)
 
        # Decode signature
        signature_bytes = bytes.fromhex(signature_hex)
 
        # Hash message
        digest = hashlib.sha256(message).digest()
 
        # Verify
        return vk.verify(signature_bytes, digest, sigdecode=ecdsa.util.sigdecode_der)
 
    except Exception:
        return False

Hybrid Mode Verification

def verify_hybrid_signature(pubkey_hex: str, signature_hex: str, message: bytes) -> bool:
    """Verify a hybrid Falcon-512 + ECDSA signature.
 
    Args:
        pubkey_hex: Combined public keys as hex string (897 + 33 = 930 bytes)
        signature_hex: Combined signatures as hex string
        message: Message bytes to verify against
 
    Returns:
        bool: True if both signatures are valid
    """
    try:
        pubkey = bytes.fromhex(pubkey_hex)
        signature = bytes.fromhex(signature_hex)
 
        # Split keys and signatures
        falcon_pubkey = pubkey[:897]
        ecdsa_pubkey = pubkey[897:]
 
        falcon_sig = signature[:len(signature)//2]
        ecdsa_sig = signature[len(signature)//2:]
 
        # Verify both signatures
        falcon_valid = verify_falcon512_signature(
            falcon_pubkey.hex(), falcon_sig.hex(), message
        )
        ecdsa_valid = verify_ecdsa_signature(
            ecdsa_pubkey.hex(), ecdsa_sig.hex(), message
        )
 
        return falcon_valid and ecdsa_valid
 
    except Exception:
        return False

Rust Implementation

Dependencies (Cargo.toml)

[dependencies]
sha2 = "0.10"
hex = "0.4"
falcon-rust = { git = "https://github.com/your-falcon-impl" }  # Custom Falcon implementation
dilithium-rust = "0.1"  # ML-DSA implementation

Falcon-512 Verification

use falcon_rust::{PublicKey, Signature};
use sha2::{Sha256, Digest};
 
fn verify_falcon512_signature(
    pubkey_hex: &str,
    signature_hex: &str,
    message: &[u8]
) -> Result<bool, Box<dyn std::error::Error>> {
    // Decode inputs
    let pubkey_bytes = hex::decode(pubkey_hex)?;
    let signature_bytes = hex::decode(signature_hex)?;
 
    // Verify sizes
    if pubkey_bytes.len() != 897 {
        return Ok(false);
    }
    if !(666..=752).contains(&signature_bytes.len()) {
        return Ok(false);
    }
 
    // Hash message
    let mut hasher = Sha256::new();
    hasher.update(message);
    let digest = hasher.finalize();
 
    // Parse public key and signature
    let pubkey = PublicKey::from_bytes(&pubkey_bytes)?;
    let signature = Signature::from_bytes(&signature_bytes)?;
 
    // Verify
    Ok(pubkey.verify(&digest, &signature))
}

ML-DSA-44 Verification

use dilithium_rust::Dilithium;
 
fn verify_mldsa44_signature(
    pubkey_hex: &str,
    signature_hex: &str,
    message: &[u8]
) -> Result<bool, Box<dyn std::error::Error>> {
    // Decode inputs
    let pubkey_bytes = hex::decode(pubkey_hex)?;
    let signature_bytes = hex::decode(signature_hex)?;
 
    // Verify sizes
    if pubkey_bytes.len() != 1312 {
        return Ok(false);
    }
    if signature_bytes.len() != 2420 {
        return Ok(false);
    }
 
    // Create verifier
    let verifier = Dilithium::new();
    let public_key = verifier.public_key_from_bytes(&pubkey_bytes)?;
 
    // Verify signature
    Ok(verifier.verify(&public_key, message, &signature_bytes))
}

Message Construction

Standard Message Format

For Dogenals v2.0 inscriptions, the message to sign is:

def build_message(metadata: dict) -> bytes:
    """Build the message that gets signed."""
    components = [
        metadata["content_hash"],
        metadata["collection_id"],
        metadata["owner_pubkey"],
        metadata.get("parent", "")
    ]
 
    message = "".join(components)
    return hashlib.sha256(message.encode('utf-8')).digest()

Example

metadata = {
    "content_hash": "a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3",
    "collection_id": "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad",
    "owner_pubkey": "04a1f3fff1fa07e998e86f7f7a27ae3a665a45920422f9d417e4867efdc4fb8a",
    "parent": "aaaa0000bbbb1111cccc2222dddd3333eeee4444ffff5555666677778888i0"
}
 
message = build_message(metadata)
# Sign this message with your chosen algorithm

Testing

Test Vectors

# Valid Falcon-512 signature test
test_pubkey = "04a1f3..."  # 897 bytes
test_signature = "b61367..."  # 666 bytes
test_message = b"Hello Dogenals v2.0"
 
assert verify_falcon512_signature(test_pubkey, test_signature, test_message)

Performance Benchmarks

  • Falcon-512: ~1ms verification on modern hardware
  • ML-DSA-44: ~5ms verification
  • ECDSA: ~0.1ms verification (but quantum-vulnerable)

Security Considerations

Key Management

  • Store private keys securely (HSM recommended)
  • Never reuse keys across different collections
  • Implement key rotation for long-lived collections

Signature Validation

  • Always validate signature before accepting inscription
  • Reject inscriptions with invalid signatures
  • Log verification failures for monitoring

Quantum Migration

  • Prefer Falcon-512 for new implementations
  • Use hybrid mode during transition period
  • Phase out ECDSA by 2030 (estimated quantum computer timeline)

Troubleshooting

Common Issues

  1. Wrong key size: Verify you’re using correct algorithm constants
  2. Invalid signature format: Ensure hex encoding is correct
  3. Message mismatch: Double-check message construction
  4. Library compatibility: Test with known good implementations

Debug Steps

# 1. Verify input sizes
print(f"Pubkey size: {len(bytes.fromhex(pubkey_hex))}")
print(f"Signature size: {len(bytes.fromhex(signature_hex))}")
 
# 2. Check message construction
message = build_message(metadata)
print(f"Message hash: {hashlib.sha256(message).hexdigest()}")
 
# 3. Test with known good values
# Use test vectors from specification

Reference Implementations

  • PyDoge: Python SDK with PQC verification
  • wonky-dogeord: Rust indexer with signature validation
  • kabosu: Go implementation with hybrid support

Remember: Quantum safety isn’t optional for Dogenals v2.0. Always verify signatures before accepting inscriptions.

// Dog’s Chosen Tech — Dogenals v2.0 — jonheaven (BC, Canada) — April 2026