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

+# 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 💻 |
 Michael Zaikin 💻 |
+
+  Harsh Pratap Singh 💻 |
+  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");
+}