mirror of
https://github.com/LORDBABUINO/stealth.git
synced 2026-06-11 23:03:30 -07:00
548 lines
23 KiB
Python
548 lines
23 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
reproduce.py
|
||
============
|
||
Reproduces 12 Bitcoin privacy vulnerabilities on a local custom Signet.
|
||
Each run creates NEW on-chain transactions that exhibit the vulnerability.
|
||
No detection logic — that lives in detect.py.
|
||
|
||
Usage:
|
||
python3 reproduce.py # Create all 12 vulnerability scenarios
|
||
python3 reproduce.py -k 3 # Create only vulnerability 3
|
||
"""
|
||
|
||
import sys
|
||
import os
|
||
import json
|
||
import time
|
||
|
||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||
from bitcoin_rpc import (
|
||
cli,
|
||
mine_blocks,
|
||
get_tx,
|
||
get_utxos,
|
||
get_balance,
|
||
get_new_address,
|
||
send_to_address,
|
||
create_raw_tx,
|
||
sign_raw_tx,
|
||
send_raw,
|
||
get_block_count,
|
||
create_funded_psbt,
|
||
process_psbt,
|
||
finalize_psbt,
|
||
IS_REGTEST,
|
||
)
|
||
|
||
|
||
# Original budget: bob 15, alice 5, exchange 5, carol 2, risky 2 BTC
|
||
# VALUE_FACTOR scales these down for testnet (÷200):
|
||
# bob ~0.075, alice ~0.025, exchange ~0.025, carol ~0.01, risky ~0.01 BTC
|
||
# Total testnet budget ≈ 0.145 BTC
|
||
VALUE_FACTOR = 0.005
|
||
|
||
# Fixed fee deduction for raw transactions (2000 sats — ~2-5 sat/vB)
|
||
FEE = 0.00002
|
||
|
||
# Use 0-conf on testnet (can't mine), 1-conf on regtest
|
||
MIN_CONF = 1 if IS_REGTEST else 0
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
# Formatting helpers
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
G = "\033[92m"
|
||
Y = "\033[93m"
|
||
C = "\033[96m"
|
||
B = "\033[1m"
|
||
R = "\033[0m"
|
||
|
||
|
||
def header(num, title):
|
||
print(f"\n{'═'*78}")
|
||
print(f"{B}{C} REPRODUCE {num}: {title}{R}")
|
||
print(f"{'═'*78}")
|
||
|
||
|
||
def ok(msg):
|
||
print(f" {G}✓{R} {msg}")
|
||
|
||
|
||
def info(msg):
|
||
print(f" {Y}ℹ{R} {msg}")
|
||
|
||
|
||
def ensure_funds(wallet, min_btc=0.5):
|
||
bal = get_balance(wallet)
|
||
if bal < min_btc:
|
||
if IS_REGTEST:
|
||
addr = get_new_address(wallet, "bech32")
|
||
send_to_address("miner", addr, round(min_btc * 2, 8))
|
||
mine_and_confirm()
|
||
else:
|
||
info(f"{wallet} balance ({bal:.8f}) < needed ({min_btc:.8f}) — fund externally")
|
||
|
||
|
||
def mine_and_confirm():
|
||
if IS_REGTEST:
|
||
mine_blocks(1)
|
||
time.sleep(0.5)
|
||
else:
|
||
# On testnet we cannot mine — wait for broadcast propagation
|
||
time.sleep(5)
|
||
|
||
|
||
def send_transaction(tx):
|
||
txid = send_raw(tx)
|
||
mine_and_confirm()
|
||
return txid
|
||
|
||
|
||
WALLETS = ["alice", "bob", "carol", "exchange", "risky"]
|
||
|
||
|
||
def setup_wallets():
|
||
"""Ensure all required wallets are loaded (create if missing)."""
|
||
wallets = list(WALLETS)
|
||
if IS_REGTEST:
|
||
wallets.append("miner")
|
||
for w in wallets:
|
||
try:
|
||
cli("getwalletinfo", wallet=w)
|
||
except Exception:
|
||
try:
|
||
cli("loadwallet", w)
|
||
ok(f"Loaded wallet: {w}")
|
||
except Exception:
|
||
try:
|
||
cli("createwallet", w)
|
||
ok(f"Created wallet: {w}")
|
||
except Exception as e:
|
||
if "already" in str(e).lower():
|
||
pass
|
||
else:
|
||
info(f"Could not setup wallet '{w}': {e}")
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
# 1. Address Reuse
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
def reproduce_01():
|
||
header(1, "Address Reuse")
|
||
ensure_funds("bob", 1.0 * VALUE_FACTOR)
|
||
reused_addr = get_new_address("alice", "bech32")
|
||
txid1 = send_to_address("bob", reused_addr, 0.01 * VALUE_FACTOR)
|
||
txid2 = send_to_address("bob", reused_addr, 0.02 * VALUE_FACTOR)
|
||
mine_and_confirm()
|
||
ok(f"Sent to same address {reused_addr} twice: TX {txid1[:16]}… and {txid2[:16]}…")
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
# 2. Multi-input / CIOH
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
def reproduce_02():
|
||
header(2, "Multi-input / CIOH (Common Input Ownership Heuristic)")
|
||
ensure_funds("bob", 2.0 * VALUE_FACTOR)
|
||
for _ in range(5):
|
||
addr = get_new_address("alice", "bech32")
|
||
send_to_address("bob", addr, 0.005 * VALUE_FACTOR)
|
||
mine_and_confirm()
|
||
|
||
utxos = get_utxos("alice", MIN_CONF)
|
||
small = [u for u in utxos if 0.004 * VALUE_FACTOR < u["amount"] < 0.006 * VALUE_FACTOR][:5]
|
||
if len(small) < 2:
|
||
info("Not enough small UTXOs, skipping consolidation step")
|
||
return
|
||
inputs = [{"txid": u["txid"], "vout": u["vout"]} for u in small]
|
||
dest = get_new_address("bob", "bech32")
|
||
total = sum(u["amount"] for u in small)
|
||
psbt_result = create_funded_psbt(
|
||
"alice",
|
||
inputs,
|
||
[{dest: round(total - 0.001 * VALUE_FACTOR, 12)}],
|
||
{"subtractFeeFromOutputs": [0], "add_inputs": False},
|
||
)
|
||
signed = process_psbt("alice", psbt_result["psbt"])
|
||
final = finalize_psbt(signed["psbt"])
|
||
txid = send_raw(final["hex"])
|
||
mine_and_confirm()
|
||
ok(f"Consolidated {len(small)} inputs in TX {txid[:16]}… (CIOH trigger)")
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
# 3. Dust UTXO Detection
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
def reproduce_03():
|
||
header(3, "Dust UTXO Detection")
|
||
ensure_funds("bob", 1.0 * VALUE_FACTOR)
|
||
dust1 = get_new_address("alice", "bech32")
|
||
dust2 = get_new_address("alice", "bech32")
|
||
bob_utxos = get_utxos("bob", MIN_CONF)
|
||
big = max(bob_utxos, key=lambda u: u["amount"])
|
||
change = get_new_address("bob", "bech32")
|
||
change_amt = round(big["amount"] - 0.00001000 - 0.00000546 - FEE, 8)
|
||
raw = create_raw_tx(
|
||
[{"txid": big["txid"], "vout": big["vout"]}],
|
||
[{dust1: 0.00001000}, {dust2: 0.00000546}, {change: change_amt}],
|
||
)
|
||
signed = sign_raw_tx("bob", raw)
|
||
txid = send_raw(signed["hex"])
|
||
mine_and_confirm()
|
||
ok(f"Created 1000-sat and 546-sat dust outputs to Alice in TX {txid[:16]}…")
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
# 4. Spending Dust with Normal Inputs
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
def reproduce_04():
|
||
header(4, "Spending Dust with Normal Inputs")
|
||
ensure_funds("alice", 0.5 * VALUE_FACTOR)
|
||
utxos = get_utxos("alice", MIN_CONF)
|
||
dust_utxos = [u for u in utxos if u["amount"] <= 0.00001]
|
||
normal_utxos = [u for u in utxos if u["amount"] > 0.0001]
|
||
|
||
if not dust_utxos:
|
||
info("No dust UTXOs, creating one first…")
|
||
ensure_funds("bob", 1.0 * VALUE_FACTOR)
|
||
a = get_new_address("alice", "bech32")
|
||
bu = get_utxos("bob", MIN_CONF)
|
||
big = max(bu, key=lambda u: u["amount"])
|
||
ch = get_new_address("bob", "bech32")
|
||
raw = create_raw_tx(
|
||
[{"txid": big["txid"], "vout": big["vout"]}],
|
||
[{a: 0.00001000}, {ch: round(big["amount"] - 0.00001000 - FEE, 8)}],
|
||
)
|
||
signed = sign_raw_tx("bob", raw)
|
||
send_raw(signed["hex"])
|
||
mine_and_confirm()
|
||
utxos = get_utxos("alice", MIN_CONF)
|
||
dust_utxos = [u for u in utxos if u["amount"] <= 0.00001]
|
||
normal_utxos = [u for u in utxos if u["amount"] > 0.0001]
|
||
|
||
if not normal_utxos:
|
||
ensure_funds("alice", 0.5 * VALUE_FACTOR)
|
||
mine_and_confirm()
|
||
utxos = get_utxos("alice", MIN_CONF)
|
||
normal_utxos = [u for u in utxos if u["amount"] > 0.0001]
|
||
|
||
dust = dust_utxos[0]
|
||
normal = normal_utxos[0]
|
||
dest = get_new_address("bob", "bech32")
|
||
total = dust["amount"] + normal["amount"]
|
||
raw = create_raw_tx(
|
||
[
|
||
{"txid": dust["txid"], "vout": dust["vout"]},
|
||
{"txid": normal["txid"], "vout": normal["vout"]},
|
||
],
|
||
[{dest: round(total - FEE, 8)}],
|
||
)
|
||
signed = sign_raw_tx("alice", raw)
|
||
txid = send_raw(signed["hex"])
|
||
mine_and_confirm()
|
||
ok(
|
||
f"Spent dust ({int(dust['amount']*1e8)} sats) + normal ({normal['amount']:.8f}) together in TX {txid[:16]}…"
|
||
)
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
# 5. Change Detection
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
def reproduce_05():
|
||
header(5, "Change Detection — Round Payment")
|
||
ensure_funds("alice", 1.0 * VALUE_FACTOR)
|
||
bob_addr = get_new_address("bob", "bech32")
|
||
txid = send_to_address("alice", bob_addr, 0.05 * VALUE_FACTOR)
|
||
mine_and_confirm()
|
||
ok(
|
||
f"Alice paid Bob {0.05 * VALUE_FACTOR:.8f} BTC (round amount) in TX {txid[:16]}… — change output is obvious"
|
||
)
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
# 6. Consolidation Origin
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
def reproduce_06():
|
||
header(6, "Consolidation Origin")
|
||
ensure_funds("bob", 2.0 * VALUE_FACTOR)
|
||
for _ in range(4):
|
||
addr = get_new_address("alice", "bech32")
|
||
send_to_address("bob", addr, 0.003 * VALUE_FACTOR)
|
||
mine_and_confirm()
|
||
|
||
utxos = get_utxos("alice", MIN_CONF)
|
||
small = [u for u in utxos if 0.002 * VALUE_FACTOR < u["amount"] < 0.004 * VALUE_FACTOR][:4]
|
||
if len(small) < 3:
|
||
info(f"Only {len(small)} small UTXOs, creating more…")
|
||
for _ in range(4):
|
||
addr = get_new_address("alice", "bech32")
|
||
send_to_address("bob", addr, 0.003 * VALUE_FACTOR)
|
||
mine_and_confirm()
|
||
utxos = get_utxos("alice", MIN_CONF)
|
||
small = [u for u in utxos if 0.002 * VALUE_FACTOR < u["amount"] < 0.004 * VALUE_FACTOR][:4]
|
||
|
||
inputs = [{"txid": u["txid"], "vout": u["vout"]} for u in small]
|
||
consol_addr = get_new_address("alice", "bech32")
|
||
total = sum(u["amount"] for u in small)
|
||
raw = create_raw_tx(inputs, [{consol_addr: round(total - FEE, 8)}])
|
||
signed = sign_raw_tx("alice", raw)
|
||
consol_txid = send_raw(signed["hex"])
|
||
mine_and_confirm()
|
||
ok(f"Consolidated {len(small)} UTXOs → 1 in TX {consol_txid[:16]}…")
|
||
|
||
# Now spend the consolidated output
|
||
utxos = get_utxos("alice", MIN_CONF)
|
||
cu = [u for u in utxos if u["txid"] == consol_txid]
|
||
if cu:
|
||
dest = get_new_address("carol", "bech32")
|
||
raw = create_raw_tx(
|
||
[{"txid": cu[0]["txid"], "vout": cu[0]["vout"]}],
|
||
[{dest: round(cu[0]["amount"] - FEE, 8)}],
|
||
)
|
||
signed = sign_raw_tx("alice", raw)
|
||
txid2 = send_raw(signed["hex"])
|
||
mine_and_confirm()
|
||
ok(
|
||
f"Spent consolidated UTXO in TX {txid2[:16]}… — carries full cluster history"
|
||
)
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
# 7. Script Type Mixing
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
def reproduce_07():
|
||
header(7, "Script Type Mixing")
|
||
ensure_funds("bob", 2.0 * VALUE_FACTOR)
|
||
wpkh = get_new_address("alice", "bech32")
|
||
tr = get_new_address("alice", "bech32m")
|
||
send_to_address("bob", wpkh, 0.005 * VALUE_FACTOR)
|
||
send_to_address("bob", tr, 0.005 * VALUE_FACTOR)
|
||
mine_and_confirm()
|
||
|
||
utxos = get_utxos("alice", MIN_CONF)
|
||
|
||
def is_wpkh(addr):
|
||
return (
|
||
addr
|
||
and not addr.startswith(("tb1p", "bc1p", "bcrt1p"))
|
||
and addr.startswith(("tb1q", "bc1q", "bcrt1q"))
|
||
)
|
||
|
||
def is_tr(addr):
|
||
return addr and addr.startswith(("tb1p", "bc1p", "bcrt1p"))
|
||
|
||
wu = next(
|
||
(u for u in utxos if is_wpkh(u.get("address", "")) and u["amount"] >= 0.004 * VALUE_FACTOR),
|
||
None,
|
||
)
|
||
tu = next(
|
||
(u for u in utxos if is_tr(u.get("address", "")) and u["amount"] >= 0.004 * VALUE_FACTOR), None
|
||
)
|
||
if not wu or not tu:
|
||
info("Could not find both UTXO types")
|
||
return
|
||
dest = get_new_address("bob", "bech32")
|
||
total = wu["amount"] + tu["amount"]
|
||
raw = create_raw_tx(
|
||
[
|
||
{"txid": wu["txid"], "vout": wu["vout"]},
|
||
{"txid": tu["txid"], "vout": tu["vout"]},
|
||
],
|
||
[{dest: round(total - FEE, 8)}],
|
||
)
|
||
signed = sign_raw_tx("alice", raw)
|
||
txid = send_raw(signed["hex"])
|
||
mine_and_confirm()
|
||
ok(f"Mixed P2WPKH + P2TR inputs in TX {txid[:16]}… — script type fingerprint")
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
# 8. Cluster Merge
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
def reproduce_08():
|
||
header(8, "Cluster Merge")
|
||
ensure_funds("bob", 2.0 * VALUE_FACTOR)
|
||
ensure_funds("carol", 2.0 * VALUE_FACTOR)
|
||
a_addr = get_new_address("alice", "bech32")
|
||
b_addr = get_new_address("alice", "bech32")
|
||
txid_a = send_to_address("bob", a_addr, 0.004 * VALUE_FACTOR)
|
||
txid_b = send_to_address("carol", b_addr, 0.004 * VALUE_FACTOR)
|
||
mine_and_confirm()
|
||
|
||
utxos = get_utxos("alice", MIN_CONF)
|
||
ua = next((u for u in utxos if u["txid"] == txid_a), None)
|
||
ub = next((u for u in utxos if u["txid"] == txid_b), None)
|
||
if not ua:
|
||
ua = next((u for u in utxos if u.get("address") == a_addr), None)
|
||
if not ub:
|
||
ub = next((u for u in utxos if u.get("address") == b_addr), None)
|
||
if not ua or not ub:
|
||
info("Could not find both cluster UTXOs")
|
||
return
|
||
dest = get_new_address("bob", "bech32")
|
||
total = ua["amount"] + ub["amount"]
|
||
raw = create_raw_tx(
|
||
[
|
||
{"txid": ua["txid"], "vout": ua["vout"]},
|
||
{"txid": ub["txid"], "vout": ub["vout"]},
|
||
],
|
||
[{dest: round(total - FEE, 8)}],
|
||
)
|
||
signed = sign_raw_tx("alice", raw)
|
||
txid = send_raw(signed["hex"])
|
||
mine_and_confirm()
|
||
ok(f"Merged Bob-cluster and Carol-cluster UTXOs in TX {txid[:16]}…")
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
# 9. Lookback Depth
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
def reproduce_09():
|
||
header(9, "Lookback Depth / UTXO Age")
|
||
funder = "miner" if IS_REGTEST else "bob"
|
||
ensure_funds(funder, 1.0 * VALUE_FACTOR)
|
||
old_addr = get_new_address("alice", "bech32")
|
||
send_to_address(funder, old_addr, 0.01 * VALUE_FACTOR)
|
||
if IS_REGTEST:
|
||
mine_blocks(20)
|
||
else:
|
||
info("Testnet: waiting for first TX to confirm (age spread will be smaller)…")
|
||
mine_and_confirm()
|
||
new_addr = get_new_address("alice", "bech32")
|
||
send_to_address(funder, new_addr, 0.01 * VALUE_FACTOR)
|
||
mine_and_confirm()
|
||
ok(f"Created old UTXO and new UTXO for Alice")
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
# 10. Exchange Origin
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
def reproduce_10():
|
||
header(10, "Exchange Origin — Batch Withdrawal")
|
||
ensure_funds("exchange", 5.0 * VALUE_FACTOR)
|
||
batch = {}
|
||
wallets = ["alice", "bob", "carol", "alice", "bob", "carol", "alice", "bob"]
|
||
for i in range(8):
|
||
addr = get_new_address(wallets[i], "bech32")
|
||
batch[addr] = round((0.01 + i * 0.001) * VALUE_FACTOR, 8)
|
||
txid = cli("sendmany", "", json.dumps(batch), wallet="exchange")
|
||
mine_and_confirm()
|
||
ok(f"Exchange batch withdrawal to 8 recipients in TX {txid[:16]}…")
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
# 11. Tainted UTXOs
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
def reproduce_11():
|
||
header(11, "Tainted UTXOs / Dirty Money")
|
||
ensure_funds("risky", 2.0 * VALUE_FACTOR)
|
||
ensure_funds("bob", 1.0 * VALUE_FACTOR)
|
||
ta = get_new_address("alice", "bech32")
|
||
taint_txid = send_to_address("risky", ta, 0.01 * VALUE_FACTOR)
|
||
ca = get_new_address("alice", "bech32")
|
||
clean_txid = send_to_address("bob", ca, 0.01 * VALUE_FACTOR)
|
||
mine_and_confirm()
|
||
|
||
utxos = get_utxos("alice", MIN_CONF)
|
||
tu = next((u for u in utxos if u["txid"] == taint_txid), None)
|
||
cu = next((u for u in utxos if u["txid"] == clean_txid), None)
|
||
if not tu:
|
||
tu = next((u for u in utxos if u.get("address") == ta), None)
|
||
if not cu:
|
||
cu = next((u for u in utxos if u.get("address") == ca), None)
|
||
if not tu or not cu:
|
||
info("Could not locate tainted + clean UTXOs")
|
||
return
|
||
dest = get_new_address("carol", "bech32")
|
||
total = tu["amount"] + cu["amount"]
|
||
raw = create_raw_tx(
|
||
[
|
||
{"txid": tu["txid"], "vout": tu["vout"]},
|
||
{"txid": cu["txid"], "vout": cu["vout"]},
|
||
],
|
||
[{dest: round(total - FEE, 8)}],
|
||
)
|
||
signed = sign_raw_tx("alice", raw)
|
||
txid = send_raw(signed["hex"])
|
||
mine_and_confirm()
|
||
ok(f"Merged tainted + clean UTXOs in TX {txid[:16]}… — taint propagation")
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
# 12. Behavioral Fingerprinting
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
def reproduce_12():
|
||
header(12, "Behavioral Fingerprinting")
|
||
ensure_funds("alice", 3.0 * VALUE_FACTOR)
|
||
ensure_funds("bob", 3.0 * VALUE_FACTOR)
|
||
|
||
info("Alice's pattern: round amounts, always bech32…")
|
||
for i in range(5):
|
||
dest = get_new_address("carol", "bech32")
|
||
send_to_address("alice", dest, 0.01 * (i + 1) * VALUE_FACTOR)
|
||
|
||
mine_and_confirm()
|
||
|
||
info("Bob's pattern: odd amounts, mixed address types…")
|
||
for i in range(5):
|
||
atype = "bech32m" if i % 2 == 0 else "bech32"
|
||
dest = get_new_address("carol", atype)
|
||
send_to_address("bob", dest, round((0.00723 * (i + 1) + 0.00011) * VALUE_FACTOR, 8))
|
||
|
||
mine_and_confirm()
|
||
ok("Created distinguishable behavioral patterns for Alice and Bob")
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
# Main
|
||
# ═══════════════════════════════════════════════════════════════════════════════
|
||
ALL = [
|
||
(1, "Address Reuse", reproduce_01),
|
||
(2, "Multi-input / CIOH", reproduce_02),
|
||
(3, "Dust UTXO Detection", reproduce_03),
|
||
(4, "Dust Spending w/ Normal", reproduce_04),
|
||
(5, "Change Detection", reproduce_05),
|
||
(6, "Consolidation Origin", reproduce_06),
|
||
(7, "Script Type Mixing", reproduce_07),
|
||
(8, "Cluster Merge", reproduce_08),
|
||
(9, "Lookback Depth", reproduce_09),
|
||
(10, "Exchange Origin", reproduce_10),
|
||
(11, "Tainted UTXOs", reproduce_11),
|
||
(12, "Behavioral Fingerprint", reproduce_12),
|
||
]
|
||
|
||
|
||
def main():
|
||
filt = None
|
||
if "-k" in sys.argv:
|
||
idx = sys.argv.index("-k")
|
||
if idx + 1 < len(sys.argv):
|
||
filt = sys.argv[idx + 1]
|
||
|
||
print(f"\n{B}{'═'*78}{R}")
|
||
print(f"{B}{C} REPRODUCE — Bitcoin Privacy Vulnerabilities{R}")
|
||
print(f"{B}{C} Custom Signet — {get_block_count()} blocks{R}")
|
||
print(f"{B}{'═'*78}{R}")
|
||
|
||
setup_wallets()
|
||
|
||
for num, name, fn in ALL:
|
||
if filt and str(num) != filt:
|
||
continue
|
||
try:
|
||
fn()
|
||
except Exception as e:
|
||
print(f" \033[91m✗ ERROR in {name}: {e}\033[0m")
|
||
import traceback
|
||
|
||
traceback.print_exc()
|
||
|
||
print(f"\n{B}{'═'*78}{R}")
|
||
print(f" {G}Done. All vulnerability scenarios have been created on-chain.{R}")
|
||
print(f" Now run: python3 detect.py <descriptor>")
|
||
print(f"{B}{'═'*78}{R}\n")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|