upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--scripts/bitflash_chain.py69
-rwxr-xr-xscripts/build-explorer.py5
-rw-r--r--scripts/prove-utxo.py7
-rw-r--r--scripts/verify-utxo-proof.py10
4 files changed, 44 insertions, 47 deletions
diff --git a/scripts/bitflash_chain.py b/scripts/bitflash_chain.py
index f3688f1..4a3231c 100644
--- a/scripts/bitflash_chain.py
+++ b/scripts/bitflash_chain.py
@@ -4,6 +4,7 @@
4import datetime as _dt 4import datetime as _dt
5import hashlib 5import hashlib
6import json 6import json
7import mmap
7import os 8import os
8import struct 9import struct
9from pathlib import Path 10from pathlib import Path
@@ -11,6 +12,9 @@ from pathlib import Path
11 12
12MAGIC = bytes([0xBF, 0x20, 0x5C, 0xFD]) 13MAGIC = bytes([0xBF, 0x20, 0x5C, 0xFD])
13MAX_BLOCK_PAYLOAD = 32 * 1024 * 1024 14MAX_BLOCK_PAYLOAD = 32 * 1024 * 1024
15UTXO_LEAF_DOMAIN = "BTFUTXO1"
16UTXO_NODE_DOMAIN_TAG = "BTFNODE1"
17UTXO_NODE_DOMAIN = (UTXO_NODE_DOMAIN_TAG + "|").encode("ascii")
14COIN = 100_000_000 18COIN = 100_000_000
15GENESIS_HASH = "5bd7cb255d814e48cebcdfb72da4dc87b34bd774227f8ceb546c5640f4bdc169" 19GENESIS_HASH = "5bd7cb255d814e48cebcdfb72da4dc87b34bd774227f8ceb546c5640f4bdc169"
16GENESIS_MERKLE = "1a45b4482532abb29b10e234d3f13132230525a339ecea91658ffa675a5b1325" 20GENESIS_MERKLE = "1a45b4482532abb29b10e234d3f13132230525a339ecea91658ffa675a5b1325"
@@ -181,40 +185,33 @@ def iter_block_files(datadir):
181def iter_blocks_from_file(path): 185def iter_blocks_from_file(path):
182 path = Path(path) 186 path = Path(path)
183 with path.open("rb") as f: 187 with path.open("rb") as f:
184 window = f.read(4) 188 f.seek(0, os.SEEK_END)
185 offset = 0 189 file_size = f.tell()
186 while len(window) == 4: 190 if file_size == 0:
187 if window != MAGIC: 191 return
188 nxt = f.read(1) 192 f.seek(0)
189 if not nxt: 193 mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
194 try:
195 pos = 0
196 while True:
197 offset = mm.find(MAGIC, pos)
198 if offset < 0 or offset + 8 > file_size:
190 break 199 break
191 window = window[1:] + nxt 200 size = struct.unpack("<I", mm[offset + 4:offset + 8])[0]
192 offset += 1 201 payload_offset = offset + 8
193 continue 202 end = payload_offset + size
194 203 if size == 0 or size > MAX_BLOCK_PAYLOAD or end > file_size:
195 size_bytes = f.read(4) 204 pos = offset + 1
196 if len(size_bytes) != 4: 205 continue
197 break 206 payload = mm[payload_offset:end]
198 size = struct.unpack("<I", size_bytes)[0] 207 try:
199 if size == 0 or size > MAX_BLOCK_PAYLOAD: 208 yield parse_block(payload, path.name, payload_offset)
200 offset += 1 209 except ParseError:
201 f.seek(offset) 210 pos = offset + 1
202 window = f.read(4) 211 continue
203 continue 212 pos = end
204 payload_offset = offset + 8 213 finally:
205 payload = f.read(size) 214 mm.close()
206 if len(payload) != size:
207 break
208 try:
209 yield parse_block(payload, path.name, payload_offset)
210 except ParseError:
211 offset += 1
212 f.seek(offset)
213 window = f.read(4)
214 continue
215
216 offset = f.tell()
217 window = f.read(4)
218 215
219 216
220def read_blocks_from_files(paths, max_blocks=0): 217def read_blocks_from_files(paths, max_blocks=0):
@@ -265,7 +262,7 @@ def is_coinbase(tx):
265 262
266def utxo_leaf_hash(outpoint, utxo): 263def utxo_leaf_hash(outpoint, utxo):
267 payload = "|".join([ 264 payload = "|".join([
268 "BTFUTXO1", 265 UTXO_LEAF_DOMAIN,
269 outpoint, 266 outpoint,
270 str(utxo["value_satoshis"]), 267 str(utxo["value_satoshis"]),
271 utxo["script_pub_key_hex"], 268 utxo["script_pub_key_hex"],
@@ -285,7 +282,7 @@ def utxo_root(utxos):
285 for i in range(0, len(layer), 2): 282 for i in range(0, len(layer), 2):
286 left = layer[i] 283 left = layer[i]
287 right = layer[i + 1] if i + 1 < len(layer) else left 284 right = layer[i + 1] if i + 1 < len(layer) else left
288 nxt.append(sha256(b"BTFNODE1|" + left + right)) 285 nxt.append(sha256(UTXO_NODE_DOMAIN + left + right))
289 layer = nxt 286 layer = nxt
290 return layer[0].hex() 287 return layer[0].hex()
291 288
@@ -327,9 +324,9 @@ def apply_blocks(blocks, strict_duplicates=False):
327 for n, out in enumerate(tx["vout"]): 324 for n, out in enumerate(tx["vout"]):
328 key = "%s:%d" % (tx["txid"], n) 325 key = "%s:%d" % (tx["txid"], n)
329 if key in utxos: 326 if key in utxos:
330 duplicate_outputs.append({"height": height, "outpoint": key})
331 if strict_duplicates: 327 if strict_duplicates:
332 raise ParseError("duplicate output at height %d: %s" % (height, key)) 328 raise ParseError("duplicate output at height %d: %s" % (height, key))
329 duplicate_outputs.append({"height": height, "outpoint": key})
333 utxos[key] = { 330 utxos[key] = {
334 "txid": tx["txid"], 331 "txid": tx["txid"],
335 "vout": n, 332 "vout": n,
diff --git a/scripts/build-explorer.py b/scripts/build-explorer.py
index 0d86b3c..97a7f4e 100755
--- a/scripts/build-explorer.py
+++ b/scripts/build-explorer.py
@@ -35,7 +35,10 @@ def hash160(b):
35 35
36 36
37def spk_info(script_hex): 37def spk_info(script_hex):
38 spk = bytes.fromhex(script_hex) 38 try:
39 spk = bytes.fromhex(script_hex)
40 except ValueError:
41 return {"type": "malformed", "hex": script_hex[:80]}
39 if ( 42 if (
40 len(spk) == 25 and spk[0] == 0x76 and spk[1] == 0xA9 43 len(spk) == 25 and spk[0] == 0x76 and spk[1] == 0xA9
41 and spk[2] == 0x14 and spk[23] == 0x88 and spk[24] == 0xAC 44 and spk[2] == 0x14 and spk[23] == 0x88 and spk[24] == 0xAC
diff --git a/scripts/prove-utxo.py b/scripts/prove-utxo.py
index 9f2704b..105e677 100644
--- a/scripts/prove-utxo.py
+++ b/scripts/prove-utxo.py
@@ -14,7 +14,6 @@ from pathlib import Path
14import bitflash_chain as chain 14import bitflash_chain as chain
15 15
16SCHEMA = "bitflash-utxo-inclusion-proof-1" 16SCHEMA = "bitflash-utxo-inclusion-proof-1"
17NODE_DOMAIN = b"BTFNODE1|"
18 17
19 18
20def canonical_bytes(obj): 19def canonical_bytes(obj):
@@ -61,7 +60,7 @@ def build_merkle_proof(keys, leaf_hashes, index):
61 for i in range(0, len(layer), 2): 60 for i in range(0, len(layer), 2):
62 left = layer[i] 61 left = layer[i]
63 right = layer[i + 1] if i + 1 < len(layer) else left 62 right = layer[i + 1] if i + 1 < len(layer) else left
64 next_layer.append(hashlib.sha256(NODE_DOMAIN + left + right).digest()) 63 next_layer.append(hashlib.sha256(chain.UTXO_NODE_DOMAIN + left + right).digest())
65 layer = next_layer 64 layer = next_layer
66 current_index //= 2 65 current_index //= 2
67 level += 1 66 level += 1
@@ -106,8 +105,8 @@ def build_proof(args):
106 }, 105 },
107 "commitment": { 106 "commitment": {
108 "algorithm": "sorted-outpoint-merkle-sha256", 107 "algorithm": "sorted-outpoint-merkle-sha256",
109 "leaf_domain": "BTFUTXO1", 108 "leaf_domain": chain.UTXO_LEAF_DOMAIN,
110 "node_domain": "BTFNODE1", 109 "node_domain": chain.UTXO_NODE_DOMAIN_TAG,
111 "root": root, 110 "root": root,
112 "leaf_count": len(keys), 111 "leaf_count": len(keys),
113 }, 112 },
diff --git a/scripts/verify-utxo-proof.py b/scripts/verify-utxo-proof.py
index c29caa9..50d9356 100644
--- a/scripts/verify-utxo-proof.py
+++ b/scripts/verify-utxo-proof.py
@@ -10,8 +10,6 @@ from pathlib import Path
10import bitflash_chain as chain 10import bitflash_chain as chain
11 11
12SCHEMA = "bitflash-utxo-inclusion-proof-1" 12SCHEMA = "bitflash-utxo-inclusion-proof-1"
13LEAF_DOMAIN = "BTFUTXO1"
14NODE_DOMAIN = b"BTFNODE1|"
15 13
16 14
17def canonical_bytes(obj): 15def canonical_bytes(obj):
@@ -32,9 +30,9 @@ def verify(proof, expected_root=None):
32 commitment = proof.get("commitment", {}) 30 commitment = proof.get("commitment", {})
33 if commitment.get("algorithm") != "sorted-outpoint-merkle-sha256": 31 if commitment.get("algorithm") != "sorted-outpoint-merkle-sha256":
34 failures.append("unexpected commitment algorithm") 32 failures.append("unexpected commitment algorithm")
35 if commitment.get("leaf_domain") != LEAF_DOMAIN: 33 if commitment.get("leaf_domain") != chain.UTXO_LEAF_DOMAIN:
36 failures.append("unexpected leaf domain") 34 failures.append("unexpected leaf domain")
37 if commitment.get("node_domain") != "BTFNODE1": 35 if commitment.get("node_domain") != chain.UTXO_NODE_DOMAIN_TAG:
38 failures.append("unexpected node domain") 36 failures.append("unexpected node domain")
39 37
40 target = proof.get("target", {}) 38 target = proof.get("target", {})
@@ -64,9 +62,9 @@ def verify(proof, expected_root=None):
64 failures.append("bad sibling hash length at proof level %d" % i) 62 failures.append("bad sibling hash length at proof level %d" % i)
65 side = step.get("side") 63 side = step.get("side")
66 if side == "right": 64 if side == "right":
67 current = chain.sha256(NODE_DOMAIN + current + sibling) 65 current = chain.sha256(chain.UTXO_NODE_DOMAIN + current + sibling)
68 elif side == "left": 66 elif side == "left":
69 current = chain.sha256(NODE_DOMAIN + sibling + current) 67 current = chain.sha256(chain.UTXO_NODE_DOMAIN + sibling + current)
70 else: 68 else:
71 failures.append("bad sibling side at proof level %d" % i) 69 failures.append("bad sibling side at proof level %d" % i)
72 70