montana/Русский/Тесты/test_cognitive_signature.py

525 lines
23 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
test_cognitive_signature.py Unit tests для cognitive_signature.py
Montana Protocol
Тестирование когнитивных подписей с domain separation
"""
import sys
import os
import unittest
import secrets
import hashlib
from datetime import datetime, timezone
# Добавляем путь к модулю
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../гиппокамп'))
from cognitive_signature import (
DomainType,
get_domain_prefix,
CognitiveSignature,
generate_keypair,
derive_key_from_passphrase
)
class TestDomainType(unittest.TestCase):
"""Тесты типов доменов"""
# ═══════════════════════════════════════════════════════════════════════
# DOMAIN TYPES
# ═══════════════════════════════════════════════════════════════════════
def test_domain_type_values(self):
"""Проверка значений доменов"""
self.assertEqual(DomainType.THOUGHT.value, "montana.thought")
self.assertEqual(DomainType.MESSAGE.value, "montana.message")
self.assertEqual(DomainType.TRANSACTION.value, "montana.transaction")
self.assertEqual(DomainType.PRESENCE.value, "montana.presence")
self.assertEqual(DomainType.NODE_AUTH.value, "montana.node.auth")
self.assertEqual(DomainType.SYNC.value, "montana.sync")
def test_get_domain_prefix(self):
"""get_domain_prefix() возвращает bytes"""
prefix = get_domain_prefix(DomainType.THOUGHT)
self.assertIsInstance(prefix, bytes)
self.assertEqual(prefix, b"montana.thought")
def test_all_domains_have_prefix(self):
"""Все домены имеют префикс"""
for domain in DomainType:
prefix = get_domain_prefix(domain)
self.assertIsInstance(prefix, bytes)
self.assertGreater(len(prefix), 0)
class TestCognitiveSignatureInit(unittest.TestCase):
"""Тесты инициализации CognitiveSignature"""
# ═══════════════════════════════════════════════════════════════════════
# INITIALIZATION
# ═══════════════════════════════════════════════════════════════════════
def test_init_generates_key(self):
"""Инициализация генерирует ключ если не указан"""
signer = CognitiveSignature()
self.assertIsNotNone(signer.secret_key)
self.assertEqual(len(signer.secret_key), 32)
def test_init_with_custom_key(self):
"""Инициализация с кастомным ключом"""
custom_key = secrets.token_bytes(32)
signer = CognitiveSignature(secret_key=custom_key)
self.assertEqual(signer.secret_key, custom_key)
def test_init_with_wrong_key_length(self):
"""Инициализация с неправильной длиной ключа"""
wrong_key = secrets.token_bytes(16) # Неправильная длина
with self.assertRaises(ValueError):
CognitiveSignature(secret_key=wrong_key)
def test_different_signers_different_keys(self):
"""Разные signers генерируют разные ключи"""
signer1 = CognitiveSignature()
signer2 = CognitiveSignature()
self.assertNotEqual(signer1.secret_key, signer2.secret_key)
class TestCognitiveSignatureSign(unittest.TestCase):
"""Тесты подписания"""
def setUp(self):
"""Создаём signer для каждого теста"""
self.signer = CognitiveSignature()
# ═══════════════════════════════════════════════════════════════════════
# SIGNING
# ═══════════════════════════════════════════════════════════════════════
def test_sign_basic(self):
"""Базовое подписание"""
message = "Test message"
signed = self.signer.sign(message, DomainType.MESSAGE)
self.assertIn("message", signed)
self.assertIn("domain", signed)
self.assertIn("timestamp", signed)
self.assertIn("signature", signed)
self.assertIn("metadata", signed)
def test_sign_preserves_message(self):
"""sign() сохраняет оригинальное сообщение"""
message = "Test message"
signed = self.signer.sign(message, DomainType.MESSAGE)
self.assertEqual(signed["message"], message)
def test_sign_includes_domain(self):
"""sign() включает домен"""
signed = self.signer.sign("message", DomainType.THOUGHT)
self.assertEqual(signed["domain"], "montana.thought")
def test_sign_generates_signature(self):
"""sign() генерирует подпись"""
signed = self.signer.sign("message", DomainType.MESSAGE)
self.assertIsInstance(signed["signature"], str)
self.assertEqual(len(signed["signature"]), 64) # SHA256 hex = 64 символа
def test_sign_with_timestamp(self):
"""sign() с кастомным timestamp"""
timestamp = "2026-01-20T12:00:00+00:00"
signed = self.signer.sign("message", DomainType.MESSAGE, timestamp=timestamp)
self.assertEqual(signed["timestamp"], timestamp)
def test_sign_generates_timestamp(self):
"""sign() генерирует timestamp если не указан"""
signed = self.signer.sign("message", DomainType.MESSAGE)
self.assertIsNotNone(signed["timestamp"])
# Должен быть в ISO format
datetime.fromisoformat(signed["timestamp"]) # Не должно упасть
def test_sign_with_metadata(self):
"""sign() с метаданными"""
metadata = {"user_id": 123, "custom": "data"}
signed = self.signer.sign("message", DomainType.MESSAGE, metadata=metadata)
self.assertEqual(signed["metadata"], metadata)
def test_sign_same_message_different_signature(self):
"""Одинаковое сообщение с разным timestamp → разные подписи"""
message = "Test"
signed1 = self.signer.sign(message, DomainType.MESSAGE, timestamp="2026-01-01T00:00:00+00:00")
signed2 = self.signer.sign(message, DomainType.MESSAGE, timestamp="2026-01-02T00:00:00+00:00")
self.assertNotEqual(signed1["signature"], signed2["signature"])
class TestCognitiveSignatureVerify(unittest.TestCase):
"""Тесты верификации"""
def setUp(self):
"""Создаём signer для каждого теста"""
self.signer = CognitiveSignature()
# ═══════════════════════════════════════════════════════════════════════
# VERIFICATION
# ═══════════════════════════════════════════════════════════════════════
def test_verify_valid_signature(self):
"""Верификация валидной подписи"""
signed = self.signer.sign("message", DomainType.MESSAGE)
is_valid = self.signer.verify(signed)
self.assertTrue(is_valid)
def test_verify_with_domain_check(self):
"""Верификация с проверкой домена"""
signed = self.signer.sign("message", DomainType.MESSAGE)
is_valid = self.signer.verify(signed, domain=DomainType.MESSAGE)
self.assertTrue(is_valid)
def test_verify_wrong_domain(self):
"""Верификация с неправильным доменом"""
signed = self.signer.sign("message", DomainType.MESSAGE)
is_valid = self.signer.verify(signed, domain=DomainType.THOUGHT)
self.assertFalse(is_valid)
def test_verify_tampered_message(self):
"""Верификация подделанного сообщения"""
signed = self.signer.sign("original", DomainType.MESSAGE)
# Подделываем сообщение
signed["message"] = "tampered"
is_valid = self.signer.verify(signed)
self.assertFalse(is_valid)
def test_verify_tampered_signature(self):
"""Верификация подделанной подписи"""
signed = self.signer.sign("message", DomainType.MESSAGE)
# Подделываем подпись
signed["signature"] = "0" * 64
is_valid = self.signer.verify(signed)
self.assertFalse(is_valid)
def test_verify_tampered_timestamp(self):
"""Верификация с подделанным timestamp"""
signed = self.signer.sign("message", DomainType.MESSAGE)
# Подделываем timestamp
signed["timestamp"] = "2030-01-01T00:00:00+00:00"
is_valid = self.signer.verify(signed)
self.assertFalse(is_valid)
def test_verify_different_signer(self):
"""Верификация подписи другим signer"""
signed = self.signer.sign("message", DomainType.MESSAGE)
# Другой signer с другим ключом
other_signer = CognitiveSignature()
is_valid = other_signer.verify(signed)
self.assertFalse(is_valid)
def test_verify_invalid_format(self):
"""Верификация невалидного формата"""
invalid_data = {"not": "correct", "format": "at all"}
is_valid = self.signer.verify(invalid_data)
self.assertFalse(is_valid)
class TestCognitiveSignatureDomainSeparation(unittest.TestCase):
"""Тесты domain separation"""
def setUp(self):
"""Создаём signer для каждого теста"""
self.signer = CognitiveSignature()
# ═══════════════════════════════════════════════════════════════════════
# DOMAIN SEPARATION
# ═══════════════════════════════════════════════════════════════════════
def test_same_message_different_domains(self):
"""Одно сообщение в разных доменах → разные подписи"""
message = "Test message"
timestamp = "2026-01-20T12:00:00+00:00"
signed_thought = self.signer.sign(message, DomainType.THOUGHT, timestamp=timestamp)
signed_message = self.signer.sign(message, DomainType.MESSAGE, timestamp=timestamp)
self.assertNotEqual(signed_thought["signature"], signed_message["signature"])
def test_domain_separation_prevents_replay(self):
"""Domain separation предотвращает replay attacks"""
# Подписываем как THOUGHT
signed = self.signer.sign("message", DomainType.THOUGHT)
# Пытаемся верифицировать как MESSAGE
is_valid = self.signer.verify(signed, domain=DomainType.MESSAGE)
# Должно быть невалидно
self.assertFalse(is_valid)
def test_all_domains_produce_different_signatures(self):
"""Все домены производят разные подписи для одного сообщения"""
message = "Test"
timestamp = "2026-01-20T12:00:00+00:00"
signatures = set()
for domain in DomainType:
signed = self.signer.sign(message, domain, timestamp=timestamp)
signatures.add(signed["signature"])
# Все подписи должны быть уникальными
self.assertEqual(len(signatures), len(DomainType))
class TestCognitiveSignatureConvenienceMethods(unittest.TestCase):
"""Тесты вспомогательных методов"""
def setUp(self):
"""Создаём signer для каждого теста"""
self.signer = CognitiveSignature()
# ═══════════════════════════════════════════════════════════════════════
# CONVENIENCE METHODS
# ═══════════════════════════════════════════════════════════════════════
def test_sign_thought(self):
"""sign_thought() работает корректно"""
thought = "Маска тяжелее лица"
signed = self.signer.sign_thought(thought, user_id=123)
self.assertEqual(signed["message"], thought)
self.assertEqual(signed["domain"], "montana.thought")
self.assertEqual(signed["metadata"]["user_id"], 123)
def test_sign_message(self):
"""sign_message() работает корректно"""
message = "Hello"
signed = self.signer.sign_message(message, sender_id=456)
self.assertEqual(signed["message"], message)
self.assertEqual(signed["domain"], "montana.message")
self.assertEqual(signed["metadata"]["sender_id"], 456)
def test_sign_transaction(self):
"""sign_transaction() работает корректно"""
tx_data = "tx_123"
signed = self.signer.sign_transaction(
tx_data,
from_address="addr1",
to_address="addr2",
amount=100.0
)
self.assertEqual(signed["message"], tx_data)
self.assertEqual(signed["domain"], "montana.transaction")
self.assertEqual(signed["metadata"]["from"], "addr1")
self.assertEqual(signed["metadata"]["to"], "addr2")
self.assertEqual(signed["metadata"]["amount"], 100.0)
def test_sign_presence(self):
"""sign_presence() работает корректно"""
presence_data = "presence_check_123"
signed = self.signer.sign_presence(presence_data, user_id=789)
self.assertEqual(signed["message"], presence_data)
self.assertEqual(signed["domain"], "montana.presence")
self.assertEqual(signed["metadata"]["user_id"], 789)
class TestKeyManagement(unittest.TestCase):
"""Тесты управления ключами"""
# ═══════════════════════════════════════════════════════════════════════
# KEY MANAGEMENT
# ═══════════════════════════════════════════════════════════════════════
def test_generate_keypair(self):
"""generate_keypair() генерирует пару ключей"""
secret_key, public_key_hash = generate_keypair()
self.assertIsInstance(secret_key, bytes)
self.assertIsInstance(public_key_hash, bytes)
self.assertEqual(len(secret_key), 32)
self.assertEqual(len(public_key_hash), 32)
def test_generate_keypair_different_keys(self):
"""generate_keypair() генерирует разные ключи"""
key1, _ = generate_keypair()
key2, _ = generate_keypair()
self.assertNotEqual(key1, key2)
def test_public_key_is_hash_of_secret(self):
"""Публичный ключ — хеш секретного"""
secret_key, public_key_hash = generate_keypair()
expected_hash = hashlib.sha256(secret_key).digest()
self.assertEqual(public_key_hash, expected_hash)
def test_derive_key_from_passphrase(self):
"""derive_key_from_passphrase() создаёт ключ"""
passphrase = "my secure passphrase"
salt = secrets.token_bytes(16)
key = derive_key_from_passphrase(passphrase, salt)
self.assertIsInstance(key, bytes)
self.assertEqual(len(key), 32)
def test_derive_key_deterministic(self):
"""derive_key_from_passphrase() детерминирован"""
passphrase = "test"
salt = b"0" * 16
key1 = derive_key_from_passphrase(passphrase, salt)
key2 = derive_key_from_passphrase(passphrase, salt)
self.assertEqual(key1, key2)
def test_derive_key_different_passphrase(self):
"""Разные passphrase → разные ключи"""
salt = secrets.token_bytes(16)
key1 = derive_key_from_passphrase("passphrase1", salt)
key2 = derive_key_from_passphrase("passphrase2", salt)
self.assertNotEqual(key1, key2)
def test_derive_key_different_salt(self):
"""Разная соль → разные ключи"""
passphrase = "test"
key1 = derive_key_from_passphrase(passphrase, b"0" * 16)
key2 = derive_key_from_passphrase(passphrase, b"1" * 16)
self.assertNotEqual(key1, key2)
class TestSecurityProperties(unittest.TestCase):
"""Тесты свойств безопасности"""
def setUp(self):
"""Создаём signer для каждого теста"""
self.signer = CognitiveSignature()
# ═══════════════════════════════════════════════════════════════════════
# SECURITY PROPERTIES
# ═══════════════════════════════════════════════════════════════════════
def test_signature_length_constant(self):
"""Длина подписи константна"""
short = self.signer.sign("a", DomainType.MESSAGE)
long = self.signer.sign("a" * 10000, DomainType.MESSAGE)
self.assertEqual(len(short["signature"]), len(long["signature"]))
self.assertEqual(len(short["signature"]), 64) # SHA256 hex
def test_cannot_forge_signature(self):
"""Невозможно подделать подпись без ключа"""
message = "Original message"
signed = self.signer.sign(message, DomainType.MESSAGE)
# Пытаемся подделать подпись для другого сообщения
forged = signed.copy()
forged["message"] = "Forged message"
# Подпись остаётся той же
is_valid = self.signer.verify(forged)
self.assertFalse(is_valid)
def test_timing_attack_resistance(self):
"""Использование hmac.compare_digest для защиты от timing attacks"""
signed = self.signer.sign("message", DomainType.MESSAGE)
# Создаём два почти одинаковых сообщения
valid = signed.copy()
invalid = signed.copy()
invalid["signature"] = invalid["signature"][:-1] + ("0" if invalid["signature"][-1] != "0" else "1")
# Оба должны верифицироваться за примерно одинаковое время
# (мы не можем измерить время в unit тесте, но знаем что используется hmac.compare_digest)
result_valid = self.signer.verify(valid)
result_invalid = self.signer.verify(invalid)
self.assertTrue(result_valid)
self.assertFalse(result_invalid)
class TestEdgeCases(unittest.TestCase):
"""Тесты граничных случаев"""
def setUp(self):
"""Создаём signer для каждого теста"""
self.signer = CognitiveSignature()
# ═══════════════════════════════════════════════════════════════════════
# EDGE CASES
# ═══════════════════════════════════════════════════════════════════════
def test_empty_message(self):
"""Подписание пустого сообщения"""
signed = self.signer.sign("", DomainType.MESSAGE)
self.assertEqual(signed["message"], "")
is_valid = self.signer.verify(signed)
self.assertTrue(is_valid)
def test_unicode_message(self):
"""Подписание Unicode сообщения"""
message = "Монтана Montana 蒙大拿 金元Ɉ"
signed = self.signer.sign(message, DomainType.THOUGHT)
self.assertEqual(signed["message"], message)
is_valid = self.signer.verify(signed)
self.assertTrue(is_valid)
def test_very_long_message(self):
"""Подписание очень длинного сообщения"""
message = "a" * 1_000_000 # 1 миллион символов
signed = self.signer.sign(message, DomainType.MESSAGE)
is_valid = self.signer.verify(signed)
self.assertTrue(is_valid)
def test_special_characters(self):
"""Подписание специальных символов"""
message = "\\n\\t\\r!@#$%^&*()[]{}||"
signed = self.signer.sign(message, DomainType.MESSAGE)
is_valid = self.signer.verify(signed)
self.assertTrue(is_valid)
# ═══════════════════════════════════════════════════════════════════════════
# RUN TESTS
# ═══════════════════════════════════════════════════════════════════════════
if __name__ == '__main__':
# Запускаем с verbose output
unittest.main(verbosity=2)