Skip to content

Instantly share code, notes, and snippets.

@jaonoctus
Created December 28, 2024 01:54
Show Gist options
  • Save jaonoctus/4ae7c625a504d35931542e3d95ed4fa6 to your computer and use it in GitHub Desktop.
Save jaonoctus/4ae7c625a504d35931542e3d95ed4fa6 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;
/*
import hashlib
from sage.all import FiniteField, EllipticCurve, Integer
# secp256k1 parameters
F = FiniteField(Integer(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F))
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 # curve order
E = EllipticCurve([F(Integer(0)), F(Integer(7))])
# Original generator point G in uncompressed format
G_DER = '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8'
# Get H point by hashing G
H = E.lift_x(F(int(hashlib.sha256(bytes.fromhex(G_DER)).hexdigest(), 16)))
print("H (NUMS base point) = ")
print('%x %x' % H.xy())
# Generate scalar from string
r = Integer(int(hashlib.sha256("jaonoctus unspendable key-path".encode()).hexdigest(), 16) % n)
print("\nScalar r = ")
print(hex(r))
# Get the original G point
G = E.lift_x(F(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798))
# Calculate rG
rG = r * G
# Calculate final NUMS point: H + rG
final_nums = H + rG
print("\nFinal NUMS point (H + rG) = ")
print('%x %x' % final_nums.xy())
*/
fn main() {
let secp: &secp256k1::Secp256k1<secp256k1::All> = &secp256k1::Secp256k1::new();
let network = Network::Signet;
let path = "86'/0'/0'";
let nums_key = "2575ac0d328639136911eb82d6164b64908fef69f2c7df59314bdcae29653c26";
let seed = Mnemonic::from_str("zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong").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).to_string();
let descriptor_string = format!("
tr(
{nums_key},
pk([00000000/{path}]{extended_public_key}/0/*)
)
").replace(&[' ', '\n', '\t'][..], "");
let descriptor_string2 = format!("
tr(
{nums_key},
pk([00000000/{path}]{extended_public_key}/0/*)
)
").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\t{}", derived_descriptor);
let address = derived_descriptor
.address(network)
.unwrap();
println!("ADDRESS\t\t{}", address);
// create the psbt
let previous_tx_hex = "02000000000102d35784d4f4eb08df16dc27798de2465136ec26c068722b427ac20b43bbd2c8d20000000000fdffffff30d9b077704997d50aa832aefde73bacba5d6ec061cde5a44fa5678f22f4b41b0000000000fdffffff0247dafa0200000000225120720eb9d5a76916f94b42ad41a62673fc0e2a9f0b19d414809c1a674313931ead10270000000000002251201c32d33f9509c9122aa5e1aa4b8a67bd546fa260872ad946a1c3356055733ac60140d45f0bbc99a821661f297c498060dd82c397d36046e6a5230f326290f1c4770726f464891b9395b4294b7e9f8fdfaf063026927e1dbe2391a9bfbec165f0a3ed014096cfdcd72b6e59048e05d8d5ae7d4b0b979b4fea67a0b36750b42fcbf81a676861850894cdbb1b942f9a852bc1a0b547e9ccc694e8ecd6bd1590ab0fed65357e00000000";
let previous_tx: Transaction = deserialize(&Vec::from_hex(previous_tx_hex).unwrap()).unwrap();
// let amount = bitcoin::Amount::from_sat(10_000);
let (outpoint, witness_utxo) = get_vout(&previous_tx, &derived_descriptor.script_pubkey());
let destination_address = bitcoin::Address::from_str("tb1pumfttsaflznkqjmm2wlw2yfzxer89l5e8na7j0xlz7qqnueu5pdsmn5t5h").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(9_700),
};
let unsigned_tx = Transaction {
version: transaction::Version::TWO,
lock_time: LockTime::from_height(228_248).unwrap(),
input: vec![txin],
output: vec![txout],
};
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();
psbt.sign(key_to_sign, secp).unwrap();
psbt.finalize_mut(&secp).unwrap();
let signed_tx = psbt.extract_tx().unwrap();
let raw_tx = serialize(&signed_tx).to_hex_string(Case::Lower);
println!("{}", 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");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment