Skip to content
Closed
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
107 changes: 100 additions & 7 deletions protocols/v2/subprotocols/mining/src/cashu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,20 +267,56 @@ impl<'a> TryFrom<Sv2KeySet<'a>> for KeySet {
}

// TODO find a better place for this
// TODO make configurable
pub fn calculate_work(hash: [u8; 32]) -> u64 {
let mut work = 0u64;
calculate_work_in_range(hash, 36, 64)
}

/// Calculate work using exponential valuation (2^n) where n is number of leading zero bits.
/// - Each additional zero bit is exponentially more difficult to find
/// - Shares should be valued proportionally to their computational difficulty
///
/// Parameters:
/// - hash: The 32-byte hash to analyze
/// - min_leading_zeros: Minimum number of leading zero bits required (e.g., 36 for mainnet)
/// - max_representable_bits: Maximum representable difficulty bits (e.g., 64 to compress from 256)
///
/// Returns the exponential work value: 2^(min(leading_zeros, max_representable_bits))
pub fn calculate_work_in_range(hash: [u8; 32], min_leading_zeros: u32, max_representable_bits: u32) -> u64 {
let leading_zero_bits = count_leading_zero_bits(hash);

// Only count work above the minimum threshold
if leading_zero_bits < min_leading_zeros {
return 0; // Below minimum difficulty, no reward
}

// Cap at maximum representable bits to prevent overflow
let effective_bits = std::cmp::min(leading_zero_bits, max_representable_bits);

// Use exponential valuation: 2^n where n is effective difficulty bits
// For values above 63, we'd overflow u64, so cap at 2^63
if effective_bits >= 63 {
1u64 << 63 // Maximum representable value in u64
} else {
1u64 << effective_bits
}
}

/// Count the number of leading zero bits in a hash
pub fn count_leading_zero_bits(hash: [u8; 32]) -> u32 {
let mut count = 0u32;

for byte in hash {
if byte == 0 {
work += 8; // Each zero byte adds 8 bits of work
count += 8; // Each zero byte adds 8 bits
} else {
// Count the leading zeros in the current byte
work += byte.leading_zeros() as u64;
count += byte.leading_zeros();
break; // Stop counting after the first non-zero byte
}
}

work
count
}

fn sv2_signing_keys_to_keys(keys: &[Sv2SigningKey]) -> Result<Keys, String> {
Expand Down Expand Up @@ -446,14 +482,71 @@ mod tests {

#[test]
fn test_calculate_work_expected_values() {
assert_eq!(calculate_work([0u8; 32]), 256);
// All zeros: 256 leading zero bits, capped at 64, gets 2^63 (max u64 value we can represent)
assert_eq!(calculate_work([0u8; 32]), 1u64 << 63);

// 255 leading zeros (one bit set at the end): gets 2^63 (still capped)
let mut one = [0u8; 32];
one[31] = 1;
assert_eq!(calculate_work(one), 255);
assert_eq!(calculate_work(one), 1u64 << 63);

// 251 leading zeros (0x10 = bit 4 set): gets 2^63 (still capped)
let mut sixteen = [0u8; 32];
sixteen[31] = 0x10;
assert_eq!(calculate_work(sixteen), 251);
assert_eq!(calculate_work(sixteen), 1u64 << 63);

// Test configurable function with lower thresholds
// 40 leading zeros: bytes 0-4 are zero (40 bits), byte 5 has bit set
let mut test_hash = [0u8; 32];
test_hash[5] = 1; // First non-zero bit is at bit 47 (5*8 + 7), so 47 leading zeros
assert_eq!(calculate_work_in_range(test_hash, 36, 50), 1u64 << 47);

// Below minimum: should get 0
let mut low_hash = [0u8; 32];
low_hash[3] = 1; // 3*8 + 7 = 31 leading zeros, below minimum of 36
assert_eq!(calculate_work_in_range(low_hash, 36, 50), 0);
}

#[test]
fn test_count_leading_zero_bits() {
// All zeros should have 256 leading zero bits
assert_eq!(count_leading_zero_bits([0u8; 32]), 256);

let mut one = [0u8; 32];
one[31] = 1;
assert_eq!(count_leading_zero_bits(one), 255);

// 0x10 in last byte: 4 leading zeros in that byte + 31*8 from previous bytes = 251
let mut sixteen = [0u8; 32];
sixteen[31] = 0x10;
assert_eq!(count_leading_zero_bits(sixteen), 251);

// First bit set should have 0 leading zeros
let mut first_bit = [0u8; 32];
first_bit[0] = 0x80;
assert_eq!(count_leading_zero_bits(first_bit), 0);

// Second bit set should have 1 leading zero
let mut second_bit = [0u8; 32];
second_bit[0] = 0x40;
assert_eq!(count_leading_zero_bits(second_bit), 1);
}

#[test]
fn test_exponential_valuation() {
// Test that each additional zero bit doubles the reward
// Use lower thresholds to avoid overflow in tests

// 10 leading zeros: 2^10 = 1024
let mut hash1 = [0u8; 32];
hash1[1] = 0x40; // Sets bit 6 of byte 1: 8 + 1 = 9 leading zeros
hash1[1] = 0x20; // Sets bit 5 of byte 1: 8 + 2 = 10 leading zeros
assert_eq!(calculate_work_in_range(hash1, 5, 20), 1u64 << 10);

// 11 leading zeros: 2^11 = 2048 (double the previous)
let mut hash2 = [0u8; 32];
hash2[1] = 0x10; // Sets bit 4 of byte 1: 8 + 3 = 11 leading zeros
assert_eq!(calculate_work_in_range(hash2, 5, 20), 1u64 << 11);
}


Expand Down
Loading