// 22/4/2021 // random various unrelated functions coded to help me figure out how to use rust-bitcoin // should be useful for figuring out why certain things in teleport are coded the way they are extern crate bitcoincore_rpc; use bitcoincore_rpc::{Client, Error, RpcApi, Auth}; extern crate bitcoin_wallet; use bitcoin_wallet::account::{ MasterAccount, Unlocker, Account, AccountAddressType }; use bitcoin_wallet::mnemonic; extern crate bitcoin; use bitcoin::Network; use bitcoin::{OutPoint, TxIn, TxOut, Transaction, Address, Script, Txid, SigHashType}; use bitcoin::hashes::hex::{FromHex, ToHex}; use bitcoin::consensus::encode::{serialize_hex, deserialize}; use bitcoin::blockdata::script::Builder; use bitcoin::blockdata::opcodes; use bitcoin::util::bip32; use bitcoin::util::key::{PrivateKey, PublicKey}; use bitcoin::util::bip143; use bitcoin::util::psbt::serialize::Serialize; use bitcoin::hashes::hash160::Hash as Hash160; use bitcoin::hashes::Hash; use serde_json; use bitcoin::secp256k1::{SecretKey, Signature}; extern crate secp256k1; use secp256k1::Secp256k1; use std::str::FromStr; use std::collections::HashMap; use std::fs::File; fn sign_tx() { const SEED_PHRASE: &str = "great rice pitch bitter stay crash among position disease enable sell road"; const EXTENSION: &str = "helloworld"; //root key according to the bip39.org site // xprv9s21ZrQH143K3x8kThmrmvTXGthvazWEVMuM18Rfz9cBijs5krbwsxSmU6PJTqQtYCb4EjinKaQzvaGQMXa45gckv1GBbahu5eh2ZWeppGA //importing seed into electrum does indeed give the same address as below // m/44'/1'/0' //addr(0, 0) = 1HVyNRjLPQiWti9P8umBv2AfYP2cSHEXNo const PASSPHRASE: &str = ""; let mut master = MasterAccount::from_mnemonic( &mnemonic::Mnemonic::from_str(SEED_PHRASE).unwrap(), 0, Network::Regtest, PASSPHRASE, Some(EXTENSION) ).unwrap(); let mut unlocker = Unlocker::new_for_master(&master, PASSPHRASE).unwrap(); let p2pkh_account = Account::new(&mut unlocker, AccountAddressType::P2PKH, 0, 0, 10).unwrap(); master.add_account(p2pkh_account); let xpub = master.get_mut((0, 0)).unwrap().master_public(); println!("xpub = {}", xpub); //xpub = tpubDE5vtSEeHLG1iGPveZffKaMJsFsr5ijqb8rcZ3ex6NxwaGn58TD522jAyi3JsAWcYUBDXPGcrs6AB8s43zxkph9ou4t4HHAQvoJSv8SNaKE //addresses from this xpub are simply in m/0, m/1, m/2, etc //so this could be used in a descriptor let p2pkh_addr_0 = master.get_mut((0, 0)).unwrap().next_key().unwrap() .address.clone(); println!("addr = {}", p2pkh_addr_0); //address = mzxWmxuY352Lw4FadGmiDNndnPg34h5P6V //sent 1 btc to it on regtest let mut spending_tx = Transaction { input: vec![ TxIn { previous_output: OutPoint { txid: Txid::from_hex("07d3e8721234468fd0ca51247a79d180dcbcaf81370cde089847a9ba698b9fcc").unwrap(), vout: 0 }, sequence: 0, witness: Vec::new(), script_sig: Script::new() } ], output: vec![ TxOut { script_pubkey: Address ::from_str("mzxWmxuY352Lw4FadGmiDNndnPg34h5P6V") .unwrap().script_pubkey(), value: 99950000, } ], lock_time: 0, version: 2, }; let prev_out = TxOut { script_pubkey: Address::from_str("mzxWmxuY352Lw4FadGmiDNndnPg34h5P6V") .unwrap().script_pubkey(), value: 100000000 }; master.sign(&mut spending_tx, SigHashType::All, &|_| Some(prev_out.clone()), &mut unlocker).expect("cannot sign"); println!("fully signed txid = {}", spending_tx.txid()); let txhex = serialize_hex(&spending_tx); println!("txhex = {}", txhex); //output //fully signed txid = c1e6276e808feaea39eb914c22d1f9ec9ab08f124f03c8254c399b98811e13a1 //txhex = 0200000001cc9f8b69baa9479808de0c3781afbcdc80d1797a2451cad08f46341272e8d307000000006b483045022100bdc5f767fe66218279d29523294d1741ef50e220d55677b5aad512ad30da84bb02206684bfc5996e27866dfa0eaa61a1fc99ccc8f0200062c2733d8b33e65463390101210306d4bb7fcee609d18f87fe34c3d7cc9e29d193beecccebdb4a3792f50ad3007c0000000001b01df505000000001976a914d53fe723f631c7c355986d3684223a666557591388ac00000000 //passing to RPC /* testmempoolaccept "[\"0200000001cc9f8b69baa9479808de0c3781afbcdc80d1797a2451cad08f46341272e8d307000000006b483045022100bdc5f767fe66218279d29523294d1741ef50e220d55677b5aad512ad30da84bb02206684bfc5996e27866dfa0eaa61a1fc99ccc8f0200062c2733d8b33e65463390101210306d4bb7fcee609d18f87fe34c3d7cc9e29d193beecccebdb4a3792f50ad3007c0000000001b01df505000000001976a914d53fe723f631c7c355986d3684223a666557591388ac00000000\"]" [ { "txid": "c1e6276e808feaea39eb914c22d1f9ec9ab08f124f03c8254c399b98811e13a1", "allowed": true } ] */ //tx is valid, and also results in the same txid calculated by the library } fn generate_without_rustwallet_masteraccount() { const SEED_PHRASE: &str = "great rice pitch bitter stay crash among position disease enable sell road"; const EXTENSION: &str = "helloworld"; let seed = mnemonic::Mnemonic::from_str(SEED_PHRASE).unwrap() .to_seed(Some(EXTENSION)); let xprv = bip32::ExtendedPrivKey::new_master(Network::Bitcoin, &seed.0) .unwrap(); println!("xprv_seed = {}", xprv); //this xprv comes from putting the above seed phrase into bip39.org let xprv_str = "xprv9s21ZrQH143K3x8kThmrmvTXGthvazWEVMuM18Rfz9cBijs5krbwsxSmU6PJTqQtYCb4EjinKaQzvaGQMXa45gckv1GBbahu5eh2ZWeppGA"; println!(" xprv_str = {}", xprv_str); //check that they are the same } fn sign_tx_from_indium_test_wallet() { const SEED_PHRASE: &str = "finger topple before stock embrace liar air now trouble beauty forum protect"; const EXTENSION: &str = "www"; const PASSPHRASE: &str = ""; let mut master = MasterAccount::from_mnemonic( &mnemonic::Mnemonic::from_str(SEED_PHRASE).unwrap(), 0, Network::Regtest, PASSPHRASE, Some(EXTENSION) ).unwrap(); let mut unlocker = Unlocker::new_for_master(&master, PASSPHRASE).unwrap(); let p2pkh_account = Account::new(&mut unlocker, AccountAddressType::P2WPKH, 0, 0, 10).unwrap(); master.add_account(p2pkh_account); let p2pkh_addr_0 = master.get_mut((0, 0)).unwrap().next_key().unwrap() .address.clone(); println!("addr = {}", p2pkh_addr_0); //address = bcrt1q7llc9avh7tklhcq6wucj5jss2jn69dcte4n4pq /* let mut spending_tx = Transaction { input: vec![ TxIn { previous_output: OutPoint { txid: Txid::from_hex("06941b3a6b65860877a5459819269c35715868b545771e0b14db496854d831b7").unwrap(), vout: 1 }, sequence: 0, witness: Vec::new(), script_sig: Script::new() } ], output: vec![ TxOut { script_pubkey: Address ::from_str("mzxWmxuY352Lw4FadGmiDNndnPg34h5P6V") .unwrap().script_pubkey(), value: 9995000, } ], lock_time: 0, version: 2, }; */ let mut spending_tx = Transaction { input: vec![ TxIn { previous_output: OutPoint { txid: Txid::from_hex("06941b3a6b65860877a5459819269c35715868b545771e0b14db496854d831b7").unwrap(), vout: 1 }, sequence: 0, witness: Vec::new(), script_sig: Script::new() } ], output: vec![ TxOut { script_pubkey: Address ::from_str("2Mvp1FFijppV7HfQLTJ11CHoRqpAJi8qXbf") .unwrap().script_pubkey(), value: 5000000, }, TxOut { script_pubkey: Address ::from_str("2Mvp1FFijppV7HfQLTJ11CHoRqpAJi8qXbf") .unwrap().script_pubkey(), value: 4998000, } ], lock_time: 0, version: 2, }; let prev_out = TxOut { script_pubkey: Address::from_str( "bcrt1q7llc9avh7tklhcq6wucj5jss2jn69dcte4n4pq") .unwrap() .script_pubkey(), value: 10000000 }; master.sign(&mut spending_tx, SigHashType::All, &|_| Some(prev_out.clone()), &mut unlocker).expect("cannot sign"); let sighash = spending_tx.signature_hash(0, &prev_out.script_pubkey, SigHashType::All as u32); println!("sighash = {}", sighash); println!("fully signed txid = {}", spending_tx.txid()); let txhex = serialize_hex(&spending_tx); println!("txhex = {}", txhex); } fn do_bip32() { //let seed: [u8; 32] = [0; 32]; //let seed = Vec::from_hex("000102030405060708090a0b0c0d0e0f").unwrap(); //possibly use: //extern crate rand; use rand::OsRng; //let mut rng = OsRng::new().expect("OsRng"); //let xprv = bip32::ExtendedPrivKey::new_master(Network::Bitcoin, &seed).unwrap(); let xprv = bip32::ExtendedPrivKey::from_str( "xprv9s21ZrQH143K2JF8RafpqtKiTbsbaxEeUaMnNHsm5o6wCW3z8ySyH4UxFVSfZ8n7ESu7fgir8imbZKLYVBxFPND1pniTZ81vKfd45EHKX73" ).unwrap(); println!("xprv = {}", xprv); println!("deserialized xprv = {:#?}", xprv); let secp = Secp256k1::new(); let child_key = xprv.derive_priv(&secp, &bip32::DerivationPath::from_str("m/0'/0").unwrap()).unwrap(); println!("child_key = {}", child_key); //if the xprv is obtained above with from_str() then this should be //xprv9wHokC2KXdTSpEepFcu53hMDUHYfAtTaLEJEMyxBPAMf78hJg17WhL5FyeDUQH5KWmGjGgEb2j74gsZqgupWpPbZgP6uFmP8MYEy5BNbyET println!("child privkey = {}", child_key.private_key); } fn rpc_descriptors() -> Result<(), Error> { let auth = Auth::UserPass( "regtestrpcuser".to_string(), "regtestrpcpass".to_string() ); let rpc = Client::new( "http://localhost:18443/wallet/wallet.dat".to_string(), auth)?; let info = rpc.get_blockchain_info()?; //println!("info = {:#?}", info); println!("best block hash = {}", info.best_block_hash); let txes = rpc.list_transactions(Some("*"), Some(1), Some(0), Some(true)).unwrap(); println!("txes = {:#?}", txes); let xpub = bip32::ExtendedPubKey::from_str( "tpubDE5vtSEeHLG1iGPveZffKaMJsFsr5ijqb8rcZ3ex6NxwaGn58TD522jAyi3JsAWcYUBDXPGcrs6AB8s43zxkph9ou4t4HHAQvoJSv8SNaKE" ).unwrap(); println!("xpub = {:?}", xpub); //ill leave it, its obviously going to work //have the xpub //use deriveaddress to get an address //check if its imported //if not, use importmulti //check the m/x number //check if its paid to // see if theres any info added to listtransactions or whatever Ok(()) } fn create_and_spend_from_2of2_multisig() -> Result<(), Error> { //creating multisig redeem scripts //https://github.com/rust-bitcoin/rust-lightning/blob/22a0dd5f339058fd6733920ffca0f5eb64db4e32/lightning/src/routing/network_graph.rs#L104 //ideas for how to sign here // https://github.com/rust-bitcoin/rust-lightning/blob/master/lightning/src/chain/keysinterface.rs //see under InMemoryChannelKeys //create two private keys, and convert to public keys //from those create address, print it //check if address is funded with listunspent // if not then exit //spend from address let secp = Secp256k1::new(); let priv_a = PrivateKey { compressed: true, network: Network::Regtest, key: secp256k1::SecretKey::from_slice(&[0xab; 32]).unwrap() }; let pub_a = priv_a.public_key(&secp); let priv_b = PrivateKey { compressed: true, network: Network::Regtest, key: secp256k1::SecretKey::from_slice(&[0xcd; 32]).unwrap() }; let pub_b = priv_b.public_key(&secp); println!("priv_a = {}", priv_a); println!("priv_b = {}", priv_b); println!("pub_a = {}", pub_a); println!("pub_b = {}", pub_b); let redeemscript = Builder::new() .push_opcode(opcodes::all::OP_PUSHNUM_2) .push_key(&pub_a) .push_key(&pub_b) .push_opcode(opcodes::all::OP_PUSHNUM_2) .push_opcode(opcodes::all::OP_CHECKMULTISIG).into_script(); println!("redeemscript hex = {:x}", redeemscript); let addr = Address::p2wsh(&redeemscript, Network::Regtest); println!("addr = {}", addr); let auth = Auth::UserPass( "regtestrpcuser".to_string(), "regtestrpcpass".to_string() ); let rpc = Client::new( "http://localhost:18443/wallet/wallet.dat".to_string(), auth)?; let utxos = rpc.list_unspent(Some(0), Some(999999), Some(&[&addr]), None, None)?; if utxos.len() != 1 { println!("wrong number of coins on address, exiting"); return Ok(()) } let utxo = &utxos[0]; println!("utxo = {:#?}", utxo); let output_value = utxo.amount.as_sat() - 50000; let gettx = rpc.get_transaction(&utxo.txid, Some(true)).unwrap(); println!("gettx = {:?}", gettx); let funding_tx = deserialize::(&gettx.hex).unwrap(); println!("funding_tx = {:?}", funding_tx); let mut spending_tx = Transaction { input: vec![ TxIn { previous_output: OutPoint { txid: utxo.txid, vout: utxo.vout }, sequence: 0, witness: Vec::new(), script_sig: Script::new() } ], output: vec![ TxOut { script_pubkey: Address ::from_str("2Mvp1FFijppV7HfQLTJ11CHoRqpAJi8qXbf") .unwrap().script_pubkey(), value: output_value, } ], lock_time: 0, version: 2, }; let sighash = secp256k1::Message::from_slice( &bip143::SighashComponents::new(&spending_tx).sighash_all( &spending_tx.input[0], &redeemscript, utxo.amount.as_sat())[..]) .unwrap(); println!("sighash = {:?}", sighash); let sig_a = secp.sign(&sighash, &priv_a.key); println!("sig_a = {}", sig_a); let sig_b = secp.sign(&sighash, &priv_b.key); println!("sig_b = {}", sig_b); spending_tx.input[0].witness.push(Vec::new()); //first is multisig dummy spending_tx.input[0].witness.push(sig_a.serialize_der().to_vec()); spending_tx.input[0].witness.push(sig_b.serialize_der().to_vec()); spending_tx.input[0].witness[1].push(SigHashType::All as u8); spending_tx.input[0].witness[2].push(SigHashType::All as u8); spending_tx.input[0].witness.push(redeemscript.into_bytes()); println!("fully signed txid = {}", spending_tx.txid()); let txhex = serialize_hex(&spending_tx); println!("txhex = {}", txhex); let accepted = rpc.test_mempool_accept(&[txhex]); println!("testmempoolaccept = {:?}", accepted); Ok(()) } fn create_and_spend_from_htlc() -> Result<(), Error> { //create htlc redeem script // https://github.com/rust-bitcoin/rust-lightning/blob/7dca3a9e29841d1d50f5b5f5ca226d65d7420710/lightning/src/ln/chan_utils.rs#L374 /* OP_HASH160 H(X) OP_EQUAL OP_IF pub_hashlock OP_ELSE locktime OP_CHECKSEQUENCEVERIFY OP_DROP pub_timelock OP_ENDIF OP_CHECKSIG */ //spent with witnesses: //hashlock case: // //timelock case: // let secp = Secp256k1::new(); let priv_hashlock = PrivateKey { compressed: true, network: Network::Regtest, key: secp256k1::SecretKey::from_slice(&[0xcd; 32]).unwrap() }; let pub_hashlock = priv_hashlock.public_key(&secp); println!("hashlock pub key = {}", pub_hashlock); let priv_timelock = PrivateKey { compressed: true, network: Network::Regtest, key: secp256k1::SecretKey::from_slice(&[0xab; 32]).unwrap() }; let pub_timelock = priv_timelock.public_key(&secp); println!("timelock pub key = {}", pub_timelock); let preimage = [0xef; 32]; println!("preimage = {:?}", preimage); let hashvalue = Hash160::hash(&preimage); println!("hashvalue = {:?}", hashvalue); let hashvalue = hashvalue.into_inner(); let locktime = 10; //blocks let redeemscript = Builder::new() .push_opcode(opcodes::all::OP_HASH160) .push_slice(&hashvalue[..]) .push_opcode(opcodes::all::OP_EQUAL) .push_opcode(opcodes::all::OP_IF) .push_key(&pub_hashlock) .push_opcode(opcodes::all::OP_ELSE) .push_int(locktime) .push_opcode(opcodes::all::OP_CSV) .push_opcode(opcodes::all::OP_DROP) .push_key(&pub_timelock) .push_opcode(opcodes::all::OP_ENDIF) .push_opcode(opcodes::all::OP_CHECKSIG) .into_script(); println!("redeemscript hex = {:x}", redeemscript); println!("redeemscript len = {}", redeemscript.len()); let addr = Address::p2wsh(&redeemscript, Network::Regtest); println!("addr = {}", addr); let auth = Auth::UserPass( "regtestrpcuser".to_string(), "regtestrpcpass".to_string() ); let rpc = Client::new( "http://localhost:18443/wallet/wallet.dat".to_string(), auth)?; let utxos = rpc.list_unspent(Some(0), Some(999999), Some(&[&addr]), None, None)?; if utxos.len() != 1 { println!("wrong number of coins on address, exiting"); return Ok(()) } let utxo = &utxos[0]; println!("utxo = {:#?}", utxo); //spend by providing a hash value let mut hash_spending_tx = Transaction { input: vec![ TxIn { previous_output: OutPoint { txid: utxo.txid, vout: utxo.vout }, sequence: 0, witness: Vec::new(), script_sig: Script::new() } ], output: vec![ TxOut { script_pubkey: Address ::from_str("2Mvp1FFijppV7HfQLTJ11CHoRqpAJi8qXbf") .unwrap().script_pubkey(), value: utxo.amount.as_sat() - 50000, } ], lock_time: 0, version: 2, }; let sighash = secp256k1::Message::from_slice( &bip143::SighashComponents::new(&hash_spending_tx).sighash_all( &hash_spending_tx.input[0], &redeemscript, utxo.amount.as_sat())[..]) .unwrap(); let sig_hashlock = secp.sign(&sighash, &priv_hashlock.key); println!("sig_hashlock = {}", sig_hashlock); hash_spending_tx.input[0].witness.push(sig_hashlock.serialize_der() .to_vec()); hash_spending_tx.input[0].witness[0].push(SigHashType::All as u8); hash_spending_tx.input[0].witness.push(preimage.to_vec()); hash_spending_tx.input[0].witness.push(redeemscript.as_bytes().to_vec()); println!("fully signed txid = {}", hash_spending_tx.txid()); let txhex = serialize_hex(&hash_spending_tx); println!("txhex = {}", txhex); let accepted = rpc.test_mempool_accept(&[txhex]); println!("testmempoolaccept = {:?}", accepted); //spend with the timelock let mut time_spending_tx = Transaction { input: vec![ TxIn { previous_output: OutPoint { txid: utxo.txid, vout: utxo.vout }, sequence: locktime as u32, witness: Vec::new(), script_sig: Script::new() } ], output: vec![ TxOut { script_pubkey: Address ::from_str("2Mvp1FFijppV7HfQLTJ11CHoRqpAJi8qXbf") .unwrap().script_pubkey(), value: utxo.amount.as_sat() - 50000, } ], lock_time: 0, version: 2, }; let sighash = secp256k1::Message::from_slice( &bip143::SighashComponents::new(&time_spending_tx).sighash_all( &time_spending_tx.input[0], &redeemscript, utxo.amount.as_sat())[..]) .unwrap(); let sig_timelock = secp.sign(&sighash, &priv_timelock.key); println!("sig_timelock = {}", sig_timelock); time_spending_tx.input[0].witness.push(sig_timelock.serialize_der() .to_vec()); time_spending_tx.input[0].witness[0].push(SigHashType::All as u8); time_spending_tx.input[0].witness.push(Vec::new()); time_spending_tx.input[0].witness.push(redeemscript.into_bytes()); println!("fully signed txid = {}", time_spending_tx.txid()); let txhex = serialize_hex(&time_spending_tx); println!("txhex = {}", txhex); let accepted = rpc.test_mempool_accept(&[txhex]); println!("testmempoolaccept = {:?}", accepted); Ok(()) } #[allow(non_snake_case)] fn create_tweaked_pubkey() { /* q = EC privkey generated by maker Q = q.G = EC pubkey published by maker p = nonce generated by taker P = p.G = nonce point calculated by taker R = Q + P = pubkey used in bitcoin transaction = (q + p).G */ let secp = Secp256k1::new(); let q_n = [0x44; 32]; //let q = let mut q = secp256k1::SecretKey::from_slice(&q_n).unwrap(); println!("q = {:?}", q); let Q = secp256k1::PublicKey::from_secret_key(&secp, &q); println!("Q = {}", Q); let p_n = [0x22; 32]; let p = secp256k1::SecretKey::from_slice(&p_n).unwrap(); println!("p = {:?}", p); let P = secp256k1::PublicKey::from_secret_key(&secp, &p); println!("P = {}", P); let R_taker = Q.combine(&P).unwrap(); println!("R_taker = {}", R_taker); q.add_assign(&p_n[..]).unwrap(); let R_maker = secp256k1::PublicKey::from_secret_key(&secp, &q); println!("R_maker = {}", R_maker); } fn get_tweakable_pubkey() { const SEED_PHRASE: &str = "great rice pitch bitter stay crash among position disease enable sell road"; const EXTENSION: &str = "helloworld"; //root key according to the bip39.org site // xprv9s21ZrQH143K3x8kThmrmvTXGthvazWEVMuM18Rfz9cBijs5krbwsxSmU6PJTqQtYCb4EjinKaQzvaGQMXa45gckv1GBbahu5eh2ZWeppGA //importing seed into electrum does indeed give the same address as below // m/44'/1'/0' //addr(0, 0) = 1HVyNRjLPQiWti9P8umBv2AfYP2cSHEXNo const PASSPHRASE: &str = ""; let master = MasterAccount::from_mnemonic( &mnemonic::Mnemonic::from_str(SEED_PHRASE).unwrap(), 0, Network::Regtest, PASSPHRASE, Some(EXTENSION) ).unwrap(); let unlocker = Unlocker::new_for_master(&master, PASSPHRASE).unwrap(); println!("master private key = {}\n = {:?}", unlocker.master_private(), unlocker.master_private()); let tweakable_privkey = unlocker.context().private_child( unlocker.master_private(), bip32::ChildNumber::from_hardened_idx(0).unwrap() ).unwrap().private_key; println!("tweakable_privkey = {}", tweakable_privkey); let secp = Secp256k1::new(); let tweakable_pubkey = tweakable_privkey.public_key(&secp); println!("tweakable_pubkey = {}", tweakable_pubkey); } fn create_and_spend_from_new_htlc_plus_tests() -> Result<(), Error> { //new htlc refers to the script which fixes the oversize preimage attack let secp = Secp256k1::new(); let priv_hashlock = PrivateKey { compressed: true, network: Network::Regtest, key: secp256k1::SecretKey::from_slice(&[0xcd; 32]).unwrap() }; let pub_hashlock = priv_hashlock.public_key(&secp); let priv_timelock = PrivateKey { compressed: true, network: Network::Regtest, key: secp256k1::SecretKey::from_slice(&[0xab; 32]).unwrap() }; let pub_timelock = priv_timelock.public_key(&secp); let preimage = [0xef; 32]; let hashvalue = Hash160::hash(&preimage); let hashvalue = hashvalue.into_inner(); let locktime = 80; //blocks //this way avoids the malleability from OP_IF //https://lists.linuxfoundation.org/pipermail/lightning-dev/2016-September/000605.html //the attack here is that OP_IF accepts anything nonzero as true, so someone // could replace the argument with something much bigger, which would // reduce the tx fee rate, the solution is to only use OP_IF after OP_EQUAL //27-10-2020 //oversize preimage attack //https://lists.linuxfoundation.org/pipermail/lightning-dev/2016-May/000529.html //the purpose is also to prevent the attack reducing the fee rate of the // transaction, possibly making it not get mined //one naive solution is OP_SIZE 32 OP_EQUALVERIFY // but then you force even the locktime case to waste 32 bytes of witness //so we use this script which requires size zero for the locktime branch //we also want the hashlock case to be locked with 1 OP_CSV //which disables CPFP and therefore avoids transaction pinning //see https://bitcoinops.org/en/topics/transaction-pinning/ /* opcodes | stack after execution | | OP_SIZE | OP_SWAP | OP_HASH160 | H(X) | H(X) OP_EQUAL | 1|0 OP_IF | pub_hashlock | 32 | 32 1 | 32 1 OP_ELSE | pub_timelock | 0 | 0 locktime | 0 OP_ENDIF | OP_CHECKSEQUENCEVERIFY | (32|0) (1|) OP_DROP | (32|0) OP_ROT | (32|0) OP_EQUALVERIFY | OP_CHECKSIG | true|false */ //spent with witnesses: //hashlock case: // //timelock case: // let redeemscript = Builder::new() .push_opcode(opcodes::all::OP_SIZE) .push_opcode(opcodes::all::OP_SWAP) .push_opcode(opcodes::all::OP_HASH160) .push_slice(&hashvalue[..]) .push_opcode(opcodes::all::OP_EQUAL) .push_opcode(opcodes::all::OP_IF) .push_key(&pub_hashlock) .push_int(32) .push_int(1) .push_opcode(opcodes::all::OP_ELSE) .push_key(&pub_timelock) .push_int(0) .push_int(locktime) .push_opcode(opcodes::all::OP_ENDIF) .push_opcode(opcodes::all::OP_CSV) .push_opcode(opcodes::all::OP_DROP) .push_opcode(opcodes::all::OP_ROT) .push_opcode(opcodes::all::OP_EQUALVERIFY) .push_opcode(opcodes::all::OP_CHECKSIG) .into_script(); println!("redeemscript hex = {:x}", redeemscript); println!("redeemscript len = {}", redeemscript.len()); let addr = Address::p2wsh(&redeemscript, Network::Regtest); println!("addr = {}", addr); //importaddress let auth = Auth::UserPass( "regtestrpcuser".to_string(), "regtestrpcpass".to_string() ); let rpc = Client::new( "http://localhost:18443/wallet/wallet.dat".to_string(), auth)?; let utxos = rpc.list_unspent(Some(0), Some(999999), Some(&[&addr]), None, None)?; if utxos.len() != 1 { println!("wrong number of coins on address, exiting"); return Ok(()) } let utxo = &utxos[0]; println!("utxo = {:#?}", utxo); //unsigned spending tx let spending_tx = Transaction { input: vec![ TxIn { previous_output: OutPoint { txid: utxo.txid, vout: utxo.vout }, sequence: locktime as u32, witness: Vec::new(), script_sig: Script::new() } ], output: vec![ TxOut { script_pubkey: Address ::from_str("2Mvp1FFijppV7HfQLTJ11CHoRqpAJi8qXbf") .unwrap().script_pubkey(), value: utxo.amount.as_sat() - 50000, } ], lock_time: 0, version: 2, }; //spend validly with hashlock let mut hash_spending_tx = spending_tx.clone(); hash_spending_tx.input[0].sequence = 1; let hash_sighash = secp256k1::Message::from_slice( &bip143::SighashComponents::new(&hash_spending_tx).sighash_all( &hash_spending_tx.input[0], &redeemscript, utxo.amount.as_sat() )[..] ).unwrap(); let sig_hashlock = secp.sign(&hash_sighash, &priv_hashlock.key); hash_spending_tx.input[0].witness.push(sig_hashlock.serialize_der() .to_vec()); hash_spending_tx.input[0].witness[0].push(SigHashType::All as u8); hash_spending_tx.input[0].witness.push(preimage.to_vec()); hash_spending_tx.input[0].witness.push(redeemscript.as_bytes().to_vec()); println!("hash spending txid = {}", hash_spending_tx.txid()); let txhex = serialize_hex(&hash_spending_tx); let accepted = rpc.test_mempool_accept(&[txhex]).unwrap(); println!("testmempoolaccept = {:?}", accepted); let time_sighash = secp256k1::Message::from_slice( &bip143::SighashComponents::new(&spending_tx).sighash_all( &spending_tx.input[0], &redeemscript, utxo.amount.as_sat() )[..] ).unwrap(); //spend validly with timelock let sig_timelock = secp.sign(&time_sighash, &priv_timelock.key); let mut time_spending_tx = spending_tx.clone(); time_spending_tx.input[0].witness.push(sig_timelock.serialize_der() .to_vec()); time_spending_tx.input[0].witness[0].push(SigHashType::All as u8); time_spending_tx.input[0].witness.push(Vec::new()); time_spending_tx.input[0].witness.push(redeemscript.as_bytes().to_vec()); println!("time spending txid = {}", time_spending_tx.txid()); let txhex = serialize_hex(&time_spending_tx); let accepted = rpc.test_mempool_accept(&[txhex]).unwrap(); println!("testmempoolaccept = {:?}", accepted); //spend invalidly with timelock but non-empty preimage let sig_timelock = secp.sign(&time_sighash, &priv_timelock.key); let mut time_spending_tx = spending_tx.clone(); time_spending_tx.input[0].witness.push(sig_timelock.serialize_der() .to_vec()); time_spending_tx.input[0].witness[0].push(SigHashType::All as u8); let non_preimage = [0xef; 1]; time_spending_tx.input[0].witness.push(non_preimage.to_vec()); time_spending_tx.input[0].witness.push(redeemscript.as_bytes().to_vec()); println!("invalid time spending txid = {}", time_spending_tx.txid()); let txhex = serialize_hex(&time_spending_tx); let accepted = rpc.test_mempool_accept(&[txhex]).unwrap(); println!("testmempoolaccept = {:?}", accepted); Ok(()) } fn pattern_match_contract() { let secp = Secp256k1::new(); let pub_hashlock = PrivateKey { compressed: true, network: Network::Regtest, key: secp256k1::SecretKey::from_slice(&[0xcd; 32]).unwrap() }.public_key(&secp); let pub_timelock = PrivateKey { compressed: true, network: Network::Regtest, key: secp256k1::SecretKey::from_slice(&[0xab; 32]).unwrap() }.public_key(&secp); println!("pub_hashlock = {}", pub_hashlock); println!("pub_timelock = {}", pub_timelock); let preimage = [0xef; 32]; let hashvalue = Hash160::hash(&preimage); let hashvalue = hashvalue.into_inner(); println!("hashvalue = {:x?}", hashvalue); let locktime = 144*2; //blocks println!("locktime = {:x}", locktime); let redeemscript = Builder::new() .push_opcode(opcodes::all::OP_SIZE) .push_opcode(opcodes::all::OP_SWAP) .push_opcode(opcodes::all::OP_HASH160) .push_slice(&hashvalue[..]) .push_opcode(opcodes::all::OP_EQUAL) .push_opcode(opcodes::all::OP_IF) .push_key(&pub_hashlock) .push_int(32) .push_int(1) .push_opcode(opcodes::all::OP_ELSE) .push_key(&pub_timelock) .push_int(0) .push_int(locktime) .push_opcode(opcodes::all::OP_ENDIF) .push_opcode(opcodes::all::OP_CSV) .push_opcode(opcodes::all::OP_DROP) .push_opcode(opcodes::all::OP_ROT) .push_opcode(opcodes::all::OP_EQUALVERIFY) .push_opcode(opcodes::all::OP_CHECKSIG) .into_script(); println!("redeemscript hex = {:x}", redeemscript); let mut vec_rs: Vec = redeemscript.to_bytes(); let slice = &vec_rs[0..8]; println!("first 8 = {:x?}", slice); let pattern = [130, 124, 169, 20, 142, 105, 180, 87]; println!("same = {}", slice == pattern); println!("hashvalue = {:x?}", &vec_rs[4..24]); println!("pub_hashlock = {:x?}", &vec_rs[27..60]); println!("pub_timelock = {:x?}", &vec_rs[65..98]); println!("locktime = {:x?}", &vec_rs[100..102]); let locktime: i64 = vec_rs[100] as i64 | (vec_rs[101] as i64) << 8; println!("locktime int = {}", locktime); const HASHVALUE_PLACEHOLDER: [u8; 20] = [0xcc; 20]; const PUB_HASHLOCK_PLACEHOLDER: [u8; 33] = [0xee; 33]; const PUB_TIMELOCK_PLACEHOLDER: [u8; 33] = [0xdd; 33]; const LOCKTIME_PLACEHOLDER: [u8; 2] = [0xb3, 0x15]; //number 0x15b3 = 5555 vec_rs.splice(4..24, HASHVALUE_PLACEHOLDER.iter().cloned()); vec_rs.splice(27..60, PUB_HASHLOCK_PLACEHOLDER.iter().cloned()); vec_rs.splice(65..98, PUB_TIMELOCK_PLACEHOLDER.iter().cloned()); vec_rs.splice(100..102, LOCKTIME_PLACEHOLDER.iter().cloned()); let template_redeemscript = Script::from(vec_rs); println!("template_rs = {}", template_redeemscript.to_hex()); //827ca9148e69b45727a12351d05d133b43b81a43d55f4d6c87632102b98a7fb8cc007048625b6446ad49a1b3a722df8c1ca975b87160023e14d1909701206755b275210381aaadc8a5e83f4576df823cf22a5b1969cf704a0d5f6f68bd757410c9917aac00687b88ac } //wrote this for indium contract.rs but it turned out to be useless //so saving it here /* fn does_redeemscript_match_contract(redeemscript: &Script) -> bool { const PUB_HASHLOCK_PLACEHOLDER: [u8; 33] = [0xee; 33]; const PUB_TIMELOCK_PLACEHOLDER: [u8; 33] = [0xdd; 33]; const HASHVALUE_PLACEHOLDER: [u8; 20] = [0xcc; 20]; const LOCKTIME_PLACEHOLDER_BYTES: [u8; 2] = [0x7f; 2]; const LOCKTIME_PLACEHOLDER_NUMBER: i64 = 0x7f7f; let template_redeemscript = create_contract_redeemscript( &PublicKey::from_slice(&PUB_HASHLOCK_PLACEHOLDER).unwrap(), &PublicKey::from_slice(&PUB_TIMELOCK_PLACEHOLDER).unwrap(), HASHVALUE_PLACEHOLDER, LOCKTIME_PLACEHOLDER_NUMBER ).into_bytes(); let mut rs_bytes = redeemscript.to_bytes(); if rs_bytes.len() != template_redeemscript.len() { return false; } rs_bytes.splice(4..24, HASHVALUE_PLACEHOLDER.iter().cloned()); rs_bytes.splice(27..60, PUB_HASHLOCK_PLACEHOLDER.iter().cloned()); rs_bytes.splice(64..66, LOCKTIME_PLACEHOLDER_BYTES.iter().cloned()); rs_bytes.splice(69..102, PUB_TIMELOCK_PLACEHOLDER.iter().cloned()); rs_bytes == template_redeemscript } */ fn create_multisig_redeemscript(key1: &PublicKey, key2: &PublicKey ) -> Script { let builder = Builder::new().push_opcode(opcodes::all::OP_PUSHNUM_2); if key1.serialize()[..] < key2.serialize()[..] { builder .push_key(key1) .push_key(key2) } else { builder .push_key(key2) .push_key(key1) } .push_opcode(opcodes::all::OP_PUSHNUM_2) .push_opcode(opcodes::all::OP_CHECKMULTISIG).into_script() } fn pattern_match_multisig_script() { let secp = Secp256k1::new(); let pub1 = PrivateKey { compressed: true, network: Network::Regtest, key: secp256k1::SecretKey::from_slice(&[0xcd; 32]).unwrap() }.public_key(&secp); let pub2 = PrivateKey { compressed: true, network: Network::Regtest, key: secp256k1::SecretKey::from_slice(&[0xab; 32]).unwrap() }.public_key(&secp); let redeemscript = create_multisig_redeemscript(&pub1, &pub2); println!("redeemscript = {:x}", redeemscript); let rs = redeemscript.to_bytes(); println!("pub1 = {:x?}", &rs[2..35]); println!("pub2 = {:x?}", &rs[36..69]); const PUB1_PLACEHOLDER: [u8; 33] = [0x02; 33]; const PUB2_PLACEHOLDER: [u8; 33] = [0x03; 33]; let pubkey1 = PublicKey::from_slice(&PUB1_PLACEHOLDER).unwrap(); let pubkey2 = PublicKey::from_slice(&PUB2_PLACEHOLDER).unwrap(); println!("pubkey1 = {}\npubkey2 = {}", pubkey1, pubkey2); /* let template_msig_redeemscript = wallet_sync::create_multisig_redeemscript( &pubkey1, &pubkey2 ); */ } #[derive(serde::Serialize, serde::Deserialize, Debug)] struct WalletFileData { version: u32, seedphrase: String, extension: String, external_index: u32, swap_coins: Vec, prevout_to_contract_map: HashMap } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct SwapCoin { pub my_privkey: SecretKey, pub other_pubkey: PublicKey, pub other_privkey: Option, pub contract_tx: Transaction, pub contract_redeemscript: Script, pub funding_amount: u64, pub others_contract_sig: Option, pub hash_preimage: Option<[u8; 32]> } fn serde_secret_key() { /* let wallet_file = File::open("taker.indium").unwrap(); let wallet_file_data: WalletFileData = serde_json::from_reader( wallet_file).unwrap(); */ let s = "{\"version\":0,\"seedphrase\":\"finger topple before stock embrace liar air now trouble beauty forum protect\",\"extension\":\"www\",\"external_index\":0,\"swap_coins\":[{\"my_privkey\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"other_pubkey\":\"02f64651c1fd49cf167d825c730e6af2d28a796ac78103dadb2070834b9300df9a\",\"other_privkey\":null,\"contract_tx\":{\"version\":2,\"lock_time\":0,\"input\":[{\"previous_output\":\"f1d5b2fbf9789f2249d2eefb44ec1aa4cb9609f97dd67c058459333e473f873d:1\",\"script_sig\":\"\",\"sequence\":0,\"witness\":[]}],\"output\":[{\"value\":499000,\"script_pubkey\":\"0020694226d1ba306bc4a6f6ebd443cbb456837055d5b4cbfa53f0572f04f3736a8c\"}]},\"contract_redeemscript\":\"827ca914794ba5ff38c2883538890a4fe167da2301d4a41e876321034c983b4b386d2b3e0b701b4a5f76f50a1297cb151463913fadb4ede276f342040120516721021617d38ed8d8657da4d4761e8057bc396ea9e4b9d29776d4be096016dbd2509b00016468b2757b88ac\",\"funding_amount\":500000,\"others_contract_sig\":\"304402207d795fab95e6e40e2cafe7342a314501079c66d5a331f6fa0abb4599e468e77a02207cce9ed2c5db1119b6c339ee31883013f6883f38aa9b753afeb3f83c1dc5af8c\",\"hash_preimage\":null}],\"prevout_to_contract_map\":{}}"; let wallet_file_data = serde_json::from_str::(&s); println!("wfd = {:?}", wallet_file_data); } fn convert_string_redeemscript_to_obj() { let redeemscript_str = "52210268680737c76dabb801cb2204f57dbe4e4579e4f710cd67dc1b4227592c81e9b521035ebc97ce40dd83f1d51fa69ede034f6bff0feb56d084b50b9992d353de29832452ae"; let redeemscript = Script::from(Vec::from_hex(redeemscript_str).unwrap()); println!("rs = {}", redeemscript); } fn pubkey_pair_to_descriptor() { let secp = Secp256k1::new(); let priv_a = PrivateKey { compressed: true, network: Network::Regtest, key: secp256k1::SecretKey::from_slice(&[0xab; 32]).unwrap() }; let pub_a = priv_a.public_key(&secp); let priv_b = PrivateKey { compressed: true, network: Network::Regtest, key: secp256k1::SecretKey::from_slice(&[0xcd; 32]).unwrap() }; let pub_b = priv_b.public_key(&secp); println!("pub_a = {}", pub_a); println!("pub_b = {}", pub_b); let pubkey_pair = (pub_a, pub_b); println!("pair = {:?}", pubkey_pair); //let d_str = format!("wsh(multi(2,{},{}))", pubkey_pair); //println!("d_str = {}", d_str); } fn reproduce_testnet_signing_bug() { let mut tx = Transaction { version: 2, lock_time: 0, input: vec![ TxIn { previous_output: OutPoint { txid: Txid::from_hex( "38924f09fdd18dea537661bb3bd15576d377406423e0e5db370b9897f0601305" ).unwrap(), vout: 0, }, script_sig: Script::new(), sequence: 0, witness: Vec::new(), }, ], output: vec![ TxOut { value: 4990000, script_pubkey: Address::from_str( "tb1qrcl26s2909ty9r734yr4dhpn6q09x2cecf2c4vllwfpnp4my6vxqq84us4" ).unwrap().script_pubkey(), }, TxOut { value: 120006951, script_pubkey: Address::from_str( "tb1qhdu4wc2aekml0lhh2czuttuyucc4m5mug3jkl2" ).unwrap().script_pubkey(), }, ], }; let privkey = PrivateKey::from_wif( "cVm2dvd7791RszBwy4Q2Uf7Efz1GCStfqErZyumr26P2WuxMVdeA").unwrap(); let amount = 124997104; println!("txid = {}", tx.txid()); println!("privkey = {}", privkey); let secp = bitcoin::secp256k1::Secp256k1::new(); let pubkey = privkey.public_key(&secp); let scriptcode = Builder::new() .push_opcode(opcodes::all::OP_DUP) .push_opcode(opcodes::all::OP_HASH160) .push_slice(&Hash160::hash( pubkey.to_bytes().as_slice())[..] ) .push_opcode(opcodes::all::OP_EQUALVERIFY) .push_opcode(opcodes::all::OP_CHECKSIG) .into_script(); let sighash = bip143::SighashComponents::new(&tx.clone()).sighash_all( &tx.input[0], &scriptcode, amount ); println!("sighash = {}", sighash); let signature = secp.sign( &bitcoin::secp256k1::Message::from_slice(&sighash[..]).unwrap(), &privkey.key ); println!("sig = {}", signature); tx.input[0].witness.push(signature.serialize_der().to_vec()); tx.input[0].witness[0].push(SigHashType::All as u8); tx.input[0].witness.push(pubkey.to_bytes()); println!("txhex = {}", serialize_hex(&tx)); } fn main() { let a = 15; match a { 0 => sign_tx(), 1 => sign_tx_from_indium_test_wallet(), 2 => generate_without_rustwallet_masteraccount(), 3 => do_bip32(), 4 => match rpc_descriptors() { Ok(_) => println!("ok"), Err(e) => println!("err = {}", e), }, 5 => println!("{:?}", create_and_spend_from_2of2_multisig()), 6 => println!("{:?}", create_and_spend_from_htlc()), 7 => create_tweaked_pubkey(), 8 => get_tweakable_pubkey(), 9 => println!("{:?}", create_and_spend_from_new_htlc_plus_tests()), 10 => pattern_match_contract(), 11 => pattern_match_multisig_script(), 12 => serde_secret_key(), 13 => convert_string_redeemscript_to_obj(), 14 => pubkey_pair_to_descriptor(), 15 => reproduce_testnet_signing_bug(), _ => println!("unknown"), }; //plan //come up with a bip32 privkey //send coins to addresses and spend from them //from that generate xpubs, use descriptors to generate single-sig addresses // will need to use descriptors at least for importing into core // rust code wont store all the addresses, but will only have the xprv // and using the index will be able to generate any privkey // so i must be able to use rust to sign a tx with a privkey //create a 2of2 multisig address, fund it and spend from it //figure out ECDH, i.e. tweaking a pubkey to get another pubkey // where the privkey can be used to get the privkey of the other pubkey // and so agree on a pubkey with one less step of interaction // its not exactly ECDH because an eavesdropper isnt a threat // rust-wallet context.rs has tweak_add() //figure out signing a hash //combine tokio and bitcoin json-rpc }