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🎯 Когнитивные подписи готовы!")
|