Dilithium Signatures
Transaction signing and verification with Dilithium
Dilithium Signatures
BTQ uses Dilithium2 signatures for quantum-resistant transaction authorization. This page covers the signing and verification process.
Signature Format
Size
| Component | Size |
|---|---|
| Dilithium signature | 2,420 bytes |
| Sighash type byte | 1 byte |
| Total | 2,421 bytes |
Compare to ECDSA: ~71 bytes (DER encoded)
Structure
[2420 bytes: Dilithium signature][1 byte: sighash type]The sighash type is appended to the signature, following Bitcoin's convention.
Sighash Types
Dilithium signatures support all Bitcoin sighash types:
| Type | Value | Description |
|---|---|---|
SIGHASH_ALL | 0x01 | Sign all inputs and outputs |
SIGHASH_NONE | 0x02 | Sign inputs only |
SIGHASH_SINGLE | 0x03 | Sign inputs and corresponding output |
SIGHASH_ANYONECANPAY | 0x80 | Can be combined with above |
Example
SIGHASH_ALL | SIGHASH_ANYONECANPAY = 0x81Signing Process
1. Compute Sighash
The sighash is computed using BIP-143 (SegWit) serialization:
sighash = SHA256(SHA256(preimage))Where preimage includes:
- Transaction version
- Prevouts hash
- Sequences hash
- Outpoint being spent
- scriptCode
- Value being spent
- Sequence
- Outputs hash
- Locktime
- Sighash type
2. Generate Signature
CDilithiumKey key;
std::vector<unsigned char> signature;
// Sign the 32-byte sighash
key.Sign(sighash, signature);
// Append sighash type
signature.push_back(SIGHASH_ALL);3. Construct Witness
For P2DWPKH (most common):
Witness:
[0]: signature (2,421 bytes)
[1]: pubkey (1,312 bytes)Verification Process
1. Extract Components
// From witness stack
std::vector<unsigned char> signature = witness[0];
std::vector<unsigned char> pubkey = witness[1];
// Remove sighash type
unsigned char sighash_type = signature.back();
signature.pop_back();2. Recompute Sighash
uint256 sighash = SignatureHash(
scriptCode,
tx,
input_index,
sighash_type,
amount,
SigVersion::WITNESS_V0
);3. Verify Signature
CDilithiumPubKey pubkey(pubkey_data);
bool valid = pubkey.Verify(sighash, signature);Script Interpreter
OP_CHECKSIGDILITHIUM
The OP_CHECKSIGDILITHIUM opcode verifies Dilithium signatures:
Input Stack: <sig> <pubkey>
Output Stack: <1> (if valid) or <0> (if invalid)Implementation
case OP_CHECKSIGDILITHIUM: {
// Pop pubkey and signature
valtype& vchSig = stacktop(-2);
valtype& vchPubKey = stacktop(-1);
// Create pubkey object
CDilithiumPubKey pubkey(vchPubKey);
// Extract sighash type
unsigned char sighash_type = vchSig.back();
vchSig.pop_back();
// Compute sighash
uint256 sighash = SignatureHash(...);
// Verify
bool fSuccess = pubkey.Verify(sighash, vchSig);
// Push result
popstack(stack);
popstack(stack);
stack.push_back(fSuccess ? vchTrue : vchFalse);
}Auto-Detection
BTQ automatically detects Dilithium vs ECDSA signatures:
// In script interpreter
if (vchSig.size() > 500) {
// Dilithium signature - skip DER check
return true;
}
// Standard DER validation for ECDSAAnd for public keys:
bool is_dilithium = (vchPubKey.size() > 100);
// 1,312 bytes = Dilithium
// 33 bytes = ECDSATransaction Signing RPC
signtransactionwithdilithium
btq-cli -rpcwallet="wallet" signtransactionwithdilithium "hex"Input: Unsigned transaction hex
Output:
{
"hex": "signed_transaction_hex",
"complete": true
}Example Flow
# 1. Create unsigned transaction
RAW=$(btq-cli createrawtransaction \
'[{"txid":"abc...","vout":0}]' \
'{"qbtc1z...":1.0}')
# 2. Sign with Dilithium
SIGNED=$(btq-cli -rpcwallet="w" signtransactionwithdilithium "$RAW")
# 3. Broadcast
btq-cli sendrawtransaction "$(echo $SIGNED | jq -r '.hex')"Message Signing
Sign Message
btq-cli signmessagewithdilithium "address" "message"Output: Base64-encoded signature (~3.2KB)
Verify Message
btq-cli verifydilithiumsignature "message" "address" "signature"Output: true or false
Message Format
The signed message is:
"Bitcoin Signed Message:\n" + message(Uses same format as Bitcoin for compatibility)
Performance
| Operation | Dilithium | ECDSA |
|---|---|---|
| Key Generation | ~2ms | ~0.1ms |
| Signing | ~3ms | ~0.5ms |
| Verification | ~1.5ms | ~2ms |
Dilithium verification is faster than ECDSA, which helps offset the larger signature size during block validation.
Security Considerations
Deterministic Signatures
Dilithium produces deterministic signatures (same inputs produce same output). This prevents:
- Nonce reuse attacks
- Random number generator failures
Side-Channel Resistance
The reference implementation includes basic side-channel protections:
- Constant-time operations where possible
- Secret key material cleared after use
Key Reuse
Like ECDSA, address reuse is discouraged:
- First spend reveals the public key
- Reduces security margin (though still quantum-resistant)
Transaction Anatomy
Dilithium P2WPKH Transaction
Version: 2
Marker: 0x00
Flag: 0x01
Inputs:
- Previous Output
- scriptSig: (empty for witness)
- Sequence
Outputs:
- Value
- scriptPubKey: OP_0 <20-byte-hash>
Witness:
- Items: 2
- [0]: 2421 bytes (signature + sighash)
- [1]: 1312 bytes (pubkey)
Locktime: 0
Total Size: ~3,824 bytesSize Comparison
| Component | ECDSA P2WPKH | Dilithium P2WPKH |
|---|---|---|
| Signature | ~71 bytes | 2,421 bytes |
| Public Key | 33 bytes | 1,312 bytes |
| Overhead | ~150 bytes | ~150 bytes |
| Total | ~254 bytes | ~3,883 bytes |
Dilithium Addresses
Dilithium address formats in BTQ. P2MR (BIP360, witness v2) Bech32m is the only Dilithium receive type. How to generate and validate one.
BIP360 P2MR
BIP360 Pay-to-Merkle-Root in BTQ: the witness v2 script-tree commitment model, script-path spending, validation rules, and the RPC workflow.