#bitcoin #post-quantum-cryptography #length #error

pq_address

A Rust library for encoding and decoding post‑quantum public keys into human‑friendly Bech32m addresses

4 releases

Uses new Rust 2024

new 0.2.0 May 15, 2025
0.1.2 May 7, 2025
0.1.1 May 7, 2025
0.1.0 May 7, 2025

#152 in Magic Beans

Download history 310/week @ 2025-05-04 198/week @ 2025-05-11

508 downloads per month

MIT license

27KB
337 lines

PQ Address Rust

A Rust library for encoding and decoding post‑quantum public keys into human‑friendly Bech32m addresses.

Motivation

Sharing a post‑quantum public key needs:

  1. A hash of the public key for privacy and fixed length.
  2. A strong checksum to catch typos and errors.
  3. A readable, typo‑resistant encoding.
  4. A clear network flag (production vs development).

pq_address provides all four. It lets you generate and parse addresses for any public‑key type (ML‑DSA, SLH‑DSA, etc.), while guaranteeing future‑proof safety.

Design & Justification

  • Bech32m (BIP‑350)

    • Case insensitive
    • Friendly character set
    • Strong error detection
    • Improved checksum over Bech32
    • Efficient encoding & decoding
    • Smaller QR codes due to use of alphanumeric mode
  • Disjoint byte ranges

    • Version codes in 0x000x3F (up to 64 versions).
    • PubKeyType codes in 0x400xFF (up to 192 public key types).
    • Any byte‑swap or mis‑read triggers a clear “unknown code” error.

    By carving out non-overlapping slots for versions (0x00–0x3F) and public key types (0x40–0xFF), parsing becomes trivial—and any stray or swapped byte instantly flags itself as an “unknown code,” preventing silent failures.

  • HRP flag

    • "yp" for production/mainnet, "rh" for development/testnet.
  • Extendable

    • Add new PubKeyType variants without breaking old addresses.

Anatomy of a PQ address

Address example: yp1qpqg39uw700gcctpahe650p9zlzpnjt60cpz09m4kx7ncz8922635hs5cdx7q

  1. HRP (yp / rh)

    • yp = Mainnet
    • rh = Testnet
  2. Separator

    • Always the character 1.
  3. Data

    • The payload bytes:
      1. Version
      2. PubKeyType
      3. Raw pubkey hash digest
    • Converted into 5-bit words and then into Bech32 characters.
  4. Checksum

    • 6 Bech32 characters (BIP-350)
    • Catches typos and bit-errors.

PQ address length is 64 characters.

Note: A Bech32 string is at most 90 characters long [BIP-173]

A Note on Hash Algorithms

The default hash function for pq_address is SHA-256. 256 bit hash functions are currently considered secure against Grover's attack. Even if the preimage is recovered, it only reveals a PQ secure public key and thus Shor's is not applicable.

Quickstart

Add to your Cargo.toml:

cargo add pq_address

Import pq_address

use pq_address::{
    AddressDecodeError, AddressParams, Network, PubKeyType, Version, decode_address,
    encode_address,
};

Encoding

let params = AddressParams {
    network: Network::Mainnet,
    version: Version::V1,
    pubkey_type: PubKeyType::MlDsa44,
    pubkey_bytes: <PUB_KEY_BYTES>,
};

match encode_address(&params) {
    Ok(pq_addr) => println!("Encoded PQ Address: {}", pq_addr),
    Err(e) => eprintln!("Encoding error: {}", e),
}

Decoding

match decode_address(&pq_addr) {
    Ok(decoded) => {
        println!("Decoded Network: {:?}", decoded.network);
        println!("Decoded Version: {:?}", decoded.version);
        println!("Decoded PubKey Type: {:?}", decoded.pubkey_type);
        println!("Decoded PubKey Hash (hex): {}", decoded.pubkey_hash_hex());
        println!("Re-encoded Address: {}", decoded.to_string());
    }
    Err(e) => eprintln!("Decoding error: {}", e),
};

Errors

Encoding errors:

enum AddressEncodeError {
    /// Invalid Bech32 structure or checksum
    #[error("Bech32 error: {0}")]
    Bech32(#[from] bech32::EncodeError),

    /// A PQ address is 64 characters long
    #[error("A PQ address is 64 characters long: got {0}")]
    InvalidEncodingLength(usize),

    /// Invalid public key length
    #[error("Invalid public key length: expected {0}, got {1}")]
    InvalidPubKeyLength(usize, usize),
}

Decoding errors:

enum AddressDecodeError {
    /// Invalid Bech32 structure or checksum
    #[error("Bech32 error: {0}")]
    Bech32(#[from] bech32::DecodeError),

    /// HRP wasn’t `yp` or `rh`
    #[error("unknown HRP: {0}")]
    UnknownHrp(String),

    /// Payload too short
    #[error("payload too short (need at least version+public key type)")]
    TooShort,

    /// First byte isn’t a known version code
    #[error("unknown version code: 0x{0:02X}")]
    UnknownVersion(u8),

    /// Second byte isn’t a known public key type
    #[error("unknown public key type code: 0x{0:02X}")]
    UnknownPubKeyType(u8),

    /// Digest length didn’t match the algorithm’s expectation
    #[error("invalid digest length: got {got}, expected {expected}")]
    InvalidHashLength { got: usize, expected: usize },
}

Contributing

See CONTRIBUTING.md for details on how to contribute to this project.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Dependencies

~1–1.6MB
~37K SLoC