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