Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@ crates/pqc-dilithium-seeded/Cargo.toml.orig

# Root junk
*.rs

# LaTeX build artifacts
whitepaper/*.aux
whitepaper/*.log
whitepaper/*.out
whitepaper/*.toc
whitepaper/*.lof
whitepaper/*.lot
whitepaper/*.bbl
whitepaper/*.blg
whitepaper/*.synctex.gz
whitepaper/*.fls
whitepaper/*.fdb_latexmk
whitepaper/_minted-*
whitepaper/__pycache__/
!crates/*/src/**/*.rs
!crates/*/tests/**/*.rs
html.html
Expand Down
74 changes: 34 additions & 40 deletions crates/consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,16 +490,21 @@ pub enum ConsensusError {
InvalidPoW(String),
}

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

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

// BQIP-0007: weight = base_bytes*4 + witness_bytes*1
// Witness (signatures) get a 4x discount → 4x more txs fit per block
let base_weight = base_size
.checked_mul(WITNESS_SCALE_FACTOR)
.ok_or(ConsensusError::WeightOverflow("base weight calculation"))?;
// Signature weight: fixed cost per signature regardless of algorithm
let sig_count = tx
.signature_count()
.map_err(|_| ConsensusError::WeightOverflow("transaction signature count"))?;
let sig_weight = sig_count
.checked_mul(ALPHA)
.ok_or(ConsensusError::WeightOverflow(
"signature weight calculation",
))?;

// Witness weight: 50% discount on witness bytes
let witness_weight = (BETA * witness_size as f32).round() as usize;

// witness_bytes × 1 (discount factor)
base_weight
.checked_add(witness_size)
// BQIP-0002: weight = base_bytes + sig_count*alpha + witness_bytes*beta
base_size
.checked_add(sig_weight)
.and_then(|v| v.checked_add(witness_weight))
.ok_or(ConsensusError::WeightOverflow("total transaction weight"))
}

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

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

/// Validates a block against the supplied consensus parameters (BQIP-0002).
Expand Down
15 changes: 7 additions & 8 deletions crates/consensus/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,21 +404,20 @@ fn test_signature_weight_scaling() {
let weight1 = calculate_tx_weight(&tx1).expect("tx1 weight");
let weight3 = calculate_tx_weight(&tx3).expect("tx3 weight");

// BQIP-0007 BQSegWit: each extra SignaturePayload adds its actual bytes (sig+pk+overhead)
// Each has sig=10 bytes + pk=10 bytes + signer_index=2 + aux_flag=1 + 2 compact-len = ~25 bytes
// Those bytes go into witness_size and count at weight×1 instead of base×4
// So diff = extra_witness_bytes * 1 (not 384 per sig)
// tx3 has 2 more sigs than tx1, each ~25 bytes → diff should be ~50 WU
// BQIP-0002: each extra signature adds fixed ALPHA=384 plus discounted witness bytes
// tx3 has 2 more sigs than tx1:
// sig_weight: 2 * 384 = 768
// witness_weight: 0.5 * ~50 extra bytes = ~25
// total diff: ~793 WU
let diff = weight3 - weight1;
assert!(
diff > 0,
"More signatures should increase weight, got diff={}",
diff
);
// The witness discount means it's much less than 2*384=768
assert!(
diff < 768,
"BQSegWit discount: diff should be < old 768 WU, got {}",
diff < 900,
"BQIP-0002: diff should be near 2*384 + witness_discount, got {}",
diff
);
}
Expand Down
31 changes: 20 additions & 11 deletions crates/mempool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ use bq_crypto::rng::{RandomSource, RngService};
use log::warn;
use std::collections::{BTreeMap, HashMap, HashSet};

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

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

// weight = base_bytes * 4 + witness_bytes * 1
let base_weight = checked!(base_size.checked_mul(WITNESS_SCALE_FACTOR), "base weight")?;
checked!(base_weight.checked_add(witness), "total weight")
let sig_count = tx
.signature_count()
.map_err(|_| Error::Overflow("signature_count"))?;
let sig_weight = checked!(sig_count.checked_mul(SIG_WEIGHT_ALPHA), "sig weight")?;
let witness_weight = (WITNESS_WEIGHT_BETA * witness as f32).round() as usize;

checked!(
base_size
.checked_add(sig_weight)
.and_then(|v| v.checked_add(witness_weight)),
"total weight"
)
}

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

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

#[test]
Expand Down
20 changes: 20 additions & 0 deletions whitepaper/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.PHONY: all clean view

TEX = whitepaper.tex
PDF = whitepaper.pdf

all: $(PDF)

$(PDF): $(TEX)
latexmk -pdf -interaction=nonstopmode $(TEX)

clean:
latexmk -C
rm -f *.aux *.log *.out *.toc *.lof *.lot *.bbl *.blg \
*.synctex.gz *.fls *.fdb_latexmk _minted-*

view: $(PDF)
open $(PDF)

watch:
latexmk -pdf -pvc $(TEX)
Loading
Loading