381 lines
14 KiB
Python
381 lines
14 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
cognitive_signature.py — Когнитивные подписи Montana
|
|||
|
|
|
|||
|
|
Montana Protocol
|
|||
|
|
Подписание мыслей и сообщений с domain separation
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import hashlib
|
|||
|
|
import hmac
|
|||
|
|
import secrets
|
|||
|
|
from typing import Optional, Dict, Tuple
|
|||
|
|
from datetime import datetime, timezone
|
|||
|
|
from enum import Enum
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
# DOMAIN SEPARATION
|
|||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
class DomainType(Enum):
|
|||
|
|
"""
|
|||
|
|
Типы доменов для domain separation
|
|||
|
|
|
|||
|
|
Domain separation гарантирует что подпись для одного контекста
|
|||
|
|
не может быть переиспользована в другом
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
THOUGHT = "montana.thought" # Сырая мысль
|
|||
|
|
MESSAGE = "montana.message" # Обычное сообщение
|
|||
|
|
TRANSACTION = "montana.transaction" # Транзакция
|
|||
|
|
PRESENCE = "montana.presence" # Proof of Presence
|
|||
|
|
NODE_AUTH = "montana.node.auth" # Аутентификация узла
|
|||
|
|
SYNC = "montana.sync" # Синхронизация данных
|
|||
|
|
|
|||
|
|
|
|||
|
|
def get_domain_prefix(domain: DomainType) -> bytes:
|
|||
|
|
"""
|
|||
|
|
Получить префикс домена для domain separation
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
domain: Тип домена
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Префикс в байтах
|
|||
|
|
|
|||
|
|
Example:
|
|||
|
|
>>> get_domain_prefix(DomainType.THOUGHT)
|
|||
|
|
b'montana.thought'
|
|||
|
|
"""
|
|||
|
|
return domain.value.encode('utf-8')
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
# COGNITIVE SIGNATURE
|
|||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
class CognitiveSignature:
|
|||
|
|
"""
|
|||
|
|
Когнитивная подпись Montana
|
|||
|
|
|
|||
|
|
Использует HMAC-SHA256 с domain separation для подписания
|
|||
|
|
мыслей, сообщений и других данных
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(self, secret_key: Optional[bytes] = None):
|
|||
|
|
"""
|
|||
|
|
Args:
|
|||
|
|
secret_key: Секретный ключ (32 байта).
|
|||
|
|
Если не указан, генерируется случайно.
|
|||
|
|
"""
|
|||
|
|
if secret_key is None:
|
|||
|
|
self.secret_key = secrets.token_bytes(32)
|
|||
|
|
else:
|
|||
|
|
if len(secret_key) != 32:
|
|||
|
|
raise ValueError("Secret key must be exactly 32 bytes")
|
|||
|
|
self.secret_key = secret_key
|
|||
|
|
|
|||
|
|
def sign(
|
|||
|
|
self,
|
|||
|
|
message: str,
|
|||
|
|
domain: DomainType,
|
|||
|
|
timestamp: Optional[str] = None,
|
|||
|
|
metadata: Optional[Dict] = None
|
|||
|
|
) -> Dict:
|
|||
|
|
"""
|
|||
|
|
Подписать сообщение
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
message: Текст сообщения
|
|||
|
|
domain: Домен для domain separation
|
|||
|
|
timestamp: Timestamp в ISO format (UTC)
|
|||
|
|
metadata: Дополнительные метаданные
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
{
|
|||
|
|
"message": "текст",
|
|||
|
|
"domain": "montana.thought",
|
|||
|
|
"timestamp": "2026-01-20T12:00:00+00:00",
|
|||
|
|
"signature": "hex...",
|
|||
|
|
"metadata": {}
|
|||
|
|
}
|
|||
|
|
"""
|
|||
|
|
if timestamp is None:
|
|||
|
|
timestamp = datetime.now(timezone.utc).isoformat()
|
|||
|
|
|
|||
|
|
# Собираем данные для подписи
|
|||
|
|
domain_prefix = get_domain_prefix(domain)
|
|||
|
|
message_bytes = message.encode('utf-8')
|
|||
|
|
timestamp_bytes = timestamp.encode('utf-8')
|
|||
|
|
|
|||
|
|
# Формат: domain || timestamp || message
|
|||
|
|
data_to_sign = domain_prefix + b'||' + timestamp_bytes + b'||' + message_bytes
|
|||
|
|
|
|||
|
|
# Подписываем с HMAC-SHA256
|
|||
|
|
signature = hmac.new(
|
|||
|
|
self.secret_key,
|
|||
|
|
data_to_sign,
|
|||
|
|
hashlib.sha256
|
|||
|
|
).hexdigest()
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"message": message,
|
|||
|
|
"domain": domain.value,
|
|||
|
|
"timestamp": timestamp,
|
|||
|
|
"signature": signature,
|
|||
|
|
"metadata": metadata or {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def verify(
|
|||
|
|
self,
|
|||
|
|
signed_data: Dict,
|
|||
|
|
domain: Optional[DomainType] = None
|
|||
|
|
) -> bool:
|
|||
|
|
"""
|
|||
|
|
Верифицировать подпись
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
signed_data: Подписанные данные (результат sign())
|
|||
|
|
domain: Ожидаемый домен (опционально)
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
True если подпись валидна
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
message = signed_data["message"]
|
|||
|
|
domain_str = signed_data["domain"]
|
|||
|
|
timestamp = signed_data["timestamp"]
|
|||
|
|
signature = signed_data["signature"]
|
|||
|
|
|
|||
|
|
# Проверяем домен если указан
|
|||
|
|
if domain:
|
|||
|
|
if domain_str != domain.value:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# Восстанавливаем domain enum
|
|||
|
|
domain_enum = DomainType(domain_str)
|
|||
|
|
|
|||
|
|
# Пересобираем данные
|
|||
|
|
domain_prefix = get_domain_prefix(domain_enum)
|
|||
|
|
message_bytes = message.encode('utf-8')
|
|||
|
|
timestamp_bytes = timestamp.encode('utf-8')
|
|||
|
|
|
|||
|
|
data_to_sign = domain_prefix + b'||' + timestamp_bytes + b'||' + message_bytes
|
|||
|
|
|
|||
|
|
# Вычисляем ожидаемую подпись
|
|||
|
|
expected_signature = hmac.new(
|
|||
|
|
self.secret_key,
|
|||
|
|
data_to_sign,
|
|||
|
|
hashlib.sha256
|
|||
|
|
).hexdigest()
|
|||
|
|
|
|||
|
|
# Сравниваем
|
|||
|
|
return hmac.compare_digest(signature, expected_signature)
|
|||
|
|
|
|||
|
|
except (KeyError, ValueError):
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def sign_thought(self, thought: str, user_id: Optional[int] = None) -> Dict:
|
|||
|
|
"""
|
|||
|
|
Подписать сырую мысль
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
thought: Текст мысли
|
|||
|
|
user_id: ID пользователя
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Подписанная мысль
|
|||
|
|
"""
|
|||
|
|
metadata = {}
|
|||
|
|
if user_id:
|
|||
|
|
metadata["user_id"] = user_id
|
|||
|
|
|
|||
|
|
return self.sign(
|
|||
|
|
message=thought,
|
|||
|
|
domain=DomainType.THOUGHT,
|
|||
|
|
metadata=metadata
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def sign_message(self, message: str, sender_id: Optional[int] = None) -> Dict:
|
|||
|
|
"""
|
|||
|
|
Подписать обычное сообщение
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
message: Текст сообщения
|
|||
|
|
sender_id: ID отправителя
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Подписанное сообщение
|
|||
|
|
"""
|
|||
|
|
metadata = {}
|
|||
|
|
if sender_id:
|
|||
|
|
metadata["sender_id"] = sender_id
|
|||
|
|
|
|||
|
|
return self.sign(
|
|||
|
|
message=message,
|
|||
|
|
domain=DomainType.MESSAGE,
|
|||
|
|
metadata=metadata
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def sign_transaction(
|
|||
|
|
self,
|
|||
|
|
transaction_data: str,
|
|||
|
|
from_address: Optional[str] = None,
|
|||
|
|
to_address: Optional[str] = None,
|
|||
|
|
amount: Optional[float] = None
|
|||
|
|
) -> Dict:
|
|||
|
|
"""
|
|||
|
|
Подписать транзакцию
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
transaction_data: Данные транзакции
|
|||
|
|
from_address: Адрес отправителя
|
|||
|
|
to_address: Адрес получателя
|
|||
|
|
amount: Сумма
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Подписанная транзакция
|
|||
|
|
"""
|
|||
|
|
metadata = {}
|
|||
|
|
if from_address:
|
|||
|
|
metadata["from"] = from_address
|
|||
|
|
if to_address:
|
|||
|
|
metadata["to"] = to_address
|
|||
|
|
if amount:
|
|||
|
|
metadata["amount"] = amount
|
|||
|
|
|
|||
|
|
return self.sign(
|
|||
|
|
message=transaction_data,
|
|||
|
|
domain=DomainType.TRANSACTION,
|
|||
|
|
metadata=metadata
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def sign_presence(self, presence_data: str, user_id: Optional[int] = None) -> Dict:
|
|||
|
|
"""
|
|||
|
|
Подписать proof of presence
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
presence_data: Данные присутствия
|
|||
|
|
user_id: ID пользователя
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Подписанное proof of presence
|
|||
|
|
"""
|
|||
|
|
metadata = {}
|
|||
|
|
if user_id:
|
|||
|
|
metadata["user_id"] = user_id
|
|||
|
|
|
|||
|
|
return self.sign(
|
|||
|
|
message=presence_data,
|
|||
|
|
domain=DomainType.PRESENCE,
|
|||
|
|
metadata=metadata
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
# KEY MANAGEMENT
|
|||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
def generate_keypair() -> Tuple[bytes, bytes]:
|
|||
|
|
"""
|
|||
|
|
Сгенерировать пару ключей (secret, public)
|
|||
|
|
|
|||
|
|
В текущей реализации используется симметричный ключ (HMAC),
|
|||
|
|
поэтому "публичный" ключ — это хеш секретного ключа
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
(secret_key, public_key_hash)
|
|||
|
|
"""
|
|||
|
|
secret_key = secrets.token_bytes(32)
|
|||
|
|
|
|||
|
|
# "Публичный" ключ — хеш секретного
|
|||
|
|
public_key_hash = hashlib.sha256(secret_key).digest()
|
|||
|
|
|
|||
|
|
return (secret_key, public_key_hash)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def derive_key_from_passphrase(passphrase: str, salt: Optional[bytes] = None) -> bytes:
|
|||
|
|
"""
|
|||
|
|
Получить ключ из парольной фразы
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
passphrase: Парольная фраза
|
|||
|
|
salt: Соль (16 байт, если не указана — генерируется)
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
32-байтовый ключ
|
|||
|
|
"""
|
|||
|
|
if salt is None:
|
|||
|
|
salt = secrets.token_bytes(16)
|
|||
|
|
|
|||
|
|
# PBKDF2 с SHA256
|
|||
|
|
key = hashlib.pbkdf2_hmac(
|
|||
|
|
'sha256',
|
|||
|
|
passphrase.encode('utf-8'),
|
|||
|
|
salt,
|
|||
|
|
100_000 # итераций
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return key
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
# EXPORTS
|
|||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
__all__ = [
|
|||
|
|
'DomainType',
|
|||
|
|
'get_domain_prefix',
|
|||
|
|
'CognitiveSignature',
|
|||
|
|
'generate_keypair',
|
|||
|
|
'derive_key_from_passphrase'
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
# CLI
|
|||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
print("🧠 Montana Cognitive Signature")
|
|||
|
|
print("=" * 60)
|
|||
|
|
|
|||
|
|
# Генерируем ключ
|
|||
|
|
print("\n🔑 Генерация ключей:")
|
|||
|
|
secret_key, public_key_hash = generate_keypair()
|
|||
|
|
print(f" Secret key (hex): {secret_key.hex()[:32]}...")
|
|||
|
|
print(f" Public key hash (hex): {public_key_hash.hex()[:32]}...")
|
|||
|
|
|
|||
|
|
# Создаём signer
|
|||
|
|
signer = CognitiveSignature(secret_key)
|
|||
|
|
|
|||
|
|
# Подписываем мысль
|
|||
|
|
print("\n💭 Подписание мысли:")
|
|||
|
|
thought = "Маска тяжелее лица"
|
|||
|
|
signed_thought = signer.sign_thought(thought, user_id=123)
|
|||
|
|
print(f" Мысль: {signed_thought['message']}")
|
|||
|
|
print(f" Домен: {signed_thought['domain']}")
|
|||
|
|
print(f" Timestamp: {signed_thought['timestamp']}")
|
|||
|
|
print(f" Подпись: {signed_thought['signature'][:32]}...")
|
|||
|
|
|
|||
|
|
# Верификация
|
|||
|
|
print("\n✅ Верификация:")
|
|||
|
|
is_valid = signer.verify(signed_thought, domain=DomainType.THOUGHT)
|
|||
|
|
print(f" Подпись валидна: {is_valid}")
|
|||
|
|
|
|||
|
|
# Подделка подписи
|
|||
|
|
print("\n❌ Попытка подделки:")
|
|||
|
|
forged = signed_thought.copy()
|
|||
|
|
forged["message"] = "Поддельная мысль"
|
|||
|
|
is_valid_forged = signer.verify(forged, domain=DomainType.THOUGHT)
|
|||
|
|
print(f" Поддельная подпись валидна: {is_valid_forged}")
|
|||
|
|
|
|||
|
|
# Domain separation
|
|||
|
|
print("\n🔒 Domain separation:")
|
|||
|
|
print(" Пытаемся верифицировать мысль как сообщение...")
|
|||
|
|
is_valid_wrong_domain = signer.verify(signed_thought, domain=DomainType.MESSAGE)
|
|||
|
|
print(f" Подпись валидна в другом домене: {is_valid_wrong_domain}")
|
|||
|
|
|
|||
|
|
print("\n🎯 Когнитивные подписи готовы!")
|