diff --git a/.all-contributorsrc b/.all-contributorsrc index 89d255a6..b873cde0 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -72,6 +72,24 @@ "contributions": [ "code" ] + }, + { + "login": "harsh-ps-2003", + "name": "Harsh Pratap Singh", + "avatar_url": "https://avatars.githubusercontent.com/u/119954739?v=4", + "profile": "https://github.com/harsh-ps-2003", + "contributions": [ + "code" + ] + }, + { + "login": "Xavek", + "name": "Xavek", + "avatar_url": "https://avatars.githubusercontent.com/u/61218841?v=4", + "profile": "https://github.com/Xavek", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index acbb1900..5c3c8408 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,11 @@ Raito is a reference to Light Yagami (夜神月, Yagami Raito) from the manga/an ![Raito and Raito](./docs/img/memes/raito_shinigami_fusion.jpg) +# Contact + +* [Raito Telegram](https://t.me/RaitoStarknet) +* [Raito OnlyDust](https://app.onlydust.com/p/raito---bitcoin-zk-client) + ## Usage This will compile all the components: @@ -131,6 +136,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d lomasson
lomasson

💻 Michael Zaikin
Michael Zaikin

💻 + + Harsh Pratap Singh
Harsh Pratap Singh

💻 + Xavek
Xavek

💻 + diff --git a/src/lib.cairo b/src/lib.cairo index 8abc785f..7778f44f 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1,3 +1,4 @@ +mod utils; +pub mod validation; mod state; -mod validation; mod main; diff --git a/src/state.cairo b/src/state.cairo index 2f5e3653..6fcaf700 100644 --- a/src/state.cairo +++ b/src/state.cairo @@ -75,8 +75,6 @@ pub struct Header { pub version: u32, /// The hash of the previous block in the blockchain. pub prev_block_hash: u256, - /// The Merkle root hash of the transactions in the block. - pub merkle_root_hash: u256, /// The timestamp of the block. pub time: u32, /// The difficulty target for mining the block. 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 14a366f0..b7d41226 100644 --- a/src/validation.cairo +++ b/src/validation.cairo @@ -1,26 +1,57 @@ -use super::state::{Block, ChainState, UtreexoState}; +use super::utils::{shl, shr}; +use super::state::{Block, ChainState, Transaction, UtreexoState}; + +const MAX_TARGET: u256 = 0x00000000FFFF0000000000000000000000000000000000000000000000000000; #[generate_trait] impl BlockValidatorImpl of BlockValidator { fn validate_and_apply(self: ChainState, block: Block) -> Result { validate_prev_block_hash(@self, @block)?; - validate_proof_of_work(@self, @block)?; + validate_proof_of_work(@0_u256, @block)?; validate_target(@self, @block)?; validate_timestamp(@self, @block)?; - validate_merkle_root(@self, @block)?; - // validate_and_apply_transactions + let (total_fees, merkle_root) = fee_and_merkle_root(@self, @block)?; + + validate_coinbase(@block, total_fees)?; + let best_block_hash = block_hash(@block, merkle_root)?; let prev_timestamps = next_prev_timestamps(@self, @block); let total_work = compute_total_work(@self, @block); let (current_target, epoch_start_time) = adjust_difficulty(@self, @block); + let block_height = self.block_height + 1; Result::Ok( - ChainState { total_work, current_target, epoch_start_time, prev_timestamps, ..self, } + ChainState { + block_height, + total_work, + best_block_hash, + current_target, + epoch_start_time, + prev_timestamps, + ..self, + } ) } } +#[generate_trait] +impl TransactionValidatorImpl of TransactionValidator { + fn txid(self: @Transaction) -> u256 { + // TODO: implement + 0 + } + fn fee(self: @Transaction) -> u256 { + // TODO: implement + 0 + } +} + +fn block_hash(block: @Block, merkle_root: u256) -> Result { + // TODO: implement + Result::Ok(0) +} + fn validate_prev_block_hash(self: @ChainState, block: @Block) -> Result<(), ByteArray> { if self.best_block_hash == block.header.prev_block_hash { Result::Ok(()) @@ -29,9 +60,14 @@ fn validate_prev_block_hash(self: @ChainState, block: @Block) -> Result<(), Byte } } -fn validate_proof_of_work(self: @ChainState, block: @Block) -> Result<(), ByteArray> { - // TODO: implement - Result::Ok(()) +fn validate_proof_of_work(target: @u256, block: @Block) -> Result<(), ByteArray> { + if block.header.prev_block_hash <= target { + Result::Ok(()) + } else { + Result::Err( + "Insufficient proof of work. Expected block hash {block.header.prev_block_hash} to be less than or equal to {target}." + ) + } } fn validate_target(self: @ChainState, block: @Block) -> Result<(), ByteArray> { @@ -83,11 +119,78 @@ fn compute_block_reward(self: @ChainState, block: @Block) -> Result 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) +} + +fn fee_and_merkle_root(self: @ChainState, block: @Block) -> Result<(u256, u256), ByteArray> { + let mut txids = ArrayTrait::new(); + let mut total_fee = 0; + + for tx in *block.txs { + txids.append(tx.txid()); + total_fee += tx.fee(); + }; + + Result::Ok((total_fee, merkle_root(txids))) +} + +fn merkle_root(txids: Array) -> u256 { + // TODO: implement + 0 +} + +fn validate_coinbase(block: @Block, total_fees: u256) -> Result<(), ByteArray> { + //TODO implement + Result::Ok(()) } #[cfg(test)] mod tests { - use super::{validate_target, validate_timestamp, compute_block_reward}; + use super::{validate_target, validate_timestamp, validate_proof_of_work}; use super::{Block, ChainState, UtreexoState}; use super::super::state::{Header, Transaction, TxIn, TxOut}; @@ -103,9 +206,7 @@ mod tests { utreexo_state: UtreexoState { roots: array![].span() }, }; let mut block = Block { - header: Header { - version: 1, prev_block_hash: 1, merkle_root_hash: 1, time: 1, bits: 1, nonce: 1, - }, + header: Header { version: 1, prev_block_hash: 1, time: 1, bits: 1, nonce: 1, }, txs: ArrayTrait::new().span(), }; @@ -135,9 +236,7 @@ mod tests { utreexo_state: UtreexoState { roots: array![].span() }, }; let mut block = Block { - header: Header { - version: 1, prev_block_hash: 1, merkle_root_hash: 1, time: 12, bits: 1, nonce: 1, - }, + header: Header { version: 1, prev_block_hash: 1, time: 12, bits: 1, nonce: 1, }, txs: ArrayTrait::new().span(), }; @@ -156,28 +255,33 @@ mod tests { assert!(result.is_err(), "Median time is greater than block's timestamp"); } - // TODO finish test #[test] - fn test_compute_block_reward() { - // TODO add correct state to compute block reward - let mut chain_state = ChainState { - block_height: 1, - total_work: 1, - best_block_hash: 1, - current_target: 1, - epoch_start_time: 1, - prev_timestamps: array![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].span(), - utreexo_state: UtreexoState { roots: array![].span() }, - }; + fn test_validate_proof_of_work() { let mut block = Block { - header: Header { - version: 1, prev_block_hash: 1, merkle_root_hash: 1, time: 12, bits: 1, nonce: 1, - }, + header: Header { version: 1, prev_block_hash: 1, time: 12, bits: 1, nonce: 1, }, txs: ArrayTrait::new().span(), }; - // let result = validate_timestamp(@chain_state, @block); - // TODO add correct state to compute block reward - let result_block = compute_block_reward(@chain_state, @block); - assert!(result_block.is_err(), "number_halvings equal 0"); + + // target is less than prev block hash + let result = validate_proof_of_work(@0_u256, @block); + assert!(result.is_err(), "Expect target less than prev block hash"); + + // target is greater than prev block hash + let result = validate_proof_of_work(@2_u256, @block); + assert!(result.is_ok(), "Expect target gt prev block hash"); + + // target is equal to prev block hash + let result = validate_proof_of_work(@1_u256, @block); + assert!(result.is_ok(), "Expect target equal to prev block hash"); + + // block prev block hash is greater than target + block.header.prev_block_hash = 2; + let result = validate_proof_of_work(@1_u256, @block); + assert!(result.is_err(), "Expect prev block hash gt target"); + + // block prev block hash is less than target + block.header.prev_block_hash = 9; + let result = validate_proof_of_work(@10_u256, @block); + assert!(result.is_ok(), "Expect prev block hash lt target"); } } diff --git a/tests/tests.cairo b/tests/tests.cairo new file mode 100644 index 00000000..0646d0a5 --- /dev/null +++ b/tests/tests.cairo @@ -0,0 +1,63 @@ +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"); +}