upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/scripts/prove-utxo.py
diff options
context:
space:
mode:
authorNsable-sh <nsable-sh@noreply.git.bitflash.network>2026-08-11 14:13:26 -0300
committerNsable-sh <nsable-sh@noreply.git.bitflash.network>2026-08-11 14:13:26 -0300
commit1839347459759fb3b23cc747ec4368b724fee008 (patch)
treee5f6f7e13b6dd9eb260d7c75bafa19d873435c5c /scripts/prove-utxo.py
parenta83fae37099cb102beb5a4c174a4abe6ef3f24a4 (diff)
tools: harden chain audit scripts
Diffstat (limited to 'scripts/prove-utxo.py')
-rw-r--r--scripts/prove-utxo.py43
1 files changed, 14 insertions, 29 deletions
diff --git a/scripts/prove-utxo.py b/scripts/prove-utxo.py
index 7a10d2a..9f2704b 100644
--- a/scripts/prove-utxo.py
+++ b/scripts/prove-utxo.py
@@ -7,27 +7,16 @@ included in the deterministic UTXO root produced by verify-utxo-set.py.
7 7
8import argparse 8import argparse
9import hashlib 9import hashlib
10import importlib.util
11import json 10import json
12import os
13import sys 11import sys
14from pathlib import Path 12from pathlib import Path
15 13
14import bitflash_chain as chain
16 15
17SCHEMA = "bitflash-utxo-inclusion-proof-1" 16SCHEMA = "bitflash-utxo-inclusion-proof-1"
18NODE_DOMAIN = b"BTFNODE1|" 17NODE_DOMAIN = b"BTFNODE1|"
19 18
20 19
21def load_utxo_tool():
22 path = Path(__file__).with_name("verify-utxo-set.py")
23 spec = importlib.util.spec_from_file_location("bitflash_verify_utxo_set", path)
24 if spec is None or spec.loader is None:
25 raise SystemExit("could not load %s" % path)
26 mod = importlib.util.module_from_spec(spec)
27 spec.loader.exec_module(mod)
28 return mod
29
30
31def canonical_bytes(obj): 20def canonical_bytes(obj):
32 return (json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + "\n").encode("ascii") 21 return (json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + "\n").encode("ascii")
33 22
@@ -80,25 +69,27 @@ def build_merkle_proof(keys, leaf_hashes, index):
80 69
81 70
82def build_proof(args): 71def build_proof(args):
83 tool = load_utxo_tool()
84 txid, vout = split_outpoint(args.outpoint) 72 txid, vout = split_outpoint(args.outpoint)
85 outpoint = "%s:%d" % (txid, vout) 73 outpoint = "%s:%d" % (txid, vout)
86 74
87 raw_blocks = tool.read_blocks(args.datadir, args.max_blocks) 75 raw_blocks = chain.read_blocks(args.datadir, args.max_blocks)
88 if not raw_blocks: 76 if not raw_blocks:
89 raise SystemExit("no Bitflash blocks found in %s" % args.datadir) 77 raise SystemExit("no Bitflash blocks found in %s" % args.datadir)
90 blocks = tool.select_main_chain(raw_blocks) 78 try:
91 state = tool.apply_blocks(blocks) 79 blocks = chain.select_main_chain(raw_blocks)
80 state = chain.apply_blocks(blocks, strict_duplicates=args.strict_duplicates)
81 except chain.ParseError as e:
82 raise SystemExit(str(e))
92 utxos = state["utxos"] 83 utxos = state["utxos"]
93 stats = state["stats"] 84 stats = state["stats"]
94 if outpoint not in utxos: 85 if outpoint not in utxos:
95 raise SystemExit("outpoint is not unspent in the scanned chain: %s" % outpoint) 86 raise SystemExit("outpoint is not unspent in the scanned chain: %s" % outpoint)
96 87
97 keys = sorted(utxos) 88 keys = sorted(utxos)
98 leaf_hashes = [tool.utxo_leaf_hash(k, utxos[k]) for k in keys] 89 leaf_hashes = [chain.utxo_leaf_hash(k, utxos[k]) for k in keys]
99 index = keys.index(outpoint) 90 index = keys.index(outpoint)
100 proof_path, root = build_merkle_proof(keys, leaf_hashes, index) 91 proof_path, root = build_merkle_proof(keys, leaf_hashes, index)
101 direct_root = tool.utxo_root(utxos) 92 direct_root = chain.utxo_root(utxos)
102 if root != direct_root: 93 if root != direct_root:
103 raise SystemExit("internal proof root mismatch") 94 raise SystemExit("internal proof root mismatch")
104 95
@@ -110,8 +101,8 @@ def build_proof(args):
110 "chain": { 101 "chain": {
111 "name": "Bitflash", 102 "name": "Bitflash",
112 "ticker": "BTF", 103 "ticker": "BTF",
113 "message_start_hex": tool.MAGIC.hex(), 104 "message_start_hex": chain.MAGIC.hex(),
114 "coin": tool.COIN, 105 "coin": chain.COIN,
115 }, 106 },
116 "commitment": { 107 "commitment": {
117 "algorithm": "sorted-outpoint-merkle-sha256", 108 "algorithm": "sorted-outpoint-merkle-sha256",
@@ -169,20 +160,14 @@ def print_text(proof):
169 print("Verdict: %s" % proof["verdict"]) 160 print("Verdict: %s" % proof["verdict"])
170 161
171 162
172def default_datadir():
173 if os.name == "nt" and os.environ.get("APPDATA"):
174 return os.path.join(os.environ["APPDATA"], "Bitflash")
175 if os.environ.get("HOME"):
176 return os.path.join(os.environ["HOME"], ".bitflash")
177 return "."
178
179
180def main(argv): 163def main(argv):
181 ap = argparse.ArgumentParser(description=__doc__) 164 ap = argparse.ArgumentParser(description=__doc__)
182 ap.add_argument("outpoint", help="unspent output to prove, as txid:vout") 165 ap.add_argument("outpoint", help="unspent output to prove, as txid:vout")
183 ap.add_argument("--datadir", default=default_datadir(), help="Bitflash data directory") 166 ap.add_argument("--datadir", default=chain.default_datadir(), help="Bitflash data directory")
184 ap.add_argument("--max-blocks", type=int, default=0, 167 ap.add_argument("--max-blocks", type=int, default=0,
185 help="number of blocks to scan; 0 means all block files") 168 help="number of blocks to scan; 0 means all block files")
169 ap.add_argument("--strict-duplicates", action="store_true",
170 help="fail instead of using legacy last-write-wins duplicate output handling")
186 ap.add_argument("--json", action="store_true", help="print canonical JSON") 171 ap.add_argument("--json", action="store_true", help="print canonical JSON")
187 ap.add_argument("--out", help="write canonical JSON proof to this path") 172 ap.add_argument("--out", help="write canonical JSON proof to this path")
188 args = ap.parse_args(argv) 173 args = ap.parse_args(argv)