commit 6e365067eb7aedb7bf6aa23088c3b0729dc18a42 Author: davide3011 Date: Thu Jan 22 15:46:58 2026 +0100 Commit iniziale diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ac7f4c --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Virtual Environment +.venv/ +venv/ +env/ +ENV/ + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db diff --git a/main.py b/main.py new file mode 100644 index 0000000..b1b06e0 --- /dev/null +++ b/main.py @@ -0,0 +1,466 @@ +import requests +import sqlite3 +import time +import json +from typing import Optional, List, Dict + +class P2PKBlockchainScanner: + def __init__(self, db_path: str = "bitcoin_p2pk.db"): + """Inizializza lo scanner con connessione al database""" + self.db_path = db_path + self.api_base = "https://mempool.space/api" + self.init_database() + + def init_database(self): + """Crea il database SQLite per memorizzare i P2PK""" + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS p2pk_addresses ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + block_height INTEGER NOT NULL, + txid TEXT NOT NULL, + output_index INTEGER NOT NULL, + scriptpubkey TEXT NOT NULL, + value_satoshi INTEGER, + timestamp INTEGER, + is_unspent INTEGER DEFAULT 0, + last_checked INTEGER DEFAULT 0, + UNIQUE(txid, output_index) + ) + ''') + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS scan_progress ( + id INTEGER PRIMARY KEY CHECK (id = 1), + last_scanned_block INTEGER DEFAULT 0, + total_p2pk_found INTEGER DEFAULT 0 + ) + ''') + + # Inizializza il progresso se non esiste + cursor.execute('INSERT OR IGNORE INTO scan_progress (id, last_scanned_block) VALUES (1, 0)') + + conn.commit() + conn.close() + + def get_block_hash(self, height: int) -> Optional[str]: + """Ottieni l'hash del blocco data l'altezza""" + try: + response = requests.get(f"{self.api_base}/block-height/{height}", timeout=10) + if response.status_code == 200: + return response.text.strip() + except Exception as e: + print(f"Errore ottenendo hash per blocco {height}: {e}") + return None + + def get_block_transactions(self, block_hash: str) -> Optional[List[Dict]]: + """Ottieni le transazioni di un blocco con paginazione""" + try: + all_transactions = [] + start_index = 0 + + # L'API restituisce massimo 25 transazioni per volta + while True: + url = f"{self.api_base}/block/{block_hash}/txs/{start_index}" + response = requests.get(url, timeout=30) + + if response.status_code != 200: + break + + txs = response.json() + + if not txs or not isinstance(txs, list) or len(txs) == 0: + break + + all_transactions.extend(txs) + + # Se riceviamo meno di 25 transazioni, siamo alla fine + if len(txs) < 25: + break + + start_index += 25 + time.sleep(0.1) # Piccola pausa tra le richieste paginate + + return all_transactions if all_transactions else None + + except Exception as e: + print(f" ⚠️ Errore ottenendo transazioni per blocco {block_hash}: {e}") + return None + + def extract_p2pk_from_transaction(self, tx: Dict) -> List[Dict]: + """Estrai output P2PK da una transazione""" + p2pk_outputs = [] + + for idx, output in enumerate(tx.get('vout', [])): + scriptpubkey_type = output.get('scriptpubkey_type', '') + scriptpubkey = output.get('scriptpubkey', '') + scriptpubkey_asm = output.get('scriptpubkey_asm', '') + + # Metodo 1: Controlla il tipo esplicito + is_p2pk_type = scriptpubkey_type in ['pubkey', 'p2pk'] + + # Metodo 2: Controlla la lunghezza dello script (P2PK = 67 o 35 byte) + # 67 byte = chiave pubblica non compressa (04 + 64 hex chars) + OP_CHECKSIG + # 35 byte = chiave pubblica compressa (02/03 + 32 hex chars) + OP_CHECKSIG + script_len = len(scriptpubkey) // 2 if scriptpubkey else 0 + is_p2pk_length = script_len in [67, 35] + + # Metodo 3: Controlla il pattern ASM (PUBLIC_KEY OP_CHECKSIG) + is_p2pk_asm = False + if scriptpubkey_asm: + parts = scriptpubkey_asm.split() + # P2PK ha esattamente 2 elementi: chiave pubblica + OP_CHECKSIG + if len(parts) == 2 and parts[1] in ['OP_CHECKSIG', 'CHECKSIG']: + # Verifica che il primo elemento sia una chiave pubblica valida + pubkey = parts[0] + # Chiave non compressa: 130 caratteri (65 byte) + # Chiave compressa: 66 caratteri (33 byte) + if len(pubkey) in [130, 66]: + is_p2pk_asm = True + + # Metodo 4: Controlla il pattern hex dello scriptPubKey + is_p2pk_hex = False + if scriptpubkey: + # P2PK non compresso: 41 (65 byte pubkey) + pubkey + ac (OP_CHECKSIG) + # P2PK compresso: 21 (33 byte pubkey) + pubkey + ac (OP_CHECKSIG) + if scriptpubkey.endswith('ac'): # OP_CHECKSIG + if scriptpubkey.startswith('41') and len(scriptpubkey) == 134: # 67 byte * 2 + is_p2pk_hex = True + elif scriptpubkey.startswith('21') and len(scriptpubkey) == 70: # 35 byte * 2 + is_p2pk_hex = True + + # Se uno dei metodi identifica P2PK + if is_p2pk_type or is_p2pk_length or is_p2pk_asm or is_p2pk_hex: + p2pk_data = { + 'txid': tx['txid'], + 'output_index': idx, # Usa l'indice dell'array + 'scriptpubkey': scriptpubkey, + 'value': output.get('value', 0), + 'asm': scriptpubkey_asm + } + p2pk_outputs.append(p2pk_data) + + return p2pk_outputs + + def save_p2pk_data(self, block_height: int, p2pk_data: List[Dict], block_timestamp: int): + """Salva i dati P2PK nel database e verifica lo stato UTXO""" + if not p2pk_data: + return + + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + for data in p2pk_data: + try: + # Verifica se l'UTXO è ancora non speso + utxo_status = self.check_utxo_status(data['txid'], data['output_index']) + is_unspent = 1 if utxo_status['is_unspent'] else 0 + current_time = int(time.time()) + + cursor.execute(''' + INSERT OR REPLACE INTO p2pk_addresses + (block_height, txid, output_index, scriptpubkey, value_satoshi, timestamp, is_unspent, last_checked) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ''', ( + block_height, + data['txid'], + data['output_index'], + data['scriptpubkey'], + data['value'], # Il valore è già in satoshi dall'API + block_timestamp, + is_unspent, + current_time + )) + + # Log dello stato + if is_unspent: + print(f" 💰 UTXO NON SPESO! Valore: {data['value']} sat") + + except Exception as e: + print(f"Errore salvataggio dato: {e}") + + # Aggiorna il progresso + cursor.execute(''' + UPDATE scan_progress + SET last_scanned_block = ?, + total_p2pk_found = total_p2pk_found + ? + WHERE id = 1 + ''', (block_height, len(p2pk_data))) + + conn.commit() + conn.close() + + def get_block_timestamp(self, block_hash: str) -> Optional[int]: + """Ottieni timestamp del blocco""" + try: + response = requests.get(f"{self.api_base}/block/{block_hash}", timeout=10) + if response.status_code == 200: + block_data = response.json() + return block_data.get('timestamp', 0) + except Exception: + pass + return None + + def check_utxo_status(self, txid: str, vout: int) -> Dict: + """Verifica se l'UTXO è ancora non speso (ha saldo)""" + try: + response = requests.get(f"{self.api_base}/tx/{txid}/outspend/{vout}", timeout=10) + if response.status_code == 200: + data = response.json() + # Se 'spent' è False, l'UTXO non è stato speso + is_unspent = not data.get('spent', True) + return { + 'is_unspent': is_unspent, + 'spent_txid': data.get('txid') if data.get('spent') else None, + 'spent_vin': data.get('vin') if data.get('spent') else None + } + except Exception as e: + print(f" ⚠️ Errore verifica UTXO {txid}:{vout} - {e}") + + return {'is_unspent': False, 'spent_txid': None, 'spent_vin': None} + + def get_last_scanned_block(self) -> int: + """Ottieni l'ultimo blocco scannerizzato""" + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + cursor.execute('SELECT last_scanned_block FROM scan_progress WHERE id = 1') + result = cursor.fetchone() + conn.close() + return result[0] if result else 0 + + def scan_blocks(self, start_block: int, end_block: int, delay: float = 0.5): + """Scannerizza blocchi in un range""" + print(f"🚀 Inizio scansione blocchi {start_block} a {end_block}") + print(f"Database: {self.db_path}") + + for height in range(start_block, end_block + 1): + try: + print(f"\n📦 Analisi blocco {height}...") + + # Ottieni hash del blocco + block_hash = self.get_block_hash(height) + if not block_hash: + print(f" ⚠️ Hash non trovato per blocco {height}") + continue + + # Ottieni timestamp + timestamp = self.get_block_timestamp(block_hash) or 0 + + # Ottieni transazioni + transactions = self.get_block_transactions(block_hash) + if transactions is None: + print(f" ⚠️ Transazioni non trovate per blocco {height}") + continue + + # Processa transazioni + total_p2pk_in_block = 0 + all_p2pk_in_block = [] + + for tx in transactions: + p2pk_outputs = self.extract_p2pk_from_transaction(tx) + if p2pk_outputs: + total_p2pk_in_block += len(p2pk_outputs) + all_p2pk_in_block.extend(p2pk_outputs) + for p2pk in p2pk_outputs: + print(f" ✅ P2PK in TX {tx['txid'][:16]}... | Valore: {p2pk['value']} sat | Script: {p2pk['scriptpubkey'][:20]}...") + + # Salva tutti i P2PK trovati nel blocco + if all_p2pk_in_block: + self.save_p2pk_data(height, all_p2pk_in_block, timestamp) + print(f" 📊 Blocco {height}: {total_p2pk_in_block} P2PK trovati e salvati") + else: + # Aggiorna comunque il progresso anche se non ci sono P2PK + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + cursor.execute('UPDATE scan_progress SET last_scanned_block = ? WHERE id = 1', (height,)) + conn.commit() + conn.close() + print(f" 🔍 Blocco {height}: Nessun P2PK trovato") + + # Aggiorna progresso ogni 10 blocchi + if height % 10 == 0: + self.print_progress() + + # Rispetta l'API (rate limiting) + time.sleep(delay) + + except KeyboardInterrupt: + print("\n⏸️ Scansione interrotta dall'utente") + break + except Exception as e: + print(f"❌ Errore durante scansione blocco {height}: {e}") + time.sleep(2) # Pausa più lunga in caso di errore + + self.print_final_report() + + def print_progress(self): + """Stampa il progresso attuale""" + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute('SELECT last_scanned_block, total_p2pk_found FROM scan_progress WHERE id = 1') + progress = cursor.fetchone() + + cursor.execute('SELECT COUNT(DISTINCT block_height) FROM p2pk_addresses') + blocks_with_p2pk = cursor.fetchone()[0] + + cursor.execute('SELECT COUNT(*) FROM p2pk_addresses') + total_p2pk = cursor.fetchone()[0] + + conn.close() + + print(f"\n{'='*50}") + print(f"📈 PROGRESSO SCANSIONE") + print(f"📦 Ultimo blocco: {progress[0]}") + print(f"🔑 P2PK totali: {progress[1]}") + print(f"📊 Blocchi con P2PK: {blocks_with_p2pk}") + print(f"📝 Indirizzi unici: {total_p2pk}") + print(f"{'='*50}\n") + + def print_final_report(self): + """Stampa report finale""" + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + # Statistiche finali + cursor.execute('SELECT MAX(block_height), MIN(block_height) FROM p2pk_addresses') + max_block, min_block = cursor.fetchone() + + cursor.execute('SELECT SUM(value_satoshi) FROM p2pk_addresses') + total_value = cursor.fetchone()[0] or 0 + + cursor.execute('SELECT COUNT(*) FROM p2pk_addresses') + total_addresses = cursor.fetchone()[0] + + print(f"\n{'='*60}") + print(f"🎉 SCANSIONE COMPLETATA") + print(f"{'='*60}") + print(f"📊 Totale indirizzi P2PK: {total_addresses}") + print(f"📦 Primo blocco con P2PK: {min_block or 'Nessuno'}") + print(f"📦 Ultimo blocco con P2PK: {max_block or 'Nessuno'}") + print(f"💰 Valore totale: {total_value / 100000000:.8f} BTC") + print(f"💾 Database: {self.db_path}") + print(f"{'='*60}") + + # Esempio query per estrarre chiavi pubbliche + if total_addresses > 0: + print(f"\n📋 Esempio dati raccolti:") + cursor.execute('SELECT scriptpubkey FROM p2pk_addresses LIMIT 3') + samples = cursor.fetchall() + for i, sample in enumerate(samples): + print(f" {i+1}. {sample[0][:50]}...") + + conn.close() + + def export_to_csv(self, output_file: str = "p2pk_data.csv"): + """Esporta dati in CSV per analisi""" + import csv + + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute(''' + SELECT block_height, txid, output_index, + scriptpubkey, value_satoshi, timestamp + FROM p2pk_addresses + ORDER BY block_height + ''') + + with open(output_file, 'w', newline='', encoding='utf-8') as f: + writer = csv.writer(f) + writer.writerow(['Block', 'TXID', 'Output Index', + 'ScriptPubKey', 'Value (satoshi)', 'Timestamp']) + + for row in cursor.fetchall(): + writer.writerow(row) + + conn.close() + print(f"📁 Dati esportati in: {output_file}") + +# USO DIDATTICO +if __name__ == "__main__": + # AVVERTENZA IMPORTANTE + print("⚠️ AVVISO: Questo script è SOLO per SCOPI EDUCATIVI") + print("⛔ NON usare per attività illegali") + print("📚 Scopo: Studio della struttura blockchain Bitcoin\n") + + # Crea scanner + scanner = P2PKBlockchainScanner("bitcoin_p2pk_study.db") + + # Recupera l'ultimo blocco scannerizzato + last_scanned = scanner.get_last_scanned_block() + + print(f"📊 Ultimo blocco scannerizzato: {last_scanned}") + print(f"💡 I primi blocchi di Bitcoin (1-10000) contengono molti P2PK") + + # Chiedi all'utente il blocco di inizio + start_input = input(f"\n📍 Blocco di inizio scansione (default: {last_scanned + 1 if last_scanned > 0 else 1}): ").strip() + + if start_input: + try: + START_BLOCK = int(start_input) + if START_BLOCK < 1: + print("⚠️ Il blocco deve essere >= 1, uso blocco 1") + START_BLOCK = 1 + except ValueError: + print(f"⚠️ Valore non valido, uso blocco {last_scanned + 1 if last_scanned > 0 else 1}") + START_BLOCK = last_scanned + 1 if last_scanned > 0 else 1 + else: + START_BLOCK = last_scanned + 1 if last_scanned > 0 else 1 + + # Chiedi all'utente il blocco finale + end_input = input(f"📍 Blocco finale scansione (default: {START_BLOCK + 999}): ").strip() + + if end_input: + try: + END_BLOCK = int(end_input) + if END_BLOCK < START_BLOCK: + print(f"⚠️ Il blocco finale deve essere >= {START_BLOCK}, uso {START_BLOCK + 999}") + END_BLOCK = START_BLOCK + 999 + except ValueError: + print(f"⚠️ Valore non valido, uso blocco {START_BLOCK + 999}") + END_BLOCK = START_BLOCK + 999 + else: + END_BLOCK = START_BLOCK + 999 + + # Chiedi il delay tra le richieste + delay_input = input("⏱️ Delay tra richieste in secondi (default: 1.0): ").strip() + + if delay_input: + try: + REQUEST_DELAY = float(delay_input) + if REQUEST_DELAY < 0.1: + print("⚠️ Il delay minimo è 0.1s per rispettare l'API") + REQUEST_DELAY = 0.1 + except ValueError: + print("⚠️ Valore non valido, uso 1.0s") + REQUEST_DELAY = 1.0 + else: + REQUEST_DELAY = 1.0 + + print(f"\n🔧 Configurazione:") + print(f" Primo blocco: {START_BLOCK}") + print(f" Ultimo blocco: {END_BLOCK}") + print(f" Totale blocchi: {END_BLOCK - START_BLOCK + 1}") + print(f" Delay richieste: {REQUEST_DELAY}s") + print(f" Tempo stimato: ~{((END_BLOCK - START_BLOCK + 1) * REQUEST_DELAY) / 60:.1f} minuti") + + # Conferma + response = input("\n▶️ Avviare la scansione? (s/n): ").strip().lower() + + if response == 's': + scanner.scan_blocks(START_BLOCK, END_BLOCK, REQUEST_DELAY) + + # Esporta in CSV + export = input("\n📤 Esportare dati in CSV? (s/n): ").strip().lower() + if export == 's': + scanner.export_to_csv() + else: + print("⏹️ Scansione annullata") + + print("\n💡 Per analisi ulteriori:") + print(" 1. Usa SQLite per query: SELECT * FROM p2pk_addresses LIMIT 10") + print(" 2. Analizza gli script P2PK con librerie Bitcoin") + print(" 3. Studia la struttura delle transazioni early Bitcoin") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0eb8cae --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests>=2.31.0 diff --git a/view_db.py b/view_db.py new file mode 100755 index 0000000..1783441 --- /dev/null +++ b/view_db.py @@ -0,0 +1,590 @@ +#!/usr/bin/env python3 +""" +Script per visualizzare i dati P2PK dal database +Genera un report HTML interattivo +""" + +import sqlite3 +import sys +from datetime import datetime +from pathlib import Path + +class P2PKDatabaseViewer: + def __init__(self, db_path: str = "bitcoin_p2pk_study.db"): + self.db_path = db_path + + def check_database_exists(self) -> bool: + """Verifica se il database esiste""" + return Path(self.db_path).exists() + + def get_statistics(self) -> dict: + """Ottieni statistiche dal database""" + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + stats = {} + + try: + # Progresso scansione + cursor.execute('SELECT last_scanned_block, total_p2pk_found FROM scan_progress WHERE id = 1') + progress = cursor.fetchone() + stats['last_block'] = progress[0] if progress else 0 + stats['total_found'] = progress[1] if progress else 0 + + # Totale P2PK nel database + cursor.execute('SELECT COUNT(*) FROM p2pk_addresses') + stats['total_in_db'] = cursor.fetchone()[0] + + # Range blocchi + cursor.execute('SELECT MIN(block_height), MAX(block_height) FROM p2pk_addresses') + min_block, max_block = cursor.fetchone() + stats['min_block'] = min_block if min_block is not None else 0 + stats['max_block'] = max_block if max_block is not None else 0 + + # Blocchi unici con P2PK + cursor.execute('SELECT COUNT(DISTINCT block_height) FROM p2pk_addresses') + stats['unique_blocks'] = cursor.fetchone()[0] + + # Transazioni unique + cursor.execute('SELECT COUNT(DISTINCT txid) FROM p2pk_addresses') + stats['unique_txs'] = cursor.fetchone()[0] + + # P2PK non spesi (con saldo attuale) + cursor.execute('SELECT COUNT(*) FROM p2pk_addresses WHERE is_unspent = 1') + stats['unspent_count'] = cursor.fetchone()[0] + + # Valore totale - calcolo manuale per evitare overflow + cursor.execute('SELECT value_satoshi FROM p2pk_addresses') + all_values = cursor.fetchall() + total_sat = 0.0 + for (val,) in all_values: + if val is not None: + total_sat += float(val) + + stats['total_value_btc'] = total_sat / 100000000.0 + stats['total_value_sat'] = int(total_sat) + + # Valore non speso + cursor.execute('SELECT value_satoshi FROM p2pk_addresses WHERE is_unspent = 1') + unspent_values = cursor.fetchall() + unspent_sat = 0.0 + for (val,) in unspent_values: + if val is not None: + unspent_sat += float(val) + + stats['unspent_value_btc'] = unspent_sat / 100000000.0 + stats['unspent_value_sat'] = int(unspent_sat) + + except Exception as e: + print(f"⚠️ Errore nel calcolo statistiche: {e}") + import traceback + traceback.print_exc() + stats = { + 'last_block': 0, + 'total_found': 0, + 'total_in_db': 0, + 'min_block': 0, + 'max_block': 0, + 'unique_blocks': 0, + 'total_value_btc': 0.0, + 'total_value_sat': 0, + 'unique_txs': 0, + 'unspent_count': 0, + 'unspent_value_btc': 0.0, + 'unspent_value_sat': 0 + } + + conn.close() + return stats + + def get_all_p2pk(self, limit: int = None) -> list: + """Ottieni tutti i P2PK dal database""" + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + query = ''' + SELECT id, block_height, txid, output_index, + scriptpubkey, value_satoshi, timestamp, is_unspent, last_checked + FROM p2pk_addresses + ORDER BY block_height ASC, id ASC + ''' + + if limit: + query += f' LIMIT {limit}' + + cursor.execute(query) + results = cursor.fetchall() + conn.close() + + return results + + def extract_pubkey_from_script(self, scriptpubkey: str) -> str: + """Estrae la chiave pubblica dallo scriptPubKey""" + if not scriptpubkey: + return "" + + # P2PK non compresso: 41 + pubkey (130 hex chars) + ac + if scriptpubkey.startswith('41') and len(scriptpubkey) == 134: + return scriptpubkey[2:132] # Rimuovi 41 all'inizio e ac alla fine + + # P2PK compresso: 21 + pubkey (66 hex chars) + ac + elif scriptpubkey.startswith('21') and len(scriptpubkey) == 70: + return scriptpubkey[2:68] # Rimuovi 21 all'inizio e ac alla fine + + return scriptpubkey + + def _generate_table_html(self, p2pk_data: list) -> str: + """Genera l'HTML della tabella""" + if not p2pk_data: + return '
📭 Nessun dato P2PK trovato nel database
' + + rows_html = [] + for row in p2pk_data: + pubkey = self.extract_pubkey_from_script(row[4]) + txid_short = row[2][:16] if len(row[2]) > 16 else row[2] + timestamp_str = datetime.fromtimestamp(row[6]).strftime('%Y-%m-%d %H:%M') if row[6] else 'N/A' + # row[5] è già in satoshi, lo convertiamo in BTC dividendo per 100000000 + value_satoshi = row[5] + value_btc = value_satoshi / 100000000.0 + + # Stato UTXO (row[7] = is_unspent) + is_unspent = row[7] if len(row) > 7 else 0 + utxo_status = '🟢 NON SPESO' if is_unspent else '🔴 SPESO' + utxo_class = 'unspent' if is_unspent else 'spent' + + row_html = f''' + + {row[0]} + {row[1]} + + {txid_short}... + + + {row[3]} + +
+ {pubkey} + +
+ + {value_btc:.8f} BTC
({value_satoshi:,} sat) + {utxo_status} + {timestamp_str} + ''' + rows_html.append(row_html) + + return f''' + + + + + + + + + + + + + + + {''.join(rows_html)} + +
IDBloccoTXIDOutputChiave PubblicaValore (BTC)Stato UTXOTimestamp
''' + + def generate_html_report(self, output_file: str = "p2pk_report.html"): + """Genera un report HTML interattivo""" + if not self.check_database_exists(): + print(f"❌ Database non trovato: {self.db_path}") + return + + stats = self.get_statistics() + p2pk_data = self.get_all_p2pk() + + html = f""" + + + + + P2PK Database Report + + + +
+
+

🔐 P2PK Database Report

+

Bitcoin Pay-to-Public-Key Transaction Scanner

+

Database: {self.db_path}

+
+ +
+
+
Ultimo Blocco
+
{stats['last_block']:,}
+
+
+
P2PK Trovati
+
{stats['total_in_db']:,}
+
+
+
Blocchi Unici
+
{stats['unique_blocks']:,}
+
+
+
Valore Totale
+
{stats['total_value_btc']:.8f} BTC
+
+
+
Range Blocchi
+
{stats['min_block']:,} - {stats['max_block']:,}
+
+
+
Transazioni Uniche
+
{stats['unique_txs']:,}
+
+
+
💰 P2PK Non Spesi
+
{stats['unspent_count']:,}
+
+
+
💎 Valore Non Speso
+
{stats['unspent_value_btc']:.8f} BTC
+
+
+ +
+ + + {self._generate_table_html(p2pk_data)} +
+ + +
+ + + + +""" + + with open(output_file, 'w', encoding='utf-8') as f: + f.write(html) + + print(f"✅ Report HTML generato: {output_file}") + print(f"📊 Statistiche:") + print(f" - P2PK totali: {stats['total_in_db']}") + print(f" - Blocchi scansionati: {stats['last_block']}") + print(f" - Valore totale: {stats['total_value_btc']:.8f} BTC") + + def print_console_report(self): + """Stampa un report nel terminale""" + if not self.check_database_exists(): + print(f"❌ Database non trovato: {self.db_path}") + return + + stats = self.get_statistics() + + print("\n" + "="*60) + print("📊 REPORT DATABASE P2PK") + print("="*60) + print(f"📁 Database: {self.db_path}") + print(f"📦 Ultimo blocco scansionato: {stats['last_block']:,}") + print(f"🔑 P2PK totali trovati: {stats['total_in_db']:,}") + print(f"📊 Blocchi unici con P2PK: {stats['unique_blocks']:,}") + print(f"📈 Range blocchi: {stats['min_block']:,} - {stats['max_block']:,}") + print(f"💰 Valore totale: {stats['total_value_btc']:.8f} BTC ({stats['total_value_sat']:,} sat)") + print(f"📝 Transazioni uniche: {stats['unique_txs']:,}") + print("-"*60) + print(f"💎 P2PK NON SPESI: {stats['unspent_count']:,}") + print(f"💵 Valore non speso: {stats['unspent_value_btc']:.8f} BTC ({stats['unspent_value_sat']:,} sat)") + print("="*60) + + # Mostra alcuni esempi + p2pk_data = self.get_all_p2pk(limit=5) + if p2pk_data: + print("\n🔍 Primi 5 P2PK trovati:") + for row in p2pk_data: + pubkey = self.extract_pubkey_from_script(row[4]) + print(f"\n Blocco {row[1]} | TX: {row[2][:16]}...") + print(f" Pubkey: {pubkey[:40]}...") + print(f" Valore: {row[5] / 100000000:.8f} BTC") + +if __name__ == "__main__": + db_file = "bitcoin_p2pk_study.db" + + # Se viene passato un argomento, usalo come path del database + if len(sys.argv) > 1: + db_file = sys.argv[1] + + viewer = P2PKDatabaseViewer(db_file) + + print("🔐 P2PK Database Viewer") + print("="*60) + print("Scegli un'opzione:") + print("1. Genera report HTML") + print("2. Mostra report nel terminale") + print("3. Entrambi") + print("="*60) + + choice = input("Scelta (1/2/3): ").strip() + + if choice == "1": + viewer.generate_html_report() + elif choice == "2": + viewer.print_console_report() + elif choice == "3": + viewer.generate_html_report() + viewer.print_console_report() + else: + print("❌ Scelta non valida")