Skip to content

Instantly share code, notes, and snippets.

@jaonoctus
Last active February 13, 2025 13:06
Show Gist options
  • Save jaonoctus/388d1f15edd2e41a2be00bc504e03ada to your computer and use it in GitHub Desktop.
Save jaonoctus/388d1f15edd2e41a2be00bc504e03ada to your computer and use it in GitHub Desktop.
use bip39::Mnemonic;
use bitcoin::absolute::LockTime;
use bitcoin::bip32::{DerivationPath, Xpriv, Xpub};
use bitcoin::consensus::{deserialize, serialize};
use bitcoin::hex::{Case, DisplayHex, FromHex};
use bitcoin::{secp256k1, transaction, Network, OutPoint, Psbt, Script, Sequence, Transaction, TxIn, TxOut};
use miniscript::psbt::PsbtExt;
use miniscript::{DefiniteDescriptorKey, Descriptor, DescriptorPublicKey};
use std::str::FromStr;
use bitcoin::hashes::{sha256, Hash};
fn main() {
let secp: &secp256k1::Secp256k1<secp256k1::All> = &secp256k1::Secp256k1::new();
let network = Network::Signet;
let path = "86'/0'/0'";
// mainnet: xpub6DP7ebuRrfK2QYDecC1MrCzkkyi9jh26uNBVhsoTasNitJ94FC3ZuvkQyeLr9qaFzepVp4e5uzLXexMBiKjVrd6nwVEpeSXLbRFkPM4SZyG
// signet: tpubDDkkAqj7ZwGUgLQW4NzT48L866ooj5XASzmo4Rrbvn8cdaomKQtf4NRFDgRdgLXVnZMQRe9xT6AaM8qo7Bajk4Z615z8KyBaBNrA9Qz2CYy
let internal_public_key = "tpubDDkkAqj7ZwGUgLQW4NzT48L866ooj5XASzmo4Rrbvn8cdaomKQtf4NRFDgRdgLXVnZMQRe9xT6AaM8qo7Bajk4Z615z8KyBaBNrA9Qz2CYy";
let seed = Mnemonic::from_str("fetch print airport upon jazz run loyal live creek oblige inflict tent").unwrap().to_seed("");
let master_key = Xpriv::new_master(network, &seed).unwrap();
let extended_private_key = master_key.derive_priv(&secp, &path.parse::<DerivationPath>().unwrap()).unwrap();
let extended_public_key = Xpub::from_priv(&secp, &extended_private_key);
let (preimage, digest) = produce_preimage("the ultimate pre-preimage");
let descriptor_string = format!("
tr(
{internal_public_key}/0/0,
and_v(
v:pk([00000000/{path}]{extended_public_key}/0/*),
sha256({digest})
)
)
").replace(&[' ', '\n', '\t'][..], "");
let descriptor = Descriptor::<DescriptorPublicKey>::from_str(&descriptor_string).unwrap();
let derived_descriptor = descriptor.derived_descriptor(&secp, 0).unwrap();
assert!(derived_descriptor.sanity_check().is_ok());
println!("DESCRIPTOR = {}", derived_descriptor);
// mainnet: bc1pkxpc2h8y9k72r868a5v93mlgxt74jcgge4s84kwm7d6uqfaj64hsu6m5np
// signet: tb1pkxpc2h8y9k72r868a5v93mlgxt74jcgge4s84kwm7d6uqfaj64hstjdmfw
let address = derived_descriptor
.address(network)
.unwrap();
println!("ADDRESS = {}", address);
// create the psbt
// mainnet: f003ac8f3edda413fad73bc27b9f1bb8271a9a6528fd17c3365e3d22b37324a0
// signet: 1bb4f4228f67a54fa4e5cd61c06e5dbaac3be7fdae32a80ad597497077b0d930
let previous_tx_hex = "0200000000010138fa92b3a22688eff761d59cb1636b8a8670b4dae6dc79556093e2c07b1b120d0000000000fdffffff0284d9fa0200000000225120a4b33b039b4f0334da0b13d10208687176e1ccc730f2a1f12bf1f7af12d3c99c8813000000000000225120b183855ce42dbca19f47ed1858efe832fd596108cd607ad9dbf375c027b2d56f01400fcbad233b97a5aa0de591a5aac9c8613d2cf80dded85679060cfd7eb4867534f08efa1d115dcb82eb8afa6530e0e090622dc7069d91838c636d00fb428ec6d900000000";
let previous_tx: Transaction = deserialize(&Vec::from_hex(previous_tx_hex).unwrap()).unwrap();
let (outpoint, witness_utxo) = get_vout(&previous_tx, &derived_descriptor.script_pubkey());
let destination_address = bitcoin::Address::from_str("tb1ph2ac76y93lfqthj7d7q5l4ck2qg4swt3yt0mqa8w78a8h0aygfcqdta5za").unwrap().assume_checked();
let txin = TxIn {
previous_output: outpoint,
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
..Default::default()
};
let txout = TxOut {
script_pubkey: destination_address.script_pubkey(),
value: bitcoin::Amount::from_sat(5_000 - 1337),
};
let unsigned_tx = Transaction {
version: transaction::Version::TWO,
lock_time: LockTime::ZERO,
input: vec![txin],
output: vec![txout],
};
println!("UNSIGNED TX = {}", serialize(&unsigned_tx).to_hex_string(Case::Lower));
let mut psbt = Psbt::from_unsigned_tx(unsigned_tx).unwrap();
// sign
psbt.inputs[0].witness_utxo = Some(witness_utxo);
let desc = Descriptor::<DefiniteDescriptorKey>::from_str(&derived_descriptor.to_string()).unwrap();
psbt.update_input_with_descriptor(0, &desc).unwrap();
let key_to_sign = &master_key.derive_priv(&secp, &"86'/0'/0'/0/0".parse::<DerivationPath>().unwrap()).unwrap();
let _res = psbt.sign(key_to_sign, secp).unwrap();
psbt.inputs[0]
.sha256_preimages
.insert(digest, preimage.to_byte_array().to_vec());
psbt.finalize_mut(&secp).unwrap();
let signed_tx = psbt.extract_tx().unwrap();
let raw_tx = serialize(&signed_tx).to_hex_string(Case::Lower);
println!("SIGNED TX = {}", raw_tx);
}
fn get_vout(tx: &Transaction, spk: &Script) -> (OutPoint, TxOut) {
for (i, txout) in tx.clone().output.into_iter().enumerate() {
if spk == &txout.script_pubkey {
return (OutPoint::new(tx.compute_txid(), i as u32), txout);
}
}
panic!("Only call get vout on functions which have the expected outpoint");
}
fn produce_preimage(secret: &str) -> (sha256::Hash, sha256::Hash) {
let preimage = sha256::Hash::hash(secret.as_bytes());
let digest = sha256::Hash::hash(&preimage.to_byte_array());
(preimage, digest)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment