├── .gitignore ├── lib ├── .gitignore ├── tx.cbor ├── src │ ├── types.rs │ ├── bin │ │ ├── block_print.rs │ │ ├── key_gen.rs │ │ ├── tx_print.rs │ │ ├── tx_gen.rs │ │ └── block_gen.rs │ ├── error.rs │ ├── lib.rs │ ├── sha256.rs │ ├── util.rs │ ├── types │ │ ├── transaction.rs │ │ ├── block.rs │ │ └── blockchain.rs │ ├── network.rs │ └── crypto.rs └── Cargo.toml ├── miner ├── .gitignore ├── block.cbor ├── bob.priv.cbor ├── alice.priv.cbor ├── bob.pub.pem ├── alice.pub.pem ├── Cargo.toml └── src │ └── main.rs ├── node ├── .gitignore ├── block.cbor ├── block2.cbor ├── Cargo.toml └── src │ ├── main.rs │ ├── util.rs │ └── handler.rs ├── wallet ├── .gitignore ├── bob.priv.cbor ├── alice.priv.cbor ├── bob.pub.pem ├── alice.pub.pem ├── wallet_config.toml ├── Cargo.toml └── src │ ├── tasks.rs │ ├── util.rs │ ├── main.rs │ ├── ui.rs │ └── core.rs ├── Cargo.toml ├── README.org └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /miner/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /node/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /wallet/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | logs 3 | -------------------------------------------------------------------------------- /lib/tx.cbor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braiins/build-bitcoin-in-rust/HEAD/lib/tx.cbor -------------------------------------------------------------------------------- /miner/block.cbor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braiins/build-bitcoin-in-rust/HEAD/miner/block.cbor -------------------------------------------------------------------------------- /node/block.cbor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braiins/build-bitcoin-in-rust/HEAD/node/block.cbor -------------------------------------------------------------------------------- /node/block2.cbor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braiins/build-bitcoin-in-rust/HEAD/node/block2.cbor -------------------------------------------------------------------------------- /miner/bob.priv.cbor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braiins/build-bitcoin-in-rust/HEAD/miner/bob.priv.cbor -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["wallet", "lib", "miner", "node"] 4 | 5 | 6 | -------------------------------------------------------------------------------- /miner/alice.priv.cbor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braiins/build-bitcoin-in-rust/HEAD/miner/alice.priv.cbor -------------------------------------------------------------------------------- /wallet/bob.priv.cbor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braiins/build-bitcoin-in-rust/HEAD/wallet/bob.priv.cbor -------------------------------------------------------------------------------- /wallet/alice.priv.cbor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braiins/build-bitcoin-in-rust/HEAD/wallet/alice.priv.cbor -------------------------------------------------------------------------------- /miner/bob.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEVrrEDL7NjGhlivFBk6RaltoomVGswvdJ 3 | K8H4qZx4ZHzjYk0DHfdEogF5evj6DtNUpaQ+Zoy9PHGCb5e2YAsIVg== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /wallet/bob.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEVrrEDL7NjGhlivFBk6RaltoomVGswvdJ 3 | K8H4qZx4ZHzjYk0DHfdEogF5evj6DtNUpaQ+Zoy9PHGCb5e2YAsIVg== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /miner/alice.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEql/daUPKqnJoGW37hJFXxwgdkQzIKuOE 3 | tDp/Z87cmmdrzppa82UrjaY5bgxQNe3kTngzNCUlFS/4TsG98+SULw== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /wallet/alice.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEql/daUPKqnJoGW37hJFXxwgdkQzIKuOE 3 | tDp/Z87cmmdrzppa82UrjaY5bgxQNe3kTngzNCUlFS/4TsG98+SULw== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /lib/src/types.rs: -------------------------------------------------------------------------------- 1 | mod block; 2 | mod blockchain; 3 | mod transaction; 4 | 5 | pub use block::{Block, BlockHeader}; 6 | pub use blockchain::Blockchain; 7 | pub use transaction::{ 8 | Transaction, TransactionInput, TransactionOutput, 9 | }; 10 | -------------------------------------------------------------------------------- /miner/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "miner" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0.86" 8 | btclib = { path = "../lib" } 9 | clap = { version = "4.5.8", features = ["derive"] } 10 | flume = "0.11.0" 11 | tokio = { version = "1.38.0", features = ["full"] } 12 | -------------------------------------------------------------------------------- /wallet/wallet_config.toml: -------------------------------------------------------------------------------- 1 | default_node = "127.0.0.1:9000" 2 | 3 | [[my_keys]] 4 | public = "./alice.pub.pem" 5 | private = "./alice.priv.cbor" 6 | 7 | [[contacts]] 8 | name = "Alice" 9 | key = "alice.pub.pem" 10 | 11 | [[contacts]] 12 | name = "Bob" 13 | key = "bob.pub.pem" 14 | 15 | [fee_config] 16 | fee_type = "Percent" 17 | value = 0.0 18 | -------------------------------------------------------------------------------- /node/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "node" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0.82" 8 | argh = "0.1.12" 9 | btclib = { version = "0.1.0", path = "../lib" } 10 | chrono = "0.4.38" 11 | dashmap = "5.5.3" 12 | static_init = "1.0.3" 13 | tokio = { version = "1.37.0", features = ["full"] } 14 | uuid = { version = "1.8.0", features = ["v4"] } 15 | -------------------------------------------------------------------------------- /lib/src/bin/block_print.rs: -------------------------------------------------------------------------------- 1 | use btclib::types::Block; 2 | use btclib::util::Saveable; 3 | 4 | use std::env; 5 | use std::fs::File; 6 | use std::process::exit; 7 | 8 | fn main() { 9 | let path = if let Some(arg) = env::args().nth(1) { 10 | arg 11 | } else { 12 | eprintln!("Usage: block_print "); 13 | exit(1); 14 | }; 15 | 16 | if let Ok(file) = File::open(path) { 17 | let block = 18 | Block::load(file).expect("Failed to load block"); 19 | println!("{:#?}", block); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/bin/key_gen.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use btclib::crypto::PrivateKey; 4 | use btclib::util::Saveable; 5 | 6 | fn main() { 7 | let name = 8 | env::args().nth(1).expect("Please provide a name"); 9 | 10 | let private_key = PrivateKey::new_key(); 11 | let public_key = private_key.public_key(); 12 | 13 | let public_key_file = name.clone() + ".pub.pem"; 14 | let private_key_file = name + ".priv.cbor"; 15 | 16 | private_key.save_to_file(&private_key_file).unwrap(); 17 | public_key.save_to_file(&public_key_file).unwrap(); 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/bin/tx_print.rs: -------------------------------------------------------------------------------- 1 | use btclib::types::Transaction; 2 | use btclib::util::Saveable; 3 | 4 | use std::env; 5 | use std::fs::File; 6 | use std::process::exit; 7 | 8 | fn main() { 9 | let path = if let Some(arg) = env::args().nth(1) { 10 | arg 11 | } else { 12 | eprintln!("Usage: tx_print "); 13 | exit(1); 14 | }; 15 | 16 | if let Ok(file) = File::open(path) { 17 | let tx = Transaction::load(file) 18 | .expect("Failed to load transaction"); 19 | println!("{:#?}", tx); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /wallet/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wallet" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0.86" 8 | clap = { version = "4.5.8", features = ["derive"] } 9 | crossbeam-skiplist = "0.1.3" 10 | cursive = "0.20.0" 11 | kanal = "0.1.0-pre8" 12 | serde = { version = "1.0.204", features = ["derive"] } 13 | text-to-ascii-art = "0.1.9" 14 | tokio = { version = "1.38.0", features = ["full"] } 15 | toml = "0.8.14" 16 | tracing = "0.1.40" 17 | tracing-appender = "0.2.3" 18 | tracing-subscriber = { version = "0.3.18", features = ["env-filter", "fmt"] } 19 | uuid = { version = "1.9.1", features = ["v4", "serde"] } 20 | 21 | # ours 22 | btclib = { version = "0.1.0", path = "../lib" } 23 | -------------------------------------------------------------------------------- /lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "btclib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | bigdecimal = "0.4.5" 8 | chrono = { version = "0.4.38", features = ["serde"] } 9 | ciborium = "0.2.2" 10 | ecdsa = { version = "0.16.9", features = [ 11 | "signing", 12 | "verifying", 13 | "serde", 14 | "pem", 15 | ] } 16 | flume = "0.11.0" 17 | hex = "0.4.3" 18 | k256 = { version = "0.13.3", features = ["serde", "pem"] } 19 | rand = "0.8.5" 20 | serde = { version = "1.0.198", features = ["derive"] } 21 | sha256 = "1.5.0" 22 | spki = { version = "0.7.3", features = ["pem"] } 23 | thiserror = "1.0.61" 24 | tokio = { version = "1.38.0", features = ["full"] } 25 | uint = "0.9.5" 26 | uuid = { version = "1.8.0", features = ["v4", "serde"] } 27 | -------------------------------------------------------------------------------- /lib/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum BtcError { 5 | #[error("Invalid transaction")] 6 | InvalidTransaction, 7 | #[error("Invalid block")] 8 | InvalidBlock, 9 | #[error("Invalid block header")] 10 | InvalidBlockHeader, 11 | #[error("Invalid transaction input")] 12 | InvalidTransactionInput, 13 | #[error("Invalid transaction output")] 14 | InvalidTransactionOutput, 15 | #[error("Invalid merkle root")] 16 | InvalidMerkleRoot, 17 | #[error("Invalid hash")] 18 | InvalidHash, 19 | #[error("Invalid signature")] 20 | InvalidSignature, 21 | #[error("Invalid public key")] 22 | InvalidPublicKey, 23 | #[error("Invalid private key")] 24 | InvalidPrivateKey, 25 | } 26 | 27 | pub type Result = std::result::Result; 28 | -------------------------------------------------------------------------------- /lib/src/bin/tx_gen.rs: -------------------------------------------------------------------------------- 1 | use btclib::crypto::PrivateKey; 2 | use btclib::types::{Transaction, TransactionOutput}; 3 | use btclib::util::Saveable; 4 | 5 | use uuid::Uuid; 6 | 7 | use std::env; 8 | use std::process::exit; 9 | 10 | fn main() { 11 | let path = if let Some(arg) = env::args().nth(1) { 12 | arg 13 | } else { 14 | eprintln!("Usage: tx_gen "); 15 | exit(1); 16 | }; 17 | 18 | let private_key = PrivateKey::new_key(); 19 | 20 | let transaction = Transaction::new( 21 | vec![], 22 | vec![TransactionOutput { 23 | unique_id: Uuid::new_v4(), 24 | value: btclib::INITIAL_REWARD * 10u64.pow(8), 25 | pubkey: private_key.public_key(), 26 | }], 27 | ); 28 | 29 | transaction 30 | .save_to_file(path) 31 | .expect("Failed to save transaction"); 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use uint::construct_uint; 3 | 4 | construct_uint! { 5 | // Construct an unsigned 256-bit integer 6 | // consisting of 4 x 64-bit words 7 | #[derive(Serialize, Deserialize)] 8 | pub struct U256(4); 9 | } 10 | 11 | // initial reward in bitcoin - multiply by 10^8 to get satoshis 12 | pub const INITIAL_REWARD: u64 = 50; 13 | // halving interval in blocks 14 | pub const HALVING_INTERVAL: u64 = 210; 15 | // ideal block time in seconds 16 | pub const IDEAL_BLOCK_TIME: u64 = 10; 17 | // minimum target 18 | pub const MIN_TARGET: U256 = U256([ 19 | 0xFFFF_FFFF_FFFF_FFFF, 20 | 0xFFFF_FFFF_FFFF_FFFF, 21 | 0xFFFF_FFFF_FFFF_FFFF, 22 | 0x0000_FFFF_FFFF_FFFF, 23 | ]); 24 | // difficulty update interval in blocks 25 | pub const DIFFICULTY_UPDATE_INTERVAL: u64 = 50; 26 | // maximum mempool transaction age in seconds 27 | pub const MAX_MEMPOOL_TRANSACTION_AGE: u64 = 600; 28 | // maximum amount of transactions allowed in a block 29 | pub const BLOCK_TRANSACTION_CAP: usize = 20; 30 | 31 | pub mod crypto; 32 | pub mod error; 33 | pub mod network; 34 | pub mod sha256; 35 | pub mod types; 36 | pub mod util; 37 | -------------------------------------------------------------------------------- /lib/src/bin/block_gen.rs: -------------------------------------------------------------------------------- 1 | use btclib::crypto::PrivateKey; 2 | use btclib::sha256::Hash; 3 | use btclib::types::{ 4 | Block, BlockHeader, Transaction, TransactionOutput, 5 | }; 6 | use btclib::util::{MerkleRoot, Saveable}; 7 | 8 | use chrono::Utc; 9 | use uuid::Uuid; 10 | 11 | use std::env; 12 | use std::process::exit; 13 | 14 | fn main() { 15 | let path = if let Some(arg) = env::args().nth(1) { 16 | arg 17 | } else { 18 | eprintln!("Usage: block_gen "); 19 | exit(1); 20 | }; 21 | 22 | let private_key = PrivateKey::new_key(); 23 | 24 | let transactions = vec![Transaction::new( 25 | vec![], 26 | vec![TransactionOutput { 27 | unique_id: Uuid::new_v4(), 28 | value: btclib::INITIAL_REWARD * 10u64.pow(8), 29 | pubkey: private_key.public_key(), 30 | }], 31 | )]; 32 | 33 | let merkle_root = MerkleRoot::calculate(&transactions); 34 | 35 | let block = Block::new( 36 | BlockHeader::new( 37 | Utc::now(), 38 | 0, 39 | Hash::zero(), 40 | merkle_root, 41 | btclib::MIN_TARGET, 42 | ), 43 | transactions, 44 | ); 45 | 46 | block.save_to_file(path).expect("Failed to save block"); 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/sha256.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use sha256::digest; 3 | 4 | use std::fmt; 5 | 6 | use crate::U256; 7 | 8 | #[derive( 9 | Clone, 10 | Copy, 11 | Serialize, 12 | Deserialize, 13 | Debug, 14 | PartialEq, 15 | Eq, 16 | Hash, 17 | )] 18 | pub struct Hash(U256); 19 | 20 | impl Hash { 21 | // hash anything that can be serde Serialized via ciborium 22 | pub fn hash(data: &T) -> Self { 23 | let mut serialized: Vec = vec![]; 24 | 25 | if let Err(e) = 26 | ciborium::into_writer(data, &mut serialized) 27 | { 28 | panic!( 29 | "Failed to serialize data: {:?}. \ 30 | This should not happen", 31 | e 32 | ); 33 | } 34 | let hash = digest(&serialized); 35 | let hash_bytes = hex::decode(hash).unwrap(); 36 | let hash_array: [u8; 32] = 37 | hash_bytes.as_slice().try_into().unwrap(); 38 | 39 | Hash(U256::from(hash_array)) 40 | } 41 | 42 | // check if a hash matches a target 43 | pub fn matches_target(&self, target: U256) -> bool { 44 | self.0 <= target 45 | } 46 | 47 | // zero hash 48 | pub fn zero() -> Self { 49 | Hash(U256::zero()) 50 | } 51 | 52 | pub fn as_bytes(&self) -> [u8; 32] { 53 | let mut bytes: Vec = vec![0; 32]; 54 | self.0.to_little_endian(&mut bytes); 55 | 56 | bytes.as_slice().try_into().unwrap() 57 | } 58 | } 59 | 60 | impl fmt::Display for Hash { 61 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 62 | write!(f, "{:x}", self.0) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/src/util.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use std::fs::File; 4 | use std::io::{Read, Result as IoResult, Write}; 5 | use std::path::Path; 6 | 7 | use crate::sha256::Hash; 8 | use crate::types::Transaction; 9 | 10 | #[derive( 11 | Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, 12 | )] 13 | 14 | pub struct MerkleRoot(Hash); 15 | 16 | impl MerkleRoot { 17 | // calculate the merkle root of a block's transactions 18 | pub fn calculate( 19 | transactions: &[Transaction], 20 | ) -> MerkleRoot { 21 | let mut layer: Vec = vec![]; 22 | 23 | for transaction in transactions { 24 | layer.push(Hash::hash(transaction)); 25 | } 26 | 27 | while layer.len() > 1 { 28 | let mut new_layer = vec![]; 29 | 30 | for pair in layer.chunks(2) { 31 | let left = pair[0]; 32 | // if there is no right, use the left hash again 33 | let right = pair.get(1).unwrap_or(&pair[0]); 34 | new_layer.push(Hash::hash(&[left, *right])); 35 | } 36 | 37 | layer = new_layer; 38 | } 39 | 40 | MerkleRoot(layer[0]) 41 | } 42 | } 43 | 44 | pub trait Saveable 45 | where 46 | Self: Sized, 47 | { 48 | fn load(reader: I) -> IoResult; 49 | fn save(&self, writer: O) -> IoResult<()>; 50 | 51 | fn save_to_file>( 52 | &self, 53 | path: P, 54 | ) -> IoResult<()> { 55 | let file = File::create(&path)?; 56 | self.save(file) 57 | } 58 | 59 | fn load_from_file>( 60 | path: P, 61 | ) -> IoResult { 62 | let file = File::open(&path)?; 63 | Self::load(file) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /wallet/src/tasks.rs: -------------------------------------------------------------------------------- 1 | use cursive::views::TextContent; 2 | use tokio::task::JoinHandle; 3 | use tokio::time::{self, Duration}; 4 | use tracing::*; 5 | 6 | use std::sync::Arc; 7 | 8 | use btclib::types::Transaction; 9 | 10 | use crate::core::Core; 11 | use crate::ui::run_ui; 12 | use crate::util::big_mode_btc; 13 | 14 | pub async fn update_utxos(core: Arc) -> JoinHandle<()> { 15 | tokio::spawn(async move { 16 | let mut interval = 17 | time::interval(Duration::from_secs(20)); 18 | loop { 19 | interval.tick().await; 20 | if let Err(e) = core.fetch_utxos().await { 21 | error!("Failed to update UTXOs: {}", e); 22 | } 23 | } 24 | }) 25 | } 26 | 27 | pub async fn handle_transactions( 28 | rx: kanal::AsyncReceiver, 29 | core: Arc, 30 | ) -> JoinHandle<()> { 31 | tokio::spawn(async move { 32 | while let Ok(transaction) = rx.recv().await { 33 | if let Err(e) = 34 | core.send_transaction(transaction).await 35 | { 36 | error!("Failed to send transaction: {}", e); 37 | } 38 | } 39 | }) 40 | } 41 | 42 | pub async fn ui_task( 43 | core: Arc, 44 | balance_content: TextContent, 45 | ) -> JoinHandle<()> { 46 | tokio::task::spawn_blocking(move || { 47 | info!("Running UI"); 48 | if let Err(e) = run_ui(core, balance_content) { 49 | eprintln!("UI ended with error: {e}"); 50 | }; 51 | }) 52 | } 53 | 54 | pub async fn update_balance( 55 | core: Arc, 56 | balance_content: TextContent, 57 | ) -> JoinHandle<()> { 58 | tokio::spawn(async move { 59 | loop { 60 | tokio::time::sleep(Duration::from_millis(500)).await; 61 | info!("updating balance string"); 62 | balance_content.set_content(big_mode_btc(&core)); 63 | } 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /lib/src/types/transaction.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use uuid::Uuid; 3 | 4 | use crate::util::Saveable; 5 | use std::io::{ 6 | Error as IoError, ErrorKind as IoErrorKind, Read, 7 | Result as IoResult, Write, 8 | }; 9 | 10 | use crate::crypto::{PublicKey, Signature}; 11 | use crate::sha256::Hash; 12 | 13 | #[derive(Serialize, Deserialize, Clone, Debug)] 14 | pub struct Transaction { 15 | pub inputs: Vec, 16 | pub outputs: Vec, 17 | } 18 | 19 | impl Transaction { 20 | pub fn new( 21 | inputs: Vec, 22 | outputs: Vec, 23 | ) -> Self { 24 | Transaction { 25 | inputs: inputs, 26 | outputs: outputs, 27 | } 28 | } 29 | 30 | pub fn hash(&self) -> Hash { 31 | Hash::hash(self) 32 | } 33 | } 34 | 35 | // save and load expecting CBOR from ciborium as format 36 | impl Saveable for Transaction { 37 | fn load(reader: I) -> IoResult { 38 | ciborium::de::from_reader(reader).map_err(|_| { 39 | IoError::new( 40 | IoErrorKind::InvalidData, 41 | "Failed to deserialize Transaction", 42 | ) 43 | }) 44 | } 45 | 46 | fn save(&self, writer: O) -> IoResult<()> { 47 | ciborium::ser::into_writer(self, writer).map_err(|_| { 48 | IoError::new( 49 | IoErrorKind::InvalidData, 50 | "Failed to serialize Transaction", 51 | ) 52 | }) 53 | } 54 | } 55 | 56 | #[derive(Serialize, Deserialize, Clone, Debug)] 57 | pub struct TransactionInput { 58 | pub prev_transaction_output_hash: Hash, 59 | pub signature: Signature, 60 | } 61 | 62 | #[derive(Serialize, Deserialize, Clone, Debug)] 63 | pub struct TransactionOutput { 64 | pub value: u64, 65 | pub unique_id: Uuid, 66 | pub pubkey: PublicKey, 67 | } 68 | 69 | impl TransactionOutput { 70 | pub fn hash(&self) -> Hash { 71 | Hash::hash(self) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /wallet/src/util.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use std::panic; 4 | use std::path::PathBuf; 5 | 6 | use tracing::*; 7 | use tracing_appender::rolling::{RollingFileAppender, Rotation}; 8 | use tracing_subscriber::{fmt, prelude::*, EnvFilter}; 9 | 10 | use crate::core::{Config, Core, FeeConfig, FeeType, Recipient}; 11 | 12 | /// Initialize tracing to save logs into the logs/ folder 13 | pub fn setup_tracing() -> Result<()> { 14 | let file_appender = RollingFileAppender::new( 15 | Rotation::DAILY, 16 | "logs", 17 | "wallet.log", 18 | ); 19 | 20 | tracing_subscriber::registry() 21 | .with(fmt::layer().with_writer(file_appender)) 22 | .with( 23 | EnvFilter::from_default_env() 24 | .add_directive(tracing::Level::TRACE.into()), 25 | ) 26 | .init(); 27 | 28 | Ok(()) 29 | } 30 | 31 | /// Make sure tracing is able to log panics ocurring in the wallet 32 | pub fn setup_panic_hook() { 33 | panic::set_hook(Box::new(|panic_info| { 34 | let backtrace = 35 | std::backtrace::Backtrace::force_capture(); 36 | error!("Application panicked!"); 37 | error!("Panic info: {:?}", panic_info); 38 | error!("Backtrace: {:?}", backtrace); 39 | })); 40 | } 41 | 42 | /// Generate a dummy config 43 | pub fn generate_dummy_config(path: &PathBuf) -> Result<()> { 44 | let dummy_config = Config { 45 | my_keys: vec![], 46 | contacts: vec![ 47 | Recipient { 48 | name: "Alice".to_string(), 49 | key: PathBuf::from("alice.pub.pem"), 50 | }, 51 | Recipient { 52 | name: "Bob".to_string(), 53 | key: PathBuf::from("bob.pub.pem"), 54 | }, 55 | ], 56 | default_node: "127.0.0.1:9000".to_string(), 57 | fee_config: FeeConfig { 58 | fee_type: FeeType::Percent, 59 | value: 0.1, 60 | }, 61 | }; 62 | 63 | let config_str = toml::to_string_pretty(&dummy_config)?; 64 | std::fs::write(path, config_str)?; 65 | info!("Dummy config generated at: {}", path.display()); 66 | Ok(()) 67 | } 68 | 69 | /// Convert satoshis to a BTC string 70 | pub fn sats_to_btc(sats: u64) -> String { 71 | let btc = sats as f64 / 100_000_000.0; 72 | format!("{} BTC", btc) 73 | } 74 | 75 | /// Make it big lmao 76 | pub fn big_mode_btc(core: &Core) -> String { 77 | text_to_ascii_art::convert(sats_to_btc(core.get_balance())) 78 | .unwrap() 79 | } 80 | -------------------------------------------------------------------------------- /wallet/src/main.rs: -------------------------------------------------------------------------------- 1 | // main.rs 2 | use anyhow::Result; 3 | use clap::{Parser, Subcommand}; 4 | use cursive::views::TextContent; 5 | use tracing::{debug, info}; 6 | 7 | use std::path::PathBuf; 8 | use std::sync::Arc; 9 | 10 | mod core; 11 | mod tasks; 12 | mod ui; 13 | mod util; 14 | 15 | use core::Core; 16 | use tasks::{ 17 | handle_transactions, ui_task, update_balance, update_utxos, 18 | }; 19 | use util::{ 20 | big_mode_btc, generate_dummy_config, setup_panic_hook, 21 | setup_tracing, 22 | }; 23 | 24 | #[derive(Parser)] 25 | #[command(author, version, about, long_about = None)] 26 | struct Cli { 27 | #[command(subcommand)] 28 | command: Option, 29 | 30 | #[arg(short, long, value_name = "FILE", default_value_os_t = PathBuf::from("wallet_config.toml"))] 31 | config: PathBuf, 32 | 33 | #[arg(short, long, value_name = "ADDRESS")] 34 | node: Option, 35 | } 36 | 37 | #[derive(Subcommand)] 38 | enum Commands { 39 | GenerateConfig { 40 | #[arg(short, long, value_name = "FILE", default_value_os_t = PathBuf::from("wallet_config.toml"))] 41 | output: PathBuf, 42 | }, 43 | } 44 | 45 | #[tokio::main] 46 | async fn main() -> Result<()> { 47 | setup_tracing()?; 48 | setup_panic_hook(); 49 | 50 | info!("Starting wallet application"); 51 | 52 | let cli = Cli::parse(); 53 | 54 | match &cli.command { 55 | Some(Commands::GenerateConfig { output }) => { 56 | debug!("Generating dummy config at: {:?}", output); 57 | return generate_dummy_config(output); 58 | } 59 | None => (), 60 | } 61 | 62 | info!("Loading config from: {:?}", cli.config); 63 | let mut core = Core::load(cli.config.clone()).await?; 64 | 65 | if let Some(node) = cli.node { 66 | info!("Overriding default node with: {}", node); 67 | core.config.default_node = node; 68 | } 69 | 70 | let (tx_sender, tx_receiver) = kanal::bounded(10); 71 | core.tx_sender = tx_sender; 72 | 73 | let core = Arc::new(core); 74 | 75 | info!("Starting background tasks"); 76 | let balance_content = TextContent::new(big_mode_btc(&core)); 77 | 78 | tokio::select! { 79 | _ = ui_task(core.clone(), balance_content.clone()).await => (), 80 | _ = update_utxos(core.clone()).await => (), 81 | _ = handle_transactions(tx_receiver.clone_async(), core.clone()).await => (), 82 | _ = update_balance(core.clone(), balance_content).await => (), 83 | } 84 | 85 | info!("Application shutting down"); 86 | Ok(()) 87 | } 88 | -------------------------------------------------------------------------------- /node/src/main.rs: -------------------------------------------------------------------------------- 1 | use argh::FromArgs; 2 | use dashmap::DashMap; 3 | use static_init::dynamic; 4 | 5 | use anyhow::Result; 6 | use tokio::net::{TcpListener, TcpStream}; 7 | use tokio::sync::RwLock; 8 | 9 | use btclib::types::Blockchain; 10 | 11 | use std::path::Path; 12 | 13 | mod handler; 14 | mod util; 15 | 16 | #[dynamic] 17 | pub static BLOCKCHAIN: RwLock = 18 | RwLock::new(Blockchain::new()); 19 | 20 | // Node pool 21 | #[dynamic] 22 | pub static NODES: DashMap = DashMap::new(); 23 | 24 | #[derive(FromArgs)] 25 | /// A toy blockchain node 26 | struct Args { 27 | #[argh(option, default = "9000")] 28 | /// port number 29 | port: u16, 30 | 31 | #[argh( 32 | option, 33 | default = "String::from(\"./blockchain.cbor\")" 34 | )] 35 | /// blockchain file location 36 | blockchain_file: String, 37 | 38 | #[argh(positional)] 39 | /// addresses of initial nodes 40 | nodes: Vec, 41 | } 42 | 43 | #[tokio::main] 44 | async fn main() -> Result<()> { 45 | // Parse command line arguments 46 | let args: Args = argh::from_env(); 47 | 48 | // Access the parsed arguments 49 | let port = args.port; 50 | let blockchain_file = args.blockchain_file; 51 | let nodes = args.nodes; 52 | 53 | util::populate_connections(&nodes).await?; 54 | println!("total amount of known nodes: {}", NODES.len()); 55 | // Check if the blockchain_file exists 56 | if Path::new(&blockchain_file).exists() { 57 | util::load_blockchain(&blockchain_file).await?; 58 | } else { 59 | println!("blockchain file does not exist!"); 60 | 61 | if nodes.is_empty() { 62 | println!("no initial nodes provided, starting as a seed node"); 63 | } else { 64 | let (longest_name, longest_count) = 65 | util::find_longest_chain_node().await?; 66 | 67 | // request the blockchain from the node with the longest blockchain 68 | util::download_blockchain( 69 | &longest_name, 70 | longest_count, 71 | ) 72 | .await?; 73 | 74 | println!( 75 | "blockchain downloaded from {}", 76 | longest_name 77 | ); 78 | 79 | // recalculate utxos 80 | { 81 | let mut blockchain = BLOCKCHAIN.write().await; 82 | blockchain.rebuild_utxos(); 83 | } 84 | 85 | // try to adjust difficulty 86 | { 87 | let mut blockchain = BLOCKCHAIN.write().await; 88 | blockchain.try_adjust_target(); 89 | } 90 | } 91 | } 92 | 93 | // Start the TCP listener on 0.0.0.0:port 94 | let addr = format!("0.0.0.0:{}", port); 95 | let listener = TcpListener::bind(&addr).await?; 96 | println!("Listening on {}", addr); 97 | 98 | // start a task to periodically cleanup the mempool 99 | // normally, you would want to keep and join the handle 100 | tokio::spawn(util::cleanup()); 101 | 102 | // and a task to periodically save the blockchain 103 | tokio::spawn(util::save(blockchain_file.clone())); 104 | 105 | loop { 106 | let (socket, _) = listener.accept().await?; 107 | tokio::spawn(handler::handle_connection(socket)); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/src/network.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use std::io::{Error as IoError, Read, Write}; 4 | use tokio::io::{ 5 | AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, 6 | }; 7 | 8 | use crate::crypto::PublicKey; 9 | use crate::types::{Block, Transaction, TransactionOutput}; 10 | 11 | #[derive(Debug, Clone, Deserialize, Serialize)] 12 | pub enum Message { 13 | /// Fetch all UTXOs belonging to a public key 14 | FetchUTXOs(PublicKey), 15 | /// UTXOs belonging to a public key 16 | UTXOs(Vec<(TransactionOutput, bool)>), 17 | /// Send a transaction to the network 18 | SubmitTransaction(Transaction), 19 | /// Broadcast a new transaction to other nodes 20 | NewTransaction(Transaction), 21 | 22 | /// Ask the node to prepare the optimal block template 23 | /// with the coinbase transaction paying the specified 24 | /// public key 25 | FetchTemplate(PublicKey), 26 | /// The template 27 | Template(Block), 28 | /// Ask the node to validate a block template. 29 | /// This is to prevent the node from mining an invalid 30 | /// block (e.g. if one has been found in the meantime, 31 | /// or if transactions have been removed from the mempool) 32 | ValidateTemplate(Block), 33 | /// If template is valid 34 | TemplateValidity(bool), 35 | /// Submit a mined block to a node 36 | SubmitTemplate(Block), 37 | 38 | /// Ask a node to report all the other nodes it knows 39 | /// about 40 | DiscoverNodes, 41 | /// This is the response to DiscoverNodes 42 | NodeList(Vec), 43 | /// Ask a node whats the highest block it knows about 44 | /// in comparison to the local blockchain 45 | AskDifference(u32), 46 | /// This is the response to AskDifference 47 | Difference(i32), 48 | /// Ask a node to send a block with the specified height 49 | FetchBlock(usize), 50 | /// Broadcast a new block to other nodes 51 | NewBlock(Block), 52 | } 53 | 54 | // We are going to use length-prefixed encoding for message 55 | // And we are going to use ciborium (CBOR) for serialization 56 | impl Message { 57 | pub fn encode( 58 | &self, 59 | ) -> Result, ciborium::ser::Error> { 60 | let mut bytes = Vec::new(); 61 | ciborium::into_writer(self, &mut bytes)?; 62 | 63 | Ok(bytes) 64 | } 65 | 66 | pub fn decode( 67 | data: &[u8], 68 | ) -> Result> { 69 | ciborium::from_reader(data) 70 | } 71 | 72 | pub fn send( 73 | &self, 74 | stream: &mut impl Write, 75 | ) -> Result<(), ciborium::ser::Error> { 76 | let bytes = self.encode()?; 77 | let len = bytes.len() as u64; 78 | stream.write_all(&len.to_be_bytes())?; 79 | stream.write_all(&bytes)?; 80 | 81 | Ok(()) 82 | } 83 | 84 | pub fn receive( 85 | stream: &mut impl Read, 86 | ) -> Result> { 87 | let mut len_bytes = [0u8; 8]; 88 | stream.read_exact(&mut len_bytes)?; 89 | let len = u64::from_be_bytes(len_bytes) as usize; 90 | 91 | let mut data = vec![0u8; len]; 92 | stream.read_exact(&mut data)?; 93 | 94 | Self::decode(&data) 95 | } 96 | 97 | pub async fn send_async( 98 | &self, 99 | stream: &mut (impl AsyncWrite + Unpin), 100 | ) -> Result<(), ciborium::ser::Error> { 101 | let bytes = self.encode()?; 102 | let len = bytes.len() as u64; 103 | stream.write_all(&len.to_be_bytes()).await?; 104 | stream.write_all(&bytes).await?; 105 | 106 | Ok(()) 107 | } 108 | 109 | pub async fn receive_async( 110 | stream: &mut (impl AsyncRead + Unpin), 111 | ) -> Result> { 112 | let mut len_bytes = [0u8; 8]; 113 | stream.read_exact(&mut len_bytes).await?; 114 | let len = u64::from_be_bytes(len_bytes) as usize; 115 | 116 | let mut data = vec![0u8; len]; 117 | stream.read_exact(&mut data).await?; 118 | 119 | Self::decode(&data) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+title: Building Bitcoin in Rust 2 | 3 | This repository contains a toy implementation of the Bitcoin protocol based on the original whitepaper. It serves as a companion to the book "Building Bitcoin in Rust". 4 | 5 | ** About the Book 6 | 7 | "Building Bitcoin in Rust" is a comprehensive guide to understanding and implementing the core concepts of Bitcoin using the Rust programming language. You can find the book at: 8 | 9 | [[https://braiins.com/books/building-bitcoin-in-rust][https://braiins.com/books/building-bitcoin-in-rust]] 10 | 11 | ** Author 12 | 13 | - Name: Lukáš Hozda 14 | - Email: lukas.hozda@braiins.cz 15 | - X/Twitter: [[https://twitter.com/LukasHozda][@LukasHozda]] (Preferred mode of communication) 16 | 17 | ** Questions and Issues 18 | 19 | If you have questions about the implementation, the book, or encounter issues with Rust, please use the [[https://github.com/your-username/building-bitcoin-in-rust/discussions][GitHub Discussions]] feature in this repository. 20 | 21 | ** Getting Started 22 | 23 | *** Installing Rust 24 | 25 | To run this project, you'll need to install Rust. We recommend using Rustup, the official Rust installer. 26 | 27 | **** On Linux or macOS: 28 | 29 | Open a terminal and run: 30 | 31 | #+BEGIN_SRC sh 32 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 33 | #+END_SRC 34 | 35 | Follow the on-screen instructions to complete the installation. 36 | 37 | **** On Windows: 38 | 39 | 1. Download the Rustup installer from [[https://rustup.rs/][rustup.rs]]. 40 | 2. Run the installer and follow the on-screen instructions. 41 | 42 | After installation, restart your terminal or command prompt. 43 | 44 | *** Compiling and Running the Project 45 | 46 | 1. Clone this repository: 47 | #+BEGIN_SRC sh 48 | git clone https://github.com/your-username/building-bitcoin-in-rust.git 49 | cd building-bitcoin-in-rust 50 | #+END_SRC 51 | 52 | 2. Compile the project: 53 | #+BEGIN_SRC sh 54 | cargo build --release 55 | #+END_SRC 56 | 57 | 3. Run the node: 58 | #+BEGIN_SRC sh 59 | cargo run --release --bin node -- [OPTIONS] [INITIAL_NODES...] 60 | #+END_SRC 61 | 62 | Options: 63 | - =--port =: Set the port number (default: 9000) 64 | - =--blockchain-file =: Set the blockchain file location (default: "./blockchain.cbor") 65 | 66 | Example: 67 | #+BEGIN_SRC sh 68 | cargo run --release --bin node -- --port 9000 --blockchain-file ./my_blockchain.cbor 127.0.0.1:9001 127.0.0.1:9002 69 | #+END_SRC 70 | 71 | 4. Run the miner: 72 | #+BEGIN_SRC sh 73 | cargo run --release --bin miner -- --address --public-key-file 74 | #+END_SRC 75 | 76 | Example: 77 | #+BEGIN_SRC sh 78 | cargo run --release --bin miner -- --address 127.0.0.1:9000 --public-key-file miner_key.pub 79 | #+END_SRC 80 | 81 | 5. Run the wallet: 82 | #+BEGIN_SRC sh 83 | cargo run --release --bin wallet -- [OPTIONS] 84 | #+END_SRC 85 | 86 | Options: 87 | - =-c, --config =: Set the wallet config file (default: "wallet_config.toml") 88 | - =-n, --node
=: Set the node address 89 | 90 | Example: 91 | #+BEGIN_SRC sh 92 | cargo run --release --bin wallet -- --config my_wallet_config.toml --node 127.0.0.1:9000 93 | #+END_SRC 94 | 95 | ** License 96 | 97 | The source code in this repository is licensed under the ISC License: 98 | 99 | #+BEGIN_SRC 100 | Copyright (c) 2023, Lukáš Hozda 101 | 102 | Permission to use, copy, modify, and/or distribute this software for any 103 | purpose with or without fee is hereby granted, provided that the above 104 | copyright notice and this permission notice appear in all copies. 105 | 106 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 107 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 108 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 109 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 110 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 111 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 112 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 113 | #+END_SRC 114 | 115 | Please note that this license applies only to the source code in this repository. The text of the book "Building Bitcoin in Rust" is proprietary and not covered by this license. 116 | -------------------------------------------------------------------------------- /lib/src/crypto.rs: -------------------------------------------------------------------------------- 1 | use ecdsa::{ 2 | signature::{Signer, Verifier}, 3 | Signature as ECDSASignature, SigningKey, VerifyingKey, 4 | }; 5 | use k256::Secp256k1; 6 | use serde::{Deserialize, Serialize}; 7 | use spki::EncodePublicKey; 8 | 9 | use std::io::{ 10 | Error as IoError, ErrorKind as IoErrorKind, Read, 11 | Result as IoResult, Write, 12 | }; 13 | 14 | use crate::sha256::Hash; 15 | use crate::util::Saveable; 16 | 17 | #[derive(Debug, Serialize, Deserialize, Clone)] 18 | pub struct Signature(ECDSASignature); 19 | 20 | impl Signature { 21 | // sign a crate::types::TransactionOutput from its Sha256 hash 22 | pub fn sign_output( 23 | output_hash: &Hash, 24 | private_key: &PrivateKey, 25 | ) -> Self { 26 | let signing_key = &private_key.0; 27 | let signature = 28 | signing_key.sign(&output_hash.as_bytes()); 29 | Signature(signature) 30 | } 31 | 32 | // verify a signature 33 | pub fn verify( 34 | &self, 35 | output_hash: &Hash, 36 | public_key: &PublicKey, 37 | ) -> bool { 38 | public_key 39 | .0 40 | .verify(&output_hash.as_bytes(), &self.0) 41 | .is_ok() 42 | } 43 | } 44 | 45 | #[derive( 46 | Debug, 47 | Serialize, 48 | Deserialize, 49 | Clone, 50 | PartialEq, 51 | Eq, 52 | PartialOrd, 53 | Ord, 54 | )] 55 | pub struct PublicKey(VerifyingKey); 56 | 57 | #[derive(Debug, Serialize, Deserialize, Clone)] 58 | pub struct PrivateKey( 59 | #[serde(with = "signkey_serde")] pub SigningKey, 60 | ); 61 | 62 | impl PrivateKey { 63 | pub fn new_key() -> Self { 64 | PrivateKey(SigningKey::random(&mut rand::thread_rng())) 65 | } 66 | 67 | pub fn public_key(&self) -> PublicKey { 68 | PublicKey(self.0.verifying_key().clone()) 69 | } 70 | } 71 | 72 | impl Saveable for PrivateKey { 73 | fn load(reader: I) -> IoResult { 74 | ciborium::de::from_reader(reader).map_err(|_| { 75 | IoError::new( 76 | IoErrorKind::InvalidData, 77 | "Failed to deserialize PrivateKey", 78 | ) 79 | }) 80 | } 81 | 82 | fn save(&self, writer: O) -> IoResult<()> { 83 | ciborium::ser::into_writer(self, writer).map_err( 84 | |_| { 85 | IoError::new( 86 | IoErrorKind::InvalidData, 87 | "Failed to serialize PrivateKey", 88 | ) 89 | }, 90 | )?; 91 | Ok(()) 92 | } 93 | } 94 | 95 | // save and load as PEM 96 | impl Saveable for PublicKey { 97 | fn load(mut reader: I) -> IoResult { 98 | // read PEM-encoded public key into string 99 | let mut buf = String::new(); 100 | reader.read_to_string(&mut buf)?; 101 | 102 | // decode the public key from PEM 103 | let public_key = buf.parse().map_err(|_| { 104 | IoError::new( 105 | IoErrorKind::InvalidData, 106 | "Failed to parse PublicKey", 107 | ) 108 | })?; 109 | 110 | Ok(PublicKey(public_key)) 111 | } 112 | 113 | fn save(&self, mut writer: O) -> IoResult<()> { 114 | let s = self 115 | .0 116 | .to_public_key_pem(Default::default()) 117 | .map_err(|_| { 118 | IoError::new( 119 | IoErrorKind::InvalidData, 120 | "Failed to serialize PublicKey", 121 | ) 122 | })?; 123 | 124 | writer.write_all(s.as_bytes())?; 125 | Ok(()) 126 | } 127 | } 128 | 129 | mod signkey_serde { 130 | use serde::Deserialize; 131 | 132 | pub fn serialize( 133 | key: &super::SigningKey, 134 | serializer: S, 135 | ) -> Result 136 | where 137 | S: serde::Serializer, 138 | { 139 | serializer.serialize_bytes(&key.to_bytes()) 140 | } 141 | 142 | pub fn deserialize<'de, D>( 143 | deserializer: D, 144 | ) -> Result, D::Error> 145 | where 146 | D: serde::Deserializer<'de>, 147 | { 148 | let bytes: Vec = 149 | Vec::::deserialize(deserializer)?; 150 | Ok(super::SigningKey::from_slice(&bytes).unwrap()) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /node/src/util.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use tokio::net::TcpStream; 3 | use tokio::time; 4 | 5 | use btclib::network::Message; 6 | use btclib::types::Blockchain; 7 | use btclib::util::Saveable; 8 | 9 | pub async fn load_blockchain( 10 | blockchain_file: &str, 11 | ) -> Result<()> { 12 | println!("blockchain file exists, loading..."); 13 | let new_blockchain = 14 | Blockchain::load_from_file(blockchain_file)?; 15 | println!("blockchain loaded"); 16 | 17 | let mut blockchain = crate::BLOCKCHAIN.write().await; 18 | *blockchain = new_blockchain; 19 | 20 | println!("rebuilding utxos..."); 21 | blockchain.rebuild_utxos(); 22 | println!("utxos rebuilt"); 23 | 24 | println!("checking if target needs to be adjusted..."); 25 | println!("current target: {}", blockchain.target()); 26 | blockchain.try_adjust_target(); 27 | println!("new target: {}", blockchain.target()); 28 | 29 | println!("initialization complete"); 30 | Ok(()) 31 | } 32 | 33 | pub async fn populate_connections( 34 | nodes: &[String], 35 | ) -> Result<()> { 36 | println!("trying to connect to other nodes..."); 37 | 38 | for node in nodes { 39 | println!("connecting to {}", node); 40 | 41 | let mut stream = TcpStream::connect(&node).await?; 42 | let message = Message::DiscoverNodes; 43 | message.send_async(&mut stream).await?; 44 | println!("sent DiscoverNodes to {}", node); 45 | let message = 46 | Message::receive_async(&mut stream).await?; 47 | match message { 48 | Message::NodeList(child_nodes) => { 49 | println!("received NodeList from {}", node); 50 | for child_node in child_nodes { 51 | println!("adding node {}", child_node); 52 | let new_stream = 53 | TcpStream::connect(&child_node).await?; 54 | crate::NODES.insert(child_node, new_stream); 55 | } 56 | } 57 | _ => { 58 | println!("unexpected message from {}", node); 59 | } 60 | } 61 | 62 | crate::NODES.insert(node.clone(), stream); 63 | } 64 | 65 | Ok(()) 66 | } 67 | 68 | pub async fn find_longest_chain_node() -> Result<(String, u32)> { 69 | println!( 70 | "finding nodes with the highest blockchain length..." 71 | ); 72 | let mut longest_name = String::new(); 73 | let mut longest_count = 0; 74 | 75 | let all_nodes = crate::NODES 76 | .iter() 77 | .map(|x| x.key().clone()) 78 | .collect::>(); 79 | 80 | for node in all_nodes { 81 | println!("asking {} for blockchain length", node); 82 | 83 | let mut stream = 84 | crate::NODES.get_mut(&node).context("no node")?; 85 | 86 | let message = Message::AskDifference(0); 87 | message.send_async(&mut *stream).await.unwrap(); 88 | 89 | println!("sent AskDifference to {}", node); 90 | 91 | let message = 92 | Message::receive_async(&mut *stream).await?; 93 | match message { 94 | Message::Difference(count) => { 95 | println!("received Difference from {}", node); 96 | if count > longest_count { 97 | println!( 98 | "new longest blockchain: \ 99 | {} blocks from {node}", 100 | count 101 | ); 102 | longest_count = count; 103 | longest_name = node; 104 | } 105 | } 106 | e => { 107 | println!( 108 | "unexpected message from {}: {:?}", 109 | node, e 110 | ); 111 | } 112 | } 113 | } 114 | 115 | Ok((longest_name, longest_count as u32)) 116 | } 117 | 118 | pub async fn download_blockchain( 119 | node: &str, 120 | count: u32, 121 | ) -> Result<()> { 122 | let mut stream = crate::NODES.get_mut(node).unwrap(); 123 | for i in 0..count as usize { 124 | let message = Message::FetchBlock(i); 125 | message.send_async(&mut *stream).await?; 126 | 127 | let message = 128 | Message::receive_async(&mut *stream).await?; 129 | match message { 130 | Message::NewBlock(block) => { 131 | let mut blockchain = 132 | crate::BLOCKCHAIN.write().await; 133 | blockchain.add_block(block)?; 134 | } 135 | _ => { 136 | println!("unexpected message from {}", node); 137 | } 138 | } 139 | } 140 | 141 | Ok(()) 142 | } 143 | 144 | pub async fn cleanup() { 145 | let mut interval = 146 | time::interval(time::Duration::from_secs(30)); 147 | 148 | loop { 149 | interval.tick().await; 150 | 151 | println!("cleaning the mempool from old transactions"); 152 | let mut blockchain = crate::BLOCKCHAIN.write().await; 153 | blockchain.cleanup_mempool(); 154 | } 155 | } 156 | 157 | pub async fn save(name: String) { 158 | let mut interval = 159 | time::interval(time::Duration::from_secs(15)); 160 | 161 | loop { 162 | interval.tick().await; 163 | 164 | println!("saving blockchain to drive..."); 165 | let blockchain = crate::BLOCKCHAIN.read().await; 166 | blockchain.save_to_file(name.clone()).unwrap(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /miner/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use btclib::crypto::PublicKey; 3 | use btclib::network::Message; 4 | use btclib::types::Block; 5 | use btclib::util::Saveable; 6 | use clap::Parser; 7 | use std::sync::{ 8 | atomic::{AtomicBool, Ordering}, 9 | Arc, 10 | }; 11 | use std::thread; 12 | use tokio::net::TcpStream; 13 | use tokio::sync::Mutex; 14 | use tokio::time::{interval, Duration}; 15 | 16 | #[derive(Parser)] 17 | #[command(author, version, about, long_about = None)] 18 | struct Cli { 19 | #[arg(short, long)] 20 | address: String, 21 | #[arg(short, long)] 22 | public_key_file: String, 23 | } 24 | 25 | struct Miner { 26 | public_key: PublicKey, 27 | stream: Mutex, 28 | current_template: Arc>>, 29 | mining: Arc, 30 | mined_block_sender: flume::Sender, 31 | mined_block_receiver: flume::Receiver, 32 | } 33 | 34 | impl Miner { 35 | async fn new( 36 | address: String, 37 | public_key: PublicKey, 38 | ) -> Result { 39 | let stream = TcpStream::connect(&address).await?; 40 | let (mined_block_sender, mined_block_receiver) = 41 | flume::unbounded(); 42 | Ok(Self { 43 | public_key, 44 | stream: Mutex::new(stream), 45 | current_template: Arc::new(std::sync::Mutex::new( 46 | None, 47 | )), 48 | mining: Arc::new(AtomicBool::new(false)), 49 | mined_block_sender, 50 | mined_block_receiver, 51 | }) 52 | } 53 | 54 | async fn run(&self) -> Result<()> { 55 | self.spawn_mining_thread(); 56 | 57 | let mut template_interval = 58 | interval(Duration::from_secs(5)); 59 | 60 | loop { 61 | let receiver_clone = 62 | self.mined_block_receiver.clone(); 63 | 64 | tokio::select! { 65 | _ = template_interval.tick() => { 66 | self.fetch_and_validate_template().await?; 67 | } 68 | Ok(mined_block) = receiver_clone.recv_async() => { 69 | self.submit_block(mined_block).await?; 70 | } 71 | } 72 | } 73 | } 74 | 75 | fn spawn_mining_thread(&self) -> thread::JoinHandle<()> { 76 | let template = self.current_template.clone(); 77 | let mining = self.mining.clone(); 78 | let sender = self.mined_block_sender.clone(); 79 | 80 | thread::spawn(move || loop { 81 | if mining.load(Ordering::Relaxed) { 82 | if let Some(mut block) = 83 | template.lock().unwrap().clone() 84 | { 85 | println!( 86 | "Mining block with target: {}", 87 | block.header.target 88 | ); 89 | if block.header.mine(2_000_000) { 90 | println!( 91 | "Block mined: {}", 92 | block.hash() 93 | ); 94 | sender.send(block).expect( 95 | "Failed to send mined block", 96 | ); 97 | mining.store(false, Ordering::Relaxed); 98 | } 99 | } 100 | } 101 | thread::yield_now(); 102 | }) 103 | } 104 | 105 | async fn fetch_and_validate_template(&self) -> Result<()> { 106 | if !self.mining.load(Ordering::Relaxed) { 107 | self.fetch_template().await?; 108 | } else { 109 | self.validate_template().await?; 110 | } 111 | Ok(()) 112 | } 113 | 114 | async fn fetch_template(&self) -> Result<()> { 115 | println!("Fetching new template"); 116 | let message = 117 | Message::FetchTemplate(self.public_key.clone()); 118 | 119 | let mut stream_lock = self.stream.lock().await; 120 | message.send_async(&mut *stream_lock).await?; 121 | drop(stream_lock); 122 | 123 | let mut stream_lock = self.stream.lock().await; 124 | match Message::receive_async(&mut *stream_lock).await? { 125 | Message::Template(template) => { 126 | drop(stream_lock); 127 | println!("Received new template with target: {}", template.header.target); 128 | *self.current_template.lock().unwrap() = Some(template); 129 | self.mining.store(true, Ordering::Relaxed); 130 | Ok(()) 131 | } 132 | _ => Err(anyhow!("Unexpected message received when fetching template")), 133 | } 134 | } 135 | 136 | async fn validate_template(&self) -> Result<()> { 137 | if let Some(template) = 138 | self.current_template.lock().unwrap().clone() 139 | { 140 | let message = Message::ValidateTemplate(template); 141 | let mut stream_lock = self.stream.lock().await; 142 | message.send_async(&mut *stream_lock).await?; 143 | drop(stream_lock); 144 | 145 | let mut stream_lock = self.stream.lock().await; 146 | match Message::receive_async(&mut *stream_lock).await? { 147 | Message::TemplateValidity(valid) => { 148 | drop(stream_lock); 149 | if !valid { 150 | println!("Current template is no longer valid"); 151 | self.mining.store(false, Ordering::Relaxed); 152 | } else { 153 | println!("Current template is still valid"); 154 | } 155 | Ok(()) 156 | } 157 | _ => Err(anyhow!("Unexpected message received when validating template")), 158 | } 159 | } else { 160 | Ok(()) 161 | } 162 | } 163 | 164 | async fn submit_block(&self, block: Block) -> Result<()> { 165 | println!("Submitting mined block"); 166 | let message = Message::SubmitTemplate(block); 167 | let mut stream_lock = self.stream.lock().await; 168 | message.send_async(&mut *stream_lock).await?; 169 | self.mining.store(false, Ordering::Relaxed); 170 | Ok(()) 171 | } 172 | } 173 | 174 | #[tokio::main] 175 | async fn main() -> Result<()> { 176 | let cli = Cli::parse(); 177 | 178 | let public_key = 179 | PublicKey::load_from_file(&cli.public_key_file) 180 | .map_err(|e| { 181 | anyhow!("Error reading public key: {}", e) 182 | })?; 183 | 184 | let miner = Miner::new(cli.address, public_key).await?; 185 | miner.run().await 186 | } 187 | -------------------------------------------------------------------------------- /wallet/src/ui.rs: -------------------------------------------------------------------------------- 1 | // ui.rs 2 | use crate::core::Core; 3 | use anyhow::Result; 4 | use cursive::event::{Event, Key}; 5 | use cursive::traits::*; 6 | use cursive::views::{ 7 | Button, Dialog, EditView, LinearLayout, Panel, ResizedView, 8 | TextContent, TextView, 9 | }; 10 | use cursive::Cursive; 11 | use std::sync::{Arc, Mutex}; 12 | use tracing::*; 13 | 14 | #[derive(Clone, Copy)] 15 | enum Unit { 16 | Btc, 17 | Sats, 18 | } 19 | 20 | /// Convert an amount between BTC and Satoshi units. 21 | fn convert_amount(amount: f64, from: Unit, to: Unit) -> f64 { 22 | match (from, to) { 23 | (Unit::Btc, Unit::Sats) => amount * 100_000_000.0, 24 | (Unit::Sats, Unit::Btc) => amount / 100_000_000.0, 25 | _ => amount, 26 | } 27 | } 28 | 29 | /// Initialize and run the user interface. 30 | pub fn run_ui( 31 | core: Arc, 32 | balance_content: TextContent, 33 | ) -> Result<()> { 34 | info!("Initializing UI"); 35 | let mut siv = cursive::default(); 36 | setup_siv(&mut siv, core.clone(), balance_content); 37 | 38 | info!("Starting UI event loop"); 39 | siv.run(); 40 | 41 | info!("UI event loop ended"); 42 | Ok(()) 43 | } 44 | 45 | /// Set up the Cursive interface with all necessary components and callbacks. 46 | fn setup_siv( 47 | siv: &mut Cursive, 48 | core: Arc, 49 | balance_content: TextContent, 50 | ) { 51 | siv.set_autorefresh(true); 52 | siv.set_fps(30); 53 | siv.set_window_title("BTC wallet".to_string()); 54 | 55 | siv.add_global_callback('q', |s| { 56 | info!("Quit command received"); 57 | s.quit() 58 | }); 59 | 60 | setup_menubar(siv, core.clone()); 61 | setup_layout(siv, core, balance_content); 62 | 63 | siv.add_global_callback(Event::Key(Key::Esc), |siv| { 64 | siv.select_menubar() 65 | }); 66 | siv.select_menubar(); 67 | } 68 | 69 | /// Set up the menu bar with "Send" and "Quit" options. 70 | fn setup_menubar(siv: &mut Cursive, core: Arc) { 71 | siv.menubar() 72 | .add_leaf("Send", move |s| { 73 | show_send_transaction(s, core.clone()) 74 | }) 75 | .add_leaf("Quit", |s| s.quit()); 76 | siv.set_autohide_menu(false); 77 | } 78 | 79 | /// Set up the main layout of the application. 80 | fn setup_layout( 81 | siv: &mut Cursive, 82 | core: Arc, 83 | balance_content: TextContent, 84 | ) { 85 | let instruction = 86 | TextView::new("Press Escape to select the top menu"); 87 | let balance_panel = 88 | Panel::new(TextView::new_with_content(balance_content)) 89 | .title("Balance"); 90 | let info_layout = create_info_layout(&core); 91 | let layout = LinearLayout::vertical() 92 | .child(instruction) 93 | .child(balance_panel) 94 | .child(info_layout); 95 | siv.add_layer(layout); 96 | } 97 | 98 | /// Create the information layout containing keys and contacts. 99 | fn create_info_layout(core: &Arc) -> LinearLayout { 100 | let mut info_layout = LinearLayout::horizontal(); 101 | 102 | let keys_content = core 103 | .config 104 | .my_keys 105 | .iter() 106 | .map(|key| format!("{}", key.private.display())) 107 | .collect::>() 108 | .join("\n"); 109 | info_layout.add_child(ResizedView::with_full_width( 110 | Panel::new(TextView::new(keys_content)) 111 | .title("Your keys"), 112 | )); 113 | 114 | let contacts_content = core 115 | .config 116 | .contacts 117 | .iter() 118 | .map(|contact| contact.name.clone()) 119 | .collect::>() 120 | .join("\n"); 121 | info_layout.add_child(ResizedView::with_full_width( 122 | Panel::new(TextView::new(contacts_content)) 123 | .title("Contacts"), 124 | )); 125 | 126 | info_layout 127 | } 128 | 129 | /// Display the send transaction dialog. 130 | fn show_send_transaction(s: &mut Cursive, core: Arc) { 131 | info!("Showing send transaction dialog"); 132 | let unit = Arc::new(Mutex::new(Unit::Btc)); 133 | 134 | s.add_layer( 135 | Dialog::around(create_transaction_layout(unit.clone())) 136 | .title("Send Transaction") 137 | .button("Send", move |s| { 138 | send_transaction( 139 | s, 140 | core.clone(), 141 | *unit.lock().unwrap(), 142 | ) 143 | }) 144 | .button("Cancel", |s| { 145 | debug!("Transaction cancelled"); 146 | s.pop_layer(); 147 | }), 148 | ); 149 | } 150 | 151 | /// Create the layout for the transaction dialog. 152 | fn create_transaction_layout( 153 | unit: Arc>, 154 | ) -> LinearLayout { 155 | LinearLayout::vertical() 156 | .child(TextView::new("Recipient:")) 157 | .child(EditView::new().with_name("recipient")) 158 | .child(TextView::new("Amount:")) 159 | .child(EditView::new().with_name("amount")) 160 | .child(create_unit_layout(unit)) 161 | } 162 | 163 | /// Create the layout for selecting the transaction unit (BTC or Sats). 164 | fn create_unit_layout(unit: Arc>) -> LinearLayout { 165 | LinearLayout::horizontal() 166 | .child(TextView::new("Unit: ")) 167 | .child( 168 | TextView::new_with_content(TextContent::new("BTC")) 169 | .with_name("unit_display"), 170 | ) 171 | .child(Button::new("Switch", move |s| { 172 | switch_unit(s, unit.clone()) 173 | })) 174 | } 175 | 176 | /// Switch the transaction unit between BTC and Sats. 177 | fn switch_unit(s: &mut Cursive, unit: Arc>) { 178 | let mut unit = unit.lock().unwrap(); 179 | *unit = match *unit { 180 | Unit::Btc => Unit::Sats, 181 | Unit::Sats => Unit::Btc, 182 | }; 183 | s.call_on_name("unit_display", |view: &mut TextView| { 184 | view.set_content(match *unit { 185 | Unit::Btc => "BTC", 186 | Unit::Sats => "Sats", 187 | }); 188 | }); 189 | } 190 | 191 | /// Process the send transaction request. 192 | fn send_transaction( 193 | s: &mut Cursive, 194 | core: Arc, 195 | unit: Unit, 196 | ) { 197 | debug!("Send button pressed"); 198 | let recipient = s 199 | .call_on_name("recipient", |view: &mut EditView| { 200 | view.get_content() 201 | }) 202 | .unwrap(); 203 | let amount: f64 = s 204 | .call_on_name("amount", |view: &mut EditView| { 205 | view.get_content() 206 | }) 207 | .unwrap() 208 | .parse() 209 | .unwrap_or(0.0); 210 | 211 | let amount_sats = 212 | convert_amount(amount, unit, Unit::Sats) as u64; 213 | 214 | info!( 215 | "Attempting to send transaction to {} for {} satoshis", 216 | recipient, amount_sats 217 | ); 218 | match core 219 | .send_transaction_async(recipient.as_str(), amount_sats) 220 | { 221 | Ok(_) => show_success_dialog(s), 222 | Err(e) => show_error_dialog(s, e), 223 | } 224 | } 225 | 226 | /// Display a success dialog after a successful transaction. 227 | fn show_success_dialog(s: &mut Cursive) { 228 | info!("Transaction sent successfully"); 229 | s.add_layer( 230 | Dialog::text("Transaction sent successfully") 231 | .title("Success") 232 | .button("OK", |s| { 233 | debug!("Closing success dialog"); 234 | s.pop_layer(); 235 | s.pop_layer(); 236 | }), 237 | ); 238 | } 239 | 240 | /// Display an error dialog when a transaction fails. 241 | fn show_error_dialog( 242 | s: &mut Cursive, 243 | error: impl std::fmt::Display, 244 | ) { 245 | error!("Failed to send transaction: {}", error); 246 | s.add_layer( 247 | Dialog::text(format!( 248 | "Failed to send transaction: {}", 249 | error 250 | )) 251 | .title("Error") 252 | .button("OK", |s| { 253 | debug!("Closing error dialog"); 254 | s.pop_layer(); 255 | }), 256 | ); 257 | } 258 | 259 | // ... -------------------------------------------------------------------------------- /lib/src/types/block.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | // add this to the imports at the top of the file 5 | use crate::util::Saveable; 6 | use std::io::{ 7 | Error as IoError, ErrorKind as IoErrorKind, Read, 8 | Result as IoResult, Write, 9 | }; 10 | 11 | use super::{Transaction, TransactionOutput}; 12 | use crate::error::{BtcError, Result}; 13 | use crate::sha256::Hash; 14 | use crate::util::MerkleRoot; 15 | use crate::U256; 16 | 17 | use std::collections::HashMap; 18 | 19 | #[derive(Serialize, Deserialize, Clone, Debug)] 20 | pub struct Block { 21 | pub header: BlockHeader, 22 | pub transactions: Vec, 23 | } 24 | 25 | impl Block { 26 | pub fn new( 27 | header: BlockHeader, 28 | transactions: Vec, 29 | ) -> Self { 30 | Block { 31 | header: header, 32 | transactions: transactions, 33 | } 34 | } 35 | 36 | pub fn hash(&self) -> Hash { 37 | Hash::hash(self) 38 | } 39 | 40 | pub fn calculate_miner_fees( 41 | &self, 42 | utxos: &HashMap, 43 | ) -> Result { 44 | let mut inputs: HashMap = 45 | HashMap::new(); 46 | let mut outputs: HashMap = 47 | HashMap::new(); 48 | 49 | // Check every transaction after coinbase 50 | for transaction in self.transactions.iter().skip(1) { 51 | for input in &transaction.inputs { 52 | let prev_output = utxos 53 | .get(&input.prev_transaction_output_hash) 54 | .map(|(_, output)| output); 55 | if prev_output.is_none() { 56 | println!("yoho"); 57 | return Err(BtcError::InvalidTransaction); 58 | } 59 | let prev_output = prev_output.unwrap(); 60 | 61 | if inputs.contains_key( 62 | &input.prev_transaction_output_hash, 63 | ) { 64 | println!("heeho"); 65 | return Err(BtcError::InvalidTransaction); 66 | } 67 | 68 | inputs.insert( 69 | input.prev_transaction_output_hash, 70 | prev_output.clone(), 71 | ); 72 | } 73 | 74 | for output in &transaction.outputs { 75 | if outputs.contains_key(&output.hash()) { 76 | println!("hoooo"); 77 | return Err(BtcError::InvalidTransaction); 78 | } 79 | 80 | outputs.insert(output.hash(), output.clone()); 81 | } 82 | } 83 | 84 | let input_value: u64 = 85 | inputs.values().map(|output| output.value).sum(); 86 | 87 | let output_value: u64 = 88 | outputs.values().map(|output| output.value).sum(); 89 | 90 | Ok(input_value - output_value) 91 | } 92 | 93 | // Verify coinbase transaction 94 | pub fn verify_coinbase_transaction( 95 | &self, 96 | predicted_block_height: u64, 97 | utxos: &HashMap, 98 | ) -> Result<()> { 99 | // coinbase tx is the first transaction in the block 100 | let coinbase_transaction = &self.transactions[0]; 101 | 102 | if coinbase_transaction.inputs.len() != 0 { 103 | return Err(BtcError::InvalidTransaction); 104 | } 105 | 106 | if coinbase_transaction.outputs.len() == 0 { 107 | return Err(BtcError::InvalidTransaction); 108 | } 109 | 110 | let miner_fees = self.calculate_miner_fees(utxos)?; 111 | let block_reward = crate::INITIAL_REWARD * 10u64.pow(8) 112 | / 2u64.pow( 113 | (predicted_block_height 114 | / crate::HALVING_INTERVAL) 115 | as u32, 116 | ); 117 | 118 | let total_coinbase_outputs: u64 = coinbase_transaction 119 | .outputs 120 | .iter() 121 | .map(|output| output.value) 122 | .sum(); 123 | 124 | if total_coinbase_outputs != block_reward + miner_fees { 125 | return Err(BtcError::InvalidTransaction); 126 | } 127 | 128 | Ok(()) 129 | } 130 | 131 | // Verify all transactions in the block 132 | pub fn verify_transactions( 133 | &self, 134 | predicted_block_height: u64, 135 | utxos: &HashMap, 136 | ) -> Result<()> { 137 | let mut inputs: HashMap = 138 | HashMap::new(); 139 | 140 | // reject completely empty blocks 141 | if self.transactions.is_empty() { 142 | return Err(BtcError::InvalidTransaction); 143 | } 144 | 145 | // verify coinbase transaction 146 | self.verify_coinbase_transaction( 147 | predicted_block_height, 148 | utxos, 149 | )?; 150 | 151 | for transaction in self.transactions.iter().skip(1) { 152 | let mut input_value = 0; 153 | let mut output_value = 0; 154 | 155 | for input in &transaction.inputs { 156 | let prev_output = utxos 157 | .get(&input.prev_transaction_output_hash) 158 | .map(|(_, output)| output); 159 | if prev_output.is_none() { 160 | return Err(BtcError::InvalidTransaction); 161 | } 162 | let prev_output = prev_output.unwrap(); 163 | 164 | // prevent same-block double-spending 165 | if inputs.contains_key( 166 | &input.prev_transaction_output_hash, 167 | ) { 168 | return Err(BtcError::InvalidTransaction); 169 | } 170 | 171 | // check if the signature is valid 172 | if !input.signature.verify( 173 | &input.prev_transaction_output_hash, 174 | &prev_output.pubkey, 175 | ) { 176 | return Err(BtcError::InvalidSignature); 177 | } 178 | 179 | input_value += prev_output.value; 180 | inputs.insert( 181 | input.prev_transaction_output_hash, 182 | prev_output.clone(), 183 | ); 184 | } 185 | 186 | for output in &transaction.outputs { 187 | output_value += output.value; 188 | } 189 | 190 | // It is fine for output value to be less than input value 191 | // as the difference is the fee for the miner 192 | if input_value < output_value { 193 | return Err(BtcError::InvalidTransaction); 194 | } 195 | } 196 | 197 | Ok(()) 198 | } 199 | } 200 | 201 | // save and load expecting CBOR from ciborium as format 202 | impl Saveable for Block { 203 | fn load(reader: I) -> IoResult { 204 | ciborium::de::from_reader(reader).map_err(|_| { 205 | IoError::new( 206 | IoErrorKind::InvalidData, 207 | "Failed to deserialize Block", 208 | ) 209 | }) 210 | } 211 | 212 | fn save(&self, writer: O) -> IoResult<()> { 213 | ciborium::ser::into_writer(self, writer).map_err(|_| { 214 | IoError::new( 215 | IoErrorKind::InvalidData, 216 | "Failed to serialize Block", 217 | ) 218 | }) 219 | } 220 | } 221 | 222 | #[derive(Serialize, Deserialize, Clone, Debug)] 223 | pub struct BlockHeader { 224 | /// Timestamp of the block 225 | pub timestamp: DateTime, 226 | /// Nonce used to mine the block 227 | pub nonce: u64, 228 | /// Hash of the previous block 229 | pub prev_block_hash: Hash, 230 | /// Merkle root of the block's transactions 231 | pub merkle_root: MerkleRoot, 232 | /// target 233 | pub target: U256, 234 | } 235 | 236 | impl BlockHeader { 237 | pub fn new( 238 | timestamp: DateTime, 239 | nonce: u64, 240 | prev_block_hash: Hash, 241 | merkle_root: MerkleRoot, 242 | target: U256, 243 | ) -> Self { 244 | BlockHeader { 245 | timestamp, 246 | nonce, 247 | prev_block_hash, 248 | merkle_root, 249 | target, 250 | } 251 | } 252 | 253 | pub fn mine(&mut self, steps: usize) -> bool { 254 | // if the block already matches target, return early 255 | if self.hash().matches_target(self.target) { 256 | return true; 257 | } 258 | 259 | for _ in 0..steps { 260 | if let Some(new_nonce) = self.nonce.checked_add(1) { 261 | self.nonce = new_nonce; 262 | } else { 263 | self.nonce = 0; 264 | self.timestamp = Utc::now() 265 | } 266 | 267 | if self.hash().matches_target(self.target) { 268 | return true; 269 | } 270 | } 271 | 272 | false 273 | } 274 | 275 | pub fn hash(&self) -> Hash { 276 | Hash::hash(self) 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /node/src/handler.rs: -------------------------------------------------------------------------------- 1 | use btclib::sha256::Hash; 2 | use chrono::Utc; 3 | use uuid::Uuid; 4 | 5 | use tokio::net::TcpStream; 6 | 7 | use btclib::network::Message; 8 | use btclib::types::{ 9 | Block, BlockHeader, Transaction, TransactionOutput, 10 | }; 11 | use btclib::util::MerkleRoot; 12 | 13 | pub async fn handle_connection(mut socket: TcpStream) { 14 | loop { 15 | // read a message from the socket 16 | let message = match Message::receive_async(&mut socket) 17 | .await 18 | { 19 | Ok(message) => message, 20 | Err(e) => { 21 | println!("invalid message from peer: {e}, closing that connection"); 22 | return; 23 | } 24 | }; 25 | 26 | use btclib::network::Message::*; 27 | match message { 28 | UTXOs(_) | Template(_) | Difference(_) 29 | | TemplateValidity(_) | NodeList(_) => { 30 | println!( 31 | "I am neither a miner nor a \ 32 | wallet! Goodbye" 33 | ); 34 | return; 35 | } 36 | FetchBlock(height) => { 37 | let blockchain = crate::BLOCKCHAIN.read().await; 38 | let Some(block) = blockchain 39 | .blocks() 40 | .nth(height as usize) 41 | .cloned() 42 | else { 43 | return; 44 | }; 45 | 46 | let message = NewBlock(block); 47 | message.send_async(&mut socket).await.unwrap(); 48 | } 49 | DiscoverNodes => { 50 | let nodes = crate::NODES 51 | .iter() 52 | .map(|x| x.key().clone()) 53 | .collect::>(); 54 | let message = NodeList(nodes); 55 | message.send_async(&mut socket).await.unwrap(); 56 | } 57 | AskDifference(height) => { 58 | let blockchain = crate::BLOCKCHAIN.read().await; 59 | let count = blockchain.block_height() as i32 60 | - height as i32; 61 | let message = Difference(count); 62 | message.send_async(&mut socket).await.unwrap(); 63 | } 64 | FetchUTXOs(key) => { 65 | println!("received request to fetch UTXOs"); 66 | let blockchain = crate::BLOCKCHAIN.read().await; 67 | 68 | let utxos = blockchain 69 | .utxos() 70 | .iter() 71 | .filter(|(_, (_, txout))| { 72 | txout.pubkey == key 73 | }) 74 | .map(|(_, (marked, txout))| { 75 | (txout.clone(), *marked) 76 | }) 77 | .collect::>(); 78 | 79 | let message = UTXOs(utxos); 80 | message.send_async(&mut socket).await.unwrap(); 81 | } 82 | 83 | NewBlock(block) => { 84 | let mut blockchain = 85 | crate::BLOCKCHAIN.write().await; 86 | println!("received new block"); 87 | 88 | if blockchain.add_block(block).is_err() { 89 | println!("block rejected"); 90 | } 91 | } 92 | NewTransaction(tx) => { 93 | let mut blockchain = 94 | crate::BLOCKCHAIN.write().await; 95 | 96 | println!("received transaction from friend"); 97 | 98 | if blockchain.add_to_mempool(tx).is_err() { 99 | println!("transaction rejected, closing connection"); 100 | return; 101 | } 102 | } 103 | ValidateTemplate(block_template) => { 104 | let blockchain = crate::BLOCKCHAIN.read().await; 105 | 106 | let status = 107 | block_template.header.prev_block_hash 108 | == blockchain 109 | .blocks() 110 | .last() 111 | .map(|last_block| last_block.hash()) 112 | .unwrap_or(Hash::zero()); 113 | 114 | let message = TemplateValidity(status); 115 | message.send_async(&mut socket).await.unwrap(); 116 | } 117 | SubmitTemplate(block) => { 118 | println!("received allegedly mined template"); 119 | let mut blockchain = 120 | crate::BLOCKCHAIN.write().await; 121 | if let Err(e) = 122 | blockchain.add_block(block.clone()) 123 | { 124 | println!( 125 | "block rejected: {e}, closing connection" 126 | ); 127 | return; 128 | } 129 | 130 | blockchain.rebuild_utxos(); 131 | 132 | println!("block looks good, broadcasting"); 133 | 134 | // send block to all friend nodes 135 | let nodes = crate::NODES 136 | .iter() 137 | .map(|x| x.key().clone()) 138 | .collect::>(); 139 | 140 | for node in nodes { 141 | if let Some(mut stream) = 142 | crate::NODES.get_mut(&node) 143 | { 144 | let message = 145 | Message::NewBlock(block.clone()); 146 | if message 147 | .send_async(&mut *stream) 148 | .await 149 | .is_err() 150 | { 151 | println!( 152 | "failed to send block to {}", 153 | node 154 | ); 155 | } 156 | } 157 | } 158 | } 159 | SubmitTransaction(tx) => { 160 | println!("submmit tx"); 161 | let mut blockchain = 162 | crate::BLOCKCHAIN.write().await; 163 | if let Err(e) = 164 | blockchain.add_to_mempool(tx.clone()) 165 | { 166 | println!("transaction rejected, closing connection: {e}"); 167 | return; 168 | } 169 | 170 | println!("added transaction to mempool"); 171 | 172 | // send transaction to all friend nodes 173 | let nodes = crate::NODES 174 | .iter() 175 | .map(|x| x.key().clone()) 176 | .collect::>(); 177 | 178 | for node in nodes { 179 | println!("sending to friend: {node}"); 180 | if let Some(mut stream) = 181 | crate::NODES.get_mut(&node) 182 | { 183 | let message = 184 | Message::NewTransaction(tx.clone()); 185 | if message 186 | .send_async(&mut *stream) 187 | .await 188 | .is_err() 189 | { 190 | println!("failed to send transaction to {}", node); 191 | } 192 | } 193 | } 194 | 195 | println!("transaction sent to friends"); 196 | } 197 | FetchTemplate(pubkey) => { 198 | let blockchain = crate::BLOCKCHAIN.read().await; 199 | 200 | let mut transactions = vec![]; 201 | // insert transactions from mempool 202 | transactions.extend( 203 | blockchain 204 | .mempool() 205 | .iter() 206 | .take(btclib::BLOCK_TRANSACTION_CAP) 207 | .map(|(_, tx)| tx) 208 | .cloned() 209 | .collect::>(), 210 | ); 211 | // insert coinbase tx with pubkey 212 | transactions.insert( 213 | 0, 214 | Transaction { 215 | inputs: vec![], 216 | outputs: vec![TransactionOutput { 217 | pubkey, 218 | unique_id: Uuid::new_v4(), 219 | value: 0, 220 | }], 221 | }, 222 | ); 223 | 224 | let merkle_root = 225 | MerkleRoot::calculate(&transactions); 226 | 227 | let mut block = Block::new( 228 | BlockHeader { 229 | timestamp: Utc::now(), 230 | prev_block_hash: blockchain 231 | .blocks() 232 | .last() 233 | .map(|last_block| last_block.hash()) 234 | .unwrap_or(Hash::zero()), 235 | nonce: 0, 236 | target: blockchain.target(), 237 | merkle_root, 238 | }, 239 | transactions, 240 | ); 241 | 242 | let miner_fees = match block 243 | .calculate_miner_fees(blockchain.utxos()) 244 | { 245 | Ok(fees) => fees, 246 | Err(e) => { 247 | eprintln!("{e}"); 248 | return; 249 | } 250 | }; 251 | 252 | let reward = blockchain.calculate_block_reward(); 253 | 254 | // update coinbase tx with reward 255 | block.transactions[0].outputs[0].value = 256 | reward + miner_fees; 257 | 258 | // recalculate merkle root 259 | block.header.merkle_root = 260 | MerkleRoot::calculate(&block.transactions); 261 | 262 | let message = Template(block); 263 | message.send_async(&mut socket).await.unwrap(); 264 | } 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /wallet/src/core.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use crossbeam_skiplist::SkipMap; 3 | use kanal::Sender; 4 | use serde::{Deserialize, Serialize}; 5 | use tokio::net::TcpStream; 6 | use tokio::sync::Mutex; 7 | use tracing::{debug, error, info}; 8 | 9 | use std::fs; 10 | use std::path::PathBuf; 11 | use std::sync::Arc; 12 | 13 | use btclib::crypto::{PrivateKey, PublicKey}; 14 | use btclib::network::Message; 15 | use btclib::types::{Transaction, TransactionOutput}; 16 | use btclib::util::Saveable; 17 | 18 | /// Represent a key pair with paths to public and private keys. 19 | #[derive(Serialize, Deserialize, Clone)] 20 | pub struct Key { 21 | pub public: PathBuf, 22 | pub private: PathBuf, 23 | } 24 | 25 | /// Represent a loaded key pair with actual public and private keys. 26 | #[derive(Clone)] 27 | struct LoadedKey { 28 | public: PublicKey, 29 | private: PrivateKey, 30 | } 31 | 32 | /// Represent a recipient with a name and a path to their public key. 33 | #[derive(Serialize, Deserialize, Clone)] 34 | pub struct Recipient { 35 | pub name: String, 36 | pub key: PathBuf, 37 | } 38 | 39 | /// Represent a loaded recipient with their actual public key. 40 | #[derive(Clone)] 41 | pub struct LoadedRecipient { 42 | pub key: PublicKey, 43 | } 44 | 45 | impl Recipient { 46 | /// Load the recipient's public key from file. 47 | pub fn load(&self) -> Result { 48 | debug!("Loading recipient key from: {:?}", self.key); 49 | let key = PublicKey::load_from_file(&self.key)?; 50 | Ok(LoadedRecipient { key }) 51 | } 52 | } 53 | 54 | /// Define the type of fee calculation. 55 | #[derive(Serialize, Deserialize, Clone)] 56 | pub enum FeeType { 57 | Fixed, 58 | Percent, 59 | } 60 | 61 | /// Configure the fee calculation. 62 | #[derive(Serialize, Deserialize, Clone)] 63 | pub struct FeeConfig { 64 | pub fee_type: FeeType, 65 | pub value: f64, 66 | } 67 | 68 | /// Store the configuration for the Core. 69 | #[derive(Serialize, Deserialize, Clone)] 70 | pub struct Config { 71 | pub my_keys: Vec, 72 | pub contacts: Vec, 73 | pub default_node: String, 74 | pub fee_config: FeeConfig, 75 | } 76 | 77 | /// Store and manage Unspent Transaction Outputs (UTXOs). 78 | #[derive(Clone)] 79 | struct UtxoStore { 80 | my_keys: Vec, 81 | utxos: 82 | Arc>>, 83 | } 84 | 85 | impl UtxoStore { 86 | /// Create a new UtxoStore. 87 | fn new() -> Self { 88 | UtxoStore { 89 | my_keys: Vec::new(), 90 | utxos: Arc::new(SkipMap::new()), 91 | } 92 | } 93 | 94 | /// Add a new key to the UtxoStore. 95 | fn add_key(&mut self, key: LoadedKey) { 96 | debug!("Adding key to UtxoStore: {:?}", key.public); 97 | self.my_keys.push(key); 98 | } 99 | } 100 | 101 | /// Represent the core functionality of the wallet. 102 | pub struct Core { 103 | pub config: Config, 104 | utxos: UtxoStore, 105 | pub tx_sender: Sender, 106 | pub stream: Mutex, 107 | } 108 | 109 | impl Core { 110 | /// Create a new Core instance. 111 | fn new( 112 | config: Config, 113 | utxos: UtxoStore, 114 | stream: TcpStream, 115 | ) -> Self { 116 | let (tx_sender, _) = kanal::bounded(10); 117 | Core { 118 | config, 119 | utxos, 120 | tx_sender, 121 | stream: Mutex::new(stream), 122 | } 123 | } 124 | 125 | /// Load the Core from a configuration file. 126 | pub async fn load(config_path: PathBuf) -> Result { 127 | info!("Loading core from config: {:?}", config_path); 128 | let config: Config = 129 | toml::from_str(&fs::read_to_string(&config_path)?)?; 130 | let mut utxos = UtxoStore::new(); 131 | 132 | let stream = 133 | TcpStream::connect(&config.default_node).await?; 134 | 135 | // Load keys from config 136 | for key in &config.my_keys { 137 | debug!("Loading key pair: {:?}", key.public); 138 | let public = PublicKey::load_from_file(&key.public)?; 139 | let private = 140 | PrivateKey::load_from_file(&key.private)?; 141 | utxos.add_key(LoadedKey { public, private }); 142 | } 143 | 144 | Ok(Core::new(config, utxos, stream)) 145 | } 146 | 147 | /// Fetch UTXOs from the node for all loaded keys. 148 | pub async fn fetch_utxos(&self) -> Result<()> { 149 | debug!( 150 | "Fetching UTXOs from node: {}", 151 | self.config.default_node 152 | ); 153 | 154 | for key in &self.utxos.my_keys { 155 | let message = 156 | Message::FetchUTXOs(key.public.clone()); 157 | message 158 | .send_async(&mut *self.stream.lock().await) 159 | .await?; 160 | 161 | if let Message::UTXOs(utxos) = 162 | Message::receive_async( 163 | &mut *self.stream.lock().await, 164 | ) 165 | .await? 166 | { 167 | debug!( 168 | "Received {} UTXOs for key: {:?}", 169 | utxos.len(), 170 | key.public 171 | ); 172 | // Replace the entire UTXO set for this key 173 | self.utxos.utxos.insert( 174 | key.public.clone(), 175 | utxos 176 | .into_iter() 177 | .map(|(output, marked)| (marked, output)) 178 | .collect(), 179 | ); 180 | } else { 181 | error!("Unexpected response from node"); 182 | return Err(anyhow::anyhow!( 183 | "Unexpected response from node" 184 | )); 185 | } 186 | } 187 | 188 | info!("UTXOs fetched successfully"); 189 | Ok(()) 190 | } 191 | 192 | /// Send a transaction to the node. 193 | pub async fn send_transaction( 194 | &self, 195 | transaction: Transaction, 196 | ) -> Result<()> { 197 | debug!( 198 | "Sending transaction to node: {}", 199 | self.config.default_node 200 | ); 201 | let message = Message::SubmitTransaction(transaction); 202 | message 203 | .send_async(&mut *self.stream.lock().await) 204 | .await?; 205 | info!("Transaction sent successfully"); 206 | Ok(()) 207 | } 208 | 209 | /// Prepare and send a transaction asynchronously. 210 | pub fn send_transaction_async( 211 | &self, 212 | recipient: &str, 213 | amount: u64, 214 | ) -> Result<()> { 215 | info!( 216 | "Preparing to send {} satoshis to {}", 217 | amount, recipient 218 | ); 219 | let recipient_key = self 220 | .config 221 | .contacts 222 | .iter() 223 | .find(|r| r.name == recipient) 224 | .ok_or_else(|| { 225 | anyhow::anyhow!("Recipient not found") 226 | })? 227 | .load()? 228 | .key; 229 | 230 | let transaction = 231 | self.create_transaction(&recipient_key, amount)?; 232 | 233 | debug!("Sending transaction asynchronously"); 234 | self.tx_sender.send(transaction)?; 235 | Ok(()) 236 | } 237 | 238 | /// Get the current balance of all UTXOs. 239 | pub fn get_balance(&self) -> u64 { 240 | let balance = self 241 | .utxos 242 | .utxos 243 | .iter() 244 | .map(|entry| { 245 | entry 246 | .value() 247 | .iter() 248 | .map(|utxo| utxo.1.value) 249 | .sum::() 250 | }) 251 | .sum(); 252 | debug!("Current balance: {} satoshis", balance); 253 | balance 254 | } 255 | 256 | /// Create a new transaction. 257 | pub fn create_transaction( 258 | &self, 259 | recipient: &PublicKey, 260 | amount: u64, 261 | ) -> Result { 262 | debug!( 263 | "Creating transaction for {} satoshis to {:?}", 264 | amount, recipient 265 | ); 266 | let fee = self.calculate_fee(amount); 267 | let total_amount = amount + fee; 268 | 269 | let mut inputs = Vec::new(); 270 | let mut input_sum = 0; 271 | 272 | for entry in self.utxos.utxos.iter() { 273 | let pubkey = entry.key(); 274 | let utxos = entry.value(); 275 | 276 | for (marked, utxo) in utxos.iter() { 277 | if *marked { 278 | continue; 279 | } // Skip marked UTXOs 280 | if input_sum >= total_amount { 281 | break; 282 | } 283 | inputs.push(btclib::types::TransactionInput { 284 | prev_transaction_output_hash: utxo.hash(), 285 | signature: 286 | btclib::crypto::Signature::sign_output( 287 | &utxo.hash(), 288 | &self 289 | .utxos 290 | .my_keys 291 | .iter() 292 | .find(|k| k.public == *pubkey) 293 | .unwrap() 294 | .private, 295 | ), 296 | }); 297 | input_sum += utxo.value; 298 | } 299 | if input_sum >= total_amount { 300 | break; 301 | } 302 | } 303 | 304 | if input_sum < total_amount { 305 | error!("Insufficient funds: have {} satoshis, need {} satoshis", input_sum, total_amount); 306 | return Err(anyhow::anyhow!("Insufficient funds")); 307 | } 308 | 309 | let mut outputs = vec![TransactionOutput { 310 | value: amount, 311 | unique_id: uuid::Uuid::new_v4(), 312 | pubkey: recipient.clone(), 313 | }]; 314 | 315 | if input_sum > total_amount { 316 | outputs.push(TransactionOutput { 317 | value: input_sum - total_amount, 318 | unique_id: uuid::Uuid::new_v4(), 319 | pubkey: self.utxos.my_keys[0].public.clone(), 320 | }); 321 | } 322 | 323 | info!("Transaction created successfully"); 324 | Ok(Transaction::new(inputs, outputs)) 325 | } 326 | 327 | /// Calculate the fee for a transaction. 328 | fn calculate_fee(&self, amount: u64) -> u64 { 329 | let fee = match self.config.fee_config.fee_type { 330 | FeeType::Fixed => { 331 | self.config.fee_config.value as u64 332 | } 333 | FeeType::Percent => { 334 | (amount as f64 * self.config.fee_config.value 335 | / 100.0) as u64 336 | } 337 | }; 338 | debug!("Calculated fee: {} satoshis", fee); 339 | fee 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /lib/src/types/blockchain.rs: -------------------------------------------------------------------------------- 1 | use bigdecimal::BigDecimal; 2 | use chrono::{DateTime, Utc}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::util::Saveable; 6 | use std::io::{ 7 | Error as IoError, ErrorKind as IoErrorKind, Read, 8 | Result as IoResult, Write, 9 | }; 10 | 11 | use super::{Block, Transaction, TransactionOutput}; 12 | use crate::error::{BtcError, Result}; 13 | use crate::sha256::Hash; 14 | use crate::util::MerkleRoot; 15 | use crate::U256; 16 | 17 | use std::collections::{HashMap, HashSet}; 18 | 19 | #[derive(Serialize, Deserialize, Clone, Debug)] 20 | pub struct Blockchain { 21 | utxos: HashMap, 22 | target: U256, 23 | blocks: Vec, 24 | #[serde(default, skip_serializing)] 25 | mempool: Vec<(DateTime, Transaction)>, 26 | } 27 | 28 | impl Blockchain { 29 | pub fn new() -> Self { 30 | Blockchain { 31 | utxos: HashMap::new(), 32 | blocks: vec![], 33 | target: crate::MIN_TARGET, 34 | mempool: vec![], 35 | } 36 | } 37 | 38 | // try to add a new block to the blockchain, 39 | // return an error if it is not valid to insert this 40 | // block to this blockchain 41 | pub fn add_block(&mut self, block: Block) -> Result<()> { 42 | // check if the block is valid 43 | if self.blocks.is_empty() { 44 | // if this is the first block, check if the 45 | // block's prev_block_hash is all zeroes 46 | if block.header.prev_block_hash != Hash::zero() { 47 | println!("zero hash"); 48 | return Err(BtcError::InvalidBlock); 49 | } 50 | } else { 51 | // if this is not the first block, check if the 52 | // block's prev_block_hash is the hash of the last block 53 | let last_block = self.blocks.last().unwrap(); 54 | if block.header.prev_block_hash != last_block.hash() 55 | { 56 | println!("prev hash is wrong"); 57 | return Err(BtcError::InvalidBlock); 58 | } 59 | 60 | // check if the block's hash is less than the target 61 | if !block 62 | .header 63 | .hash() 64 | .matches_target(block.header.target) 65 | { 66 | println!("does not match target"); 67 | return Err(BtcError::InvalidBlock); 68 | } 69 | 70 | // check if the block's merkle root is correct 71 | let calculated_merkle_root = 72 | MerkleRoot::calculate(&block.transactions); 73 | if calculated_merkle_root != block.header.merkle_root 74 | { 75 | return Err(BtcError::InvalidMerkleRoot); 76 | } 77 | 78 | // check if the block's timestamp is after the 79 | // last block's timestamp 80 | if block.header.timestamp 81 | <= last_block.header.timestamp 82 | { 83 | println!("old timestamp"); 84 | return Err(BtcError::InvalidBlock); 85 | } 86 | 87 | // Verify all transactions in the block 88 | block.verify_transactions( 89 | self.block_height(), 90 | &self.utxos, 91 | )?; 92 | } 93 | 94 | // Remove transactions from mempool that are now in the block 95 | let block_transactions: HashSet<_> = block 96 | .transactions 97 | .iter() 98 | .map(|tx| tx.hash()) 99 | .collect(); 100 | self.mempool.retain(|(_, tx)| { 101 | !block_transactions.contains(&tx.hash()) 102 | }); 103 | 104 | self.blocks.push(block); 105 | self.try_adjust_target(); 106 | 107 | Ok(()) 108 | } 109 | 110 | // try to adjust the target of the blockchain 111 | pub fn try_adjust_target(&mut self) { 112 | if self.blocks.len() 113 | < crate::DIFFICULTY_UPDATE_INTERVAL as usize 114 | { 115 | return; 116 | } 117 | 118 | if self.blocks.len() 119 | % crate::DIFFICULTY_UPDATE_INTERVAL as usize 120 | != 0 121 | { 122 | println!("won't update on this block"); 123 | return; 124 | } 125 | 126 | println!("actually updating"); 127 | // measure the time it took to mine the last 128 | // crate::DIFFICULTY_UPDATE_INTERVAL blocks 129 | // with chrono 130 | let start_time = self.blocks[self.blocks.len() 131 | - crate::DIFFICULTY_UPDATE_INTERVAL as usize] 132 | .header 133 | .timestamp; 134 | let end_time = 135 | self.blocks.last().unwrap().header.timestamp; 136 | let time_diff = end_time - start_time; 137 | 138 | // convert time_diff to seconds 139 | let time_diff_seconds = time_diff.num_seconds(); 140 | // calculate the ideal number of seconds 141 | let target_seconds = crate::IDEAL_BLOCK_TIME 142 | * crate::DIFFICULTY_UPDATE_INTERVAL; 143 | 144 | // multiply the current target by actual time divided by ideal time 145 | let new_target = BigDecimal::parse_bytes( 146 | &self.target.to_string().as_bytes(), 147 | 10, 148 | ) 149 | .expect("BUG: impossible") 150 | * (BigDecimal::from(time_diff_seconds) 151 | / BigDecimal::from(target_seconds)); 152 | 153 | // cut off decimal point and everything after 154 | // it from string representation of new_target 155 | let new_target_str = new_target 156 | .to_string() 157 | .split('.') 158 | .next() 159 | .expect("BUG: Expected a decimal point") 160 | .to_owned(); 161 | 162 | let new_target: U256 = 163 | U256::from_str_radix(&new_target_str, 10) 164 | .expect("BUG: impossible"); 165 | 166 | dbg!(new_target); 167 | 168 | // clamp new_target to be within the range of 169 | // 4 * self.target and self.target / 4 170 | let new_target = if new_target < self.target / 4 { 171 | dbg!(self.target / 4) 172 | } else if new_target > self.target * 4 { 173 | dbg!(self.target * 4) 174 | } else { 175 | new_target 176 | }; 177 | 178 | dbg!(new_target); 179 | 180 | // if the new target is more than the minimum target, 181 | // set it to the minimum target 182 | self.target = new_target.min(crate::MIN_TARGET); 183 | dbg!(self.target); 184 | } 185 | 186 | // Rebuild UTXO set from the blockchain 187 | pub fn rebuild_utxos(&mut self) { 188 | for block in &self.blocks { 189 | for transaction in &block.transactions { 190 | for input in &transaction.inputs { 191 | self.utxos.remove( 192 | &input.prev_transaction_output_hash, 193 | ); 194 | } 195 | 196 | for output in transaction.outputs.iter() { 197 | self.utxos.insert( 198 | output.hash(), 199 | (false, output.clone()), 200 | ); 201 | } 202 | } 203 | } 204 | } 205 | 206 | pub fn calculate_block_reward(&self) -> u64 { 207 | let block_height = self.block_height(); 208 | let halvings = block_height / crate::HALVING_INTERVAL; 209 | 210 | if halvings >= 64 { 211 | // After 64 halvings, the reward becomes 0 212 | 0 213 | } else { 214 | (crate::INITIAL_REWARD * 10u64.pow(8)) >> halvings 215 | } 216 | } 217 | 218 | // utxos 219 | pub fn utxos( 220 | &self, 221 | ) -> &HashMap { 222 | &self.utxos 223 | } 224 | 225 | // target 226 | pub fn target(&self) -> U256 { 227 | self.target 228 | } 229 | 230 | // blocks 231 | pub fn blocks(&self) -> impl Iterator { 232 | self.blocks.iter() 233 | } 234 | 235 | // block height 236 | pub fn block_height(&self) -> u64 { 237 | self.blocks.len() as u64 238 | } 239 | 240 | // mempool 241 | pub fn mempool(&self) -> &[(DateTime, Transaction)] { 242 | &self.mempool 243 | } 244 | 245 | // add a transaction to mempool 246 | pub fn add_to_mempool( 247 | &mut self, 248 | transaction: Transaction, 249 | ) -> Result<()> { 250 | // validate transaction before insertion 251 | // all inputs must match known UTXOs, and must be unique 252 | let mut known_inputs = HashSet::new(); 253 | for input in &transaction.inputs { 254 | if !self.utxos.contains_key( 255 | &input.prev_transaction_output_hash, 256 | ) { 257 | println!("UTXO not found"); 258 | dbg!(&self.utxos); 259 | return Err(BtcError::InvalidTransaction); 260 | } 261 | 262 | if known_inputs 263 | .contains(&input.prev_transaction_output_hash) 264 | { 265 | println!("duplicate input"); 266 | return Err(BtcError::InvalidTransaction); 267 | } 268 | 269 | known_inputs 270 | .insert(input.prev_transaction_output_hash); 271 | } 272 | 273 | // check if any of the utxos have the bool mark set to true 274 | // and if so, find the transaction that references them 275 | // in mempool, remove it, and set all the utxos it references 276 | // to false 277 | for input in &transaction.inputs { 278 | if let Some((true, _)) = self 279 | .utxos 280 | .get(&input.prev_transaction_output_hash) 281 | { 282 | // find the transaction that references the UTXO 283 | // we are trying to reference 284 | let referencing_transaction = self.mempool 285 | .iter() 286 | .enumerate() 287 | .find( 288 | |(_, (_, transaction))| { 289 | transaction 290 | .outputs 291 | .iter() 292 | .any(|output| { 293 | output.hash() 294 | == input.prev_transaction_output_hash 295 | }) 296 | }, 297 | ); 298 | 299 | // If we have found one, unmark all of its UTXOs 300 | if let Some(( 301 | idx, 302 | (_, referencing_transaction), 303 | )) = referencing_transaction 304 | { 305 | for input in &referencing_transaction.inputs 306 | { 307 | // set all utxos from this transaction to false 308 | self.utxos 309 | .entry(input.prev_transaction_output_hash) 310 | .and_modify(|(marked, _)| { 311 | *marked = false; 312 | }); 313 | } 314 | 315 | // remove the transaction from the mempool 316 | self.mempool.remove(idx); 317 | } else { 318 | // if, somehow, there is no matching transaction, 319 | // set this utxo to false 320 | self.utxos 321 | .entry( 322 | input.prev_transaction_output_hash, 323 | ) 324 | .and_modify(|(marked, _)| { 325 | *marked = false; 326 | }); 327 | } 328 | } 329 | } 330 | 331 | // all inputs must be lower than all outputs 332 | let all_inputs = transaction 333 | .inputs 334 | .iter() 335 | .map(|input| { 336 | self.utxos 337 | .get(&input.prev_transaction_output_hash) 338 | .expect("BUG: impossible") 339 | .1 340 | .value 341 | }) 342 | .sum::(); 343 | let all_outputs = transaction 344 | .outputs 345 | .iter() 346 | .map(|output| output.value) 347 | .sum(); 348 | 349 | if all_inputs < all_outputs { 350 | print!("inputs are lower than outputs"); 351 | return Err(BtcError::InvalidTransaction); 352 | } 353 | 354 | // Mark the UTXOs as used 355 | for input in &transaction.inputs { 356 | self.utxos 357 | .entry(input.prev_transaction_output_hash) 358 | .and_modify(|(marked, _)| { 359 | *marked = true; 360 | }); 361 | } 362 | 363 | // push the transaction to the mempool 364 | self.mempool.push((Utc::now(), transaction)); 365 | 366 | // sort by miner fee 367 | self.mempool.sort_by_key(|(_, transaction)| { 368 | let all_inputs = transaction 369 | .inputs 370 | .iter() 371 | .map(|input| { 372 | self.utxos 373 | .get(&input.prev_transaction_output_hash) 374 | .expect("BUG: impossible") 375 | .1 376 | .value 377 | }) 378 | .sum::(); 379 | 380 | let all_outputs: u64 = transaction 381 | .outputs 382 | .iter() 383 | .map(|output| output.value) 384 | .sum(); 385 | 386 | let miner_fee = all_inputs - all_outputs; 387 | miner_fee 388 | }); 389 | 390 | Ok(()) 391 | } 392 | 393 | // Cleanup mempool - remove transactions older than 394 | // MAX_MEMPOOL_TRANSACTION_AGE 395 | pub fn cleanup_mempool(&mut self) { 396 | let now = Utc::now(); 397 | let mut utxo_hashes_to_unmark: Vec = vec![]; 398 | 399 | self.mempool.retain(|(timestamp, transaction)| { 400 | if now - *timestamp 401 | > chrono::Duration::seconds( 402 | crate::MAX_MEMPOOL_TRANSACTION_AGE as i64, 403 | ) 404 | { 405 | // push all utxos to unmark to the vector 406 | // so we can unmark them later 407 | utxo_hashes_to_unmark.extend( 408 | transaction.inputs.iter().map(|input| { 409 | input.prev_transaction_output_hash 410 | }), 411 | ); 412 | 413 | false 414 | } else { 415 | true 416 | } 417 | }); 418 | 419 | // unmark all of the UTXOs 420 | for hash in utxo_hashes_to_unmark { 421 | self.utxos.entry(hash).and_modify(|(marked, _)| { 422 | *marked = false; 423 | }); 424 | } 425 | } 426 | } 427 | 428 | // save and load expecting CBOR from ciborium as format 429 | impl Saveable for Blockchain { 430 | fn load(reader: I) -> IoResult { 431 | ciborium::de::from_reader(reader).map_err(|_| { 432 | IoError::new( 433 | IoErrorKind::InvalidData, 434 | "Failed to deserialize Blockchain", 435 | ) 436 | }) 437 | } 438 | 439 | fn save(&self, writer: O) -> IoResult<()> { 440 | ciborium::ser::into_writer(self, writer).map_err(|_| { 441 | IoError::new( 442 | IoErrorKind::InvalidData, 443 | "Failed to serialize Blockchain", 444 | ) 445 | }) 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.22.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "ahash" 22 | version = "0.8.11" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 25 | dependencies = [ 26 | "cfg-if", 27 | "getrandom", 28 | "once_cell", 29 | "version_check", 30 | "zerocopy", 31 | ] 32 | 33 | [[package]] 34 | name = "aho-corasick" 35 | version = "1.1.3" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 38 | dependencies = [ 39 | "memchr", 40 | ] 41 | 42 | [[package]] 43 | name = "android-tzdata" 44 | version = "0.1.1" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 47 | 48 | [[package]] 49 | name = "android_system_properties" 50 | version = "0.1.5" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 53 | dependencies = [ 54 | "libc", 55 | ] 56 | 57 | [[package]] 58 | name = "anstream" 59 | version = "0.6.14" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 62 | dependencies = [ 63 | "anstyle", 64 | "anstyle-parse", 65 | "anstyle-query", 66 | "anstyle-wincon", 67 | "colorchoice", 68 | "is_terminal_polyfill", 69 | "utf8parse", 70 | ] 71 | 72 | [[package]] 73 | name = "anstyle" 74 | version = "1.0.7" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 77 | 78 | [[package]] 79 | name = "anstyle-parse" 80 | version = "0.2.4" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 83 | dependencies = [ 84 | "utf8parse", 85 | ] 86 | 87 | [[package]] 88 | name = "anstyle-query" 89 | version = "1.1.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" 92 | dependencies = [ 93 | "windows-sys 0.52.0", 94 | ] 95 | 96 | [[package]] 97 | name = "anstyle-wincon" 98 | version = "3.0.3" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 101 | dependencies = [ 102 | "anstyle", 103 | "windows-sys 0.52.0", 104 | ] 105 | 106 | [[package]] 107 | name = "anyhow" 108 | version = "1.0.86" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 111 | 112 | [[package]] 113 | name = "argh" 114 | version = "0.1.12" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219" 117 | dependencies = [ 118 | "argh_derive", 119 | "argh_shared", 120 | ] 121 | 122 | [[package]] 123 | name = "argh_derive" 124 | version = "0.1.12" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" 127 | dependencies = [ 128 | "argh_shared", 129 | "proc-macro2", 130 | "quote", 131 | "syn 2.0.68", 132 | ] 133 | 134 | [[package]] 135 | name = "argh_shared" 136 | version = "0.1.12" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531" 139 | dependencies = [ 140 | "serde", 141 | ] 142 | 143 | [[package]] 144 | name = "async-trait" 145 | version = "0.1.80" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" 148 | dependencies = [ 149 | "proc-macro2", 150 | "quote", 151 | "syn 2.0.68", 152 | ] 153 | 154 | [[package]] 155 | name = "autocfg" 156 | version = "1.3.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 159 | 160 | [[package]] 161 | name = "backtrace" 162 | version = "0.3.73" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" 165 | dependencies = [ 166 | "addr2line", 167 | "cc", 168 | "cfg-if", 169 | "libc", 170 | "miniz_oxide", 171 | "object", 172 | "rustc-demangle", 173 | ] 174 | 175 | [[package]] 176 | name = "base16ct" 177 | version = "0.2.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" 180 | 181 | [[package]] 182 | name = "base64ct" 183 | version = "1.6.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 186 | 187 | [[package]] 188 | name = "bigdecimal" 189 | version = "0.4.5" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "51d712318a27c7150326677b321a5fa91b55f6d9034ffd67f20319e147d40cee" 192 | dependencies = [ 193 | "autocfg", 194 | "libm", 195 | "num-bigint", 196 | "num-integer", 197 | "num-traits", 198 | ] 199 | 200 | [[package]] 201 | name = "bitflags" 202 | version = "1.3.2" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 205 | 206 | [[package]] 207 | name = "bitflags" 208 | version = "2.6.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 211 | 212 | [[package]] 213 | name = "block-buffer" 214 | version = "0.10.4" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 217 | dependencies = [ 218 | "generic-array", 219 | ] 220 | 221 | [[package]] 222 | name = "btclib" 223 | version = "0.1.0" 224 | dependencies = [ 225 | "bigdecimal", 226 | "chrono", 227 | "ciborium", 228 | "ecdsa", 229 | "flume", 230 | "hex", 231 | "k256", 232 | "rand", 233 | "serde", 234 | "sha256", 235 | "spki", 236 | "thiserror", 237 | "tokio", 238 | "uint", 239 | "uuid", 240 | ] 241 | 242 | [[package]] 243 | name = "bumpalo" 244 | version = "3.16.0" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 247 | 248 | [[package]] 249 | name = "byteorder" 250 | version = "1.5.0" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 253 | 254 | [[package]] 255 | name = "bytes" 256 | version = "1.6.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 259 | 260 | [[package]] 261 | name = "cc" 262 | version = "1.0.104" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" 265 | 266 | [[package]] 267 | name = "cfg-if" 268 | version = "1.0.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 271 | 272 | [[package]] 273 | name = "cfg_aliases" 274 | version = "0.1.1" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" 277 | 278 | [[package]] 279 | name = "chrono" 280 | version = "0.4.38" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 283 | dependencies = [ 284 | "android-tzdata", 285 | "iana-time-zone", 286 | "js-sys", 287 | "num-traits", 288 | "serde", 289 | "wasm-bindgen", 290 | "windows-targets 0.52.6", 291 | ] 292 | 293 | [[package]] 294 | name = "ciborium" 295 | version = "0.2.2" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" 298 | dependencies = [ 299 | "ciborium-io", 300 | "ciborium-ll", 301 | "serde", 302 | ] 303 | 304 | [[package]] 305 | name = "ciborium-io" 306 | version = "0.2.2" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" 309 | 310 | [[package]] 311 | name = "ciborium-ll" 312 | version = "0.2.2" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" 315 | dependencies = [ 316 | "ciborium-io", 317 | "half", 318 | ] 319 | 320 | [[package]] 321 | name = "clap" 322 | version = "4.5.8" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" 325 | dependencies = [ 326 | "clap_builder", 327 | "clap_derive", 328 | ] 329 | 330 | [[package]] 331 | name = "clap_builder" 332 | version = "4.5.8" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" 335 | dependencies = [ 336 | "anstream", 337 | "anstyle", 338 | "clap_lex", 339 | "strsim", 340 | ] 341 | 342 | [[package]] 343 | name = "clap_derive" 344 | version = "4.5.8" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" 347 | dependencies = [ 348 | "heck", 349 | "proc-macro2", 350 | "quote", 351 | "syn 2.0.68", 352 | ] 353 | 354 | [[package]] 355 | name = "clap_lex" 356 | version = "0.7.1" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" 359 | 360 | [[package]] 361 | name = "colorchoice" 362 | version = "1.0.1" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 365 | 366 | [[package]] 367 | name = "const-oid" 368 | version = "0.9.6" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 371 | 372 | [[package]] 373 | name = "core-foundation-sys" 374 | version = "0.8.6" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 377 | 378 | [[package]] 379 | name = "cpufeatures" 380 | version = "0.2.12" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 383 | dependencies = [ 384 | "libc", 385 | ] 386 | 387 | [[package]] 388 | name = "crossbeam-channel" 389 | version = "0.5.13" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" 392 | dependencies = [ 393 | "crossbeam-utils", 394 | ] 395 | 396 | [[package]] 397 | name = "crossbeam-epoch" 398 | version = "0.9.18" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 401 | dependencies = [ 402 | "crossbeam-utils", 403 | ] 404 | 405 | [[package]] 406 | name = "crossbeam-skiplist" 407 | version = "0.1.3" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b" 410 | dependencies = [ 411 | "crossbeam-epoch", 412 | "crossbeam-utils", 413 | ] 414 | 415 | [[package]] 416 | name = "crossbeam-utils" 417 | version = "0.8.20" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 420 | 421 | [[package]] 422 | name = "crunchy" 423 | version = "0.2.2" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 426 | 427 | [[package]] 428 | name = "crypto-bigint" 429 | version = "0.5.5" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" 432 | dependencies = [ 433 | "generic-array", 434 | "rand_core", 435 | "subtle", 436 | "zeroize", 437 | ] 438 | 439 | [[package]] 440 | name = "crypto-common" 441 | version = "0.1.6" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 444 | dependencies = [ 445 | "generic-array", 446 | "typenum", 447 | ] 448 | 449 | [[package]] 450 | name = "cursive" 451 | version = "0.20.0" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "5438eb16bdd8af51b31e74764fef5d0a9260227a5ec82ba75c9d11ce46595839" 454 | dependencies = [ 455 | "ahash", 456 | "cfg-if", 457 | "crossbeam-channel", 458 | "cursive_core", 459 | "lazy_static", 460 | "libc", 461 | "log", 462 | "maplit", 463 | "ncurses", 464 | "signal-hook", 465 | "term_size", 466 | "unicode-segmentation", 467 | "unicode-width", 468 | ] 469 | 470 | [[package]] 471 | name = "cursive_core" 472 | version = "0.3.7" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "4db3b58161228d0dcb45c7968c5e74c3f03ad39e8983e58ad7d57061aa2cd94d" 475 | dependencies = [ 476 | "ahash", 477 | "crossbeam-channel", 478 | "enum-map", 479 | "enumset", 480 | "lazy_static", 481 | "log", 482 | "num", 483 | "owning_ref", 484 | "time", 485 | "unicode-segmentation", 486 | "unicode-width", 487 | "xi-unicode", 488 | ] 489 | 490 | [[package]] 491 | name = "darling" 492 | version = "0.20.10" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 495 | dependencies = [ 496 | "darling_core", 497 | "darling_macro", 498 | ] 499 | 500 | [[package]] 501 | name = "darling_core" 502 | version = "0.20.10" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 505 | dependencies = [ 506 | "fnv", 507 | "ident_case", 508 | "proc-macro2", 509 | "quote", 510 | "syn 2.0.68", 511 | ] 512 | 513 | [[package]] 514 | name = "darling_macro" 515 | version = "0.20.10" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 518 | dependencies = [ 519 | "darling_core", 520 | "quote", 521 | "syn 2.0.68", 522 | ] 523 | 524 | [[package]] 525 | name = "dashmap" 526 | version = "5.5.3" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" 529 | dependencies = [ 530 | "cfg-if", 531 | "hashbrown", 532 | "lock_api", 533 | "once_cell", 534 | "parking_lot_core 0.9.10", 535 | ] 536 | 537 | [[package]] 538 | name = "der" 539 | version = "0.7.9" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" 542 | dependencies = [ 543 | "const-oid", 544 | "pem-rfc7468", 545 | "zeroize", 546 | ] 547 | 548 | [[package]] 549 | name = "deranged" 550 | version = "0.3.11" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 553 | dependencies = [ 554 | "powerfmt", 555 | ] 556 | 557 | [[package]] 558 | name = "digest" 559 | version = "0.10.7" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 562 | dependencies = [ 563 | "block-buffer", 564 | "const-oid", 565 | "crypto-common", 566 | "subtle", 567 | ] 568 | 569 | [[package]] 570 | name = "ecdsa" 571 | version = "0.16.9" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" 574 | dependencies = [ 575 | "der", 576 | "digest", 577 | "elliptic-curve", 578 | "rfc6979", 579 | "serdect", 580 | "signature", 581 | "spki", 582 | ] 583 | 584 | [[package]] 585 | name = "elliptic-curve" 586 | version = "0.13.8" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" 589 | dependencies = [ 590 | "base16ct", 591 | "crypto-bigint", 592 | "digest", 593 | "ff", 594 | "generic-array", 595 | "group", 596 | "pem-rfc7468", 597 | "pkcs8", 598 | "rand_core", 599 | "sec1", 600 | "serdect", 601 | "subtle", 602 | "zeroize", 603 | ] 604 | 605 | [[package]] 606 | name = "enum-map" 607 | version = "2.7.3" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" 610 | dependencies = [ 611 | "enum-map-derive", 612 | ] 613 | 614 | [[package]] 615 | name = "enum-map-derive" 616 | version = "0.17.0" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" 619 | dependencies = [ 620 | "proc-macro2", 621 | "quote", 622 | "syn 2.0.68", 623 | ] 624 | 625 | [[package]] 626 | name = "enumset" 627 | version = "1.1.3" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d" 630 | dependencies = [ 631 | "enumset_derive", 632 | ] 633 | 634 | [[package]] 635 | name = "enumset_derive" 636 | version = "0.8.1" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" 639 | dependencies = [ 640 | "darling", 641 | "proc-macro2", 642 | "quote", 643 | "syn 2.0.68", 644 | ] 645 | 646 | [[package]] 647 | name = "equivalent" 648 | version = "1.0.1" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 651 | 652 | [[package]] 653 | name = "ff" 654 | version = "0.13.0" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" 657 | dependencies = [ 658 | "rand_core", 659 | "subtle", 660 | ] 661 | 662 | [[package]] 663 | name = "flume" 664 | version = "0.11.0" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" 667 | dependencies = [ 668 | "futures-core", 669 | "futures-sink", 670 | "nanorand", 671 | "spin", 672 | ] 673 | 674 | [[package]] 675 | name = "fnv" 676 | version = "1.0.7" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 679 | 680 | [[package]] 681 | name = "futures-core" 682 | version = "0.3.30" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 685 | 686 | [[package]] 687 | name = "futures-sink" 688 | version = "0.3.30" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 691 | 692 | [[package]] 693 | name = "generic-array" 694 | version = "0.14.7" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 697 | dependencies = [ 698 | "typenum", 699 | "version_check", 700 | "zeroize", 701 | ] 702 | 703 | [[package]] 704 | name = "getrandom" 705 | version = "0.2.15" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 708 | dependencies = [ 709 | "cfg-if", 710 | "js-sys", 711 | "libc", 712 | "wasi", 713 | "wasm-bindgen", 714 | ] 715 | 716 | [[package]] 717 | name = "gimli" 718 | version = "0.29.0" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" 721 | 722 | [[package]] 723 | name = "group" 724 | version = "0.13.0" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" 727 | dependencies = [ 728 | "ff", 729 | "rand_core", 730 | "subtle", 731 | ] 732 | 733 | [[package]] 734 | name = "half" 735 | version = "2.4.1" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" 738 | dependencies = [ 739 | "cfg-if", 740 | "crunchy", 741 | ] 742 | 743 | [[package]] 744 | name = "hashbrown" 745 | version = "0.14.5" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 748 | 749 | [[package]] 750 | name = "heck" 751 | version = "0.5.0" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 754 | 755 | [[package]] 756 | name = "hermit-abi" 757 | version = "0.3.9" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 760 | 761 | [[package]] 762 | name = "hex" 763 | version = "0.4.3" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 766 | 767 | [[package]] 768 | name = "hmac" 769 | version = "0.12.1" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 772 | dependencies = [ 773 | "digest", 774 | ] 775 | 776 | [[package]] 777 | name = "iana-time-zone" 778 | version = "0.1.60" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" 781 | dependencies = [ 782 | "android_system_properties", 783 | "core-foundation-sys", 784 | "iana-time-zone-haiku", 785 | "js-sys", 786 | "wasm-bindgen", 787 | "windows-core", 788 | ] 789 | 790 | [[package]] 791 | name = "iana-time-zone-haiku" 792 | version = "0.1.2" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 795 | dependencies = [ 796 | "cc", 797 | ] 798 | 799 | [[package]] 800 | name = "ident_case" 801 | version = "1.0.1" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 804 | 805 | [[package]] 806 | name = "indexmap" 807 | version = "2.2.6" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 810 | dependencies = [ 811 | "equivalent", 812 | "hashbrown", 813 | ] 814 | 815 | [[package]] 816 | name = "instant" 817 | version = "0.1.13" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" 820 | dependencies = [ 821 | "cfg-if", 822 | ] 823 | 824 | [[package]] 825 | name = "is_terminal_polyfill" 826 | version = "1.70.0" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 829 | 830 | [[package]] 831 | name = "itoa" 832 | version = "1.0.11" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 835 | 836 | [[package]] 837 | name = "js-sys" 838 | version = "0.3.69" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 841 | dependencies = [ 842 | "wasm-bindgen", 843 | ] 844 | 845 | [[package]] 846 | name = "k256" 847 | version = "0.13.3" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" 850 | dependencies = [ 851 | "cfg-if", 852 | "ecdsa", 853 | "elliptic-curve", 854 | "once_cell", 855 | "serdect", 856 | "sha2", 857 | "signature", 858 | ] 859 | 860 | [[package]] 861 | name = "kanal" 862 | version = "0.1.0-pre8" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "b05d55519627edaf7fd0f29981f6dc03fb52df3f5b257130eb8d0bf2801ea1d7" 865 | dependencies = [ 866 | "futures-core", 867 | "lock_api", 868 | ] 869 | 870 | [[package]] 871 | name = "lazy_static" 872 | version = "1.5.0" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 875 | 876 | [[package]] 877 | name = "libc" 878 | version = "0.2.155" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 881 | 882 | [[package]] 883 | name = "libm" 884 | version = "0.2.8" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" 887 | 888 | [[package]] 889 | name = "lock_api" 890 | version = "0.4.12" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 893 | dependencies = [ 894 | "autocfg", 895 | "scopeguard", 896 | ] 897 | 898 | [[package]] 899 | name = "log" 900 | version = "0.4.22" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 903 | 904 | [[package]] 905 | name = "maplit" 906 | version = "1.0.2" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 909 | 910 | [[package]] 911 | name = "matchers" 912 | version = "0.1.0" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 915 | dependencies = [ 916 | "regex-automata 0.1.10", 917 | ] 918 | 919 | [[package]] 920 | name = "memchr" 921 | version = "2.7.4" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 924 | 925 | [[package]] 926 | name = "miner" 927 | version = "0.1.0" 928 | dependencies = [ 929 | "anyhow", 930 | "btclib", 931 | "clap", 932 | "flume", 933 | "tokio", 934 | ] 935 | 936 | [[package]] 937 | name = "miniz_oxide" 938 | version = "0.7.4" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 941 | dependencies = [ 942 | "adler", 943 | ] 944 | 945 | [[package]] 946 | name = "mio" 947 | version = "0.8.11" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 950 | dependencies = [ 951 | "libc", 952 | "wasi", 953 | "windows-sys 0.48.0", 954 | ] 955 | 956 | [[package]] 957 | name = "nanorand" 958 | version = "0.7.0" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" 961 | dependencies = [ 962 | "getrandom", 963 | ] 964 | 965 | [[package]] 966 | name = "ncurses" 967 | version = "5.101.0" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "5e2c5d34d72657dc4b638a1c25d40aae81e4f1c699062f72f467237920752032" 970 | dependencies = [ 971 | "cc", 972 | "libc", 973 | "pkg-config", 974 | ] 975 | 976 | [[package]] 977 | name = "node" 978 | version = "0.1.0" 979 | dependencies = [ 980 | "anyhow", 981 | "argh", 982 | "btclib", 983 | "chrono", 984 | "dashmap", 985 | "static_init", 986 | "tokio", 987 | "uuid", 988 | ] 989 | 990 | [[package]] 991 | name = "nu-ansi-term" 992 | version = "0.46.0" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 995 | dependencies = [ 996 | "overload", 997 | "winapi", 998 | ] 999 | 1000 | [[package]] 1001 | name = "num" 1002 | version = "0.4.3" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" 1005 | dependencies = [ 1006 | "num-complex", 1007 | "num-integer", 1008 | "num-iter", 1009 | "num-rational", 1010 | "num-traits", 1011 | ] 1012 | 1013 | [[package]] 1014 | name = "num-bigint" 1015 | version = "0.4.6" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 1018 | dependencies = [ 1019 | "num-integer", 1020 | "num-traits", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "num-complex" 1025 | version = "0.4.6" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" 1028 | dependencies = [ 1029 | "num-traits", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "num-conv" 1034 | version = "0.1.0" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1037 | 1038 | [[package]] 1039 | name = "num-integer" 1040 | version = "0.1.46" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1043 | dependencies = [ 1044 | "num-traits", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "num-iter" 1049 | version = "0.1.45" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" 1052 | dependencies = [ 1053 | "autocfg", 1054 | "num-integer", 1055 | "num-traits", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "num-rational" 1060 | version = "0.4.2" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" 1063 | dependencies = [ 1064 | "num-integer", 1065 | "num-traits", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "num-traits" 1070 | version = "0.2.19" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1073 | dependencies = [ 1074 | "autocfg", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "num_cpus" 1079 | version = "1.16.0" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 1082 | dependencies = [ 1083 | "hermit-abi", 1084 | "libc", 1085 | ] 1086 | 1087 | [[package]] 1088 | name = "num_threads" 1089 | version = "0.1.7" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 1092 | dependencies = [ 1093 | "libc", 1094 | ] 1095 | 1096 | [[package]] 1097 | name = "object" 1098 | version = "0.36.1" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" 1101 | dependencies = [ 1102 | "memchr", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "once_cell" 1107 | version = "1.19.0" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 1110 | 1111 | [[package]] 1112 | name = "overload" 1113 | version = "0.1.1" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1116 | 1117 | [[package]] 1118 | name = "owning_ref" 1119 | version = "0.4.1" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" 1122 | dependencies = [ 1123 | "stable_deref_trait", 1124 | ] 1125 | 1126 | [[package]] 1127 | name = "parking_lot" 1128 | version = "0.11.2" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 1131 | dependencies = [ 1132 | "instant", 1133 | "lock_api", 1134 | "parking_lot_core 0.8.6", 1135 | ] 1136 | 1137 | [[package]] 1138 | name = "parking_lot" 1139 | version = "0.12.3" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1142 | dependencies = [ 1143 | "lock_api", 1144 | "parking_lot_core 0.9.10", 1145 | ] 1146 | 1147 | [[package]] 1148 | name = "parking_lot_core" 1149 | version = "0.8.6" 1150 | source = "registry+https://github.com/rust-lang/crates.io-index" 1151 | checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" 1152 | dependencies = [ 1153 | "cfg-if", 1154 | "instant", 1155 | "libc", 1156 | "redox_syscall 0.2.16", 1157 | "smallvec", 1158 | "winapi", 1159 | ] 1160 | 1161 | [[package]] 1162 | name = "parking_lot_core" 1163 | version = "0.9.10" 1164 | source = "registry+https://github.com/rust-lang/crates.io-index" 1165 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1166 | dependencies = [ 1167 | "cfg-if", 1168 | "libc", 1169 | "redox_syscall 0.5.2", 1170 | "smallvec", 1171 | "windows-targets 0.52.6", 1172 | ] 1173 | 1174 | [[package]] 1175 | name = "pem-rfc7468" 1176 | version = "0.7.0" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" 1179 | dependencies = [ 1180 | "base64ct", 1181 | ] 1182 | 1183 | [[package]] 1184 | name = "pin-project-lite" 1185 | version = "0.2.14" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 1188 | 1189 | [[package]] 1190 | name = "pkcs8" 1191 | version = "0.10.2" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 1194 | dependencies = [ 1195 | "der", 1196 | "spki", 1197 | ] 1198 | 1199 | [[package]] 1200 | name = "pkg-config" 1201 | version = "0.3.30" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 1204 | 1205 | [[package]] 1206 | name = "powerfmt" 1207 | version = "0.2.0" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1210 | 1211 | [[package]] 1212 | name = "ppv-lite86" 1213 | version = "0.2.17" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 1216 | 1217 | [[package]] 1218 | name = "proc-macro2" 1219 | version = "1.0.86" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 1222 | dependencies = [ 1223 | "unicode-ident", 1224 | ] 1225 | 1226 | [[package]] 1227 | name = "quote" 1228 | version = "1.0.36" 1229 | source = "registry+https://github.com/rust-lang/crates.io-index" 1230 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 1231 | dependencies = [ 1232 | "proc-macro2", 1233 | ] 1234 | 1235 | [[package]] 1236 | name = "rand" 1237 | version = "0.8.5" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1240 | dependencies = [ 1241 | "libc", 1242 | "rand_chacha", 1243 | "rand_core", 1244 | ] 1245 | 1246 | [[package]] 1247 | name = "rand_chacha" 1248 | version = "0.3.1" 1249 | source = "registry+https://github.com/rust-lang/crates.io-index" 1250 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1251 | dependencies = [ 1252 | "ppv-lite86", 1253 | "rand_core", 1254 | ] 1255 | 1256 | [[package]] 1257 | name = "rand_core" 1258 | version = "0.6.4" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1261 | dependencies = [ 1262 | "getrandom", 1263 | ] 1264 | 1265 | [[package]] 1266 | name = "redox_syscall" 1267 | version = "0.2.16" 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" 1269 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 1270 | dependencies = [ 1271 | "bitflags 1.3.2", 1272 | ] 1273 | 1274 | [[package]] 1275 | name = "redox_syscall" 1276 | version = "0.5.2" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" 1279 | dependencies = [ 1280 | "bitflags 2.6.0", 1281 | ] 1282 | 1283 | [[package]] 1284 | name = "regex" 1285 | version = "1.10.5" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" 1288 | dependencies = [ 1289 | "aho-corasick", 1290 | "memchr", 1291 | "regex-automata 0.4.7", 1292 | "regex-syntax 0.8.4", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "regex-automata" 1297 | version = "0.1.10" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 1300 | dependencies = [ 1301 | "regex-syntax 0.6.29", 1302 | ] 1303 | 1304 | [[package]] 1305 | name = "regex-automata" 1306 | version = "0.4.7" 1307 | source = "registry+https://github.com/rust-lang/crates.io-index" 1308 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 1309 | dependencies = [ 1310 | "aho-corasick", 1311 | "memchr", 1312 | "regex-syntax 0.8.4", 1313 | ] 1314 | 1315 | [[package]] 1316 | name = "regex-syntax" 1317 | version = "0.6.29" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 1320 | 1321 | [[package]] 1322 | name = "regex-syntax" 1323 | version = "0.8.4" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 1326 | 1327 | [[package]] 1328 | name = "rfc6979" 1329 | version = "0.4.0" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" 1332 | dependencies = [ 1333 | "hmac", 1334 | "subtle", 1335 | ] 1336 | 1337 | [[package]] 1338 | name = "rustc-demangle" 1339 | version = "0.1.24" 1340 | source = "registry+https://github.com/rust-lang/crates.io-index" 1341 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1342 | 1343 | [[package]] 1344 | name = "scopeguard" 1345 | version = "1.2.0" 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" 1347 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1348 | 1349 | [[package]] 1350 | name = "sec1" 1351 | version = "0.7.3" 1352 | source = "registry+https://github.com/rust-lang/crates.io-index" 1353 | checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" 1354 | dependencies = [ 1355 | "base16ct", 1356 | "der", 1357 | "generic-array", 1358 | "pkcs8", 1359 | "serdect", 1360 | "subtle", 1361 | "zeroize", 1362 | ] 1363 | 1364 | [[package]] 1365 | name = "serde" 1366 | version = "1.0.204" 1367 | source = "registry+https://github.com/rust-lang/crates.io-index" 1368 | checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" 1369 | dependencies = [ 1370 | "serde_derive", 1371 | ] 1372 | 1373 | [[package]] 1374 | name = "serde_derive" 1375 | version = "1.0.204" 1376 | source = "registry+https://github.com/rust-lang/crates.io-index" 1377 | checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" 1378 | dependencies = [ 1379 | "proc-macro2", 1380 | "quote", 1381 | "syn 2.0.68", 1382 | ] 1383 | 1384 | [[package]] 1385 | name = "serde_spanned" 1386 | version = "0.6.6" 1387 | source = "registry+https://github.com/rust-lang/crates.io-index" 1388 | checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" 1389 | dependencies = [ 1390 | "serde", 1391 | ] 1392 | 1393 | [[package]] 1394 | name = "serdect" 1395 | version = "0.2.0" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" 1398 | dependencies = [ 1399 | "base16ct", 1400 | "serde", 1401 | ] 1402 | 1403 | [[package]] 1404 | name = "sha2" 1405 | version = "0.10.8" 1406 | source = "registry+https://github.com/rust-lang/crates.io-index" 1407 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1408 | dependencies = [ 1409 | "cfg-if", 1410 | "cpufeatures", 1411 | "digest", 1412 | ] 1413 | 1414 | [[package]] 1415 | name = "sha256" 1416 | version = "1.5.0" 1417 | source = "registry+https://github.com/rust-lang/crates.io-index" 1418 | checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" 1419 | dependencies = [ 1420 | "async-trait", 1421 | "bytes", 1422 | "hex", 1423 | "sha2", 1424 | "tokio", 1425 | ] 1426 | 1427 | [[package]] 1428 | name = "sharded-slab" 1429 | version = "0.1.7" 1430 | source = "registry+https://github.com/rust-lang/crates.io-index" 1431 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1432 | dependencies = [ 1433 | "lazy_static", 1434 | ] 1435 | 1436 | [[package]] 1437 | name = "signal-hook" 1438 | version = "0.3.17" 1439 | source = "registry+https://github.com/rust-lang/crates.io-index" 1440 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 1441 | dependencies = [ 1442 | "libc", 1443 | "signal-hook-registry", 1444 | ] 1445 | 1446 | [[package]] 1447 | name = "signal-hook-registry" 1448 | version = "1.4.2" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1451 | dependencies = [ 1452 | "libc", 1453 | ] 1454 | 1455 | [[package]] 1456 | name = "signature" 1457 | version = "2.2.0" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" 1460 | dependencies = [ 1461 | "digest", 1462 | "rand_core", 1463 | ] 1464 | 1465 | [[package]] 1466 | name = "smallvec" 1467 | version = "1.13.2" 1468 | source = "registry+https://github.com/rust-lang/crates.io-index" 1469 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1470 | 1471 | [[package]] 1472 | name = "socket2" 1473 | version = "0.5.7" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1476 | dependencies = [ 1477 | "libc", 1478 | "windows-sys 0.52.0", 1479 | ] 1480 | 1481 | [[package]] 1482 | name = "spin" 1483 | version = "0.9.8" 1484 | source = "registry+https://github.com/rust-lang/crates.io-index" 1485 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1486 | dependencies = [ 1487 | "lock_api", 1488 | ] 1489 | 1490 | [[package]] 1491 | name = "spki" 1492 | version = "0.7.3" 1493 | source = "registry+https://github.com/rust-lang/crates.io-index" 1494 | checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 1495 | dependencies = [ 1496 | "base64ct", 1497 | "der", 1498 | ] 1499 | 1500 | [[package]] 1501 | name = "stable_deref_trait" 1502 | version = "1.2.0" 1503 | source = "registry+https://github.com/rust-lang/crates.io-index" 1504 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1505 | 1506 | [[package]] 1507 | name = "static_assertions" 1508 | version = "1.1.0" 1509 | source = "registry+https://github.com/rust-lang/crates.io-index" 1510 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1511 | 1512 | [[package]] 1513 | name = "static_init" 1514 | version = "1.0.3" 1515 | source = "registry+https://github.com/rust-lang/crates.io-index" 1516 | checksum = "8a2a1c578e98c1c16fc3b8ec1328f7659a500737d7a0c6d625e73e830ff9c1f6" 1517 | dependencies = [ 1518 | "bitflags 1.3.2", 1519 | "cfg_aliases", 1520 | "libc", 1521 | "parking_lot 0.11.2", 1522 | "parking_lot_core 0.8.6", 1523 | "static_init_macro", 1524 | "winapi", 1525 | ] 1526 | 1527 | [[package]] 1528 | name = "static_init_macro" 1529 | version = "1.0.2" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" 1532 | dependencies = [ 1533 | "cfg_aliases", 1534 | "memchr", 1535 | "proc-macro2", 1536 | "quote", 1537 | "syn 1.0.109", 1538 | ] 1539 | 1540 | [[package]] 1541 | name = "strsim" 1542 | version = "0.11.1" 1543 | source = "registry+https://github.com/rust-lang/crates.io-index" 1544 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1545 | 1546 | [[package]] 1547 | name = "subtle" 1548 | version = "2.6.1" 1549 | source = "registry+https://github.com/rust-lang/crates.io-index" 1550 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1551 | 1552 | [[package]] 1553 | name = "syn" 1554 | version = "1.0.109" 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" 1556 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1557 | dependencies = [ 1558 | "proc-macro2", 1559 | "quote", 1560 | "unicode-ident", 1561 | ] 1562 | 1563 | [[package]] 1564 | name = "syn" 1565 | version = "2.0.68" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" 1568 | dependencies = [ 1569 | "proc-macro2", 1570 | "quote", 1571 | "unicode-ident", 1572 | ] 1573 | 1574 | [[package]] 1575 | name = "term_size" 1576 | version = "0.3.2" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" 1579 | dependencies = [ 1580 | "libc", 1581 | "winapi", 1582 | ] 1583 | 1584 | [[package]] 1585 | name = "text-to-ascii-art" 1586 | version = "0.1.9" 1587 | source = "registry+https://github.com/rust-lang/crates.io-index" 1588 | checksum = "fff85e7ccbae668e075f262a80e7315b399cb07fb5dc7ae1cd7261c80d007976" 1589 | 1590 | [[package]] 1591 | name = "thiserror" 1592 | version = "1.0.61" 1593 | source = "registry+https://github.com/rust-lang/crates.io-index" 1594 | checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" 1595 | dependencies = [ 1596 | "thiserror-impl", 1597 | ] 1598 | 1599 | [[package]] 1600 | name = "thiserror-impl" 1601 | version = "1.0.61" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" 1604 | dependencies = [ 1605 | "proc-macro2", 1606 | "quote", 1607 | "syn 2.0.68", 1608 | ] 1609 | 1610 | [[package]] 1611 | name = "thread_local" 1612 | version = "1.1.8" 1613 | source = "registry+https://github.com/rust-lang/crates.io-index" 1614 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1615 | dependencies = [ 1616 | "cfg-if", 1617 | "once_cell", 1618 | ] 1619 | 1620 | [[package]] 1621 | name = "time" 1622 | version = "0.3.36" 1623 | source = "registry+https://github.com/rust-lang/crates.io-index" 1624 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 1625 | dependencies = [ 1626 | "deranged", 1627 | "itoa", 1628 | "libc", 1629 | "num-conv", 1630 | "num_threads", 1631 | "powerfmt", 1632 | "serde", 1633 | "time-core", 1634 | "time-macros", 1635 | ] 1636 | 1637 | [[package]] 1638 | name = "time-core" 1639 | version = "0.1.2" 1640 | source = "registry+https://github.com/rust-lang/crates.io-index" 1641 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1642 | 1643 | [[package]] 1644 | name = "time-macros" 1645 | version = "0.2.18" 1646 | source = "registry+https://github.com/rust-lang/crates.io-index" 1647 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 1648 | dependencies = [ 1649 | "num-conv", 1650 | "time-core", 1651 | ] 1652 | 1653 | [[package]] 1654 | name = "tokio" 1655 | version = "1.38.0" 1656 | source = "registry+https://github.com/rust-lang/crates.io-index" 1657 | checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" 1658 | dependencies = [ 1659 | "backtrace", 1660 | "bytes", 1661 | "libc", 1662 | "mio", 1663 | "num_cpus", 1664 | "parking_lot 0.12.3", 1665 | "pin-project-lite", 1666 | "signal-hook-registry", 1667 | "socket2", 1668 | "tokio-macros", 1669 | "windows-sys 0.48.0", 1670 | ] 1671 | 1672 | [[package]] 1673 | name = "tokio-macros" 1674 | version = "2.3.0" 1675 | source = "registry+https://github.com/rust-lang/crates.io-index" 1676 | checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" 1677 | dependencies = [ 1678 | "proc-macro2", 1679 | "quote", 1680 | "syn 2.0.68", 1681 | ] 1682 | 1683 | [[package]] 1684 | name = "toml" 1685 | version = "0.8.14" 1686 | source = "registry+https://github.com/rust-lang/crates.io-index" 1687 | checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" 1688 | dependencies = [ 1689 | "serde", 1690 | "serde_spanned", 1691 | "toml_datetime", 1692 | "toml_edit", 1693 | ] 1694 | 1695 | [[package]] 1696 | name = "toml_datetime" 1697 | version = "0.6.6" 1698 | source = "registry+https://github.com/rust-lang/crates.io-index" 1699 | checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" 1700 | dependencies = [ 1701 | "serde", 1702 | ] 1703 | 1704 | [[package]] 1705 | name = "toml_edit" 1706 | version = "0.22.14" 1707 | source = "registry+https://github.com/rust-lang/crates.io-index" 1708 | checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" 1709 | dependencies = [ 1710 | "indexmap", 1711 | "serde", 1712 | "serde_spanned", 1713 | "toml_datetime", 1714 | "winnow", 1715 | ] 1716 | 1717 | [[package]] 1718 | name = "tracing" 1719 | version = "0.1.40" 1720 | source = "registry+https://github.com/rust-lang/crates.io-index" 1721 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1722 | dependencies = [ 1723 | "pin-project-lite", 1724 | "tracing-attributes", 1725 | "tracing-core", 1726 | ] 1727 | 1728 | [[package]] 1729 | name = "tracing-appender" 1730 | version = "0.2.3" 1731 | source = "registry+https://github.com/rust-lang/crates.io-index" 1732 | checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" 1733 | dependencies = [ 1734 | "crossbeam-channel", 1735 | "thiserror", 1736 | "time", 1737 | "tracing-subscriber", 1738 | ] 1739 | 1740 | [[package]] 1741 | name = "tracing-attributes" 1742 | version = "0.1.27" 1743 | source = "registry+https://github.com/rust-lang/crates.io-index" 1744 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 1745 | dependencies = [ 1746 | "proc-macro2", 1747 | "quote", 1748 | "syn 2.0.68", 1749 | ] 1750 | 1751 | [[package]] 1752 | name = "tracing-core" 1753 | version = "0.1.32" 1754 | source = "registry+https://github.com/rust-lang/crates.io-index" 1755 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1756 | dependencies = [ 1757 | "once_cell", 1758 | "valuable", 1759 | ] 1760 | 1761 | [[package]] 1762 | name = "tracing-log" 1763 | version = "0.2.0" 1764 | source = "registry+https://github.com/rust-lang/crates.io-index" 1765 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1766 | dependencies = [ 1767 | "log", 1768 | "once_cell", 1769 | "tracing-core", 1770 | ] 1771 | 1772 | [[package]] 1773 | name = "tracing-subscriber" 1774 | version = "0.3.18" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 1777 | dependencies = [ 1778 | "matchers", 1779 | "nu-ansi-term", 1780 | "once_cell", 1781 | "regex", 1782 | "sharded-slab", 1783 | "smallvec", 1784 | "thread_local", 1785 | "tracing", 1786 | "tracing-core", 1787 | "tracing-log", 1788 | ] 1789 | 1790 | [[package]] 1791 | name = "typenum" 1792 | version = "1.17.0" 1793 | source = "registry+https://github.com/rust-lang/crates.io-index" 1794 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1795 | 1796 | [[package]] 1797 | name = "uint" 1798 | version = "0.9.5" 1799 | source = "registry+https://github.com/rust-lang/crates.io-index" 1800 | checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" 1801 | dependencies = [ 1802 | "byteorder", 1803 | "crunchy", 1804 | "hex", 1805 | "static_assertions", 1806 | ] 1807 | 1808 | [[package]] 1809 | name = "unicode-ident" 1810 | version = "1.0.12" 1811 | source = "registry+https://github.com/rust-lang/crates.io-index" 1812 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1813 | 1814 | [[package]] 1815 | name = "unicode-segmentation" 1816 | version = "1.11.0" 1817 | source = "registry+https://github.com/rust-lang/crates.io-index" 1818 | checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" 1819 | 1820 | [[package]] 1821 | name = "unicode-width" 1822 | version = "0.1.13" 1823 | source = "registry+https://github.com/rust-lang/crates.io-index" 1824 | checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" 1825 | 1826 | [[package]] 1827 | name = "utf8parse" 1828 | version = "0.2.2" 1829 | source = "registry+https://github.com/rust-lang/crates.io-index" 1830 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1831 | 1832 | [[package]] 1833 | name = "uuid" 1834 | version = "1.9.1" 1835 | source = "registry+https://github.com/rust-lang/crates.io-index" 1836 | checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" 1837 | dependencies = [ 1838 | "getrandom", 1839 | "serde", 1840 | ] 1841 | 1842 | [[package]] 1843 | name = "valuable" 1844 | version = "0.1.0" 1845 | source = "registry+https://github.com/rust-lang/crates.io-index" 1846 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1847 | 1848 | [[package]] 1849 | name = "version_check" 1850 | version = "0.9.4" 1851 | source = "registry+https://github.com/rust-lang/crates.io-index" 1852 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1853 | 1854 | [[package]] 1855 | name = "wallet" 1856 | version = "0.1.0" 1857 | dependencies = [ 1858 | "anyhow", 1859 | "btclib", 1860 | "clap", 1861 | "crossbeam-skiplist", 1862 | "cursive", 1863 | "kanal", 1864 | "serde", 1865 | "text-to-ascii-art", 1866 | "tokio", 1867 | "toml", 1868 | "tracing", 1869 | "tracing-appender", 1870 | "tracing-subscriber", 1871 | "uuid", 1872 | ] 1873 | 1874 | [[package]] 1875 | name = "wasi" 1876 | version = "0.11.0+wasi-snapshot-preview1" 1877 | source = "registry+https://github.com/rust-lang/crates.io-index" 1878 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1879 | 1880 | [[package]] 1881 | name = "wasm-bindgen" 1882 | version = "0.2.92" 1883 | source = "registry+https://github.com/rust-lang/crates.io-index" 1884 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 1885 | dependencies = [ 1886 | "cfg-if", 1887 | "wasm-bindgen-macro", 1888 | ] 1889 | 1890 | [[package]] 1891 | name = "wasm-bindgen-backend" 1892 | version = "0.2.92" 1893 | source = "registry+https://github.com/rust-lang/crates.io-index" 1894 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 1895 | dependencies = [ 1896 | "bumpalo", 1897 | "log", 1898 | "once_cell", 1899 | "proc-macro2", 1900 | "quote", 1901 | "syn 2.0.68", 1902 | "wasm-bindgen-shared", 1903 | ] 1904 | 1905 | [[package]] 1906 | name = "wasm-bindgen-macro" 1907 | version = "0.2.92" 1908 | source = "registry+https://github.com/rust-lang/crates.io-index" 1909 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 1910 | dependencies = [ 1911 | "quote", 1912 | "wasm-bindgen-macro-support", 1913 | ] 1914 | 1915 | [[package]] 1916 | name = "wasm-bindgen-macro-support" 1917 | version = "0.2.92" 1918 | source = "registry+https://github.com/rust-lang/crates.io-index" 1919 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 1920 | dependencies = [ 1921 | "proc-macro2", 1922 | "quote", 1923 | "syn 2.0.68", 1924 | "wasm-bindgen-backend", 1925 | "wasm-bindgen-shared", 1926 | ] 1927 | 1928 | [[package]] 1929 | name = "wasm-bindgen-shared" 1930 | version = "0.2.92" 1931 | source = "registry+https://github.com/rust-lang/crates.io-index" 1932 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 1933 | 1934 | [[package]] 1935 | name = "winapi" 1936 | version = "0.3.9" 1937 | source = "registry+https://github.com/rust-lang/crates.io-index" 1938 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1939 | dependencies = [ 1940 | "winapi-i686-pc-windows-gnu", 1941 | "winapi-x86_64-pc-windows-gnu", 1942 | ] 1943 | 1944 | [[package]] 1945 | name = "winapi-i686-pc-windows-gnu" 1946 | version = "0.4.0" 1947 | source = "registry+https://github.com/rust-lang/crates.io-index" 1948 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1949 | 1950 | [[package]] 1951 | name = "winapi-x86_64-pc-windows-gnu" 1952 | version = "0.4.0" 1953 | source = "registry+https://github.com/rust-lang/crates.io-index" 1954 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1955 | 1956 | [[package]] 1957 | name = "windows-core" 1958 | version = "0.52.0" 1959 | source = "registry+https://github.com/rust-lang/crates.io-index" 1960 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 1961 | dependencies = [ 1962 | "windows-targets 0.52.6", 1963 | ] 1964 | 1965 | [[package]] 1966 | name = "windows-sys" 1967 | version = "0.48.0" 1968 | source = "registry+https://github.com/rust-lang/crates.io-index" 1969 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1970 | dependencies = [ 1971 | "windows-targets 0.48.5", 1972 | ] 1973 | 1974 | [[package]] 1975 | name = "windows-sys" 1976 | version = "0.52.0" 1977 | source = "registry+https://github.com/rust-lang/crates.io-index" 1978 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1979 | dependencies = [ 1980 | "windows-targets 0.52.6", 1981 | ] 1982 | 1983 | [[package]] 1984 | name = "windows-targets" 1985 | version = "0.48.5" 1986 | source = "registry+https://github.com/rust-lang/crates.io-index" 1987 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1988 | dependencies = [ 1989 | "windows_aarch64_gnullvm 0.48.5", 1990 | "windows_aarch64_msvc 0.48.5", 1991 | "windows_i686_gnu 0.48.5", 1992 | "windows_i686_msvc 0.48.5", 1993 | "windows_x86_64_gnu 0.48.5", 1994 | "windows_x86_64_gnullvm 0.48.5", 1995 | "windows_x86_64_msvc 0.48.5", 1996 | ] 1997 | 1998 | [[package]] 1999 | name = "windows-targets" 2000 | version = "0.52.6" 2001 | source = "registry+https://github.com/rust-lang/crates.io-index" 2002 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2003 | dependencies = [ 2004 | "windows_aarch64_gnullvm 0.52.6", 2005 | "windows_aarch64_msvc 0.52.6", 2006 | "windows_i686_gnu 0.52.6", 2007 | "windows_i686_gnullvm", 2008 | "windows_i686_msvc 0.52.6", 2009 | "windows_x86_64_gnu 0.52.6", 2010 | "windows_x86_64_gnullvm 0.52.6", 2011 | "windows_x86_64_msvc 0.52.6", 2012 | ] 2013 | 2014 | [[package]] 2015 | name = "windows_aarch64_gnullvm" 2016 | version = "0.48.5" 2017 | source = "registry+https://github.com/rust-lang/crates.io-index" 2018 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2019 | 2020 | [[package]] 2021 | name = "windows_aarch64_gnullvm" 2022 | version = "0.52.6" 2023 | source = "registry+https://github.com/rust-lang/crates.io-index" 2024 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2025 | 2026 | [[package]] 2027 | name = "windows_aarch64_msvc" 2028 | version = "0.48.5" 2029 | source = "registry+https://github.com/rust-lang/crates.io-index" 2030 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2031 | 2032 | [[package]] 2033 | name = "windows_aarch64_msvc" 2034 | version = "0.52.6" 2035 | source = "registry+https://github.com/rust-lang/crates.io-index" 2036 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2037 | 2038 | [[package]] 2039 | name = "windows_i686_gnu" 2040 | version = "0.48.5" 2041 | source = "registry+https://github.com/rust-lang/crates.io-index" 2042 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2043 | 2044 | [[package]] 2045 | name = "windows_i686_gnu" 2046 | version = "0.52.6" 2047 | source = "registry+https://github.com/rust-lang/crates.io-index" 2048 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2049 | 2050 | [[package]] 2051 | name = "windows_i686_gnullvm" 2052 | version = "0.52.6" 2053 | source = "registry+https://github.com/rust-lang/crates.io-index" 2054 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2055 | 2056 | [[package]] 2057 | name = "windows_i686_msvc" 2058 | version = "0.48.5" 2059 | source = "registry+https://github.com/rust-lang/crates.io-index" 2060 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2061 | 2062 | [[package]] 2063 | name = "windows_i686_msvc" 2064 | version = "0.52.6" 2065 | source = "registry+https://github.com/rust-lang/crates.io-index" 2066 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2067 | 2068 | [[package]] 2069 | name = "windows_x86_64_gnu" 2070 | version = "0.48.5" 2071 | source = "registry+https://github.com/rust-lang/crates.io-index" 2072 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2073 | 2074 | [[package]] 2075 | name = "windows_x86_64_gnu" 2076 | version = "0.52.6" 2077 | source = "registry+https://github.com/rust-lang/crates.io-index" 2078 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2079 | 2080 | [[package]] 2081 | name = "windows_x86_64_gnullvm" 2082 | version = "0.48.5" 2083 | source = "registry+https://github.com/rust-lang/crates.io-index" 2084 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2085 | 2086 | [[package]] 2087 | name = "windows_x86_64_gnullvm" 2088 | version = "0.52.6" 2089 | source = "registry+https://github.com/rust-lang/crates.io-index" 2090 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2091 | 2092 | [[package]] 2093 | name = "windows_x86_64_msvc" 2094 | version = "0.48.5" 2095 | source = "registry+https://github.com/rust-lang/crates.io-index" 2096 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2097 | 2098 | [[package]] 2099 | name = "windows_x86_64_msvc" 2100 | version = "0.52.6" 2101 | source = "registry+https://github.com/rust-lang/crates.io-index" 2102 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2103 | 2104 | [[package]] 2105 | name = "winnow" 2106 | version = "0.6.13" 2107 | source = "registry+https://github.com/rust-lang/crates.io-index" 2108 | checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" 2109 | dependencies = [ 2110 | "memchr", 2111 | ] 2112 | 2113 | [[package]] 2114 | name = "xi-unicode" 2115 | version = "0.3.0" 2116 | source = "registry+https://github.com/rust-lang/crates.io-index" 2117 | checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" 2118 | 2119 | [[package]] 2120 | name = "zerocopy" 2121 | version = "0.7.35" 2122 | source = "registry+https://github.com/rust-lang/crates.io-index" 2123 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2124 | dependencies = [ 2125 | "zerocopy-derive", 2126 | ] 2127 | 2128 | [[package]] 2129 | name = "zerocopy-derive" 2130 | version = "0.7.35" 2131 | source = "registry+https://github.com/rust-lang/crates.io-index" 2132 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2133 | dependencies = [ 2134 | "proc-macro2", 2135 | "quote", 2136 | "syn 2.0.68", 2137 | ] 2138 | 2139 | [[package]] 2140 | name = "zeroize" 2141 | version = "1.8.1" 2142 | source = "registry+https://github.com/rust-lang/crates.io-index" 2143 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2144 | --------------------------------------------------------------------------------