diff --git a/packages/onchain/Scarb.lock b/packages/onchain/Scarb.lock index ac734f9..aee286b 100644 --- a/packages/onchain/Scarb.lock +++ b/packages/onchain/Scarb.lock @@ -6,15 +6,27 @@ name = "alexandria_math" version = "0.2.1" source = "git+https://github.com/keep-starknet-strange/alexandria#95d98a5182001d07673b856a356eff0e6bd05354" +[[package]] +name = "consensus" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/raito.git?rev=02a13045b7074ae2b3247431cd91f1ad76263fb2#02a13045b7074ae2b3247431cd91f1ad76263fb2" +dependencies = [ + "shinigami_engine", + "utils", +] + [[package]] name = "onchain" version = "0.1.0" dependencies = [ "alexandria_math", + "consensus", "openzeppelin", "openzeppelin_token", "openzeppelin_utils", "snforge_std", + "utils", + "utu_relay", ] [[package]] @@ -123,6 +135,31 @@ name = "openzeppelin_utils" version = "0.19.0" source = "git+https://github.com/openzeppelin/cairo-contracts?tag=v0.19.0#8d49e8c445efd9bdc99b050c8b7d11ae5ad19628" +[[package]] +name = "ripemd160" +version = "0.1.0" +source = "git+https://github.com/j1mbo64/ripemd160_cairo.git#833e07d7d074d4ee51ceb40a5bcb4af2fe6898f3" + +[[package]] +name = "sha1" +version = "0.1.0" +source = "git+https://github.com/j1mbo64/sha1_cairo.git#2b65bc00a829bdcc244c140d0f31feda32f8d2c4" + +[[package]] +name = "shinigami_engine" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/shinigami.git?rev=3415ed6#3415ed6331d3ea2dc2de6f9ab8e0be6562585f2d" +dependencies = [ + "ripemd160", + "sha1", + "shinigami_utils", +] + +[[package]] +name = "shinigami_utils" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/shinigami.git?rev=3415ed6#3415ed6331d3ea2dc2de6f9ab8e0be6562585f2d" + [[package]] name = "snforge_scarb_plugin" version = "0.33.0" @@ -135,3 +172,18 @@ source = "git+https://github.com/foundry-rs/starknet-foundry?tag=v0.33.0#221b1db dependencies = [ "snforge_scarb_plugin", ] + +[[package]] +name = "utils" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/raito.git?rev=02a13045b7074ae2b3247431cd91f1ad76263fb2#02a13045b7074ae2b3247431cd91f1ad76263fb2" + +[[package]] +name = "utu_relay" +version = "0.1.0" +source = "git+https://github.com/lana-shanghai/utu_relay.git#25f8d9799df04465c716155e9ece61d9145b0f8c" +dependencies = [ + "openzeppelin", + "openzeppelin_upgrades", + "utils", +] diff --git a/packages/onchain/Scarb.toml b/packages/onchain/Scarb.toml index 4a81186..dc3e2ff 100644 --- a/packages/onchain/Scarb.toml +++ b/packages/onchain/Scarb.toml @@ -5,6 +5,9 @@ edition = "2024_07" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html +[patch.crates-io] +openzeppelin = "0.19.0" + [dependencies] snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.33.0" } openzeppelin = { git = "https://github.com/openzeppelin/cairo-contracts", tag = "v0.19.0" } @@ -12,11 +15,19 @@ starknet = "2.9.1" alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria" } openzeppelin_token = { git = "https://github.com/openzeppelin/cairo-contracts", tag = "v0.19.0" } openzeppelin_utils = { git = "https://github.com/openzeppelin/cairo-contracts", tag = "v0.19.0" } +utils = { git = "https://github.com/keep-starknet-strange/raito.git", rev = "02a13045b7074ae2b3247431cd91f1ad76263fb2" } +consensus = { git = "https://github.com/keep-starknet-strange/raito.git", rev = "02a13045b7074ae2b3247431cd91f1ad76263fb2" } +utu_relay = { git = "https://github.com/lana-shanghai/utu_relay.git" } [[target.starknet-contract]] casm = true sierra = true -build-external-contracts = ["openzeppelin_presets::erc20::ERC20Upgradeable"] +build-external-contracts = [ + "openzeppelin_presets::erc20::ERC20Upgradeable", + "utu_relay::utu_relay::UtuRelay" +] +allowed-libfuncs-list.name = "experimental" +casm-add-pythonic-hints = true [dev-dependencies] assert_macros = "2.9.1" diff --git a/packages/onchain/src/lib.cairo b/packages/onchain/src/lib.cairo index 9bfc256..f253d44 100644 --- a/packages/onchain/src/lib.cairo +++ b/packages/onchain/src/lib.cairo @@ -1,3 +1,4 @@ mod escrow; mod orderbook; mod utils; +mod relay; \ No newline at end of file diff --git a/packages/onchain/src/relay.cairo b/packages/onchain/src/relay.cairo new file mode 100644 index 0000000..6e71656 --- /dev/null +++ b/packages/onchain/src/relay.cairo @@ -0,0 +1 @@ +mod relay; \ No newline at end of file diff --git a/packages/onchain/src/relay/relay.cairo b/packages/onchain/src/relay/relay.cairo new file mode 100644 index 0000000..fcd50e1 --- /dev/null +++ b/packages/onchain/src/relay/relay.cairo @@ -0,0 +1,61 @@ +use utils::hash::Digest; +use utu_relay::bitcoin::block::BlockHeader; + +#[starknet::interface] +pub trait IBitcoinDepositor { + fn prove_inclusion( + ref self: TContractState, + tx_id: Digest, + block_height: u64, + block_header: BlockHeader, + tx_inclusion: Array<(Digest, bool)> + ); +} + +#[starknet::contract] +mod BitcoinDepositor { + use onchain::utils::utils::compute_merkle_root; + use utu_relay::bitcoin::block::BlockHashTrait; + use starknet::{ContractAddress, get_block_timestamp}; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use utils::{hash::Digest, numeric::u32_byte_reverse}; + use utu_relay::{ + interfaces::{IUtuRelayDispatcher, IUtuRelayDispatcherTrait}, bitcoin::block::BlockHeader + }; + + #[storage] + struct Storage { + depositor: ContractAddress, + utu_address: ContractAddress, + } + + #[constructor] + fn constructor(ref self: ContractState, utu_address: ContractAddress) { + self.utu_address.write(utu_address); + } + + #[abi(embed_v0)] + impl BitcoinDepositorImpl of super::IBitcoinDepositor { + fn prove_inclusion( + ref self: ContractState, + tx_id: Digest, + block_height: u64, + block_header: BlockHeader, + tx_inclusion: Array<(Digest, bool)> + ) { + // we verify this tx is included in the provided block + let merkle_root = compute_merkle_root(tx_id, tx_inclusion); + assert( + block_header.merkle_root_hash.value == merkle_root.value, 'Invalid inclusion proof.' + ); + + // we verify this block is safe to use (part of the canonical chain & sufficient pow) + // sufficient pow for our usecase: 100 sextillion expected hashes + let utu = IUtuRelayDispatcher { contract_address: self.utu_address.read() }; + utu.assert_safe(block_height, block_header.hash(), 100_000_000_000_000_000_000_000, 0); + // we ensure this block was not premined + let block_time = u32_byte_reverse(block_header.time).into(); + assert(block_time <= get_block_timestamp(), 'Block comes from the future.'); + } + } +} \ No newline at end of file diff --git a/packages/onchain/src/utils/utils.cairo b/packages/onchain/src/utils/utils.cairo index 8b13789..9f2f270 100644 --- a/packages/onchain/src/utils/utils.cairo +++ b/packages/onchain/src/utils/utils.cairo @@ -1 +1,37 @@ +use utils::hash::Digest; +use utils::double_sha256::double_sha256_parent; +/// Computes the Merkle root from a transaction hash and its siblings. +/// +/// Arguments: +/// - `tx_hash: Digest`: The transaction hash as a Digest +/// - `siblings: Array<(Digest, bool)>`: An array of tuples (Digest, bool), where the bool indicates if the sibling is on +/// the right +/// +/// Returns: +/// - `Digest`: The computed Merkle root as a Digest +pub fn compute_merkle_root(tx_hash: Digest, siblings: Array<(Digest, bool)>) -> Digest { + let mut current_hash = tx_hash; + + // Iterate through all siblings + let mut i = 0; + loop { + if i == siblings.len() { + break; + } + + let (sibling, is_left) = *siblings.at(i); + + // Concatenate current_hash and sibling based on the order + current_hash = + if is_left { + double_sha256_parent(@sibling, @current_hash) + } else { + double_sha256_parent(@current_hash, @sibling) + }; + + i += 1; + }; + + current_hash +} \ No newline at end of file