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

525 lines
23 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
"""
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)