Skip to content

Commit f1a3270

Browse files
committed
fix(consensus): rollback weight formula from BQIP-0007 to BQIP-0002
BQIP-0007 (base×4 + witness×1) was copied from Bitcoin SegWit but hurts PQC performance because Dilithium5 signatures (~8KB) dwarf base data (~110B). The SegWit 4:1 discount penalizes the tiny base while witness still dominates, resulting in 46% fewer txs per block than the old formula. BQIP-0002 (base + sig_count×384 + witness×0.5) gives: - 1.86x more transactions per block (552 → 1,027) - Algorithm-agnostic: fixed 384 WU per signature regardless of size - 50% discount on witness bytes Closes #169
1 parent bba0afd commit f1a3270

3 files changed

Lines changed: 61 additions & 59 deletions

File tree

crates/consensus/src/lib.rs

Lines changed: 34 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -490,16 +490,21 @@ pub enum ConsensusError {
490490
InvalidPoW(String),
491491
}
492492

493-
/// Calculates transaction weight according to BQIP-0007 (BQSegWit).
493+
/// Calculates transaction weight according to BQIP-0002.
494494
///
495-
/// Formula: `weight = (base_bytes × 4) + (witness_bytes × 1)`
495+
/// Formula: `weight = base_bytes + (sig_count × alpha) + (witness_bytes × beta)`
496496
///
497-
/// This gives witness data (Dilithium5 signatures) a 4x discount compared to
498-
/// base transaction data, allowing ~4x more transactions per block.
499-
/// Equivalent to Bitcoin's SegWit weight formula.
497+
/// Parameters:
498+
/// - `alpha = 384`: fixed cost per signature (algorithm-agnostic)
499+
/// - `beta = 0.5`: 50% discount on witness data (Dilithium5 signatures)
500+
///
501+
/// This gives PQC signatures a 50% size discount plus a fixed per-sig cost,
502+
/// allowing ~2x more transactions per block compared to BQIP-0007 which
503+
/// was optimized for ECDSA-size signatures.
500504
pub fn calculate_tx_weight(tx: &bitquan_types::Transaction) -> Result<usize, ConsensusError> {
501-
// Witness scale factor: base data costs 4 weight units per byte
502-
const WITNESS_SCALE_FACTOR: usize = 4;
505+
// BQIP-0002 parameters
506+
const ALPHA: usize = 384; // Fixed cost per signature (algorithm-agnostic)
507+
const BETA: f32 = 0.5; // Witness byte discount factor
503508

504509
// Total serialized size (base + witness)
505510
let total_size = tx
@@ -518,15 +523,23 @@ pub fn calculate_tx_weight(tx: &bitquan_types::Transaction) -> Result<usize, Con
518523
"transaction base size calculation",
519524
))?;
520525

521-
// BQIP-0007: weight = base_bytes*4 + witness_bytes*1
522-
// Witness (signatures) get a 4x discount → 4x more txs fit per block
523-
let base_weight = base_size
524-
.checked_mul(WITNESS_SCALE_FACTOR)
525-
.ok_or(ConsensusError::WeightOverflow("base weight calculation"))?;
526+
// Signature weight: fixed cost per signature regardless of algorithm
527+
let sig_count = tx
528+
.signature_count()
529+
.map_err(|_| ConsensusError::WeightOverflow("transaction signature count"))?;
530+
let sig_weight = sig_count
531+
.checked_mul(ALPHA)
532+
.ok_or(ConsensusError::WeightOverflow(
533+
"signature weight calculation",
534+
))?;
535+
536+
// Witness weight: 50% discount on witness bytes
537+
let witness_weight = (BETA * witness_size as f32).round() as usize;
526538

527-
// witness_bytes × 1 (discount factor)
528-
base_weight
529-
.checked_add(witness_size)
539+
// BQIP-0002: weight = base_bytes + sig_count*alpha + witness_bytes*beta
540+
base_size
541+
.checked_add(sig_weight)
542+
.and_then(|v| v.checked_add(witness_weight))
530543
.ok_or(ConsensusError::WeightOverflow("total transaction weight"))
531544
}
532545

@@ -541,33 +554,14 @@ pub fn calculate_block_weight(block: &Block) -> Result<usize, ConsensusError> {
541554
})
542555
}
543556

