Created
December 28, 2024 01:54
-
-
Save jaonoctus/4ae7c625a504d35931542e3d95ed4fa6 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; | |
/* | |
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
https://mempool.space/signet/tx/080af3e3b86e10f1455719281f2727c4410f2480a8541d50583cbe11e6cb56d0