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
508 downloads per month
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:
- A hash of the public key for privacy and fixed length.
- A strong checksum to catch typos and errors.
- A readable, typo‑resistant encoding.
- 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.
- Version codes in
-
HRP flag
"yp"
for production/mainnet,"rh"
for development/testnet.
-
Extendable
- Add new
PubKeyType
variants without breaking old addresses.
- Add new
Anatomy of a PQ address
Address example: yp1qpqg39uw700gcctpahe650p9zlzpnjt60cpz09m4kx7ncz8922635hs5cdx7q
-
HRP (
yp
/rh
)yp
= Mainnetrh
= Testnet
-
Separator
- Always the character
1
.
- Always the character
-
Data
- The payload bytes:
- Version
- PubKeyType
- Raw pubkey hash digest
- Converted into 5-bit words and then into Bech32 characters.
- The payload bytes:
-
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(¶ms) {
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