diff --git a/side-node/src/clients/btc_other_rpc.rs b/side-node/src/clients/btc_other_rpc.rs index 9f0c523..fe9ca56 100644 --- a/side-node/src/clients/btc_other_rpc.rs +++ b/side-node/src/clients/btc_other_rpc.rs @@ -1,38 +1,76 @@ -use bdk::{ - bitcoin::Network, - database::MemoryDatabase, - keys::{ - bip39::{Language, Mnemonic, WordCount}, - DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey, - }, - miniscript, - template::Bip84, - KeychainKind, Wallet, -}; +use std::io::Write; + +use bdk::{bitcoin::Script, KeychainKind}; +use bdk_esplora::esplora_client; + +use crate::{keys, utils}; pub async fn run() -> Result<(), anyhow::Error> { - // Generate a new mnemonic - let mnemonic: GeneratedKey<_, miniscript::Segwitv0> = - Mnemonic::generate((WordCount::Words12, Language::English)).unwrap(); - let mnemonic_words = mnemonic.to_string(); - let mnemonic = Mnemonic::parse(&mnemonic_words).unwrap(); + let dave = utils::home(&"dave".to_string()); + let sammy = utils::home(&"sammy".to_string()); - // Generate the extended key - let xkey: ExtendedKey = mnemonic.into_extended_key()?; + // Load mnemonics from disk + let dave_wallet = keys::bitcoin::load_from_file(&dave).unwrap(); + let sammy_wallet = keys::bitcoin::load_from_file(&sammy).unwrap(); - // Get private key from the extended key - let xprv = xkey.into_xprv(Network::Signet).unwrap(); + let dave_address = dave_wallet.get_address(bdk::wallet::AddressIndex::New)?; + println!("Dave address: {:?}", dave_address); - // Create a BDK wallet using BIP 84 descriptor ("m/84h/1h/0h/0" and "m/84h/1h/0h/1") - let wallet = Wallet::new( - Bip84(xprv, KeychainKind::External), - Some(Bip84(xprv, KeychainKind::Internal)), - Network::Signet, - MemoryDatabase::default(), - )?; + let sammy_address = sammy_wallet.get_address(bdk::wallet::AddressIndex::New)?; + println!("Sammy address: {:?}", sammy_address); - let address = wallet.get_address(bdk::wallet::AddressIndex::New)?; - println!("Generated Address: {:?}", address); + print!("Syncing..."); + let client = esplora_client::Builder::new("http://signet.bitcoindevkit.net").build_async()?; + + fn generate_inspect(kind: KeychainKind) -> impl FnMut(u32, &Script) + Send + Sync + 'static { + let mut once = Some(()); + let mut stdout = std::io::stdout(); + move |spk_i, _| { + match once.take() { + Some(_) => print!("\nScanning keychain [{:?}]", kind), + None => print!(" {:<3}", spk_i), + }; + stdout.flush().expect("must flush"); + } + } + + // let request = dave_wallet + // .start_full_scan() + // .inspect_spks_for_all_keychains({ + // let mut once = BTreeSet::::new(); + // move |keychain, spk_i, _| { + // match once.insert(keychain) { + // true => print!("\nScanning keychain [{:?}]", keychain), + // false => print!(" {:<3}", spk_i), + // } + // std::io::stdout().flush().expect("must flush") + // } + // }) + // .inspect_spks_for_keychain( + // KeychainKind::External, + // generate_inspect(KeychainKind::External), + // ) + // .inspect_spks_for_keychain( + // KeychainKind::Internal, + // generate_inspect(KeychainKind::Internal), + // ); + + // let mut update = client + // .full_scan(request, STOP_GAP, PARALLEL_REQUESTS) + // .await?; + // let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs(); + // let _ = update.graph_update.update_last_seen_unconfirmed(now); + + // wallet.apply_update(update)?; + // if let Some(changeset) = wallet.take_staged() { + // db.write(&changeset)?; + // } + + let dave_balance = dave_wallet.get_balance()?; + println!("Wallet balance before syncing: {} sats", dave_balance); + + let sammy_balance = sammy_wallet.get_balance()?; + println!("Wallet balance before syncing: {} sats", sammy_balance); Ok(()) } diff --git a/side-node/src/keys/bitcoin.rs b/side-node/src/keys/bitcoin.rs index 968752f..aaf0a95 100644 --- a/side-node/src/keys/bitcoin.rs +++ b/side-node/src/keys/bitcoin.rs @@ -1,40 +1,59 @@ -use bitcoin::secp256k1::{rand, Keypair, Secp256k1, SecretKey}; +use bdk::{ + bitcoin::Network, + database::MemoryDatabase, + keys::{ + bip39::{Language, Mnemonic, WordCount}, + DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey, + }, + miniscript, + template::Bip84, + KeychainKind, Wallet, +}; + use std::{ fs::{self, File}, io::Write, path::PathBuf, - str::FromStr, }; -pub fn make_keypair() -> bitcoin::secp256k1::Keypair { - let secp = Secp256k1::new(); - let (secret_key, _) = secp.generate_keypair(&mut rand::thread_rng()); - Keypair::from_secret_key(&secp, &secret_key) +pub fn make_mnemonic() -> String { + let mnemonic: GeneratedKey<_, miniscript::Segwitv0> = + Mnemonic::generate((WordCount::Words12, Language::English)).unwrap(); + mnemonic.to_string() } -pub(crate) fn write(key_path: &PathBuf) -> Result<(), std::io::Error> { - let key_pair = make_keypair(); - let mut file = File::create(key_path)?; - - let pub_str = key_pair.public_key().to_string(); - let priv_str = key_pair.display_secret().to_string(); - println!("private key: {priv_str}"); - let out = format!("{pub_str}/{priv_str}"); - println!("out: {out}"); - file.write(out.as_bytes())?; +pub(crate) fn write(mnemonic_path: &PathBuf) -> Result<(), std::io::Error> { + let mnemonic = make_mnemonic(); + let mut file = File::create(mnemonic_path)?; + println!("mnemonic: {mnemonic}"); + file.write(mnemonic.as_bytes())?; Ok(()) } -pub(crate) fn load_from_file(side_dir: &PathBuf) -> bitcoin::secp256k1::Keypair { - let bitcoin_key_path = crate::utils::side_paths(side_dir.clone()).1; // TODO: this tuple stinks +pub fn create_wallet(mnemonic_words: String) -> anyhow::Result> { + let mnemonic = Mnemonic::parse(mnemonic_words).unwrap(); - let data = fs::read_to_string(bitcoin_key_path).expect("couldn't read bitcoin key file"); - println!("bitcoin keys: {:?}", data); + // Generate the extended key + let xkey: ExtendedKey = mnemonic.into_extended_key()?; - let secret_key_data = data.split("/").collect::>()[1]; - let secp = Secp256k1::new(); - let secret_key = - SecretKey::from_str(secret_key_data).expect("couldn't load secret key from file"); - Keypair::from_secret_key(&secp, &secret_key) + // Get private key from the extended key + let xprv = xkey.into_xprv(Network::Signet).unwrap(); + + // Create a BDK wallet using BIP 84 descriptor ("m/84h/1h/0h/0" and "m/84h/1h/0h/1") + let wallet = Wallet::new( + Bip84(xprv, KeychainKind::External), + Some(Bip84(xprv, KeychainKind::Internal)), + Network::Signet, + MemoryDatabase::default(), + )?; + + Ok(wallet) +} + +pub(crate) fn load_from_file(side_dir: &PathBuf) -> anyhow::Result> { + let mnemonic_path = crate::utils::side_paths(side_dir.clone()).1; // TODO: this tuple stinks + let mnemonic_words = fs::read_to_string(mnemonic_path).expect("couldn't read bitcoin key file"); + println!("Creating wallet from mnemonic: {mnemonic_words}"); + create_wallet(mnemonic_words) } diff --git a/side-node/src/lib.rs b/side-node/src/lib.rs index 9397bbf..0426b25 100644 --- a/side-node/src/lib.rs +++ b/side-node/src/lib.rs @@ -42,7 +42,7 @@ async fn setup(name: &String) -> SideNode { // First, load up the keys and create a bft-crdt let side_dir = utils::home(name); let bft_crdt_keys = keys::bft_crdt::load_from_file(&side_dir); - let bitcoin_keys = keys::bitcoin::load_from_file(&side_dir); + let bitcoin_wallet = keys::bitcoin::load_from_file(&side_dir).unwrap(); let crdt = BaseCrdt::::new(&bft_crdt_keys); // Channels for internal communication, and a tokio task for stdin input @@ -57,7 +57,7 @@ async fn setup(name: &String) -> SideNode { let node = SideNode::new( crdt, bft_crdt_keys, - bitcoin_keys, + bitcoin_wallet, incoming_receiver, stdin_receiver, handle, diff --git a/side-node/src/node.rs b/side-node/src/node.rs index 882b4a0..5496691 100644 --- a/side-node/src/node.rs +++ b/side-node/src/node.rs @@ -1,3 +1,4 @@ +use bdk::database::MemoryDatabase; use bft_json_crdt::json_crdt::{BaseCrdt, SignedOp}; use fastcrypto::ed25519::Ed25519KeyPair; use tokio::sync::mpsc; @@ -7,7 +8,7 @@ use crate::{clients::websocket::Client, crdt::TransactionList, utils}; pub struct SideNode { crdt: BaseCrdt, bft_crdt_keys: fastcrypto::ed25519::Ed25519KeyPair, - bitcoin_keys: bitcoin::secp256k1::Keypair, + bitcoin_wallet: bdk::Wallet, incoming_receiver: mpsc::Receiver, stdin_receiver: std::sync::mpsc::Receiver, handle: ezsockets::Client, @@ -17,7 +18,7 @@ impl SideNode { pub fn new( crdt: BaseCrdt, bft_crdt_keys: Ed25519KeyPair, - bitcoin_keys: bitcoin::secp256k1::Keypair, + bitcoin_wallet: bdk::Wallet, incoming_receiver: mpsc::Receiver, stdin_receiver: std::sync::mpsc::Receiver, handle: ezsockets::Client, @@ -25,7 +26,7 @@ impl SideNode { let node = Self { crdt, bft_crdt_keys, - bitcoin_keys, + bitcoin_wallet, incoming_receiver, stdin_receiver, handle, diff --git a/side-node/tests/side_node.rs b/side-node/tests/side_node.rs index 951a1a9..b7478cf 100644 --- a/side-node/tests/side_node.rs +++ b/side-node/tests/side_node.rs @@ -35,7 +35,8 @@ async fn test_distribute_via_websockets() { async fn setup(_: &str) -> SideNode { // First, load up the keys and create a bft-crdt let bft_crdt_keys = make_keypair(); - let bitcoin_keys = keys::bitcoin::make_keypair(); + let mnemonic_words = keys::bitcoin::make_mnemonic(); + let bitcoin_wallet = keys::bitcoin::create_wallet(mnemonic_words).unwrap(); let crdt = BaseCrdt::::new(&bft_crdt_keys); // Channels for internal communication, and a tokio task for stdin input @@ -47,7 +48,7 @@ async fn setup(_: &str) -> SideNode { let node = SideNode::new( crdt, bft_crdt_keys, - bitcoin_keys, + bitcoin_wallet, incoming_receiver, stdin_receiver, handle,