Commit iniziale

This commit is contained in:
2026-01-30 09:32:05 +01:00
commit cc34cf23df
10 changed files with 779 additions and 0 deletions

10
.gitignore vendored Normal file
View File

@@ -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

21
LICENSE Normal file
View File

@@ -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.

142
README.md Normal file
View File

@@ -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**: `<pubkey> 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 <pubkey_hash> 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 <script_hash> 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 <pubkey_hash>` (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 <taproot_output>` (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

34
main.py Normal file
View File

@@ -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()

97
p2pk.py Normal file
View File

@@ -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()

96
p2pkh.py Normal file
View File

@@ -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()

152
p2sh.py Normal file
View File

@@ -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 <pub1> ... <pubN> 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> <h160> 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()

126
p2tr.py Normal file
View File

@@ -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()

97
p2wpkh.py Normal file
View File

@@ -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()

4
requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
base58==2.1.1
bech32==1.2.0
ecdsa==0.19.0
six==1.17.0