pq_address

Crates.iopq_address
lib.rspq_address
version0.2.0
created_at2025-05-07 08:02:43.630795+00
updated_at2025-05-15 12:31:20.281282+00
descriptionA Rust library for encoding and decoding post‑quantum public keys into human‑friendly Bech32m addresses.
homepage
repositoryhttps://github.com/p-11/pq-address-rs/
max_upload_size
id1663599
size31,235
David Nugent (davidnugent2425)

documentation

https://github.com/p-11/pq-address-rs#readme

README

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 0x00–0x3F (up to 64 versions).
    • PubKeyType codes in 0x40–0xFF (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.

Commit count: 12

cargo fmt