From bb2e1af427a122f7ab0a9c924edd4dcd448d3194 Mon Sep 17 00:00:00 2001 From: Shaan Batra Date: Wed, 17 Jan 2024 12:28:08 -0800 Subject: [PATCH] Rust setup (#1) * initial rust setup for wallet challenge * remove lock file and fix lines * bring back bonus comments and comment out cookie file * return wallet state * fix return type * fix up comments * another comment fix * obfuscate even more, add docs --------- Co-authored-by: Matthew Zipkin --- .gitignore | 5 +- recover_balance.md | 7 +- send_multisig.md | 4 + solution/run_balance.sh | 5 +- solution/run_spend.sh | 5 +- solution/rust/Cargo.toml | 6 ++ solution/rust/balance/Cargo.toml | 16 ++++ solution/rust/balance/src/lib.rs | 145 ++++++++++++++++++++++++++++++ solution/rust/balance/src/main.rs | 18 ++++ solution/rust/spend/Cargo.toml | 9 ++ solution/rust/spend/src/main.rs | 6 ++ 11 files changed, 220 insertions(+), 6 deletions(-) create mode 100644 solution/rust/Cargo.toml create mode 100644 solution/rust/balance/Cargo.toml create mode 100644 solution/rust/balance/src/lib.rs create mode 100644 solution/rust/balance/src/main.rs create mode 100644 solution/rust/spend/Cargo.toml create mode 100644 solution/rust/spend/src/main.rs diff --git a/.gitignore b/.gitignore index a40c85b..604b568 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -example_solution/balance_solution.py -example_solution/spend_solution.py +/solution/rust/**/target .mypy_cache -__pycache__ \ No newline at end of file +__pycache__ diff --git a/recover_balance.md b/recover_balance.md index a262ab3..43419b2 100644 --- a/recover_balance.md +++ b/recover_balance.md @@ -41,8 +41,9 @@ You are allowed to write your wallet code in any of the following programming languages: - Python -- NodeJS +- C - C++ +- C# - Rust - Go @@ -61,6 +62,10 @@ way to complete this project is to complete the obfuscated code template in to be modified unless you want to start from scratch in Python or write in one of the other languages. +There is also a Rust template in [solution/rust/balance/src](solution/rust/balance/src). +If you choose to work in Rust you will need to modify [solution/run_balance.sh](solution/run_balance.sh) +to execute the Rust code. + You **MAY** import an ECDSA library to access constants like `G` or the order of the curve, but you **MAY NOT** use a Bitcoin-specific library to avoid implementing BIP32 yourself. diff --git a/send_multisig.md b/send_multisig.md index 9a761fa..f0072de 100644 --- a/send_multisig.md +++ b/send_multisig.md @@ -60,6 +60,10 @@ Like last week, the default language is Python and an obfuscated code template is yours to play with in [solution/python/spend.py](solution/python/spend.py). If you choose to write in a different language you MUST edit [solution/run_spend.sh](solution/run_spend.sh). +There is also a Rust template in [solution/rust/spend/src](solution/rust/spend/src). +If you choose to work in Rust you will need to modify [solution/run_spend.sh](solution/run_spend.sh) +to execute the Rust code. + You **MAY** import an ECDSA library to access constants like `G` or the order of the curve, and you **MAY** use an external library for message signing (although we encourage you to implement ECDSA signing yourself!). You **MAY NOT** use a diff --git a/solution/run_balance.sh b/solution/run_balance.sh index fd98861..a86a7ef 100644 --- a/solution/run_balance.sh +++ b/solution/run_balance.sh @@ -1,3 +1,6 @@ # PYTHON pip install ecdsa -python ./solution/python/balance.py \ No newline at end of file +python ./solution/python/balance.py + +# RUST +# cargo run --manifest-path ./solution/rust/Cargo.toml -p balance -- ~/.bitcoin/signet/.cookie diff --git a/solution/run_spend.sh b/solution/run_spend.sh index 4f0938d..b58c7b3 100644 --- a/solution/run_spend.sh +++ b/solution/run_spend.sh @@ -1,2 +1,5 @@ # PYTHON -python ./solution/python/spend.py \ No newline at end of file +python ./solution/python/spend.py + +# RUST +# cargo run --manifest-path ./solution/rust/Cargo.toml -p spend -- ~/.bitcoin/signet/.cookie diff --git a/solution/rust/Cargo.toml b/solution/rust/Cargo.toml new file mode 100644 index 0000000..d57911c --- /dev/null +++ b/solution/rust/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] + +members = [ + "balance", + "spend", +] diff --git a/solution/rust/balance/Cargo.toml b/solution/rust/balance/Cargo.toml new file mode 100644 index 0000000..9ece57d --- /dev/null +++ b/solution/rust/balance/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "balance" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bs58 = "0.5.0" +hex = "0.4.3" +secp256k1 = "0.28.1" +hmac-sha512 = "1.1.5" +num-bigint = "0.4.4" +sha2 = "0.10.8" +ripemd = "0.1.3" +bitcoincore-rpc = "0.18.0" diff --git a/solution/rust/balance/src/lib.rs b/solution/rust/balance/src/lib.rs new file mode 100644 index 0000000..67f1776 --- /dev/null +++ b/solution/rust/balance/src/lib.rs @@ -0,0 +1,145 @@ +extern crate bitcoincore_rpc; +use bitcoincore_rpc::{Auth, Client, RpcApi}; +use bs58; +use hex; +use hmac_sha512::HMAC; +use num_bigint::BigUint; // for modulus math on large numbers +use ripemd::{Ripemd160}; +use secp256k1::{Secp256k1, SecretKey, PublicKey}; +use sha2::{Sha256, Digest}; +use std::error::Error; +use std::io::Read; +use std::path::PathBuf; +use std::str; + +// Provided by administrator +const WALLET_NAME: &str = "wallet_000"; +const EXTENDED_PRIVATE_KEY: &str = tprv8ZgxMBicQKsPfCxvMSGLjZegGFnZn9VZfVdsnEbuzTGdS9aZjvaYpyh7NsxsrAc8LsRQZ2EYaCfkvwNpas8cKUBbptDzadY7c3hUi8i33XJ +const HARDENED_OFFSET: u32 = 2_u32.pow(31); + +struct ExtendedKey { + +} + +struct ChildKey { + +} + +pub struct OutgoingTx { + +} + +struct SpendingTx { + +} + +// final wallet state struct +pub struct WalletState { + utxos: Vec, + witness_programs: Vec<[u8; 22]>, + public_keys: Vec<[u8; 33]>, + private_keys: Vec<[u8; 32]>, +} + +// Decode a base58 string into an array of bytes +fn base58_decode(base58_string: &str) -> Vec { + let base58_alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + // Convert Base58 string to a big integer + + // Convert the integer to bytes + + // Chop off the 32 checksum bits and return + + // BONUS POINTS: Verify the checksum! +} + +// Deserialize the extended key bytes and return a JSON object +// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format +// 4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public, 0x04358394 private) +// 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 derived keys, .... +// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key) +// 4 bytes: child number. This is ser32(i) for i in xi = xpar/i, with xi the key being serialized. (0x00000000 if master key) +// 32 bytes: the chain code +// 33 bytes: the public key or private key data (serP(K) for public keys, 0x00 || ser256(k) for private keys) +fn deserialize_key(bytes: Vec) -> ExtendedKey { + +} + +// Derive the secp256k1 compressed public key from a given private key +// BONUS POINTS: Implement ECDSA yourself and multiply you key by the generator point! +fn derive_public_key_from_private(key: &[u8; 32]) -> [u8; 33] { + +} + +// Perform a BIP32 parent private key -> child private key operation +// Return a JSON object with "key" and "chaincode" properties as bytes +// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#user-content-Private_parent_key_rarr_private_child_key +fn derive_priv_child(key: &[u8; 32], chaincode: &[u8; 32], index: u32) -> ChildKey { + +} + +// Given an extended private key and a BIP32 derivation path, compute the child private key found at the last path +// The derivation path is formatted as an array of (index: int, hardened: bool) tuples. +fn get_child_key_at_path(key: [u8; 32], chaincode: [u8; 32], paths: Vec<(u32, bool)>) -> ChildKey { + +} + +// Compute the first N child private keys. +// Return an array of keys encoded as bytes. +fn get_keys_at_child_key_path(child_key: ChildKey, num_keys: u32) -> Vec<[u8; 32]> { + +} + +// Derive the p2wpkh witness program (aka scriptPubKey) for a given compressed public key. +// Return a bytes array to be compared with the JSON output of Bitcoin Core RPC getblock +// so we can find our received transactions in blocks. +// These are segwit version 0 pay-to-public-key-hash witness programs. +// https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#user-content-P2WPKH +fn get_p2wpkh_program(pubkey: [u8; 33]) -> [u8; 22] { + +} + +// public function that will be called by `run` here as well as the spend program externally +pub fn recover_wallet_state(extended_private_key: &str, cookie_filepath: &str) -> Result> { + // Deserialize the provided extended private key + + // Derive the key and chaincode at the path in the descriptor (`84h/1h/0h/0`) + + // Get the child key at the derivation path + + // Compute 2000 private keys from the child key path + // For each private key, collect compressed public keys and witness programs + let private_keys = vec![]; + let public_keys = vec![]; + let witness_programs = vec![]; + + // Collect outgoing and spending txs from a block scan + let mut outgoing_txs: Vec = vec![]; + let mut spending_txs: Vec = vec![]; + let mut utxos: Vec = vec![]; + + // set up bitcoin-core-rpc on signet + let path = PathBuf::from(cookie_filepath); + let rpc = Client::new("http://localhost:38332", Auth::CookieFile(path))?; + + // Scan blocks 0 to 300 for transactions + // Check every tx input (witness) for our own compressed public keys. These are coins we have spent. + // Check every tx output for our own witness programs. These are coins we have received. + // Keep track of outputs by their outpoint so we can check if it was spent later by an input + // Collect outputs that have not been spent into a utxo set + // Return Wallet State + Ok(WalletState { + utxos, + public_keys, + private_keys, + witness_programs, + }) +} + +pub fn run(rpc_cookie_filepath: &str) -> Result<(), ()> { + let utxos = recover_wallet_state(EXTENDED_PRIVATE_KEY, rpc_cookie_filepath)?; + let balance: + + println!("{} {:.8}", WALLET_NAME, balance); + Ok(()) +} diff --git a/solution/rust/balance/src/main.rs b/solution/rust/balance/src/main.rs new file mode 100644 index 0000000..838eb23 --- /dev/null +++ b/solution/rust/balance/src/main.rs @@ -0,0 +1,18 @@ +use std::env; + +fn main() { + // Set up for RPC client on local and remote CI server + let args: Vec = env::args().collect(); + + // Default Bitcoin Core cookie path + let mut cookie_filepath = r"~/.bitcoin/signet/.cookie"; + + if args.len() > 1 { + cookie_filepath = &args[1]; + } + + // Run the program in lib.rs and print any errors + if let Err(e) = balance::run(cookie_filepath) { + eprintln!("{}", e); + } +} diff --git a/solution/rust/spend/Cargo.toml b/solution/rust/spend/Cargo.toml new file mode 100644 index 0000000..31a3939 --- /dev/null +++ b/solution/rust/spend/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "spend" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +balance = { path = "../balance" } diff --git a/solution/rust/spend/src/main.rs b/solution/rust/spend/src/main.rs new file mode 100644 index 0000000..de4beb6 --- /dev/null +++ b/solution/rust/spend/src/main.rs @@ -0,0 +1,6 @@ +use balance; + +fn main() { + let state = balance::recover_wallet_state(); + +}