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/.github/CODEOWNERS b/.github/CODEOWNERS index 65ea647d..dc1830d5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @b-j-roberts @m-kus @maciejka +* @m-kus @maciejka diff --git a/README.md b/README.md index 2df41baa..5c3c8408 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,36 @@ Although this is a highly experimental project without immediate plans for deplo ## Roadmap -* [ ] verify block header (block hash, previous block hash, Merkle root, proof-of-work, median time, and difficulty adjustment) -* [ ] verify transactions -* [ ] integrate with Shinigami and verify scripts -* [ ] verify previous chain proofs -* [ ] add utreexo accumulator to the chain state +### Milestone 1 - Block Verification + +* header verification + * [ ] block hash + * [ ] previous block hash + * [ ] proof-of-work + * [ ] median time + * [ ] difficulty adjustment +* transaction verification + * [ ] tx hash + * [ ] tx merkle root + * [ ] verify transaction fee +* utreexo + * [ ] fetch utreexo from some kind of bridge node, tbd + * [ ] use utreexo to verify tx inputs +* verify scripts + * integration with Shinigami, tbd +* block verification + * [ ] verify coinbase tx +* integration testing + * [ ] test on individual historical blocks + +### Milestone 2 - Real Data + +* [ ] feed it with real data +* [ ] test that you can produce and verify proofs of individual blocks + +### Milestone 3 - Recursive Verification + +* verify chain proofs with cairo verifier, tbd ## Name reference @@ -65,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: @@ -106,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..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/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 286ae3d6..55352da2 100644 --- a/src/validation.cairo +++ b/src/validation.cairo @@ -1,27 +1,57 @@ -use super::state::{Block, ChainState, UtreexoState}; +use super::state::{Block, ChainState, Transaction, UtreexoState}; +use super::utils::{shl, shr}; + +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 (current_target, epoch_start_time) = adjust_difficulty(@self, @block); - let bits_to_target: u256 = block.header.bits.into(); //will replace with bits to target implementation - let total_work = compute_total_work(self.total_work, bits_to_target); + let total_work = compute_total_work(self.total_work, bits_to_target(block.header.bits).unwrap()); + 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(()) @@ -30,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> { @@ -78,9 +113,109 @@ fn validate_merkle_root(self: @ChainState, block: @Block) -> Result<(), ByteArra Result::Ok(()) } +// Helper functions +pub fn bits_to_target(bits: u32) -> Result { + // Extract exponent and mantissa + let exponent: u32 = (bits / 0x1000000); + let mantissa: u32 = bits & 0x00FFFFFF; + + // Check if mantissa is valid (should be less than 0x1000000) + if mantissa > 0x7FFFFF && exponent != 0 { + return Result::Err('Invalid mantissa'); + } + + // Calculate the full target value + let mut target: u256 = mantissa.into(); + + if exponent == 0 { + // Special case: exponent 0 means we use the mantissa as-is + return Result::Ok(target); + } else if exponent <= 3 { + // For exponents 1, 2, and 3, divide by 256^(3 - exponent) i.e right shift + let shift = 8 * (3 - exponent); + target = shr(target, shift); + } else { + let shift = 8 * (exponent - 3); + target = shl(target, shift); + } + + // Ensure the target doesn't exceed the maximum allowed value + if target > MAX_TARGET { + return Result::Err('Target exceeds maximum'); + } + + Result::Ok(target) +} + +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) +} + +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_work_from_target, compute_total_work}; + use super::{validate_target, validate_timestamp, validate_proof_of_work, compute_work_from_target, compute_total_work}; use super::{Block, ChainState, UtreexoState}; use super::super::state::{Header, Transaction, TxIn, TxOut}; @@ -96,9 +231,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(), }; @@ -128,9 +261,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(), }; @@ -184,4 +315,34 @@ mod tests { let work = compute_work_from_target(target); assert(expected_work == work, 'Failed to compute target'); } + + #[test] + fn test_validate_proof_of_work() { + let mut block = Block { + header: Header { version: 1, prev_block_hash: 1, time: 12, bits: 1, nonce: 1, }, + txs: ArrayTrait::new().span(), + }; + + // 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..c7e52cb8 --- /dev/null +++ b/tests/tests.cairo @@ -0,0 +1,139 @@ +use raito::validation::bits_to_target; +use raito::validation::target_to_bits; + +#[test] +fn test_bits_to_target_01003456() { + let result = bits_to_target(0x01003456); + assert!(result.is_ok(), "Should be valid"); + assert!(result.unwrap() == 0x00_u256, "Incorrect target for 0x01003456"); +} + +#[test] +fn test_bits_to_target_01123456() { + let result = bits_to_target(0x01123456); + assert!(result.is_ok(), "Should be valid"); + assert!(result.unwrap() == 0x12_u256, "Incorrect target for 0x01123456"); +} + +#[test] +fn test_bits_to_target_02008000() { + let result = bits_to_target(0x02008000); + assert!(result.is_ok(), "Should be valid"); + assert!(result.unwrap() == 0x80_u256, "Incorrect target for 0x02008000"); +} + +#[test] +fn test_bits_to_target_181bc330() { + let result = bits_to_target(0x181bc330); + assert!(result.is_ok(), "Should be valid"); + assert!( + result.unwrap() == 0x1bc330000000000000000000000000000000000000000000_u256, + "Incorrect target for 0x181bc330" + ); +} + +#[test] +fn test_bits_to_target_05009234() { + let result = bits_to_target(0x05009234); + assert!(result.is_ok(), "Should be valid"); + assert!(result.unwrap() == 0x92340000_u256, "Incorrect target for 0x05009234"); +} + +#[test] +fn test_bits_to_target_04123456() { + let result = bits_to_target(0x04123456); + assert!(result.is_ok(), "Should be valid"); + assert!(result.unwrap() == 0x12345600_u256, "Incorrect target for 0x04123456"); +} + +#[test] +fn test_bits_to_target_1d00ffff() { + let result = bits_to_target(0x1d00ffff); + assert!(result.is_ok(), "Should be valid"); + assert!( + result.unwrap() == 0x00000000ffff0000000000000000000000000000000000000000000000000000_u256, + "Incorrect target for 0x1d00ffff" + ); +} + +#[test] +fn test_bits_to_target_1c0d3142() { + let result = bits_to_target(0x1c0d3142); + assert!(result.is_ok(), "Should be valid"); + assert!( + result.unwrap() == 0x000000000d314200000000000000000000000000000000000000000000000000_u256, + "Incorrect target for 0x1c0d3142" + ); +} + +#[test] +fn test_bits_to_target_1707a429() { + let result = bits_to_target(0x1707a429); + assert!(result.is_ok(), "Should be valid"); + assert!( + result.unwrap() == 0x00000000000000000007a4290000000000000000000000000000000000000000_u256, + "Incorrect target for 0x1707a429" + ); +} + +#[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"); +}