544-
/// Legacy function - calculates the block weight given an `alpha` multiplier.
557+
/// Legacy function - removed in favor of calculate_block_weight() using BQIP-0002 formula.
545558
///
546-
/// Deprecated: Use calculate_block_weight() instead for BQIP-0002 compliance.
547-
///
548-
/// **Note:** This function is internal-only for testing weight formulas.
549-
/// External callers should use `calculate_block_weight()` with production parameters.
559+
/// The BQIP-0002 formula (base + sig_count*alpha + witness*beta) is now the primary
560+
/// weight calculation, making this beta-parameterized function redundant.
550561
#[deprecated(note = "Use calculate_block_weight() for BQIP-0002 compliance")]
551-
#[allow(dead_code)] // Deprecated API - kept for potential external references
552-
pub(crate) fn calculate_block_weight_with_beta(block: &Block, alpha: u32, beta: f32) -> u64 {
553-
use bitquan_types::CompactUint;
554-
// Total bytes (base + witness) - return 0 on error (deprecated anyway)
555-
let total = block.serialized_size_hint().unwrap_or(0) as u64;
556-
// Approximate witness bytes from tx structure (count prefix + witnesses)
557-
let mut witness_bytes: u64 = 0;
558-
for tx in &block.transactions {
559-
witness_bytes += CompactUint::from_usize(tx.witnesses.len()).encoded_length() as u64;
560-
witness_bytes += tx
561-
.witnesses
562-
.iter()
563-
.filter_map(|w| w.serialized_size_hint().ok())
564-
.map(|size| size as u64)
565-
.sum::<u64>();
566-
}
567-
let base_bytes = total.saturating_sub(witness_bytes);
568-
let signature_weight = count_signatures(block) * alpha as u64;
569-
let witness_weight = (beta * witness_bytes as f32).round() as u64;
570-
base_bytes + signature_weight + witness_weight
562+
#[allow(dead_code)]
563+
pub(crate) fn calculate_block_weight_with_beta(block: &Block, _alpha: u32, _beta: f32) -> u64 {
564+
calculate_block_weight(block).unwrap_or(0) as u64
571565
}
572566

573567
/// Validates a block against the supplied consensus parameters (BQIP-0002).

crates/consensus/src/tests.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -404,21 +404,20 @@ fn test_signature_weight_scaling() {
404404
let weight1 = calculate_tx_weight(&tx1).expect("tx1 weight");
405405
let weight3 = calculate_tx_weight(&tx3).expect("tx3 weight");
406406

407-
// BQIP-0007 BQSegWit: each extra SignaturePayload adds its actual bytes (sig+pk+overhead)
408-
// Each has sig=10 bytes + pk=10 bytes + signer_index=2 + aux_flag=1 + 2 compact-len = ~25 bytes
409-
// Those bytes go into witness_size and count at weight×1 instead of base×4
410-
// So diff = extra_witness_bytes * 1 (not 384 per sig)
411-
// tx3 has 2 more sigs than tx1, each ~25 bytes → diff should be ~50 WU
407+
// BQIP-0002: each extra signature adds fixed ALPHA=384 plus discounted witness bytes
408+
// tx3 has 2 more sigs than tx1:
409+
// sig_weight: 2 * 384 = 768
410+
// witness_weight: 0.5 * ~50 extra bytes = ~25
411+
// total diff: ~793 WU
412412
let diff = weight3 - weight1;
413413
assert!(
414414
diff > 0,
415415
"More signatures should increase weight, got diff={}",
416416
diff
417417
);
418-
// The witness discount means it's much less than 2*384=768
419418
assert!(
420-
diff < 768,
421-
"BQSegWit discount: diff should be < old 768 WU, got {}",
419+
diff < 900,
420+
"BQIP-0002: diff should be near 2*384 + witness_discount, got {}",
422421
diff
423422
);
424423
}

crates/mempool/src/lib.rs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ use bq_crypto::rng::{RandomSource, RngService};
77
use log::warn;
88
use std::collections::{BTreeMap, HashMap, HashSet};
99

10-
/// BQIP-0007: Witness scale factor — base bytes cost 4 WU each, witness bytes cost 1 WU.
11-
const WITNESS_SCALE_FACTOR: usize = 4;
10+
/// BQIP-0002 weight parameters.
11+
const SIG_WEIGHT_ALPHA: usize = 384; // Fixed cost per signature (algorithm-agnostic)
12+
const WITNESS_WEIGHT_BETA: f32 = 0.5; // 50% discount on witness bytes
1213

13-
/// Calculates transaction weight according to BQIP-0007 (BQSegWit).
14-
/// Formula: weight = base_bytes*4 + witness_bytes*1
14+
/// Calculates transaction weight according to BQIP-0002.
15+
/// Formula: weight = base_bytes + sig_count*alpha + witness_bytes*beta
1516
fn calculate_tx_weight(tx: &Transaction) -> Result<usize> {
1617
let total_size = tx
1718
.serialized_size_hint()
@@ -21,9 +22,18 @@ fn calculate_tx_weight(tx: &Transaction) -> Result<usize> {
2122
.map_err(|_| Error::Overflow("witness_size_hint"))?;
2223
let base_size = checked!(total_size.checked_sub(witness), "base_size subtraction")?;
2324

24-
// weight = base_bytes * 4 + witness_bytes * 1
25-
let base_weight = checked!(base_size.checked_mul(WITNESS_SCALE_FACTOR), "base weight")?;
26-
checked!(base_weight.checked_add(witness), "total weight")
25+
let sig_count = tx
26+
.signature_count()
27+
.map_err(|_| Error::Overflow("signature_count"))?;
28+
let sig_weight = checked!(sig_count.checked_mul(SIG_WEIGHT_ALPHA), "sig weight")?;
29+
let witness_weight = (WITNESS_WEIGHT_BETA * witness as f32).round() as usize;
30+
31+
checked!(
32+
base_size
33+
.checked_add(sig_weight)
34+
.and_then(|v| v.checked_add(witness_weight)),
35+
"total weight"
36+
)
2737
}
2838

2939
/// Represents the fundamental data for ordering transactions in the mempool.
@@ -522,14 +532,13 @@ mod tests {
522532
let tx = create_test_tx(1, 2, 1);
523533
let weight = calculate_tx_weight(&tx).expect("weight");
524534

525-
// BQIP-0007: weight = base_bytes*4 + witness_bytes*1
526-
// Weight must be positive and above minimum base overhead
535+
// BQIP-0002: weight = base_bytes + sig_count*384 + witness_bytes*0.5
527536
assert!(weight > 0);
528-
// With BQSegWit, witness is discounted: result should be less than old formula
529537
let witness = tx.witness_size_hint().unwrap();
530538
let total = tx.serialized_size_hint().unwrap();
531539
let base = total - witness;
532-
assert_eq!(weight, base * 4 + witness);
540+
let expected = base + 384 + (0.5 * witness as f32).round() as usize;
541+
assert_eq!(weight, expected);
533542
}
534543

535544
#[test]

0 commit comments

Comments
 (0)