Dilithium Addresses
Dilithium address formats in BTQ. P2MR (BIP360, witness v2) Bech32m is the only Dilithium receive type. How to generate and validate one.
Dilithium Addresses
BTQ introduces Dilithium-specific address formats while maintaining backward compatibility with ECDSA addresses for legacy support.
As of v0.4.2-testnet, Dilithium lives in exactly one address type: P2MR (BIP360, witness version 2). Dilithium opcodes are consensus-valid only inside P2MR tapscript leaves. The Base58 and witness-v0 Dilithium formats described further down are historical — kept here because such outputs still exist on testnet, not because you should create new ones.
Address Types
P2MR — the current Dilithium address (Bech32m, witness v2)
A P2MR output commits to the 32-byte Merkle root of a script tree. Leaves may hold a single
Dilithium key check, Dilithium multisig (OP_CHECKMULTISIGDILITHIUM), a threshold accumulator
built from OP_CHECKSIGDILITHIUM, or other script policies.
P2MR uses the standard network HRP with witness version 2, encoded Bech32m — so the
distinguishing marker is the 1z separator, versus 1q for ECDSA SegWit v0:
| Network | HRP | Dilithium P2MR | ECDSA SegWit v0 |
|---|---|---|---|
| Mainnet | qbtc | qbtc1z… | qbtc1q… |
| Testnet | tbtq | tbtq1z… | tbtq1q… |
| Signet | qtb | qtb1z… | qtb1q… |
| Regtest | qcrt | qcrt1z… | qcrt1q… |
The witness program is the 32-byte script-tree Merkle root.
Historical: Legacy Addresses (Base58)
Superseded by P2MR. getnewdilithiumaddress no longer produces these. Documented for reading
existing testnet outputs.
Base58 addresses are identified by a version byte, not by a fixed letter. The leading character is a consequence of that byte:
| Type | Mainnet version byte | Renders as | Testnet / Signet / Regtest | Renders as |
|---|---|---|---|---|
| ECDSA Pubkey | 75 | X… | 111 | m… / n… |
| ECDSA Script | 135 | w… | 196 | 2… |
| Dilithium Pubkey | 76 | X… | 112 | n… |
| Dilithium Script | 136 | w… / x… | 197 | 2… |
On mainnet you cannot tell an ECDSA pubkey address from a Dilithium one by eye — version bytes 75
and 76 both render as X… and their two-character prefixes overlap at Xa. Use
validateaddress or getaddressinfo to determine the actual type; never infer it from the prefix.
Historical: Dilithium Witness v0 (Bech32)
This path was an early experiment and is disabled. Dilithium and ECDSA witness-v0 programs were too easy to confuse, and the resulting outputs were not spendable with Dilithium keys. There are no live examples of it on testnet.
The dedicated HRPs below remain in the chain parameters and are still recognized by the address decoder, but the wallet will not generate them:
| Network | ECDSA HRP | Dilithium witness-v0 HRP |
|---|---|---|
| Mainnet | qbtc | dbtc |
| Testnet | tbtq | tdbt |
| Signet | qtb | sdbt |
| Regtest | qcrt | rdbt |
Generating Addresses
Using RPC
getnewdilithiumaddress is the only supported way to create a Dilithium receive address. It takes
an optional label; the address_type argument, if given, must be "p2mr":
# Generate a new Dilithium P2MR address
btq-cli getnewdilithiumaddress
# Optionally attach a label
btq-cli getnewdilithiumaddress "Mining Rewards"This RPC returns a JSON object, not a bare string. Shell snippets that capture it directly
(ADDR=$(btq-cli ... getnewdilithiumaddress)) will capture the whole object. Extract the field:
btq-cli getnewdilithiumaddress | jq -r '.address'Response fields:
| Field | Meaning |
|---|---|
address | The P2MR receive address |
p2mr_id | Wallet-local P2MR metadata id |
scriptPubKey | The output script |
merkle_root | Root of the committed script tree |
Support Matrix
| Command | Result |
|---|---|
getnewdilithiumaddress | ✅ P2MR (witness v2) address |
getnewdilithiumaddress "label" "p2mr" | ✅ same, with a label |
getnewdilithiumaddress "" "legacy" | ❌ Dilithium receives must use P2MR |
getnewdilithiumaddress "" "bech32" | ❌ rejected — disabled format |
getnewaddress "" "dilithium-legacy" | ⚠️ historical Base58 format; not for new wallets |
getnewaddress "" "dilithium-bech32" | ❌ disabled — witness-v0 experiment, turned off |
Dilithium opcodes are consensus-valid only inside P2MR tapscript leaves. Legacy Dilithium P2PKH and witness-v0 destinations are no longer created by the wallet.
Address Validation
validateaddress and getaddressinfo both return an isdilithium flag. This is the reliable
way to tell a Dilithium address from an ECDSA one — the Base58 prefix is not sufficient on mainnet.
$ btq-cli -testnet validateaddress "n5KchJ9AJaiHRkLpKF7Bx8dbjsgt1CZdC4"
{
"isvalid": true,
"address": "n5KchJ9AJaiHRkLpKF7Bx8dbjsgt1CZdC4",
"scriptPubKey": "...",
"isscript": false,
"iswitness": false,
"isdilithium": true
}A Dilithium legacy address resolves to a P2PKH-shaped script terminated by
OP_CHECKSIGDILITHIUM rather than OP_CHECKSIG:
OP_DUP OP_HASH160 <20-byte keyhash> OP_EQUALVERIFY OP_CHECKSIGDILITHIUMScript Patterns
P2MR (current)
The output commits only to a Merkle root — no key is revealed until a spend:
scriptPubKey:
OP_2 <32-byte script tree merkle root>Spending reveals the chosen leaf script, a control block proving the leaf is committed to the root, and the authorization the leaf requires. Common leaf shapes:
| Leaf | Script |
|---|---|
| Single key | <dilithium pubkey> OP_CHECKSIGDILITHIUM |
| Multisig 2-of-3 | OP_CHECKMULTISIGDILITHIUM over three committed keys |
| Threshold accumulator | per-key OP_CHECKSIGDILITHIUM summed with OP_ADD, compared to a threshold |
Dilithium witnesses are far larger than ECDSA ones — a signature is 2,420 bytes and a public key 1,312 bytes, so expect P2MR spends to dwarf equivalent ECDSA transactions.
Historical: Pay-to-Dilithium-Public-Key (P2DPK)
Direct payment to a Dilithium public key:
<1312-byte Dilithium pubkey> OP_CHECKSIGDILITHIUMHistorical: Pay-to-Dilithium-Witness-PubKey-Hash (P2DWPKH)
The disabled witness-v0 form, whose scriptPubKey is indistinguishable from ECDSA P2WPKH. Documented for completeness only:
scriptPubKey:
OP_0 <20-byte keyhash>Witness stack:
[0]: <2421-byte signature> (2420 Dilithium + 1 sighash type)
[1]: <1312-byte pubkey>scriptCode (for signing/verification):
OP_DUP OP_HASH160 <20-byte keyhash> OP_EQUALVERIFY OP_CHECKSIGDILITHIUMKey Hash Computation
The 20-byte key hash is computed the same way as Bitcoin:
KeyHash = RIPEMD160(SHA256(dilithium_pubkey))Where dilithium_pubkey is the full 1,312-byte Dilithium public key.
Address Encoding Details
Bech32m Encoding (P2MR, current)
Address = Bech32m(hrp, witness_version = 2, merkle_root)Where hrp is the standard network HRP (qbtc / tbtq / qtb / qcrt) and the witness program
is the 32-byte script-tree Merkle root. Witness version 2 encodes as the character z, which is
why these read qbtc1z… / tbtq1z….
Historical: Base58 Encoding
Legacy Dilithium addresses use Base58Check encoding:
Address = Base58Check(version_byte + keyhash + checksum)Version bytes:
- Mainnet Dilithium pubkey:
0x4c(76) — starts withX - Mainnet Dilithium script:
0x88(136) — starts withworx - Testnet / Signet / Regtest Dilithium pubkey:
0x70(112) — starts withn - Testnet / Signet / Regtest Dilithium script:
0xc5(197) — starts with2
Historical: Dilithium Witness-v0 Bech32 Encoding
The disabled witness-v0 path used dedicated HRPs:
Address = Bech32(hrp, witness_version = 0, keyhash)Where hrp = dbtc (mainnet), tdbt (testnet), sdbt (signet), rdbt (regtest), and
witness_program is a 20-byte keyhash. These decode but are never generated — see the
support matrix.
Auto-Detection
BTQ's script interpreter automatically detects Dilithium vs ECDSA based on data size:
// In script interpreter
bool is_dilithium = (pubkey.size() > 100); // 1312 vs 33 bytes
if (is_dilithium) {
// Use OP_CHECKSIGDILITHIUM
} else {
// Use OP_CHECKSIG (ECDSA)
}This allows seamless mixed-mode operation without explicit type markers.
Address Book
Wallets maintain separate tracking for Dilithium and ECDSA addresses:
# List all addresses
btq-cli listreceivedbyaddress
# Filter by address type (future feature)
btq-cli listdilithiumaddressesSecurity Considerations
Address Reuse
Like Bitcoin, address reuse is discouraged:
- ECDSA: Reveals public key after first spend, reducing security margin
- Dilithium: Same concern, though Dilithium's security margin is larger
Always generate a new address for each transaction.
Backup
Dilithium private keys are 2,560 bytes (vs 32 bytes for ECDSA):
Dilithium key backup: ~76x larger than ECDSAEnsure your wallet backup includes all Dilithium keys. The wallet.dat file contains both ECDSA and Dilithium keys.
HD derivation (BIP-32 style) for Dilithium keys is not yet implemented. Each key must be backed up individually.
Network Prefixes Summary
The Dilithium P2MR column is the current Dilithium receive format. The Base58 columns are the historical formats, retained for reading existing outputs.
| Network | Dilithium P2MR (current) | ECDSA SegWit v0 | ECDSA Legacy (v75/111) | Dilithium Legacy (v76/112) | P2P Port |
|---|---|---|---|---|---|
| Mainnet | qbtc1z… | qbtc1q… | X… | X… | 9333 |
| Testnet | tbtq1z… | tbtq1q… | m… / n… | n… | 19333 |
| Signet | qtb1z… | qtb1q… | m… / n… | n… | 38333 |
| Regtest | qcrt1z… | qcrt1q… | m… / n… | n… | 19444 |
On mainnet the ECDSA and Dilithium legacy columns are both X… — version bytes 75 and 76 are
adjacent and overlap in Base58. Use the isdilithium flag from validateaddress, never the prefix.
The disabled Dilithium witness-v0 HRPs (dbtc, tdbt, sdbt, rdbt) are listed separately under
Historical: Dilithium Witness v0 — they are not a
current address format and do not appear in the table above.