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

474 lines
18 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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
}