Skip to content

Instantly share code, notes, and snippets.

@chris-belcher
Created April 22, 2021 15:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chris-belcher/7988c798292883fd8421c6fa6b846ef5 to your computer and use it in GitHub Desktop.
Save chris-belcher/7988c798292883fd8421c6fa6b846ef5 to your computer and use it in GitHub Desktop.
rust bitcoin play teleport coinswap
// 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::<Transaction>(&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:
//<hashlock_signature> <preimage>
//timelock case:
//<timelock_signature> <empty>
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
|
| <sig> <preimage>
OP_SIZE | <sig> <preimage> <size>
OP_SWAP | <sig> <size> <preimage>
OP_HASH160 | <sig> <size> <hash>
H(X) | <sig> <size> <hash> H(X)
OP_EQUAL | <sig> <size> 1|0
OP_IF |
pub_hashlock | <sig> <size> <pub>
32 | <sig> <size> <pub> 32
1 | <sig> <size> <pub> 32 1
OP_ELSE |
pub_timelock | <sig> <size> <pub>
0 | <sig> <size> <pub> 0
locktime | <sig> <size> <pub> 0 <locktime>
OP_ENDIF |
OP_CHECKSEQUENCEVERIFY | <sig> <size> <pub> (32|0) (1|<locktime>)
OP_DROP | <sig> <size> <pub> (32|0)
OP_ROT | <sig> <pub> (32|0) <size>
OP_EQUALVERIFY | <sig> <pub>
OP_CHECKSIG | true|false
*/
//spent with witnesses:
//hashlock case:
//<hashlock_signature> <preimage>
//timelock case:
//<timelock_signature> <empty_vector>
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<u8> = 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<SwapCoin>,
prevout_to_contract_map: HashMap<OutPoint, Script>
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct SwapCoin {
pub my_privkey: SecretKey,
pub other_pubkey: PublicKey,
pub other_privkey: Option<SecretKey>,
pub contract_tx: Transaction,
pub contract_redeemscript: Script,
pub funding_amount: u64,
pub others_contract_sig: Option<Signature>,
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::<WalletFileData>(&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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment