SHA-256 хеш-цепочка для LLM в Termux: аудит multi-agent диалога на Android | AiManual
AiManual Logo Ai / Manual.
01 Фев 2026 Гайд

SHA-256 хеш-цепочка для LLM-дискурса в Termux: как создать криптографический аудит multi-agent диалога на телефоне

Пошаговый гайд по созданию криптографически верифицируемого лога дискуссии между агентами ИИ на Android через Termux. Ollama, llama3.2:3b, SHA-256 хеширование.

Зачем вам эта цепочка хешей на телефоне

Представьте: вы запускаете дискуссию между тремя локальными LLM на своем Android. Один агент генерирует код, второй его ревьюит, третий пишет документацию. Через час у вас 50 сообщений. А теперь вопрос: как доказать, что никто не подменил ответы после генерации? Как убедиться, что диалог остался именно таким, каким его создали модели?

Проблема не в паранойе. В multi-agent системах воспроизводимость - это всё. Особенно когда вы тестируете разные промпты или сравниваете версии моделей. Без криптографической привязки каждый запуск превращается в "верю на слово".

На 01.02.2026 llama.cpp достиг версии 0.4.7, а Ollama - 0.5.3. Обе поддерживают работу на ARM64 через Termux, но с ограничениями. Новейшая модель llama3.2:3b (релиз октябрь 2025) работает на телефонах с 6+ ГБ ОЗУ без особых проблем.

Что ломается без хеш-цепочки

Без системы верификации вы сталкиваетесь с тремя проблемами:

  • Неверифицируемая целостность: кто-то (или что-то) мог изменить историю диалога после генерации
  • Невоспроизводимость экспериментов: запустили тот же промпт - получили другой результат. Модель изменилась? Или промпт подправили?
  • Отсутствие аудита для критических систем: если LLM принимают решения (например, в автономном RAG-пайплайне), нужно знать, что ответы не были подменены

Решение - SHA-256 хеш-цепочка. Каждое сообщение в диалоге включает хеш предыдущего сообщения. Измените хоть одну запятую - вся цепочка после этого сломается. Это как блокчейн, но для LLM-диалогов.

Подготовка: что должно быть на телефоне

Прежде чем строить хеш-цепочки, нужно запустить сами модели. Если у вас еще нет работающего стека, начните с базового гайда по llama.cpp на телефоне. Но для нашей задачи лучше использовать Ollama - она проще для multi-agent сценариев.

1 Установка Termux и зависимостей

Открываем Termux и выполняем:

pkg update && pkg upgrade -y
pkg install python python-pip git wget curl openssl-tool -y
pip install --upgrade pip
💡
На 01.02.2026 Python в репозиториях Termux - версия 3.12. Убедитесь, что установили именно её. Старые версии могут не поддерживать нужные криптографические библиотеки.

2 Установка Ollama для ARM64

Ollama не имеет официального пакета для Termux, но есть рабочий способ:

curl -fsSL https://ollama.ai/install.sh | sh

Если скрипт не работает (частая проблема на 01.02.2026), качаем бинарник напрямую:

wget https://github.com/ollama/ollama/releases/download/v0.5.3/ollama-linux-arm64
chmod +x ollama-linux-arm64
mv ollama-linux-arm64 $PREFIX/bin/ollama

Запускаем сервер в фоне:

ollama serve &
# Ждем 10 секунд
sleep 10

3 Загрузка модели llama3.2:3b

На 01.02.2026 llama3.2:3b - оптимальный выбор для телефонов. Она достаточно умная для multi-agent диалогов и при этом помещается в 4-6 ГБ ОЗУ.

ollama pull llama3.2:3b

Загрузка займет 1.8 ГБ трафика. Убедитесь, что вы на Wi-Fi. Если нужна более легкая модель, рассмотрите Gemma 3N из нашего гайда по мультимодальному ассистенту, но для хеш-цепочки лучше более способная модель.

Сердце системы: Python-скрипт с хеш-цепочкой

Вот где начинается магия. Мы создаем скрипт, который:

  1. Генерирует сообщение через Ollama API
  2. Вычисляет SHA-256 хеш сообщения + предыдущего хеша
  3. Сохраняет всё в JSON с криптографической привязкой
  4. Позволяет проверить целостность в любой момент

Создаем файл hash_chain_llm.py:

