diff --git a/src/lib.cairo b/src/lib.cairo index 8abc785f..ce41db81 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1,3 +1,5 @@ +pub mod utils; +pub mod validation; + mod state; -mod validation; mod main; diff --git a/src/utils.cairo b/src/utils.cairo new file mode 100644 index 00000000..0e542db3 --- /dev/null +++ b/src/utils.cairo @@ -0,0 +1,36 @@ +use core::traits::Into; +use core::traits::TryInto; + +// Bitwise shift left for u256 +pub fn shl(value: u256, shift: u32) -> u256 { + value * fast_pow(2.into(), shift.into()) +} + +// Bitwise shift right for u256 +pub fn shr(value: u256, shift: u32) -> u256 { + value / fast_pow(2.into(), shift.into()) +} + +// Fast exponentiation using the square-and-multiply algorithm +// Reference: +// https://github.com/keep-starknet-strange/alexandria/blob/bcdca70afdf59c9976148e95cebad5cf63d75a7f/packages/math/src/fast_power.cairo#L12 +pub fn fast_pow(base: u256, exp: u32) -> u256 { + if exp == 0 { + return 1_u256; + } + + let mut res: u256 = 1_u256; + let mut base: u256 = base; + let mut exp: u32 = exp; + + loop { + if exp % 2 == 1 { + res = res * base; + } + exp = exp / 2; + if exp == 0 { + break res; + } + base = base * base; + } +} diff --git a/src/validation.cairo b/src/validation.cairo index 2b65e832..02501b68 100644 --- a/src/validation.cairo +++ b/src/validation.cairo @@ -1,4 +1,8 @@ use super::state::{Block, ChainState, UtreexoState}; +use raito::utils::shl; +use raito::utils::shr; + +const MAX_TARGET: u256 = 0x00000000FFFF0000000000000000000000000000000000000000000000000000; #[generate_trait] impl BlockValidatorImpl of BlockValidator { @@ -70,6 +74,51 @@ fn validate_merkle_root(self: @ChainState, block: @Block) -> Result<(), ByteArra Result::Ok(()) } + +pub fn target_to_bits(target: u256) -> Result { + if target == 0 { + return Result::Err('Target is zero'); + } + + if target > MAX_TARGET { + return Result::Err('Exceeds max value'); + } + + // Find the most significant byte + let mut size: u32 = 32; + let mut compact = target; + + // Count leading zero bytes by finding the first non-zero byte + while size > 1 && shr(compact, (size - 1) * 8) == 0 { + size -= 1; + }; + + // Extract mantissa (most significant 3 bytes) + let mut mantissa: u32 = shr(compact, (size - 3) * 8).try_into().unwrap(); + + // Normalize + if mantissa > 0x7fffff { + mantissa = (mantissa + 0x80) / 0x100; + size += 1; + } + + // Ensure the mantissa is only 3 bytes + mantissa = mantissa & 0xffffff; + + // Check size doesn't exceed maximum + if size > 34 { + return Result::Err('Overflow'); + } + + // Convert size to u256 + let size_u256: u256 = size.into(); + + // Combine size and mantissa + let result: u32 = (shl(size_u256, 24) + mantissa.into()).try_into().unwrap(); + + Result::Ok(result) +} + #[cfg(test)] mod tests { use super::{validate_target, validate_timestamp}; diff --git a/tests/tests.cairo b/tests/tests.cairo new file mode 100644 index 00000000..76c15f51 --- /dev/null +++ b/tests/tests.cairo @@ -0,0 +1,66 @@ +use core::result::ResultTrait; +use core::option::OptionTrait; +use core::traits::Into; +use raito::validation::target_to_bits; + +#[test] +fn test_target_to_bits_large_target() { + let target: u256 = 0x1bc330000000000000000000000000000000000000000000; + let result = target_to_bits(target).unwrap(); + assert!(result == 0x181bc330, "Incorrect bits for large target"); +} + +#[test] +fn test_target_to_bits_small_target() { + let target: u256 = 0x92340000; + let result = target_to_bits(target).unwrap(); + assert!(result == 0x05009234, "Incorrect bits for small target"); +} + +#[test] +fn test_target_to_bits_medium_target() { + let target: u256 = 0x12345600; + let result = target_to_bits(target).unwrap(); + assert!(result == 0x04123456, "Incorrect bits for medium target"); +} + +#[test] +fn test_target_to_bits_max_target() { + let max_target: u256 = 0x00000000ffff0000000000000000000000000000000000000000000000000000; + let result = target_to_bits(max_target).unwrap(); + assert!(result == 0x1d00ffff, "Incorrect bits for max target"); +} + +#[test] +fn test_target_to_bits_high_precision_target() { + let target: u256 = 0x000000000d314200000000000000000000000000000000000000000000000000; + let result = target_to_bits(target).unwrap(); + assert!(result == 0x1c0d3142, "Incorrect bits for high precision target"); +} + +#[test] +fn test_target_to_bits_low_precision_target() { + let target: u256 = 0x00000000000000000007a4290000000000000000000000000000000000000000; + let result = target_to_bits(target).unwrap(); + assert!(result == 0x1707a429, "Incorrect bits for low precision target"); +} + +#[test] +fn test_target_to_bits_full_mantissa() { + let target: u256 = 0xd86a528bc8bc8bc8bc8bc8bc8bc8bc8bc8bc8bc8bc8bc8bc8bc8bc8b; + let result = target_to_bits(target).unwrap(); + assert!(result == 0x1d00d86a, "Incorrect bits for full mantissa target"); +} + +#[test] +fn test_target_to_bits_zero_target() { + let result = target_to_bits(0.into()); + assert!(result.is_err(), "Should error on zero target"); +} + +#[test] +fn test_target_to_bits_overflow_target() { + let target: u256 = 0x01000000000000000000000000000000000000000000000000000000000000000; + let result = target_to_bits(target); + assert!(result.is_err(), "Should error on overflow target"); +}