montana/Русский/Бот/node_crypto.py

474 lines
18 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# node_crypto.py
# Криптографическая система для узлов Montana
# POST-QUANTUM КРИПТОГРАФИЯ ML-DSA-65 (FIPS 204)
# Защита от квантовых компьютеров с genesis
import hashlib
import json
from pathlib import Path
from typing import Optional, Dict, Tuple
from datetime import datetime, timezone
# POST-QUANTUM: ML-DSA-65 (Dilithium)
from dilithium_py.ml_dsa import ML_DSA_65
# ═══════════════════════════════════════════════════════════════════════════════
# ML-DSA-65 КРИПТОГРАФИЧЕСКИЕ ФУНКЦИИ
# ═══════════════════════════════════════════════════════════════════════════════
def generate_keypair() -> Tuple[str, str]:
"""
Генерирует пару ключей ML-DSA-65 для узла
POST-QUANTUM защита от Shor's algorithm
FIPS 204 стандарт
Returns:
(private_key_hex, public_key_hex)
Размеры ключей ML-DSA-65:
Private key: 4032 байта
Public key: 1952 байта
Signature: 3309 байта
"""
public_key, private_key = ML_DSA_65.keygen()
return private_key.hex(), public_key.hex()
def public_key_to_address(public_key_hex: str) -> str:
"""
Преобразует public key в адрес кошелька Montana
Формат: mt + SHA256(pubkey)[:18].hex() + checksum = 42 chars
Checksum: SHA256(SHA256("mt" + payload))[:2].hex()
POST-QUANTUM: Адрес деривируется от ML-DSA-65 public key
"""
public_bytes = bytes.fromhex(public_key_hex)
hash_bytes = hashlib.sha256(public_bytes).digest()
# Первые 18 байт (36 hex) — payload
payload = hash_bytes[:18].hex()
# Контрольная сумма: SHA256(SHA256("mt" + payload))[:2]
checksum_input = ("mt" + payload).encode('utf-8')
checksum = hashlib.sha256(hashlib.sha256(checksum_input).digest()).digest()[:2].hex()
return "mt" + payload + checksum
def validate_address(address: str) -> bool:
"""Validate Montana address format + checksum"""
if len(address) != 42 or not address.startswith("mt"):
return False
body = address[2:]
if not all(c in '0123456789abcdef' for c in body):
return False
payload = body[:36]
checksum = body[36:]
checksum_input = ("mt" + payload).encode('utf-8')
expected = hashlib.sha256(hashlib.sha256(checksum_input).digest()).digest()[:2].hex()
return checksum == expected
def sign_message(private_key_hex: str, message: str) -> str:
"""
Подписывает сообщение приватным ключом ML-DSA-65
POST-QUANTUM подпись, устойчивая к атакам квантовых компьютеров
Returns:
signature_hex (3309 байт = 6618 hex символов)
"""
private_bytes = bytes.fromhex(private_key_hex)
message_bytes = message.encode('utf-8')
signature = ML_DSA_65.sign(private_bytes, message_bytes)
return signature.hex()
def verify_signature(public_key_hex: str, message: str, signature_hex: str) -> bool:
"""
Проверяет подпись сообщения ML-DSA-65
Returns:
True если подпись валидна
"""
try:
public_bytes = bytes.fromhex(public_key_hex)
message_bytes = message.encode('utf-8')
signature = bytes.fromhex(signature_hex)
# ML_DSA_65.verify возвращает True/False или выбрасывает исключение
return ML_DSA_65.verify(public_bytes, message_bytes, signature)
except Exception:
return False
# ═══════════════════════════════════════════════════════════════════════════════
# КРИПТОГРАФИЧЕСКАЯ СИСТЕМА УЗЛОВ
# ═══════════════════════════════════════════════════════════════════════════════
class NodeCryptoSystem:
"""
Криптографическая система для узлов Montana
POST-QUANTUM ЗАЩИТА (ML-DSA-65, FIPS 204):
- Устойчивость к Shor's algorithm
- NIST Level 3 security (128-bit post-quantum)
- Защита от "harvest now, decrypt later" атак
Защита от классических атак:
- IP hijacking
- DNS spoofing
- Man-in-the-middle атак
Концепция:
- Адрес кошелька = hash(public_key)
- Доступ = подпись private key (ML-DSA-65)
- IP адрес = только для networking
- Владелец = Montana address оператора
"""
# Версия криптосистемы
CRYPTO_VERSION = "ML-DSA-65"
FIPS_STANDARD = "FIPS 204"
SECURITY_LEVEL = "NIST Level 3 (128-bit post-quantum)"
def __init__(self, data_dir: Path):
self.data_dir = data_dir / "node_crypto"
self.data_dir.mkdir(parents=True, exist_ok=True)
self.nodes_file = self.data_dir / "nodes.json"
self.keys_file = self.data_dir / "keys.json" # Приватные ключи (зашифрованы)
def _load_nodes(self) -> dict:
"""Загрузить зарегистрированные узлы"""
if self.nodes_file.exists():
with open(self.nodes_file, 'r', encoding='utf-8') as f:
return json.load(f)
return {}
def _save_nodes(self, nodes: dict):
"""Сохранить узлы"""
with open(self.nodes_file, 'w', encoding='utf-8') as f:
json.dump(nodes, f, indent=2, ensure_ascii=False)
def register_node(
self,
owner_address: int,
node_name: str,
location: str,
ip_address: str,
node_type: str = "light"
) -> Dict:
"""
Регистрирует новый узел с криптографическим адресом ML-DSA-65
POST-QUANTUM: Ключи генерируются по стандарту FIPS 204
Args:
owner_address: Montana address владельца узла
node_name: Короткое имя (например "amsterdam")
location: Локация с флагом (например "🇳🇱 Amsterdam")
ip_address: IP адрес узла (только для networking)
node_type: Тип узла (full, light, client)
Returns:
{
"address": "mt1a2b3c...",
"public_key": "...", # 1952 байта
"private_key": "...", # 4032 байта - СОХРАНИ В БЕЗОПАСНОМ МЕСТЕ!
"owner": address,
"node_name": "amsterdam",
"alias": "amsterdam.efir.org",
"ip": "72.56.102.240",
"crypto_version": "ML-DSA-65"
}
"""
# Генерируем пару ключей ML-DSA-65
private_key, public_key = generate_keypair()
# Получаем адрес из public key
address = public_key_to_address(public_key)
# Создаем alias
alias = f"{node_name}.efir.org"
nodes = self._load_nodes()
# Проверка дубликатов
if address in nodes:
return {"error": "Node already exists", "address": address}
# Проверка дубликатов по IP
for existing in nodes.values():
if existing.get("ip") == ip_address:
return {"error": "IP already registered", "ip": ip_address}
# Регистрация узла
node_data = {
"address": address,
"public_key": public_key,
"owner": owner_address,
"node_name": node_name,
"alias": alias,
"location": location,
"ip": ip_address,
"type": node_type,
"created_at": datetime.now(timezone.utc).isoformat(),
"official": False,
"priority": len(nodes) + 10,
"crypto_version": self.CRYPTO_VERSION,
"fips_standard": self.FIPS_STANDARD
}
nodes[address] = node_data
self._save_nodes(nodes)
return {
"success": True,
"address": address,
"public_key": public_key,
"private_key": private_key, # ⚠️ СОХРАНИ В БЕЗОПАСНОМ МЕСТЕ!
"alias": alias,
"owner": owner_address,
"node_data": node_data,
"crypto_version": self.CRYPTO_VERSION,
"key_sizes": {
"private_key_bytes": len(bytes.fromhex(private_key)),
"public_key_bytes": len(bytes.fromhex(public_key))
}
}
def import_official_nodes(self) -> Dict:
"""
Импортирует 5 официальных узлов Montana с ML-DSA-65
Для каждого генерируются новые POST-QUANTUM ключи.
Private keys нужно сохранить у владельцев узлов.
"""
official_nodes = [
{
"name": "amsterdam",
"location": "🇳🇱 Amsterdam",
"ip": "72.56.102.240",
"priority": 1,
"owner": 8552053404 # BOT_CREATOR_ID
},
{
"name": "moscow",
"location": "🇷🇺 Moscow",
"ip": "176.124.208.93",
"priority": 2,
"owner": 8552053404
},
{
"name": "almaty",
"location": "🇰🇿 Almaty",
"ip": "91.200.148.93",
"priority": 3,
"owner": 8552053404
},
{
"name": "spb",
"location": "🇷🇺 St.Petersburg",
"ip": "188.225.58.98",
"priority": 4,
"owner": 8552053404
},
{
"name": "novosibirsk",
"location": "🇷🇺 Novosibirsk",
"ip": "147.45.147.247",
"priority": 5,
"owner": 8552053404
}
]
results = {}
nodes = self._load_nodes()
for node_info in official_nodes:
# Проверяем, не зарегистрирован ли уже
existing = None
for addr, data in nodes.items():
if data.get("node_name") == node_info["name"]:
existing = addr
break
if existing:
results[node_info["name"]] = {
"status": "already_exists",
"address": existing
}
continue
# Генерируем ML-DSA-65 ключи
private_key, public_key = generate_keypair()
address = public_key_to_address(public_key)
alias = f"{node_info['name']}.efir.org"
# Регистрируем
node_data = {
"address": address,
"public_key": public_key,
"owner": node_info["owner"],
"node_name": node_info["name"],
"alias": alias,
"location": node_info["location"],
"ip": node_info["ip"],
"type": "full",
"created_at": datetime.now(timezone.utc).isoformat(),
"official": True,
"priority": node_info["priority"],
"crypto_version": self.CRYPTO_VERSION,
"fips_standard": self.FIPS_STANDARD
}
nodes[address] = node_data
results[node_info["name"]] = {
"status": "registered",
"address": address,
"private_key": private_key, # ⚠️ СОХРАНИ!
"alias": alias,
"ip": node_info["ip"],
"crypto_version": self.CRYPTO_VERSION
}
self._save_nodes(nodes)
return results
def get_node_by_address(self, address: str) -> Optional[Dict]:
"""Получить узел по адресу"""
nodes = self._load_nodes()
return nodes.get(address)
def get_node_by_alias(self, alias: str) -> Optional[Dict]:
"""Получить узел по alias"""
nodes = self._load_nodes()
for node in nodes.values():
if node.get("alias") == alias:
return node
return None
def get_node_by_ip(self, ip: str) -> Optional[Dict]:
"""Получить узел по IP (для networking)"""
nodes = self._load_nodes()
for node in nodes.values():
if node.get("ip") == ip:
return node
return None
def get_all_nodes(self) -> list:
"""Получить все узлы"""
nodes = self._load_nodes()
return list(nodes.values())
def verify_node_ownership(
self,
address: str,
message: str,
signature_hex: str
) -> bool:
"""
Проверяет что владелец узла подписал сообщение ML-DSA-65
POST-QUANTUM верификация:
- Устойчива к атакам квантовых компьютеров
- FIPS 204 compliant
Используется для:
- Авторизации переводов с кошелька узла
- Обновления конфигурации узла
- Подтверждения владения
"""
node = self.get_node_by_address(address)
if not node:
return False
public_key = node["public_key"]
return verify_signature(public_key, message, signature_hex)
def get_node_display(self, address: str) -> str:
"""
Отображение узла для бота
"""
node = self.get_node_by_address(address)
if not node:
return "Узел не найден"
crypto_version = node.get('crypto_version', 'ML-DSA-65')
display = f"Ɉ\n\n"
display += f"**Узел Montana:** {node['location']}\n\n"
display += f"**Адрес кошелька:** `{address}`\n"
display += f"**Alias:** `{node['alias']}`\n"
display += f"_(криптографический адрес — защищен {crypto_version})_\n\n"
display += f"**IP:** `{node['ip']}` _(только для networking)_\n"
display += f"**Владелец TG ID:** `{node['owner']}`\n"
display += f"**Тип:** {node['type'].upper()} NODE\n"
display += f"**Приоритет:** #{node['priority']}\n\n"
if node.get('official'):
display += f"⭐️ **Официальный узел Montana Foundation**\n\n"
display += f"🔐 **POST-QUANTUM БЕЗОПАСНОСТЬ:**\n"
display += f" • Криптография: **{crypto_version}**\n"
display += f" • Стандарт: **FIPS 204**\n"
display += f" • Уровень: **NIST Level 3**\n"
display += f" • Public key: `{node['public_key'][:32]}...`\n"
display += f" • Защита от квантовых компьютеров: ✅\n"
display += f" • Защита от IP hijacking: ✅\n\n"
display += f"⚠️ Для доступа к кошельку нужна подпись private key ML-DSA-65"
return display
# ═══════════════════════════════════════════════════════════════════════════════
# ГЛОБАЛЬНЫЙ ИНСТАНС
# ═══════════════════════════════════════════════════════════════════════════════
_node_crypto_system = None
def get_node_crypto_system(data_dir: Path = None) -> NodeCryptoSystem:
"""Получить глобальную криптосистему узлов (ML-DSA-65)"""
global _node_crypto_system
if _node_crypto_system is None:
if data_dir is None:
data_dir = Path(__file__).parent / "data"
_node_crypto_system = NodeCryptoSystem(data_dir)
return _node_crypto_system
# ═══════════════════════════════════════════════════════════════════════════════
# ИНФОРМАЦИЯ О КРИПТОСИСТЕМЕ
# ═══════════════════════════════════════════════════════════════════════════════
def get_crypto_info() -> Dict:
"""
Информация о криптографической системе Montana
"""
return {
"algorithm": "ML-DSA-65",
"standard": "FIPS 204",
"security_level": "NIST Level 3 (128-bit post-quantum)",
"key_sizes": {
"private_key": "4032 bytes",
"public_key": "1952 bytes",
"signature": "3309 bytes"
},
"protections": [
"Quantum computer attacks (Shor's algorithm)",
"Harvest now, decrypt later attacks",
"IP hijacking",
"DNS spoofing",
"Man-in-the-middle attacks"
],
"address_format": "mt + SHA256(public_key)[:20].hex()",
"address_length": 42
}