Last active
February 13, 2025 13:06
-
-
Save jaonoctus/388d1f15edd2e41a2be00bc504e03ada to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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