#!/usr/bin/env python3
import hashlib
import json
import time
from datetime import datetime
import requests
import sys

class LLMHashChain:
    def __init__(self, chain_file="llm_chain.json"):
        self.chain_file = chain_file
        self.chain = self.load_chain()
        self.ollama_url = "http://localhost:11434/api/generate"
        
    def load_chain(self):
        try:
            with open(self.chain_file, 'r') as f:
                return json.load(f)
        except FileNotFoundError:
            return {"blocks": [], "metadata": {"created": datetime.now().isoformat()}}
    
    def save_chain(self):
        with open(self.chain_file, 'w') as f:
            json.dump(self.chain, f, indent=2)
    
    def calculate_hash(self, data):
        """Вычисляем SHA-256 хеш для данных"""
        if isinstance(data, dict):
            data_str = json.dumps(data, sort_keys=True)
        else:
            data_str = str(data)
        
        return hashlib.sha256(data_str.encode('utf-8')).hexdigest()
    
    def get_previous_hash(self):
        """Получаем хеш последнего блока в цепочке"""
        if not self.chain["blocks"]:
            return "0" * 64  # Genesis hash
        
        return self.chain["blocks"][-1]["current_hash"]
    
    def generate_with_llm(self, prompt, model="llama3.2:3b", agent_name="default"):
        """Генерируем ответ через Ollama и сразу создаем блок в цепочке"""
        
        # Генерация через LLM
        payload = {
            "model": model,
            "prompt": prompt,
            "stream": False
        }
        
        try:
            response = requests.post(self.ollama_url, json=payload, timeout=120)
            response.raise_for_status()
            llm_response = response.json()["response"]
        except Exception as e:
            print(f"Ошибка при обращении к Ollama: {e}")
            return None
        
        # Создаем блок данных
        previous_hash = self.get_previous_hash()
        
        block_data = {
            "timestamp": datetime.now().isoformat(),
            "agent": agent_name,
            "prompt": prompt,
            "response": llm_response,
            "model": model,
            "previous_hash": previous_hash
        }
        
        # Вычисляем хеш для этого блока
        # Важно: хешируем ВСЕ данные блока + предыдущий хеш
        hash_input = json.dumps(block_data, sort_keys=True) + previous_hash
        current_hash = hashlib.sha256(hash_input.encode('utf-8')).hexdigest()
        
        block_data["current_hash"] = current_hash
        
        # Добавляем в цепочку
        self.chain["blocks"].append(block_data)
        self.save_chain()
        
        print(f"[+] Блок добавлен. Хеш: {current_hash[:16]}...")
        print(f"    Агент: {agent_name}")
        print(f"    Ответ: {llm_response[:100]}...\n")
        
        return llm_response, current_hash
    
    def verify_chain(self):
        """Проверяем целостность всей цепочки"""
        if not self.chain["blocks"]:
            print("Цепочка пуста")
            return True
        
        print(f"Проверяем цепочку из {len(self.chain['blocks'])} блоков...")
        
        previous_hash = "0" * 64
        all_valid = True
        
        for i, block in enumerate(self.chain["blocks"]):
            # Проверяем, что previous_hash совпадает
            if block["previous_hash"] != previous_hash:
                print(f"[!] Блок {i}: предыдущий хеш не совпадает!")
                print(f"    Ожидался: {previous_hash[:16]}...")
                print(f"    Получен:  {block['previous_hash'][:16]}...")
                all_valid = False
            
            # Пересчитываем хеш блока
            block_copy = block.copy()
            current_hash_stored = block_copy.pop("current_hash")
            
            hash_input = json.dumps(block_copy, sort_keys=True) + previous_hash
            calculated_hash = hashlib.sha256(hash_input.encode('utf-8')).hexdigest()
            
            if calculated_hash != current_hash_stored:
                print(f"[!] Блок {i}: хеш не совпадает!")
                print(f"    Ожидался: {calculated_hash[:16]}...")
                print(f"    Получен:  {current_hash_stored[:16]}...")
                all_valid = False
            else:
                print(f"[✓] Блок {i} валиден ({block['agent']})")
            
            previous_hash = current_hash_stored
        
        if all_valid:
            print("\n[✓] Вся цепочка валидна!")
        else:
            print("\n[!] Обнаружены нарушения целостности!")
        
        return all_valid
    
    def multi_agent_discussion(self, topic, rounds=3):
        """Запускаем дискуссию между несколькими агентами"""
        
        agents = [
            ("coder", "llama3.2:3b", "Ты - senior разработчик. Пиши код и технические решения."),
            ("reviewer", "llama3.2:3b", "Ты - code reviewer. Критикуй код, находи уязвимости и предлагай улучшения."),
            ("documenter", "llama3.2:3b", "Ты - технический писатель. Объясняй сложные концепции просто.")
        ]
        
        print(f"Начинаем дискуссию на тему: {topic}\n")
        
        current_prompt = f"Обсуди тему: {topic}. Представься как senior разработчик и начни дискуссию."
        
        for round_num in range(rounds):
            print(f"--- Раунд {round_num + 1} ---")
            
            for agent_name, model, system_prompt in agents:
                full_prompt = f"{system_prompt}\n\nКонтекст: {current_prompt}\n\nТвой ответ:"
                
                response, _ = self.generate_with_llm(
                    prompt=full_prompt,
                    model=model,
                    agent_name=agent_name
                )
                
                if response:
                    current_prompt = f"Предыдущий участник сказал: {response[:300]}... Продолжи дискуссию на тему {topic} с точки зрения своей роли ({system_prompt[:50]}...)"
                
                time.sleep(2)  # Даем Ollama передышку

