Зачем вам эта цепочка хешей на телефоне
Представьте: вы запускаете дискуссию между тремя локальными 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
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-скрипт с хеш-цепочкой
Вот где начинается магия. Мы создаем скрипт, который:
- Генерирует сообщение через Ollama API
- Вычисляет SHA-256 хеш сообщения + предыдущего хеша
- Сохраняет всё в JSON с криптографической привязкой
- Позволяет проверить целостность в любой момент
Создаем файл 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-агентов"
Скрипт создаст трех агентов, которые будут обсуждать тему. Каждое сообщение попадает в цепочку с криптографической привязкой.
Проверяем целостность: где система ломается
После генерации диалога проверяем, что никто ничего не подменил:
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 ошибки:
- Хеширование только текста ответа. Это фатально. Нужно хешировать ВСЕ метаданные: timestamp, агента, промпт, модель, предыдущий хеш. Иначе можно подменить контекст, оставив тот же ответ.
- Использование MD5 или SHA-1. На 01.02.2026 эти алгоритмы считаются небезопасными. Только SHA-256 или SHA-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.
Последний совет: не используйте эту систему для чего-то действительно важного (медицина, финансы) без дополнительной криптографической подписи. Хеш-цепочка доказывает целостность, но не аутентичность источника. Для этого нужны цифровые подписи - но это уже тема для отдельного гайда.
А пока - запускайте дискуссии, ломайте цепочки, смотрите, как система детектирует изменения. Это лучший способ понять, почему криптография для ИИ - не паранойя, а необходимость.