Files
addressgen/p2tr.py
2026-01-30 09:32:05 +01:00

127 lines
4.6 KiB
Python

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