From cc34cf23dfccbecf4f134d4fdc3cd20833d2bcf7 Mon Sep 17 00:00:00 2001 From: davide3011 Date: Fri, 30 Jan 2026 09:32:05 +0100 Subject: [PATCH] Commit iniziale --- .gitignore | 10 ++++ LICENSE | 21 +++++++ README.md | 142 +++++++++++++++++++++++++++++++++++++++++++ main.py | 34 +++++++++++ p2pk.py | 97 ++++++++++++++++++++++++++++++ p2pkh.py | 96 ++++++++++++++++++++++++++++++ p2sh.py | 152 +++++++++++++++++++++++++++++++++++++++++++++++ p2tr.py | 126 +++++++++++++++++++++++++++++++++++++++ p2wpkh.py | 97 ++++++++++++++++++++++++++++++ requirements.txt | 4 ++ 10 files changed, 779 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 main.py create mode 100644 p2pk.py create mode 100644 p2pkh.py create mode 100644 p2sh.py create mode 100644 p2tr.py create mode 100644 p2wpkh.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a1f1eef --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Generated wallet files (contain private keys) +wallet*.json +dati_p2pk*.json +p2sh.json + +# Virtual envs +venv/ + +# JSON files (default ignore) +*.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..846d8bc --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Davide + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4521fe2 --- /dev/null +++ b/README.md @@ -0,0 +1,142 @@ +# Generatore di Indirizzi Bitcoin + +Questo programma permette di generare diversi tipi di indirizzi Bitcoin in modo semplice e interattivo. L'output prodotto include chiavi private, chiavi pubbliche, indirizzi e formati WIF, e può essere salvato in file JSON. + +Per la verifica della validità degli indirizzi, è stato utilizzato lo strumento esterno [SecretScan](https://secretscan.org/). + +Attualmente il programma supporta i seguenti tipi di indirizzi: +- **P2PK (Pay-to-PubKey)** +- **P2PKH (Pay-to-PubKey-Hash)** +- **P2SH (Pay-to-Script-Hash, con supporto multisig)** +- **P2WPKH (Pay-to-Witness-PubKey-Hash, SegWit v0)** +- **P2TR (Pay-to-Taproot, SegWit v1)** + +Sono in fase di sviluppo anche: +- **P2WSH (Pay-to-Witness-Script-Hash)** + +--- + +## Come funziona il programma + +Il file principale è `main.py`. Quando viene eseguito, mostra un menu interattivo che consente di scegliere il tipo di indirizzo da generare: + +```bash +python main.py +``` + +Esempio di esecuzione: +``` +=== GENERATORE INDIRIZZI BITCOIN === +Seleziona il tipo di indirizzo: +1. P2PK +2. P2PKH +3. P2SH +4. P2WPKH +5. P2TR +``` + +Dopo aver selezionato un'opzione, lo script dedicato verrà eseguito e guida l'utente attraverso: +- Scelta della rete (mainnet, testnet, regtest) +- Eventuale utilizzo di chiavi compresse/non compresse +- Visualizzazione e salvataggio dei dati in un file `.json` + +Ogni script è indipendente (`p2pk.py`, `p2pkh.py`, `p2sh.py`, `p2wpkh.py`, `p2tr.py`) e implementa le regole specifiche del relativo standard Bitcoin. + +--- + +## Tipologie di indirizzi supportati + +### 1. P2PK (Pay-to-PubKey) +- **Standard**: Formato originale di Bitcoin, definito nel whitepaper di Satoshi Nakamoto +- **Formato indirizzo**: Non ha un formato di indirizzo standard, usa direttamente la chiave pubblica +- **Script**: ` OP_CHECKSIG` +- **Pro**: molto semplice, rappresenta direttamente la chiave pubblica, dimensioni di transazione minime +- **Contro**: obsoleto, non compatibile con la maggior parte dei wallet moderni. Espone la chiave pubblica subito alla blockchain, vulnerabile agli attacchi quantistici +- **Uso attuale**: Principalmente per coinbase transactions e casi molto specifici + +### 2. P2PKH (Pay-to-PubKey-Hash) +- **Standard**: BIP-13 (Base58Check), formato legacy standard +- **Formato indirizzo**: Inizia con '1' (mainnet), 'm' o 'n' (testnet) +- **Script**: `OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG` +- **Codifica**: Base58Check con prefisso 0x00 (mainnet) +- **Pro**: è lo standard "legacy", molto diffuso, supportato da tutti i wallet ed exchange. Protezione contro attacchi quantistici (hash della chiave pubblica) +- **Contro**: gli indirizzi sono più lunghi e le fee di transazione sono più alte rispetto a quelli più moderni (SegWit), dimensioni di transazione maggiori + +### 3. P2SH (Pay-to-Script-Hash) +- **Standard**: BIP-16 (Pay to Script Hash), BIP-67 (Deterministic Pay-to-script-hash multi-signature addresses) +- **Formato indirizzo**: Inizia con '3' (mainnet), '2' (testnet/regtest) +- **Script**: `OP_HASH160 OP_EQUAL` +- **Codifica**: Base58Check con prefisso 0x05 (mainnet), 0xC4 (testnet/regtest) +- **Pro**: permette indirizzi basati su script arbitrari, ideale per multisig e contratti complessi. Supportato da tutti i wallet moderni. Maggiore flessibilità e funzionalità avanzate +- **Contro**: le fee sono leggermente più alte rispetto ai singoli indirizzi e richiede la rivelazione dello script al momento della spesa. Dimensioni di transazione maggiori per il redeem script + +**Implementazione attuale: Multisig** +Attualmente il supporto P2SH è limitato agli script multisig, che rappresentano il caso d'uso più comune per P2SH. + +**Opzioni disponibili per l'utente:** +- **Configurazione m-of-n**: l'utente può scegliere quante firme sono richieste (m) su un totale di chiavi (n) + - Esempi: 2-of-3, 3-of-5, 1-of-2, ecc. + - Limite: 1 ≤ m ≤ n ≤ 16 +- **Rete**: mainnet, testnet, regtest +- **Chiavi compresse**: opzione per utilizzare chiavi pubbliche compresse (33 byte) o non compresse (65 byte) +- **Ordinamento BIP67**: le chiavi pubbliche vengono automaticamente ordinate per evitare malleabilità + +**Funzionalità implementate:** +- Generazione automatica di n coppie di chiavi (privata/pubblica) +- Creazione del redeem script multisig +- Calcolo dell'hash160 del redeem script +- Generazione dell'indirizzo P2SH finale +- Output JSON strutturato con tutti i dati necessari +- Esportazione delle chiavi private in formato WIF +- Salvataggio completo in file JSON per backup e utilizzo futuro + +### 4. P2WPKH (SegWit v0, Bech32) +- **Standard**: BIP-141 (Segregated Witness), BIP-173 (Base32 address format) +- **Formato indirizzo**: Inizia con 'bc1q' (mainnet), 'tb1q' (testnet), 'bcrt1q' (regtest) +- **Script**: `OP_0 ` (20 bytes) +- **Codifica**: Bech32 con HRP 'bc' (mainnet), 'tb' (testnet), 'bcrt' (regtest) +- **Witness Program**: versione 0, 20 bytes (hash160 della chiave pubblica) +- **Pro**: riduce le fee grazie al formato SegWit (witness data separato), indirizzi più compatti, supportato da quasi tutti i wallet moderni, protezione contro transaction malleability +- **Contro**: non tutti i vecchi servizi accettano Bech32, richiede supporto SegWit + +### 5. P2TR (Taproot, SegWit v1, Bech32m) +- **Standard**: BIP-340 (Schnorr Signatures), BIP-341 (Taproot), BIP-342 (Tapscript), BIP-350 (Bech32m) +- **Formato indirizzo**: Inizia con 'bc1p' (mainnet), 'tb1p' (testnet), 'bcrt1p' (regtest) +- **Script**: `OP_1 ` (32 bytes) +- **Codifica**: Bech32m con HRP 'bc' (mainnet), 'tb' (testnet), 'bcrt' (regtest) +- **Witness Program**: versione 1, 32 bytes (tweaked public key) +- **Pro**: sono i più recenti, con maggiore privacy e flessibilità (supporta script complessi nascosti dietro un singolo indirizzo). Le fee sono basse, firme Schnorr più efficienti, aggregazione delle firme +- **Contro**: ancora relativamente nuovo, non supportato da tutti i servizi, complessità implementativa maggiore + +### In sviluppo +- **P2SH (Pay-to-Script-Hash)**: permette indirizzi basati su script arbitrari, molto usato per multisig e contratti complessi. +- **P2WSH (Pay-to-Witness-Script-Hash)**: versione SegWit del P2SH, più efficiente e sicura. + +--- + +## Utilizzo pratico + +1. Clona o scarica il repository. +2. Assicurati di avere Python 3 installato e i requisiti: + ```bash + # Consigliato ambiente virtuale + python -m venv venv + source venv/bin/activate + + # Installazione requisiti + pip install -r requirements.txt + ``` + + +3. Esegui il programma principale: + ```bash + python main.py + ``` +4. Segui le istruzioni sullo schermo per generare e salvare il tuo indirizzo. + +I dati saranno salvati in un file `.json` leggibile e riutilizzabile. + +--- + +## LICENZA +Questo progetto è rilasciato sotto licenza MIT \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..2b80868 --- /dev/null +++ b/main.py @@ -0,0 +1,34 @@ +import subprocess +import sys + +def main(): + print("=== GENERATORE INDIRIZZI BITCOIN ===") + print("Seleziona il tipo di indirizzo:") + print("1. P2PK") + print("2. P2PKH") + print("3. P2SH") + print("4. P2WPKH") + print("5. P2TR") + + choice = input("Inserisci la tua scelta: ").strip() + + scripts = { + '1': 'p2pk.py', + '2': 'p2pkh.py', + '3': 'p2sh.py', + '4': 'p2wpkh.py', + '5': 'p2tr.py' + } + + if choice in scripts: + try: + subprocess.run([sys.executable, scripts[choice]], check=True) + except subprocess.CalledProcessError as e: + print(f"Errore nell'esecuzione dello script: {e}") + except KeyboardInterrupt: + print("\nOperazione interrotta.") + else: + print("Scelta non valida.") + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/p2pk.py b/p2pk.py new file mode 100644 index 0000000..46465b5 --- /dev/null +++ b/p2pk.py @@ -0,0 +1,97 @@ +import secrets +import hashlib +import json +import ecdsa +import base58 +from typing import Dict + +NETWORK_CONFIG = { + 'mainnet': {'wif_prefix': b'\x80'}, + 'testnet': {'wif_prefix': b'\xEF'}, + 'regtest': {'wif_prefix': b'\xEF'}, +} + +def generate_p2pk(network: str = 'mainnet', compressed: bool = False) -> Dict[str, str]: + """ + Genera chiave privata, chiave pubblica e WIF per P2PK. + + Args: + network: 'mainnet', 'testnet' o 'regtest' + compressed: True per chiave pubblica compressa (33 byte), False per non compressa (65 byte) + + Returns: + Dizionario con network, script_type, private_key_hex, private_key_wif, public_key_hex + """ + config = NETWORK_CONFIG.get(network) + if config is None: + raise ValueError("Network non supportato. Scegli tra 'mainnet', 'testnet' o 'regtest'.") + + private_key = secrets.token_bytes(32) + private_key_hex = private_key.hex() + + sk = ecdsa.SigningKey.from_string(private_key, curve=ecdsa.SECP256k1) + vk = sk.get_verifying_key() + pubkey_bytes = vk.to_string() + + if compressed: + x = pubkey_bytes[:32] + y = pubkey_bytes[32:] + prefix = b'\x02' if int.from_bytes(y, 'big') % 2 == 0 else b'\x03' + public_key = prefix + x + else: + public_key = b'\x04' + pubkey_bytes + + public_key_hex = public_key.hex() + + if compressed: + extended_key = config['wif_prefix'] + private_key + b'\x01' + else: + extended_key = config['wif_prefix'] + private_key + + checksum = hashlib.sha256(hashlib.sha256(extended_key).digest()).digest()[:4] + wif = base58.b58encode(extended_key + checksum).decode() + + return { + 'network': network, + 'script_type': 'p2pk', + 'private_key_hex': private_key_hex, + 'private_key_wif': wif, + 'public_key_hex': public_key_hex + } + +def main(): + """Genera e salva dati P2PK.""" + network = input("Seleziona il tipo di rete (mainnet, testnet, regtest): ").strip().lower() + compressed_input = input("Utilizzare chiavi compresse (s/n): ").strip().lower() + while compressed_input not in ['s', 'n']: + print("Inserisci 's' per si o 'n' per no.") + compressed_input = input("Utilizzare chiavi compresse (s/n): ").strip().lower() + compressed = (compressed_input == 's') + + try: + result = generate_p2pk(network, compressed) + + print("\n--- Risultati ---") + print("Network:", result['network']) + print("Script type:", result['script_type']) + print("Chiave privata (hex):", result['private_key_hex']) + print("Chiave privata (WIF):", result['private_key_wif']) + key_type = "compressa" if compressed else "non compressa" + print(f"Chiave pubblica ({key_type}, hex):", result['public_key_hex']) + + nome_file = input("\nInserisci il nome del file (senza estensione) per salvare i dati: ").strip() + if not nome_file: + nome_file = "dati_p2pk" + print("Nome del file non valido. Verrà utilizzato il nome di default: dati_p2pk.json") + if not nome_file.endswith('.json'): + nome_file += '.json' + + with open(nome_file, 'w') as f: + json.dump(result, f, indent=4) + print(f"Dati salvati correttamente nel file: {nome_file}") + + except Exception as e: + print("Errore:", e) + +if __name__ == '__main__': + main() diff --git a/p2pkh.py b/p2pkh.py new file mode 100644 index 0000000..dd57867 --- /dev/null +++ b/p2pkh.py @@ -0,0 +1,96 @@ +import secrets +import hashlib +import json +import ecdsa +import base58 +from typing import Dict + +NETWORK_CONFIG = { + 'mainnet': {'addr_prefix': b'\x00', 'wif_prefix': b'\x80'}, + 'testnet': {'addr_prefix': b'\x6f', 'wif_prefix': b'\xEF'}, + 'regtest': {'addr_prefix': b'\x6f', 'wif_prefix': b'\xEF'}, +} + +def generate_legacy_address(network: str = 'mainnet', compressed: bool = True) -> Dict[str, str]: + """Genera chiave privata, pubblica, WIF e indirizzo Bitcoin Legacy (P2PKH).""" + config = NETWORK_CONFIG.get(network) + if config is None: + raise ValueError("Network non supportato. Scegli tra 'mainnet', 'testnet' o 'regtest'.") + + private_key = secrets.token_bytes(32) + private_key_hex = private_key.hex() + + sk = ecdsa.SigningKey.from_string(private_key, curve=ecdsa.SECP256k1) + vk = sk.get_verifying_key() + pubkey_bytes = vk.to_string() + + if compressed: + x = pubkey_bytes[:32] + y = pubkey_bytes[32:] + prefix = b'\x02' if int.from_bytes(y, 'big') % 2 == 0 else b'\x03' + pubkey = prefix + x + else: + pubkey = b'\x04' + pubkey_bytes + + pubkey_hex = pubkey.hex() + + sha256_pubkey = hashlib.sha256(pubkey).digest() + ripemd160 = hashlib.new('ripemd160', sha256_pubkey).digest() + + addr_payload = config['addr_prefix'] + ripemd160 + checksum = hashlib.sha256(hashlib.sha256(addr_payload).digest()).digest()[:4] + address = base58.b58encode(addr_payload + checksum).decode() + + if compressed: + extended_key = config['wif_prefix'] + private_key + b'\x01' + else: + extended_key = config['wif_prefix'] + private_key + + checksum = hashlib.sha256(hashlib.sha256(extended_key).digest()).digest()[:4] + private_key_wif = base58.b58encode(extended_key + checksum).decode() + + return { + 'network': network, + 'script_type': 'p2pkh', + 'private_key_hex': private_key_hex, + 'private_key_wif': private_key_wif, + 'public_key_hex': pubkey_hex, + 'address': address + } + + + +def main(): + """Funzione principale che gestisce l'interazione con l'utente e il salvataggio dei dati.""" + network = input("Seleziona il tipo di rete (mainnet, testnet, regtest): ").strip().lower() + compressed_input = input("Utilizzare chiavi compresse? (s/n): ").strip().lower() + compressed = compressed_input != 'n' + + try: + result = generate_legacy_address(network, compressed) + + print("\n--- Risultati ---") + print(f"Network: {result['network']}") + print(f"Script type: {result['script_type']}") + print("Chiave privata (hex):", result['private_key_hex']) + print("Chiave privata (WIF):", result['private_key_wif']) + key_type = "compressa" if compressed else "non compressa" + print(f"Chiave pubblica ({key_type}, hex):", result['public_key_hex']) + print("Indirizzo:", result['address']) + + nome_file = input("\nInserisci il nome del file (senza estensione) per salvare i dati: ").strip() + if not nome_file: + nome_file = "wallet" + print("Nome del file non valido. Verrà utilizzato il nome di default: wallet.json") + if not nome_file.endswith('.json'): + nome_file += '.json' + + with open(nome_file, 'w') as f: + json.dump(result, f, indent=4) + print(f"Dati salvati correttamente nel file: {nome_file}") + + except Exception as e: + print("Errore:", e) + +if __name__ == '__main__': + main() diff --git a/p2sh.py b/p2sh.py new file mode 100644 index 0000000..3f6cb1b --- /dev/null +++ b/p2sh.py @@ -0,0 +1,152 @@ +import secrets +import hashlib +import json +import ecdsa +import base58 +from typing import Dict, List + +NETWORK_CONFIG = { + "mainnet": {"p2sh_prefix": b"\x05", "wif_prefix": b"\x80"}, + "testnet": {"p2sh_prefix": b"\xC4", "wif_prefix": b"\xEF"}, + "regtest": {"p2sh_prefix": b"\xC4", "wif_prefix": b"\xEF"}, +} + +def _to_wif(privkey: bytes, wif_prefix: bytes, compressed: bool = True) -> str: + """Converte una chiave privata in WIF (aggiunge 0x01 se compressa).""" + payload = wif_prefix + privkey + (b"\x01" if compressed else b"") + checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4] + return base58.b58encode(payload + checksum).decode() + +def _gen_keypair(compressed: bool = True): + """Genera (priv_hex, wif, pub_hex).""" + sk_bytes = secrets.token_bytes(32) + sk = ecdsa.SigningKey.from_string(sk_bytes, curve=ecdsa.SECP256k1) + vk = sk.get_verifying_key() + raw = vk.to_string() # 64 byte X||Y + + if compressed: + x = raw[:32] + y = raw[32:] + prefix = b"\x02" if int.from_bytes(y, "big") % 2 == 0 else b"\x03" + pub = prefix + x + else: + pub = b"\x04" + raw + + return sk_bytes.hex(), pub.hex(), pub + +def _op_push(data: bytes) -> bytes: + """pushdata minimale (lunghezze pubkey/redeem < 0x4c gestite direttamente).""" + assert len(data) < 0x4c + return bytes([len(data)]) + data + +def _encode_multisig_redeem(m: int, pubkeys: List[bytes], n: int) -> bytes: + """Costruisce redeemScript: OP_m ... OP_n OP_CHECKMULTISIG.""" + if not (1 <= m <= n <= 16): + raise ValueError("Richiesto 1 <= m <= n <= 16") + if any(len(pk) not in (33, 65) for pk in pubkeys): + raise ValueError("Pubkey non valida (attese compresse 33B o non compresse 65B)") + + OP_CHECKMULTISIG = b"\xAE" + OP_m = bytes([0x50 + m]) # OP_1 .. OP_16 + OP_n = bytes([0x50 + n]) + + script = OP_m + for pk in pubkeys: + script += _op_push(pk) + script += OP_n + OP_CHECKMULTISIG + return script + +def _hash160(b: bytes) -> bytes: + return hashlib.new("ripemd160", hashlib.sha256(b).digest()).digest() + +def _script_pubkey_p2sh(script_hash160: bytes) -> bytes: + # OP_HASH160 <20> OP_EQUAL + return b"\xA9\x14" + script_hash160 + b"\x87" + +def _address_p2sh(h160: bytes, ver: bytes) -> str: + payload = ver + h160 + checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4] + return base58.b58encode(payload + checksum).decode() + +def generate_p2sh_multisig( + network: str = "mainnet", + m: int = 2, + n: int = 3, + compressed: bool = True, + sort_pubkeys: bool = True, # BIP67: ordina le pubkey per evitare malleabilità del redeem +) -> Dict: + """Genera JSON per un P2SH multisig m-of-n (con chiavi locali).""" + cfg = NETWORK_CONFIG.get(network) + if cfg is None: + raise ValueError("Network non supportato (mainnet, testnet, regtest).") + if not (1 <= m <= n <= 16): + raise ValueError("Parametri m/n non validi (1 <= m <= n <= 16).") + + # genera n coppie chiave + participants = [] + pubkeys_bytes = [] + for _ in range(n): + priv_hex, pub_hex, pub_bytes = _gen_keypair(compressed) + participants.append({ + "private_key_hex": priv_hex, + "private_key_wif": _to_wif(bytes.fromhex(priv_hex), cfg["wif_prefix"], compressed), + "public_key_hex": pub_hex, + }) + pubkeys_bytes.append(pub_bytes) + + # BIP67: ordina le pubkey in modo deterministico (lexicografico sul byte array) + if sort_pubkeys: + pubkeys_bytes.sort() + + redeem = _encode_multisig_redeem(m, pubkeys_bytes, n) + redeem_h160 = _hash160(redeem) + spk = _script_pubkey_p2sh(redeem_h160) + address = _address_p2sh(redeem_h160, cfg["p2sh_prefix"]) + + result = { + "network": network, + "script_type": "p2sh-multisig", + "m": m, + "n": n, + "redeem_script_hex": redeem.hex(), + "participants": participants, + "address": address + } + return result + +def _redeem_asm(m: int, pubkeys: List[bytes], n: int) -> str: + """Rappresentazione ASM comoda per debug.""" + def opnum(x): return f"OP_{x}" + items = [opnum(m)] + [pk.hex() for pk in pubkeys] + [opnum(n), "OP_CHECKMULTISIG"] + return " ".join(items) + +def main(): + print("=== Generatore P2SH Multisig ===") + net = input("Seleziona rete (mainnet/testnet/regtest): ").strip().lower() + m = int(input("Quante firme richieste (m)? ").strip()) + n = int(input("Quante chiavi totali (n)? ").strip()) + comp_in = input("Pubkey compresse? (s/n): ").strip().lower() + compressed = comp_in != "n" + + try: + res = generate_p2sh_multisig(net, m, n, compressed, sort_pubkeys=True) + print("\n--- Risultati ---") + for k in ["network","script_type","m","n","redeem_script_hex"]: + print(f"{k}: {res[k]}") + print("\n-- Partecipanti --") + for i, p in enumerate(res["participants"], 1): + print(f"[{i}] pub: {p['public_key_hex']}") + print(f" priv: {p['private_key_hex']}") + print(f" wif: {p['private_key_wif']}") + print(f"\naddress: {res['address']}") + + nome = input("\nNome file per salvare (senza estensione): ").strip() or "wallet_p2sh_multisig" + if not nome.endswith(".json"): nome += ".json" + with open(nome, "w") as f: + json.dump(res, f, indent=4) + print(f"Salvato in {nome}") + except Exception as e: + print("Errore:", e) + +if __name__ == "__main__": + main() diff --git a/p2tr.py b/p2tr.py new file mode 100644 index 0000000..bf61aa0 --- /dev/null +++ b/p2tr.py @@ -0,0 +1,126 @@ +import secrets, hashlib, json, base58 +from typing import Dict, Optional +from ecdsa import SECP256k1, SigningKey +from ecdsa.ellipticcurve import Point + +NETWORK_CONFIG = { + 'mainnet': {'hrp': 'bc', 'wif_prefix': b'\x80'}, + 'testnet': {'hrp': 'tb', 'wif_prefix': b'\xEF'}, + 'regtest': {'hrp': 'bcrt', 'wif_prefix': b'\xEF'}, +} + +_BECH32_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + +def _bech32_polymod(values): + """Calcola il polymod per bech32/bech32m""" + GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] + chk = 1 + for v in values: + b = (chk >> 25) & 0xff + chk = ((chk & 0x1ffffff) << 5) ^ v + for i in range(5): + chk ^= GEN[i] if ((b >> i) & 1) else 0 + return chk + +def _bech32_hrp_expand(hrp): + """Espande l'HRP per il calcolo bech32""" + return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] + +def _bech32_create_checksum(hrp, data, spec="bech32m"): + """Crea il checksum per bech32/bech32m""" + const = 0x2bc830a3 if spec == "bech32m" else 1 + values = _bech32_hrp_expand(hrp) + data + polymod = _bech32_polymod(values + [0,0,0,0,0,0]) ^ const + return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] + +def bech32m_encode(hrp: str, data: list) -> str: + """Codifica dati in formato bech32m""" + combined = data + _bech32_create_checksum(hrp, data, "bech32m") + return hrp + "1" + "".join([_BECH32_CHARSET[d] for d in combined]) + +def convertbits(data: bytes, frombits: int, tobits: int, pad: bool = True) -> Optional[list]: + """Converte bit tra diverse basi""" + acc = 0; bits = 0; ret = [] + maxv = (1 << tobits) - 1 + max_acc = (1 << (frombits + tobits - 1)) - 1 + for b in data: + if b < 0 or b >> frombits: return None + acc = ((acc << frombits) | b) & max_acc + bits += frombits + while bits >= tobits: + bits -= tobits + ret.append((acc >> bits) & maxv) + if pad: + if bits: ret.append((acc << (tobits - bits)) & maxv) + elif bits >= frombits or ((acc << (tobits - bits)) & maxv): return None + return ret + +def tagged_hash(tag: str, msg: bytes) -> bytes: + """Calcola tagged hash BIP340""" + tagh = hashlib.sha256(tag.encode()).digest() + return hashlib.sha256(tagh + tagh + msg).digest() + +curve = SECP256k1 +G: Point = curve.generator +n = curve.order + +def point_from_sk(sk_bytes: bytes) -> Point: + """Genera punto pubblico da chiave privata""" + sk = int.from_bytes(sk_bytes, 'big') + if not (1 <= sk < n): raise ValueError("Chiave privata fuori range") + return SigningKey.from_string(sk_bytes, curve=SECP256k1).verifying_key.pubkey.point + +def xonly_bytes(P: Point) -> bytes: + """Estrae coordinata x da punto (32 byte)""" + return int(P.x()).to_bytes(32, 'big') + +def pubkey_tweak(P: Point, merkle_root: Optional[bytes] = None): + """Applica tweak Taproot al punto pubblico""" + mr = b"" if merkle_root is None else merkle_root + t = int.from_bytes(tagged_hash("TapTweak", xonly_bytes(P) + mr), 'big') % n + if t == 0: raise ValueError("Tweak nullo, rigenera la chiave") + return P + t*G, t + +def to_wif(privkey: bytes, wif_prefix: bytes) -> str: + """Converte chiave privata in formato WIF""" + extended = wif_prefix + privkey + b'\x01' + checksum = hashlib.sha256(hashlib.sha256(extended).digest()).digest()[:4] + return base58.b58encode(extended + checksum).decode() + +def generate_p2tr_address(network: str = 'mainnet') -> Dict[str, str]: + """Genera indirizzo P2TR completo""" + cfg = NETWORK_CONFIG.get(network) + if not cfg: raise ValueError("Network non supportato (mainnet, testnet, regtest).") + + sk = secrets.token_bytes(32) + P = point_from_sk(sk) + Q, t = pubkey_tweak(P, merkle_root=None) + prog = xonly_bytes(Q) + data = [1] + convertbits(prog, 8, 5, True) + address = bech32m_encode(cfg['hrp'], data) + wif = to_wif(sk, cfg['wif_prefix']) + + return { + "network": network, + "script_type": "p2tr", + "private_key_hex": sk.hex(), + "private_key_wif": wif, + "internal_pubkey_x_hex": xonly_bytes(P).hex(), + "address": address + } + +def main(): + """Funzione principale interattiva""" + net = input("Seleziona rete (mainnet/testnet/regtest): ").strip().lower() + try: + res = generate_p2tr_address(net) + print("\n--- Risultati P2TR ---") + for k, v in res.items(): print(f"{k}: {v}") + nome = input("\nNome file per salvare (senza estensione): ").strip() or "wallet_p2tr" + if not nome.endswith(".json"): nome += ".json" + with open(nome, "w") as f: json.dump(res, f, indent=4) + print(f"Salvato in {nome}") + except Exception as e: print("Errore:", e) + +if __name__ == "__main__": + main() diff --git a/p2wpkh.py b/p2wpkh.py new file mode 100644 index 0000000..98ff2ef --- /dev/null +++ b/p2wpkh.py @@ -0,0 +1,97 @@ +import secrets +import hashlib +import json +import ecdsa +import base58 +from bech32 import bech32_encode, convertbits +from typing import Dict + +NETWORK_CONFIG = { + 'mainnet': {'hrp': 'bc', 'wif_prefix': b'\x80'}, + 'testnet': {'hrp': 'tb', 'wif_prefix': b'\xEF'}, + 'regtest': {'hrp': 'bcrt', 'wif_prefix': b'\xEF'}, +} + +def generate_segwit_address(network: str = 'mainnet', compressed: bool = True) -> Dict[str, str]: + """Genera chiave privata, pubblica, WIF e indirizzo SegWit bech32.""" + config = NETWORK_CONFIG.get(network) + if config is None: + raise ValueError("Network non supportato. Scegli tra 'mainnet', 'testnet' o 'regtest'.") + + private_key = secrets.token_bytes(32) + private_key_hex = private_key.hex() + + sk = ecdsa.SigningKey.from_string(private_key, curve=ecdsa.SECP256k1) + vk = sk.get_verifying_key() + pubkey_bytes = vk.to_string() + + if compressed: + x = pubkey_bytes[:32] + y = pubkey_bytes[32:] + prefix = b'\x02' if int.from_bytes(y, 'big') % 2 == 0 else b'\x03' + pubkey = prefix + x + else: + pubkey = b'\x04' + pubkey_bytes + + pubkey_hex = pubkey.hex() + + sha256_pubkey = hashlib.sha256(pubkey).digest() + ripemd160 = hashlib.new('ripemd160', sha256_pubkey).digest() + + converted = convertbits(list(ripemd160), 8, 5) + if converted is None: + raise ValueError("Errore nella conversione dei bit per la codifica Bech32") + data = [0] + converted + address = bech32_encode(config['hrp'], data) + + if compressed: + extended_key = config['wif_prefix'] + private_key + b'\x01' + else: + extended_key = config['wif_prefix'] + private_key + + checksum = hashlib.sha256(hashlib.sha256(extended_key).digest()).digest()[:4] + private_key_wif = base58.b58encode(extended_key + checksum).decode() + + return { + 'network': network, + 'script_type': 'p2wpkh', + 'private_key_hex': private_key_hex, + 'private_key_wif': private_key_wif, + 'public_key_hex': pubkey_hex, + 'address': address + } + +def main(): + """Funzione principale che gestisce l'interazione con l'utente e il salvataggio dei dati.""" + network = input("Seleziona il tipo di rete (mainnet, testnet, regtest): ").strip().lower() + compressed_input = input("Utilizzare chiavi compresse? (s/n): ").strip().lower() + compressed = compressed_input != 'n' + + try: + result = generate_segwit_address(network, compressed) + + print("\n--- Risultati ---") + print(f"Network: {result['network']}") + print(f"Script type: {result['script_type']}") + print("Chiave privata (hex):", result['private_key_hex']) + print("Chiave privata (WIF):", result['private_key_wif']) + key_type = "compressa" if compressed else "non compressa" + print(f"Chiave pubblica ({key_type}, hex):", result['public_key_hex']) + print("Indirizzo:", result['address']) + + nome_file = input("\nInserisci il nome del file (senza estensione) per salvare i dati: ").strip() + if not nome_file: + nome_file = "wallet" + print("Nome del file non valido. Verrà utilizzato il nome di default: wallet.json") + if not nome_file.endswith('.json'): + nome_file += '.json' + + with open(nome_file, 'w') as f: + json.dump(result, f, indent=4) + print(f"Dati salvati correttamente nel file: {nome_file}") + + except Exception as e: + print("Errore:", e) + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3009014 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +base58==2.1.1 +bech32==1.2.0 +ecdsa==0.19.0 +six==1.17.0