feat: prima implementazione del generatore di indirizzi Bitcoin
This commit is contained in:
142
README.md
Normal file
142
README.md
Normal 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
34
main.py
Normal 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
97
p2pk.py
Normal 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
96
p2pkh.py
Normal 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
152
p2sh.py
Normal 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
126
p2tr.py
Normal 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
97
p2wpkh.py
Normal 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
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
base58==2.1.1
|
||||
bech32==1.2.0
|
||||
ecdsa==0.19.0
|
||||
six==1.17.0
|
||||
Reference in New Issue
Block a user