diff --git a/assessment/Cargo.lock b/assessment/Cargo.lock new file mode 100644 index 0000000..afadbb7 --- /dev/null +++ b/assessment/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "rust-week-2-exercises" +version = "0.1.0" +dependencies = [ + "hex", +] diff --git a/submissions/bitcheck/Cargo.lock b/submissions/bitcheck/Cargo.lock new file mode 100644 index 0000000..e116b2b --- /dev/null +++ b/submissions/bitcheck/Cargo.lock @@ -0,0 +1,439 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bitcheck" +version = "0.1.0" +dependencies = [ + "anyhow", + "bitcoin", + "colored", + "hex", + "lazy_static", + "ripemd", + "secp256k1 0.29.1", + "sha2", +] + +[[package]] +name = "bitcoin" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" +dependencies = [ + "bech32", + "bitcoin_hashes", + "secp256k1 0.24.3", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cc" +version = "1.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "colored" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + +[[package]] +name = "secp256k1" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" +dependencies = [ + "bitcoin_hashes", + "secp256k1-sys 0.6.1", + "serde", +] + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "rand", + "secp256k1-sys 0.10.1", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/submissions/bitcheck/Cargo.toml b/submissions/bitcheck/Cargo.toml new file mode 100644 index 0000000..1ba884f --- /dev/null +++ b/submissions/bitcheck/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "bitcheck" +version = "0.1.0" +edition = "2024" + +[dependencies] +hex = "0.4" +sha2 = "0.10" +ripemd = "0.1" +lazy_static = "1.4" +anyhow = "1.0" +secp256k1 = { version = "0.29", features = ["rand-std", "serde"] } +bitcoin = { version = "0.29", features = ["serde"] } +colored = "3.0.0" diff --git a/submissions/bitcheck/README.md b/submissions/bitcheck/README.md new file mode 100644 index 0000000..10ac3af --- /dev/null +++ b/submissions/bitcheck/README.md @@ -0,0 +1,47 @@ +# bit_check + +Bitcoin script validator in Rust. Runs locking and unlocking scripts through a stack interpreter. + +## Setup + +```toml +[package] +name = "bit_check" +version = "0.1.0" +edition = "2021" + +[dependencies] +sha2 = "0.10" +ripemd = "0.1" +hex = "0.4" +``` + +```bash +cargo run +``` + +## Usage + +Input locking script (scriptPubKey) and unlocking script (scriptSig). Accepts hex or assembly format. + +``` +Locking Script: OP_DUP OP_HASH160 6291ad5107bf1ab687dc744cc9d082aa9522eff0 OP_EQUALVERIFY OP_CHECKSIG +Type: P2PKH + +Unlocking Script: 3045022100... 03d19433... + +Run this script? (y/n): y + +This is a valid script! +``` + +## Features + +- Parses hex and assembly scripts +- Detects P2PK, P2PKH, P2SH, P2MS, OP_RETURN +- Stack-based execution +- Hash160 (SHA256 + RIPEMD160) + +## Opcodes + +OP_DUP, OP_HASH160, OP_EQUAL, OP_EQUALVERIFY, OP_CHECKSIG, OP_CHECKMULTISIG, OP_RETURN, OP_0-16 \ No newline at end of file diff --git a/submissions/bitcheck/src/hashing.rs b/submissions/bitcheck/src/hashing.rs new file mode 100644 index 0000000..5a8016d --- /dev/null +++ b/submissions/bitcheck/src/hashing.rs @@ -0,0 +1,9 @@ +use sha2::{Digest, Sha256}; +use ripemd::{Ripemd160}; + +pub fn hash160(data: &str) -> String { + let binary = hex::decode(data).expect("Invalid hex string"); + let sha256 = Sha256::digest(&binary); + let ripemd = Ripemd160::digest(&sha256); + hex::encode(ripemd) +} \ No newline at end of file diff --git a/submissions/bitcheck/src/main.rs b/submissions/bitcheck/src/main.rs new file mode 100644 index 0000000..1beaebf --- /dev/null +++ b/submissions/bitcheck/src/main.rs @@ -0,0 +1,54 @@ +mod script_type; +mod script_impl; +mod opcodes; +mod hashing; + +use std::io::{self, Write}; +use colored::*; + +use script_impl::{Script}; + + +fn main() { + println!("{}", "\nBit_Check".yellow().bold()); + println!("{}", "==========================\n".black()); + + print!("{}", "Locking Script: ".blue().bold()); + io::stdout().flush().unwrap(); + let mut locking_input = String::new(); + io::stdin().read_line(&mut locking_input).unwrap(); + let locking_script = Script::new(locking_input.trim()); + println!("Type: {:?}\n", locking_script.script_type); + + print!("{}", "Unlocking Script: ".blue().bold()); + io::stdout().flush().unwrap(); + let mut unlocking_input = String::new(); + io::stdin().read_line(&mut unlocking_input).unwrap(); + let unlocking_script = Script::new(unlocking_input.trim()); + + println!("\nLocking Script: {}", locking_script.asm); + println!("Unlocking Script: {}", unlocking_script.asm); + println!(); + + print!("Run this script? (y/n): "); + io::stdout().flush().unwrap(); + let mut yn = String::new(); + io::stdin().read_line(&mut yn).unwrap(); + + if yn.trim() == "y" || yn.trim().is_empty() { + match Script::run(&[unlocking_script, locking_script]) { + Ok(stack) => { + println!("\nFinal Stack: {:?}", stack); + if Script::validate(&stack) { + println!("{}", "\n✓ This is a valid script!".green()); + } else { + println!("\n✗ This is not a valid script."); + println!(""); + } + } + Err(e) => { + println!("\n Script execution failed: {}", e); + } + } + } +} \ No newline at end of file diff --git a/submissions/bitcheck/src/opcodes.rs b/submissions/bitcheck/src/opcodes.rs new file mode 100644 index 0000000..299c208 --- /dev/null +++ b/submissions/bitcheck/src/opcodes.rs @@ -0,0 +1,162 @@ +use crate::hashing::hash160; +pub struct Opcodes; + +impl Opcodes { + pub fn get_opcode(hex: &str) -> String { + match hex.to_lowercase().as_str() { + "00" => "OP_0".to_string(), + "51" => "OP_1".to_string(), + "52" => "OP_2".to_string(), + "53" => "OP_3".to_string(), + "54" => "OP_4".to_string(), + "55" => "OP_5".to_string(), + "56" => "OP_6".to_string(), + "57" => "OP_7".to_string(), + "58" => "OP_8".to_string(), + "59" => "OP_9".to_string(), + "5a" => "OP_10".to_string(), + "5b" => "OP_11".to_string(), + "5c" => "OP_12".to_string(), + "5d" => "OP_13".to_string(), + "5e" => "OP_14".to_string(), + "5f" => "OP_15".to_string(), + "60" => "OP_16".to_string(), + "6a" => "OP_RETURN".to_string(), + "76" => "OP_DUP".to_string(), + "87" => "OP_EQUAL".to_string(), + "88" => "OP_EQUALVERIFY".to_string(), + "ac" => "OP_CHECKSIG".to_string(), + "ae" => "OP_CHECKMULTISIG".to_string(), + "a9" => "OP_HASH160".to_string(), + _ => hex.to_string(), + } + } + + pub fn get_hex(opcode: &str) -> Option { + match opcode { + "OP_0" => Some("00".to_string()), + "OP_1" => Some("51".to_string()), + "OP_2" => Some("52".to_string()), + "OP_3" => Some("53".to_string()), + "OP_4" => Some("54".to_string()), + "OP_5" => Some("55".to_string()), + "OP_6" => Some("56".to_string()), + "OP_7" => Some("57".to_string()), + "OP_8" => Some("58".to_string()), + "OP_9" => Some("59".to_string()), + "OP_10" => Some("5a".to_string()), + "OP_11" => Some("5b".to_string()), + "OP_12" => Some("5c".to_string()), + "OP_13" => Some("5d".to_string()), + "OP_14" => Some("5e".to_string()), + "OP_15" => Some("5f".to_string()), + "OP_16" => Some("60".to_string()), + "OP_RETURN" => Some("6a".to_string()), + "OP_DUP" => Some("76".to_string()), + "OP_EQUAL" => Some("87".to_string()), + "OP_EQUALVERIFY" => Some("88".to_string()), + "OP_CHECKSIG" => Some("ac".to_string()), + "OP_CHECKMULTISIG" => Some("ae".to_string()), + "OP_HASH160" => Some("a9".to_string()), + _ => None, + } + } + + pub fn is_opcode(s: &str) -> bool { + s.starts_with("OP_") + } + + pub fn execute(opcode: &str, mut stack: Vec) -> Result, String> { + match opcode { + "OP_DUP" => { + if stack.is_empty() { + return Err("Stack is empty, cannot duplicate".to_string()); + } + let top = stack.last().unwrap().clone(); + stack.push(top); + Ok(stack) + } + "OP_HASH160" => { + if stack.is_empty() { + return Err("Stack is empty, cannot hash160".to_string()); + } + let top = stack.pop().unwrap(); + let hashed = hash160(&top); + stack.push(hashed); + Ok(stack) + } + "OP_EQUAL" => { + if stack.len() < 2 { + return Err("Not enough items on stack for OP_EQUAL".to_string()); + } + let a = stack.pop().unwrap(); + let b = stack.pop().unwrap(); + if a == b { + stack.push("OP_TRUE".to_string()); + Ok(stack) + } else { + Err(format!("Items not equal: {} != {}", a, b)) + } + } + "OP_EQUALVERIFY" => { + if stack.len() < 2 { + return Err("Not enough items on stack for OP_EQUALVERIFY".to_string()); + } + let a = stack.pop().unwrap(); + let b = stack.pop().unwrap(); + if a == b { + Ok(stack) + } else { + Err(format!("Items not equal: {} != {}", a, b)) + } + } + "OP_CHECKSIG" => { + if stack.len() < 2 { + return Err("Not enough items on stack for OP_CHECKSIG".to_string()); + } + let _pubkey = stack.pop().unwrap(); + let _signature = stack.pop().unwrap(); + stack.push("OP_TRUE".to_string()); + Ok(stack) + } + "OP_CHECKMULTISIG" => { + if stack.is_empty() { + return Err("Stack empty for OP_CHECKMULTISIG".to_string()); + } + let m_str = stack.pop().unwrap(); + let m = m_str.strip_prefix("OP_") + .and_then(|s| s.parse::().ok()) + .ok_or("Invalid m value")?; + + if stack.len() < m { + return Err("Not enough signatures on stack".to_string()); + } + for _ in 0..m { + stack.pop(); + } + + if stack.is_empty() { + return Err("Stack empty getting n value".to_string()); + } + let n_str = stack.pop().unwrap(); + let n = n_str.strip_prefix("OP_") + .and_then(|s| s.parse::().ok()) + .ok_or("Invalid n value")?; + + if stack.len() < n + 1 { + return Err("Not enough public keys on stack".to_string()); + } + for _ in 0..n + 1 { + stack.pop(); + } + + stack.push("OP_TRUE".to_string()); + Ok(stack) + } + "OP_RETURN" => { + Err("Script is invalid. (OP_RETURN always invalidates a script.)".to_string()) + } + _ => Ok(stack), + } + } +} \ No newline at end of file diff --git a/submissions/bitcheck/src/script_impl.rs b/submissions/bitcheck/src/script_impl.rs new file mode 100644 index 0000000..fbbd3ce --- /dev/null +++ b/submissions/bitcheck/src/script_impl.rs @@ -0,0 +1,175 @@ +use crate::script_type::ScriptType; +use crate::opcodes::Opcodes; + +#[derive(Debug, Clone)] +pub struct Script { + pub asm: String, + pub hex: String, + pub script_type: ScriptType, +} + +impl Script { + pub fn new(data: &str) -> Self { + let (hex, asm) = if data.chars().all(|c| c.is_ascii_hexdigit() || c.is_whitespace()) + && !data.contains(' ') { + let h = data.to_string(); + let a = Self::hex_to_asm(&h); + (h, a) + } else { + let a = data.to_string(); + let h = Self::asm_to_hex(&a); + (h, a) + }; + + let script_type = Self::get_type(&asm); + + Script { + asm, + hex, + script_type, + } + } + + pub fn hex_to_asm(hex: &str) -> String { + let mut asm = Vec::new(); + let bytes: Vec<&str> = hex.as_bytes() + .chunks(2) + .map(|chunk| std::str::from_utf8(chunk).unwrap()) + .collect(); + + let mut i = 0; + while i < bytes.len() { + let byte = bytes[i]; + let int_val = u8::from_str_radix(byte, 16).unwrap(); + + if int_val > 0 && int_val < 0x4b { + let data_len = int_val as usize; + i += 1; + let data: String = bytes[i..i + data_len].join(""); + asm.push(data); + i += data_len; + } else { + asm.push(Opcodes::get_opcode(byte)); + i += 1; + } + } + + asm.join(" ") + } + + pub fn asm_to_hex(asm: &str) -> String { + let mut hex = String::new(); + let pieces: Vec<&str> = asm.split_whitespace().collect(); + + for piece in pieces { + if let Some(opcode_hex) = Opcodes::get_hex(piece) { + hex.push_str(&opcode_hex); + } else { + let push = format!("{:02x}", piece.len() / 2); + hex.push_str(&push); + hex.push_str(piece); + } + } + + hex + } + + pub fn get_type(asm: &str) -> ScriptType { + let parts: Vec<&str> = asm.split_whitespace().collect(); + + if parts.len() == 2 && parts[1] == "OP_CHECKSIG" { + ScriptType::P2PK + } else if parts.len() == 5 + && parts[0] == "OP_DUP" + && parts[1] == "OP_HASH160" + && parts[3] == "OP_EQUALVERIFY" + && parts[4] == "OP_CHECKSIG" { + ScriptType::P2PKH + } else if parts.len() == 3 + && parts[0] == "OP_HASH160" + && parts[2] == "OP_EQUAL" { + ScriptType::P2SH + } else if parts.len() >= 3 + && parts[0].starts_with("OP_") + && parts[parts.len() - 2].starts_with("OP_") + && parts[parts.len() - 1] == "OP_CHECKMULTISIG" { + ScriptType::P2MS + } else if parts.len() == 2 && parts[0] == "OP_RETURN" { + ScriptType::Return + } else { + ScriptType::Unknown + } + } + + pub fn run(scripts: &[Script]) -> Result, String> { + + if scripts.len() > 1 && scripts[1].script_type == ScriptType::P2SH { + return Self::run_p2sh(scripts); + } + + let mut script: Vec = scripts + .iter() + .flat_map(|s| s.asm.split_whitespace().map(|x| x.to_string())) + .collect(); + + let mut stack: Vec = Vec::new(); + + while !script.is_empty() { + let opcode = script.remove(0); + + if Opcodes::is_opcode(&opcode) { + stack = Opcodes::execute(&opcode, stack)?; + } else { + stack.push(opcode); + } + } + + Ok(stack) + } + + pub fn run_p2sh(scripts: &[Script]) -> Result, String> { + let unlocking = &scripts[0]; + let locking = &scripts[1]; + + let mut stack = Self::run(&[unlocking.clone()])?; + let stack_copy = stack.clone(); + + let combined = Script::new(&format!("{}{}", unlocking.hex, locking.hex)); + stack = Self::run(&[combined])?; + + if Self::validate(&stack) { + let mut stack_copy = stack_copy; + let redeem_hex = stack_copy.pop().ok_or("No redeem script on stack")?; + let redeem_script = Script::new(&redeem_hex); + + let combined2 = Script::new(&format!("{}{}", unlocking.hex, redeem_script.hex)); + let stack2 = Self::run(&[combined2])?; + + return Ok(stack2); + } + + Ok(stack) + } + + pub fn validate(stack: &[String]) -> bool { + if stack.is_empty() { + return false; + } + + let top = &stack[stack.len() - 1]; + + if top == "OP_TRUE" { + return true; + } + + if top.starts_with("OP_") { + if let Some(num_str) = top.strip_prefix("OP_") { + if let Ok(num) = num_str.parse::() { + return num > 0; + } + } + } + + false + } +} \ No newline at end of file diff --git a/submissions/bitcheck/src/script_type.rs b/submissions/bitcheck/src/script_type.rs new file mode 100644 index 0000000..2f13745 --- /dev/null +++ b/submissions/bitcheck/src/script_type.rs @@ -0,0 +1,9 @@ +#[derive(Debug, Clone, PartialEq)] +pub enum ScriptType { + P2PK, + P2PKH, + P2SH, + P2MS, + Return, + Unknown, +} \ No newline at end of file