if __name__ == "__main__":
    chain = LLMHashChain()
    
    if len(sys.argv) > 1 and sys.argv[1] == "verify":
        chain.verify_chain()
    elif len(sys.argv) > 1 and sys.argv[1] == "discuss":
        topic = sys.argv[2] if len(sys.argv) > 2 else "безопасность локальных LLM на мобильных устройствах"
        chain.multi_agent_discussion(topic, rounds=2)
    else:
        # Тестовый запуск
        print("Тестовый запуск хеш-цепочки...")
        response, hash_val = chain.generate_with_llm(
            "Объясни концепцию SHA-256 хеш-цепочки в двух предложениях.",
            agent_name="explainer"
        )
        print(f"Проверяем цепочку:")
        chain.verify_chain()

Запускаем multi-agent дискуссию с аудитом

Теперь самое интересное. Запускаем скрипт в Termux:

# Убедитесь, что Ollama работает
pgrep ollama || ollama serve &

# Даем серверу время на запуск
sleep 5

# Запускаем дискуссию
python hash_chain_llm.py discuss "этические аспекты автономных AI-агентов"

Скрипт создаст трех агентов, которые будут обсуждать тему. Каждое сообщение попадает в цепочку с криптографической привязкой.

💡
На телефоне ограничены ресурсы. Не ставьте больше 2 раундов для 3 агентов - это уже 6 вызовов LLM. Каждый вызов llama3.2:3b занимает 15-30 секунд на Snapdragon 8 Gen 2.

Проверяем целостность: где система ломается

После генерации диалога проверяем, что никто ничего не подменил:

python hash_chain_llm.py verify

Скрипт пройдет по всей цепочке и пересчитает все хеши. Если всё в порядке - вы увидите список [✓] отметок.

А теперь давайте сломаем цепочку, чтобы понять, как работает детектирование:

# Пример того, как НЕ надо делать
# (не запускайте это, если хотите сохранить цепочку)

import json

with open("llm_chain.json", "r") as f:
    data = json.load(f)

# Подменяем ответ в третьем блоке
if len(data["blocks"]) > 2:
    data["blocks"][2]["response"] = "ПОДМЕНЕННЫЙ ТЕКСТ"
    
with open("llm_chain.json", "w") as f:
    json.dump(data, f, indent=2)

# Теперь verify покажет ошибку!

После такой подмены verify выдаст ошибку на блоке 3 и всех последующих. Потому что хеш блока 3 изменился, а блок 4 все еще содержит старый previous_hash.

Оптимизации для реального использования

Базовый скрипт работает, но в бою нужно улучшить три вещи:

1. Сжатие данных

LLM-ответы могут быть огромными. Хешировать мегабайты текста - дорого для телефона. Решение:

