474 lines
18 KiB
Python
474 lines
18 KiB
Python
|
|
#!/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
|
|||
|
|
}
|