commit cc34cf23dfccbecf4f134d4fdc3cd20833d2bcf7 Author: davide3011 Date: Fri Jan 30 09:32:05 2026 +0100 Commit iniziale 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