def compress_for_hashing(text, max_length=1000):
    """Сжимаем текст для хеширования, сохраняя уникальность"""
    if len(text) <= max_length:
        return text
    
    # Берем начало, середину и конец
    part1 = text[:max_length//3]
    part2 = text[len(text)//2 - max_length//6:len(text)//2 + max_length//6]
    part3 = text[-max_length//3:]
    
    return part1 + part2 + part3

2. Инкрементальная проверка

Проверять всю цепочку каждый раз - избыточно. Вместо этого храним "чекпоинты":

def add_checkpoint(self, checkpoint_file="checkpoint.json"):
    """Сохраняем текущий хеш как чекпоинт"""
    if not self.chain["blocks"]:
        return
    
    last_hash = self.chain["blocks"][-1]["current_hash"]
    checkpoint = {
        "timestamp": datetime.now().isoformat(),
        "last_hash": last_hash,
        "block_count": len(self.chain["blocks"])
    }
    
    with open(checkpoint_file, "w") as f:
        json.dump(checkpoint, f)
    
    print(f"[+] Чекпоинт сохранен: {last_hash[:16]}...")

3. Интеграция с существующими пайплайнами

Хеш-цепочку можно встроить в RAG-системы или использовать для аудита офлайн пайплайнов. Каждый этап обработки данных получает свой блок в цепочке.

Где это реально нужно

Вы думаете: "Зачем мне это на телефоне?" Вот три сценария, где без хеш-цепочки не обойтись:

Сценарий Проблема без хеш-цепочки Решение с цепочкой
Полевые исследования с автономным ИИ Невозможно доказать, что данные не менялись после сбора Каждый вывод LLM имеет криптографическую привязку ко времени
Тестирование промптов Запустили тест, изменили промпт, забыли - результаты несопоставимы Цепочка хранит точные промпты и ответы с возможностью верификации
Медицинские/юридические консультации офлайн Юридическая сила нулевая - можно подменить рекомендации Доказательство того, что ответы не изменялись после генерации

Ошибки, которые сломают вашу систему

Я видел, как люди портят хеш-цепочки. Вот топ-3 ошибки:

  1. Хеширование только текста ответа. Это фатально. Нужно хешировать ВСЕ метаданные: timestamp, агента, промпт, модель, предыдущий хеш. Иначе можно подменить контекст, оставив тот же ответ.
  2. Использование MD5 или SHA-1. На 01.02.2026 эти алгоритмы считаются небезопасными. Только SHA-256 или SHA-3.
  3. Хранение цепочки в оперативной памяти без сохранения. Termux может быть убит в любой момент. Сохраняйте цепочку после КАЖДОГО блока.

Особенно опасна первая ошибка. Вот как выглядит неправильный хеш-расчет:

# КАК НЕ НАДО
wrong_hash = hashlib.sha256(response_text.encode()).hexdigest()
# Теперь можно изменить промпт или timestamp, и хеш останется тем же

И правильный:

# КАК НАДО
block_data = {
    "timestamp": "2026-02-01T14:30:00",
    "agent": "coder",
    "prompt": "Напиши функцию",
    "response": response_text,
    "model": "llama3.2:3b",
    "previous_hash": "abc123..."
}
hash_input = json.dumps(block_data, sort_keys=True) + previous_hash
correct_hash = hashlib.sha256(hash_input.encode()).hexdigest()

Что будет дальше с хеш-цепочками для ИИ

К 2027 году эта техника станет стандартом для любых серьёзных multi-agent систем. Но не в том виде, как мы сделали сегодня.

Будущее за:

  • ZK-доказательствами целостности: можно будет доказать, что цепочка валидна, не раскрывая её содержимого
  • Квантово-устойчивыми алгоритмами
  • Встроенной поддержкой в фреймворках: представьте флаг --hash-chain в Ollama или llama.cpp

Но пока что - ваш Android с Termux и нашим скриптом обгоняет индустрию на год. Вы можете уже сегодня делать то, о чем большие компании будут говорить на конференциях в 2026.

Последний совет: не используйте эту систему для чего-то действительно важного (медицина, финансы) без дополнительной криптографической подписи. Хеш-цепочка доказывает целостность, но не аутентичность источника. Для этого нужны цифровые подписи - но это уже тема для отдельного гайда.

А пока - запускайте дискуссии, ломайте цепочки, смотрите, как система детектирует изменения. Это лучший способ понять, почему криптография для ИИ - не паранойя, а необходимость.