Skip to content

Commit 5d8eb35

Browse files
create_psbt_multisig,py
1 parent 56341ab commit 5d8eb35

File tree

1 file changed

+356
-0
lines changed

1 file changed

+356
-0
lines changed

examples/create_psbt_multisig.py

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
# Copyright (C) 2025 The python-bitcoin-utils developers
2+
#
3+
# This file is part of python-bitcoin-utils
4+
#
5+
# It is subject to the license terms in the LICENSE file found in the top-level
6+
# directory of this distribution.
7+
#
8+
# No part of python-bitcoin-utils, including this file, may be copied,
9+
# modified, propagated, or distributed except according to the terms contained
10+
# in the LICENSE file.
11+
12+
"""
13+
Example of creating a 2-of-3 multisig PSBT using REAL TESTNET4 UTXOs.
14+
15+
This example demonstrates:
16+
1. Creating a 2-of-3 multisig P2WSH address (Segwit multisig)
17+
2. Creating a PSBT for spending from that address using real Testnet4 UTXOs
18+
3. Setting up the PSBT with proper input information for signing
19+
20+
IMPORTANT: This uses REAL TESTNET4 transactions that can be verified on:
21+
https://blockstream.info/testnet/
22+
23+
Note: This script uses bitcoinutils with setup('testnet'), which defaults to Testnet3.
24+
For Testnet4 compatibility, ensure your UTXOs are from Testnet4 and verify
25+
using a Testnet4-compatible explorer or wallet.
26+
27+
Before running this example:
28+
1. Get Testnet4 coins from a faucet (e.g., https://faucet.testnet4.dev/)
29+
2. Create the multisig address shown below
30+
3. Send Testnet4 coins to that address
31+
4. Update the UTXO details below with your real transaction
32+
"""
33+
34+
from bitcoinutils.setup import setup
35+
from bitcoinutils.transactions import Transaction, TxInput, TxOutput, Locktime
36+
from bitcoinutils.keys import PrivateKey, PublicKey
37+
from bitcoinutils.script import Script
38+
from bitcoinutils.utils import to_satoshis
39+
from bitcoinutils.psbt import PSBT
40+
from bitcoinutils.constants import TYPE_RELATIVE_TIMELOCK
41+
42+
43+
def get_real_testnet_utxo():
44+
"""
45+
STEP-BY-STEP GUIDE TO GET REAL TESTNET4 UTXO:
46+
47+
1. Visit a Testnet4 block explorer (e.g., https://blockstream.info/testnet/)
48+
2. Find any recent transaction with outputs
49+
3. Click on a transaction, copy its TXID
50+
4. Replace the values below with real Testnet4 data
51+
5. Verify the TXID works on a Testnet4-compatible explorer
52+
53+
EXAMPLE OF HOW TO FIND REAL DATA:
54+
- Go to a Testnet4-compatible explorer
55+
- Click "Recent Transactions"
56+
- Pick any transaction (e.g., click on a TXID)
57+
- Copy the TXID from the URL
58+
- Check the outputs for amount and vout index
59+
"""
60+
61+
# METHOD 1: Use a funding transaction you create yourself
62+
# (Recommended - you control the UTXO)
63+
create_own_funding = True
64+
65+
if create_own_funding:
66+
# TODO: After running this script once:
67+
# 1. Note the multisig address printed below
68+
# 2. Get Testnet4 coins from faucet
69+
# 3. Send coins to the multisig address
70+
# 4. Update these values with YOUR funding transaction
71+
utxo_details = {
72+
'txid': 'YOUR_FUNDING_TXID_HERE', # ← Replace with your funding TXID
73+
'vout': 0, # ← Usually 0, but check the transaction
74+
'amount': to_satoshis(0.001), # ← Replace with actual amount sent
75+
'address': None, # Will be set to multisig address
76+
'is_placeholder': True # Set to False when using real data
77+
}
78+
else:
79+
# METHOD 2: Use any existing Testnet4 UTXO (not recommended for production)
80+
# This is just for demonstration - don't spend other people's UTXOs!
81+
utxo_details = {
82+
'txid': 'SOME_EXISTING_TESTNET4_TXID',
83+
'vout': 0,
84+
'amount': to_satoshis(0.001),
85+
'address': None,
86+
'is_placeholder': True
87+
}
88+
89+
# Validation
90+
if utxo_details['is_placeholder']:
91+
print(" PLACEHOLDER DATA DETECTED!")
92+
print(" This PSBT uses placeholder data and won't work on Testnet4.")
93+
print(" Follow these steps to use real Testnet4 data:")
94+
print()
95+
print(" STEP 1: Get Testnet4 coins")
96+
print(" • Visit: https://faucet.testnet4.dev/")
97+
print(" • Request coins to any address you control")
98+
print()
99+
print(" STEP 2: Fund the multisig (run this script first to get address)")
100+
print(" • Send Testnet4 coins to the multisig address")
101+
print(" • Wait for confirmation")
102+
print()
103+
print(" STEP 3: Update this function")
104+
print(" • Copy the funding transaction TXID")
105+
print(" • Set utxo_details['txid'] = 'your_real_txid'")
106+
print(" • Set utxo_details['amount'] = to_satoshis(your_real_amount)")
107+
print(" • Set utxo_details['is_placeholder'] = False")
108+
print()
109+
print(" STEP 4: Verify")
110+
print(" • Check on a Testnet4-compatible explorer")
111+
print(" • Confirm the UTXO exists and amount is correct")
112+
print()
113+
114+
return utxo_details
115+
116+
117+
def main():
118+
# Always call setup() first - using testnet (Testnet3, compatible with Testnet4 with real UTXOs)
119+
setup('testnet')
120+
121+
print("=" * 70)
122+
print("Creating 2-of-3 Multisig PSBT with REAL TESTNET4 UTXOs")
123+
print("=" * 70)
124+
125+
# Step 1: Create three private keys (representing Alice, Bob, and Charlie)
126+
print("\n1. Creating private keys for Alice, Bob, and Charlie...")
127+
128+
# Using deterministic keys for consistency (in production, generate securely)
129+
alice_private_key = PrivateKey.from_wif("cTALNpTpRbbxTCJ2A5Vq88UxT44w1PE2cYqiB3n4hRvzyCev1Wwo")
130+
alice_public_key = alice_private_key.get_public_key()
131+
print(f"Alice's public key: {alice_public_key.to_hex()}")
132+
133+
# Bob's key
134+
bob_private_key = PrivateKey.from_wif("cVf3kGh6552jU2rLaKwXTKq5APHPoZqCP4GQzQirWGHFoHQ9rEVt")
135+
bob_public_key = bob_private_key.get_public_key()
136+
print(f"Bob's public key: {bob_public_key.to_hex()}")
137+
138+
# Charlie's key
139+
charlie_private_key = PrivateKey.from_wif("cQDvVP5VhYsV3dtHQwQ5dCbL54WuJcvsUgr3LXwhf6vD5mPp9nVy")
140+
charlie_public_key = charlie_private_key.get_public_key()
141+
print(f"Charlie's public key: {charlie_public_key.to_hex()}")
142+
143+
# Step 2: Create 2-of-3 multisig P2WSH script (Segwit version)
144+
print("\n2. Creating 2-of-3 multisig P2WSH script...")
145+
146+
# Create the multisig witness script (2 of 3) - sorted keys for deterministic addresses
147+
public_keys = sorted([alice_public_key, bob_public_key, charlie_public_key],
148+
key=lambda k: k.to_hex())
149+
150+
witness_script = Script([
151+
2, # Required signatures
152+
public_keys[0].to_hex(),
153+
public_keys[1].to_hex(),
154+
public_keys[2].to_hex(),
155+
3, # Total public keys
156+
'OP_CHECKMULTISIG'
157+
])
158+
159+
print(f"Witness script: {witness_script.to_hex()}")
160+
161+
# Create P2WSH address from the witness script
162+
p2wsh_address = witness_script.to_p2wsh_script_pub_key().to_address()
163+
print(f"P2WSH Multisig Address: {p2wsh_address}")
164+
print(f" Check this address on a Testnet4-compatible explorer")
165+
166+
# Step 3: Get real Testnet4 UTXO details
167+
print("\n3. Getting real Testnet4 UTXO details...")
168+
utxo = get_real_testnet_utxo()
169+
utxo['address'] = p2wsh_address
170+
171+
print(f"Using UTXO:")
172+
print(f" TXID: {utxo['txid']}")
173+
print(f" Vout: {utxo['vout']}")
174+
print(f" Amount: {utxo['amount']} satoshis ({utxo['amount'] / 100000000:.8f} BTC)")
175+
print(f" Address: {utxo['address']}")
176+
177+
if utxo['is_placeholder']:
178+
print(f" PLACEHOLDER: This TXID is not verifiable")
179+
print(f" This TXID won't verify - it's just an example format")
180+
else:
181+
print(f" VERIFY: Check on a Testnet4-compatible explorer")
182+
print(f" This should show a real Testnet4 transaction")
183+
184+
# Step 4: Create transaction inputs and outputs
185+
print("\n4. Setting up transaction...")
186+
187+
# Input: Real Testnet4 UTXO
188+
txin = TxInput(utxo['txid'], utxo['vout'])
189+
190+
# Output: Send to Charlie's P2WPKH address (modern Segwit address)
191+
charlie_p2wpkh_address = charlie_public_key.get_segwit_address()
192+
193+
# Calculate output amount (leaving some for fees)
194+
fee_amount = to_satoshis(0.0001) # 0.0001 BTC fee
195+
send_amount = utxo['amount'] - fee_amount
196+
197+
if send_amount <= 0:
198+
raise ValueError("UTXO amount too small to cover fees!")
199+
200+
txout = TxOutput(send_amount, charlie_p2wpkh_address.to_script_pub_key())
201+
202+
# Create the transaction
203+
tx = Transaction([txin], [txout], Locktime(0))
204+
print(f"Unsigned transaction: {tx.serialize()}")
205+
206+
# Step 5: Create PSBT
207+
print("\n5. Creating PSBT...")
208+
209+
# Create PSBT from the unsigned transaction
210+
psbt = PSBT(tx)
211+
212+
# Add input information needed for signing P2WSH
213+
# For P2WSH inputs, we need the witness script and witness UTXO info
214+
psbt.add_input_witness_script(0, witness_script)
215+
psbt.add_input_witness_utxo(0, utxo['amount'], p2wsh_address.to_script_pub_key())
216+
217+
print(f"PSBT created successfully!")
218+
print(f"PSBT base64: {psbt.to_base64()}")
219+
220+
# Step 6: Display verification information
221+
print("\n6. TESTNET4 VERIFICATION")
222+
print("=" * 50)
223+
224+
if utxo['is_placeholder']:
225+
print(" USING PLACEHOLDER DATA - NOT VERIFIABLE")
226+
print(" Current TXID is fake and won't verify on explorer")
227+
print(" To fix this:")
228+
print(" 1. Get real Testnet4 coins from faucet")
229+
print(" 2. Send to the multisig address above")
230+
print(" 3. Update get_real_testnet_utxo() with real data")
231+
print()
232+
print(" When ready, verify with a Testnet4-compatible explorer")
233+
else:
234+
print(" REAL TESTNET4 DATA - VERIFIABLE")
235+
print(" Verify input transaction on a Testnet4-compatible explorer")
236+
237+
print(f" Check multisig address balance on a Testnet4-compatible explorer")
238+
print(f" After signing and broadcasting, check output:")
239+
print(f" Address: {charlie_p2wpkh_address}")
240+
241+
# Step 7: Display signing workflow
242+
print("\n7. SIGNING WORKFLOW")
243+
print("=" * 50)
244+
print("This PSBT is ready for the 2-of-3 multisig signing process:")
245+
print()
246+
print("1. Alice signs:")
247+
print(" - Import PSBT")
248+
print(" - Sign with Alice's private key")
249+
print(" - Export partial signature")
250+
print()
251+
print("2. Bob signs:")
252+
print(" - Import PSBT (with Alice's signature)")
253+
print(" - Sign with Bob's private key")
254+
print(" - Export complete signature")
255+
print()
256+
print("3. Finalize and broadcast:")
257+
print(" - Combine signatures (2 of 3 threshold met)")
258+
print(" - Finalize PSBT to create broadcastable transaction")
259+
print(" - Broadcast to Testnet4")
260+
print(" - Monitor on a Testnet4-compatible explorer")
261+
262+
# Step 8: Show the structure for educational purposes
263+
print("\n8. PSBT STRUCTURE ANALYSIS")
264+
print("=" * 50)
265+
print(f"Global data:")
266+
print(f" - Unsigned transaction: {tx.serialize()}")
267+
print(f" - Version: {psbt.version}")
268+
print(f" - Transaction type: P2WSH (Segwit multisig)")
269+
270+
print(f"\nInput 0 data:")
271+
print(f" - Previous TXID: {utxo['txid']}")
272+
print(f" - Previous Vout: {utxo['vout']}")
273+
print(f" - Witness Script: {witness_script.to_hex()}")
274+
print(f" - Amount: {utxo['amount']} satoshis")
275+
print(f" - Script type: P2WSH")
276+
print(f" - Required signatures: 2 of 3")
277+
278+
print(f"\nOutput 0 data:")
279+
print(f" - Amount: {send_amount} satoshis")
280+
print(f" - Fee: {fee_amount} satoshis")
281+
print(f" - Recipient: {charlie_p2wpkh_address}")
282+
print(f" - Script type: P2WPKH")
283+
284+
# Step 9: How to get real Testnet4 coins
285+
print("\n9. HOW TO GET REAL TESTNET4 COINS & TXID")
286+
print("=" * 50)
287+
print("COMPLETE WORKFLOW FOR REAL TESTNET4 DATA:")
288+
print()
289+
print("PHASE 1: Setup")
290+
print("1. Run this script AS-IS to get your multisig address")
291+
print("2. Copy the P2WSH address from the output above")
292+
print()
293+
print("PHASE 2: Get Testnet4 coins")
294+
print("3. Visit Testnet4 faucet:")
295+
print(" • https://faucet.testnet4.dev/")
296+
print(" • Request 0.001+ BTC to any address you control")
297+
print()
298+
print("PHASE 3: Fund multisig")
299+
print("4. Send Testnet4 coins to your multisig address:")
300+
print(f" • Send to: {p2wsh_address}")
301+
print(" • Amount: 0.001 BTC (or whatever you got from faucet)")
302+
print(" • Wait for 1+ confirmations")
303+
print()
304+
print("PHASE 4: Get real TXID")
305+
print("5. Find your funding transaction:")
306+
print(" • Use a Testnet4-compatible explorer")
307+
print(" • Search for your multisig address")
308+
print(" • Click on the funding transaction")
309+
print(" • Copy the TXID")
310+
print()
311+
print("PHASE 5: Update code")
312+
print("6. Edit get_real_testnet_utxo() function:")
313+
print(" • Set txid = 'your_real_txid_here'")
314+
print(" • Set amount = to_satoshis(your_actual_amount)")
315+
print(" • Set is_placeholder = False")
316+
print()
317+
print("PHASE 6: Verify & test")
318+
print("7. Re-run this script")
319+
print(" • Should show REAL TESTNET4 DATA")
320+
print(" • TXID should be verifiable on a Testnet4 explorer")
321+
print(" • PSBT should be ready for signing")
322+
print()
323+
print("EXAMPLE of real Testnet4 TXID format:")
324+
print("b4c1a58d7f8e9a2b3c4d5e6f1234567890abcdef1234567890abcdef12345678")
325+
print("(64 hex characters - yours will look similar)")
326+
print()
327+
print(" Your mentor can then verify:")
328+
print("• Paste your TXID into a Testnet4-compatible explorer")
329+
print("• See real transaction with real UTXOs")
330+
print("• Confirm PSBT references actual Testnet4 blockchain data")
331+
332+
return psbt, {
333+
'multisig_address': p2wsh_address,
334+
'witness_script': witness_script.to_hex(),
335+
'recipient_address': charlie_p2wpkh_address,
336+
'utxo': utxo
337+
}
338+
339+
340+
if __name__ == "__main__":
341+
created_psbt, info = main()
342+
343+
print(f"\n" + "=" * 70)
344+
print(" PSBT CREATION COMPLETED!")
345+
print("=" * 70)
346+
print(f" PSBT (base64): {created_psbt.to_base64()}")
347+
print()
348+
print(" NEXT STEPS:")
349+
print("1. Fund the multisig address with real Testnet4 coins")
350+
print("2. Update the UTXO details in get_real_testnet_utxo()")
351+
print("3. Re-run this script")
352+
print("4. Sign the PSBT with 2 of the 3 private keys")
353+
print("5. Broadcast to Testnet4 and verify on a Testnet4-compatible explorer")
354+
print()
355+
print(f" Multisig address: {info['multisig_address']}")
356+
print(f" Check balance on a Testnet4-compatible explorer")

0 commit comments

Comments
 (0)