BTQ Docs
Dilithium

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

ComponentSize
Dilithium signature2,420 bytes
Sighash type byte1 byte
Total2,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:

TypeValueDescription
SIGHASH_ALL0x01Sign all inputs and outputs
SIGHASH_NONE0x02Sign inputs only
SIGHASH_SINGLE0x03Sign inputs and corresponding output
SIGHASH_ANYONECANPAY0x80Can be combined with above

Example

SIGHASH_ALL | SIGHASH_ANYONECANPAY = 0x81

Signing 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 ECDSA

And for public keys:

bool is_dilithium = (vchPubKey.size() > 100);
// 1,312 bytes = Dilithium
// 33 bytes = ECDSA

Transaction 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

OperationDilithiumECDSA
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 bytes

Size Comparison

ComponentECDSA P2WPKHDilithium P2WPKH
Signature~71 bytes2,421 bytes
Public Key33 bytes1,312 bytes
Overhead~150 bytes~150 bytes
Total~254 bytes~3,883 bytes

On this page