Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Ecosystem Docs (dev-first)

Структура

  • Папка сервиса (escrow/, wallet/, bridge/): свой README.md, а внутри — flows/, decisions/ (ADRы), problems/, diagrams/ (Mermaid/PlantUML). Всё локально к сервису.
  • flows/ — общие/сквозные флоу (когда затрагиваются несколько сервисов).
  • main/ — общие черновики, резюме ключевых решений.
  • problems/ — расследования инцидентов, руткозы, «что пошло не так».

Основные правила

  1. Определи цель заметки в первой строке.
  2. Если фиксируется решение → мини‑ADR: Context → Decision → Consequences.
  3. Диаграммы — только текстовые (Mermaid/PlantUML) рядом с MD.
  4. Ссылки вместо копипасты (код, регуляторные документы, RFC/ADR).
  5. В конце — 3–5 буллетов «что дальше» или чек‑лист.

Быстрый шаблон записи (копируй в новый файл)

Title: <кратко>
Status: draft
Owner: <ник>
Context: <1–3 предложения>
Decision / Notes: <факты, решения>
Links: <код/ADR/источники>
Next: <до 5 пунктов>

Rules (anti‑redundant)

  • Цель первой строкой. Нет цели — нет файла.
  • Один файл — одна тема. >600 строк или >3 крупные темы → дроби.
  • Папка сервиса (escrow/wallet/bridge) имеет свой README.md; внутри — flows/, decisions/ (ADR), problems/, diagrams/. Общие/сквозные флоу — в корневом flows/.
  • Не дублируй определения: всё в glossary.md (когда появится) или ссылайся на первоисточник.
  • Решения фиксируй как мини‑ADR (Context/Decision/Consequences), не переписывай длинными абзацами.
  • Ссылки вместо копипаста: код, RFC/ADR, регуляторные PDF.
  • Диаграммы только в текстовых форматах (Mermaid/PlantUML) рядом с MD; картинки без исходников запрещены.
  • Статус в шапке: draft/accepted/deprecated/superseded; помечай владельца.
  • В конце каждого файла — блок Next (до 5 пунктов) или чек‑лист; пустым не оставлять.
  • Убирай воду: факты, решения, риски, next steps. Истории и рассуждения — в main/ или в отдельный черновик.
  • Обновил код/арх/процесс → оставь ссылку в релевантном файле (не копируй текст).

ADR / flows (минимальный набор)

  1. Именуй decision с датой: decision-YYYY-MM-DD-topic-name.md (облегчает хронологию).
  2. Flow и код идут одним PR; документация лежит в src/.../flows или src/.../decisions (mdbook).
  3. Каждое решение = одна страница по шаблону (adr/adr-template.md).
  4. Статусы: draft → proposed → accepted → deprecated → superseded; фиксируй переход.
  5. В релизной ноте пиши, какие решения стали accepted (живыми) в релизе.

ADR / Flow процесс (MVP)

  • Где лежит: код + flow в одном PR; текст — в src/<domain>/(decisions|flows)/. Ставь ссылку на PR/код в Links.
  • Нумерация: глобально ADR-001, ADR-002, … без дат в имени файла. Храни в src/<domain>/decisions/ADR-xxx-<slug>.md.
  • Формат: один экран по шаблону adr-template.md (Context/Decision/Consequences/Next).
  • Статус: draft → accepted → deprecated → superseded; обновляй при изменении решения.
  • Релизы: в релиз-ноте фиксируй, какие ADR перешли в accepted.

Как завести новый ADR

  1. Возьми src/adr/adr-template.md.
  2. Подставь следующий номер ADR-xxx (смотри соседние ADR) и короткий slug.
  3. Положи файл в src/<domain>/decisions/.
  4. Добавь ссылку в src/SUMMARY.md и в README домена; обнови релизную заметку при переходе в accepted.

ADR-XXX: <краткое имя>

Status: draft
Owner: <владелец>
Domain: <wallet/escrow/bridge/...>
Links: PR <#>, code refs, related flows/diagrams
Created:

Context

  • Почему меняем/фиксируем решение. Коротко, фактами.

Decision

  • Суть решения (1–3 пункта).

Consequences

  • Что выигрываем/риски/альтернативы.

Next

  • Следующие шаги (до 5 пунктов).

Title: Mervey Ltd — company snapshot Summary:

  • Юрисдикция: AIFC/AFSA, Казахстан (Астана). Mervey Ltd, БИН 251040901583.
  • Продукт: некостодиальный мульти-блокчейн кошелёк + P2P-эскроу + E2E-мессенджер.
  • Ключевой принцип: компания не хранит приватные ключи/сид; эскроу — некастодиальный.
  • Гео-фокус: Центральная Азия/СНГ; санкционные и ограниченные юрисдикции — блокируем.

Problem (blockchain team focus):

  • Нет безопасного и доступного фиат↔крипто P2P в СНГ/ЦА после закрытия/ограничений крупных рампов (LocalBitcoins, Binance P2P).
  • Высокий риск мошенничества в оффчейн P2P: нужен on-chain escrow с прозрачными правилами и событиями для споров.
  • Мерчанты распылены: нужен агрегатор с проверенными адресами/ролями и единым SLA/таймерами в контракте.
  • Требуются кросс-чейн онбординг и релиз без централизованного custody.

Goal (blockchain team):

  • Построить безопасную, отказоустойчивую, проверяемую архитектуру (некастодиальный кошелёк + on-chain escrow + кросс-чейн свопы), с чёткими правилами ролей/доступов, таймерами, аудитом и минимизацией доверия к серверу.

Invariants (must-hold):

  • Некастодиальность: приватные ключи/сид/подписи — только на клиенте; серверы не имеют доступа и не подписывают.
  • On-chain escrow: средства блокируются в смарт-контракте; релиз только по правилам контракта/ролей; никакого серверного custody.
  • Кросс-чейн взаимодействия.
  • Шифрование: E2E для мессенджера (чаты/звонки); на сервере — только метаданные, без контента.
  • KYC/KYB и санкции: risk-based, санкционные юрисдикции блокируются, проверка мерчантов обязательна.
  • Гео/санкции в продуктах: офферинг фиат ↔ крипто доступен только в разрешённых регионах; применение гео-блокировок.
  • Минимизация данных: храним только то, что нужно для P2P/безопасности/комплаенса; нет журналов приватных данных ключей/сид - ни в коем случае.
  • Прозрачность/аудит: контракты открыты, адреса ролей публичны; события в цепочке — источник правды для споров.
  • Безопасные таймеры: SLA/timeout для сделок в эскроу формализованы в контракте; автоканцел без ручного custody.

Links:

  • Регуляторный бизнес-план (PDF): см. репо/хранилище, не копировать текст.
  • Архитектура ключей (Notion): <ссылка>
  • P2P Escrow Exchange Aggregator (Notion): <ссылка>
  • Liquidity Treasury Service (Notion): <ссылка>
  • Cross-chain bridge services (Notion): <ссылка>
  • Merchant flow (Notion): <ссылка>

Next:

  • Слинковать с ADR по эскроу/кошельку, когда появятся.
  • Уточнить владельца и дату ревью.

Wallet service docs

Цель: держать флоу и решения по кошельку (non-custodial, SSS/SLIP-39, E2E) в одном месте.

Состав

  • flows/ — флоу кошелька (создание, восстановление, шардинг).
  • decisions/ — ADR/мини-решения.
  • problems/ — локальные инциденты/руткозы.
  • diagrams/ — Mermaid/PlantUML источники.

Как обновлять

  • Любое решение → мини-ADR: Context / Decision / Consequences / Links / Next.
  • Флоу храним здесь; сквозные темы — в корневом flows/.
  • Ссылки на код/репы/tx, без копипасты.

Links: добавить адреса SDK/клиентов, когда будут.

Архитектура управления криптографическими ключами

Последнее обновление: 2025-01-11

Корневая архитектура: OpenZeppelin AccessControl

Release Wallet (1-2)

Кошелек, решающий проблему Release. Имеет децентрализованное хранение и роль release в Solidity-контракте.

Проблема: есть 3 агента поддержки. Каким образом они будут подписывать release он-чейн?

Если 1 агент может release

  • один недовольный сотрудник → украл деньги
  • взлом одного ноутбука → денег как и не было
  • шантаж одного человека → украли деньги
  • невозможно доказать злоупотребление

Трейдофф: или делать Gnosis Safe Wallet тоже для поддержки и их подписи, по газу 140–160K (эквивалент ~0.4 доллара).

Wallet / Flows

Title: Wallet creation + recovery (2-of-3, user+server+guard) Status: draft (superseded by cloud-backup decision, оставляем как архив) Owner: <ник> Goal: Описать опциональный флоу кошелька с recovery через 2/3 SSS: шард пользователя, E2E-зашифрованный шард на сервере, шард гварда. Сервер не способен восстановить без участия гварда/пользователя.

Decisions:

  • Active: wallet/decisions/decision-2026-01-17-cloud-backup-supersedes-sss.md (cloud backup заменяет этот флоу).
  • Archive: wallet/decisions/decision-2026-01-13-recovery-2of3.md, wallet/decisions/decision-2026-01-14-sss-format-and-oob.md.

Open questions (если вернёмся к 2-of-3):

  • Формат SSS-конверта: точный layout/AAD, тест-векторы, негативные тесты на подмену metadata.
  • Протокол E2E: Noise/X3DH детали, double-wrapping S-shard (кто чей pubkey использует, ротация).
  • UX гварда: OOB-канал, подтверждение/отклонение, таймауты, rate-limit попыток.
  • Replay/abort: как инвалидировать старые recovery sessions, защита от re-play уведомлений гварду.
  • Хранение и удаление: сроки retention S-shard ciphertext, когда подчистка, аудит действий.
  • Device posture: требования к устройству гварда (TEE/KeyStore/SE, KDF параметры), политика при смене устройства.
  • KYC/антириск: кто источник, какие сигналы, частота повторного KYC.

Roles:

  • User (U) — владелец кошелька, генерирует сид, держит шард U.
  • Server (S) — курьер шифртекста для шарда S; не имеет ключей к расшифровке.
  • Guard (G) — доверенное лицо/устройство, держит шард G, подтверждает recovery, тревожит владельца.
  • KYC provider — даёт сигнал допуска (не даёт ключи).

Invariants (must hold):

  • Non-custodial: сервер не видит сид/приватные ключи; хранит только шифртекст шарда S и метаданные.
  • SSS k=2, n=3 (classic Shamir): любые 2 из 3 восстанавливают секрет.
  • Share binding: шарды всегда упакованы в аутентифицированную обёртку (AEAD) с wallet_id/sss_profile_id/version/role/index (нельзя незаметно перемешать шарды между кошельками/сессиями).
  • E2E: шарды S и G всегда в шифре под ключи получателей; локальный KDF (Argon2id) + AES-GCM/ChaCha20-Poly1305.
  • Recovery требует участия G (OOB подтверждение) и сессионного ключа U; KYC — только сигнал, не ключ.
  • Таймауты сессий, аудит событий; без участия G и U сервер не способен инициировать/завершить recovery.

Terminology:

  • OOB (out-of-band) подтверждение: гвард подтверждает recovery по независимому каналу (звонок/код/мессенджер/второе устройство), чтобы проверить запрос и тревожить владельца при атаке.

Flow A — обычный кошелёк (без шардов):

  1. U генерирует seed (12/24), локально шифрует бэкап (пароль→Argon2id→AES-GCM).
  2. Нет шардов, нет серверных копий. Потеря seed = невосстановимо.

Flow B — кошелёк с recovery 2/3: Creation:

  1. U генерирует seed локально.
  2. Split seed → classic Shamir SSS 2/3: U-shard, S-shard, G-shard.
  3. U-shard: хранится локально, шифруется паролем (Argon2id).
  4. S-shard: шифруется E2E под pubkey U_recovery и pubkey G (двойное обёртывание или гибрид), отправляется и хранится на сервере как шифртекст. Метаданные: wallet_id, sss_profile_id, sss_format_version, k/n, share_index, guard_id, статус.
  5. G-shard: хранится у гварда локально, шифруется паролем/био-гейтом + KDF.

Recovery (когда утеряно устройство U):

  1. U с новым устройством создаёт recovery session, публикует сессионный pubkey, проходит KYC (сигнал допуска/антириск).
  2. Сервер уведомляет G о запросе (push). G выполняет OOB проверку (звонок/мессенджер/код).
  3. G разблокирует G-shard (паскод+био), шифрует под сессионный pubkey U, отправляет по E2E.
  4. Сервер отдаёт S-shard шифртекст (у него нет ключей), U расшифровывает S-shard локально своим recovery key (который сервер не знает).
  5. U объединяет любые 2 из 3 (обычно G-shard + S-shard) → получает seed → импортирует кошелёк.
  6. Сессия закрывается, сессионные ключи стираются, событие аудита фиксируется.

Anti-abuse / внутренний атакующий:

  • Сервер не имеет ключей для S-shard; без G участие невозможно завершить recovery.
  • Любая попытка «тихого» запуска recovery требует G-активации, которая тревожит владельца.
  • Rate-limit/таймаут recovery-сессий; логирование событий recovery_requested, guard_approved/denied, recovery_completed/failed.

Риски и митигация (остаточные):

  • Компрометация устройства G или слабый KDF → оффлайн-брут G-shard. Требуется жёсткий KDF/био-гейт и защитное хранилище (TEE/KeyStore/Secure Enclave).
  • Социнжиниринг G: смягчать OOB-подтверждением и уведомлением владельца.
  • Потеря и U-shard, и G-shard одновременно при недоступности S-shard → невосстановимо.

Wallet / Decisions

Active

Superseded / Archive

Title: Cloud Backup Recovery (supersedes 2-of-3 SSS/Guard approach) Status: proposed Owner: Artur Date: 17.01.2026

Context

Рассматривались два подхода к recovery:

  1. 2-of-3 SSS с guard + server shard + user shard (decision-2026-01-13)
  2. Cloud backup с шифрованием на клиенте

Проблема: SSS+guard — избыточная сложность для B2C non-custodial wallet без чёткого целевого пользователя.

Decision

Принимаем подход: Encrypted Cloud Backup в облако пользователя (iCloud/Google Drive).

Отказываемся от SSS/guard для основного флоу. Концентрируем ресурсы на escrow.

Обоснование

1. Анализ индустрии

Сравнение топовых non-custodial кошельков:

WalletMAU/скачиванияRecovery подход
MetaMask30+ млнSeed phrase only, нет встроенного recovery
Trust Wallet60+ млн скачиванийPure non-custodial, seed only
Ledger6 млн устройствSeed + опциональный Ledger Recover (платно, semi-custody)
Trezor1+ млн устройствПолностью некастодиальный, seed only
Exodus6 млнSelf-custody, seed only
Phantom7+ млнSeed-based recovery
Rainbow3+ млнSelf-custody, seed
Rabbyрастущая базаSeed фраза
Coinbase WalletUser-controlled keys, seed
Zerion1+ млнSeed-based

Вывод: Ни один успешный кошелёк не использует SSS для массового пользователя. Быть может они глупее нас? Нет — они выбрали простоту.

2. Целевой пользователь (Максат)

Кто: Максат, 29–35, не крипто-гик, пользуется Kaspi/банками. Хочет хранить небольшую/среднюю сумму, переводы, P2P/escrow.

Проблема: "Боюсь потерять доступ при утере телефона. Не хочу разбираться в seed-фразах."

Что нужно: "Потерял телефон → купил новый → восстановил кошелёк". Точка.

Что НЕ нужно: Разбираться в шардах, гвардах, OOB-подтверждениях, процедурах rewrap.

3. Риск-анализ SSS для B2C

Формула риска:

risk = кол-во объектов хранения × кол-во операций × человеческий фактор

Для 2-of-3 SSS:

  • 3 объекта (U-shard, S-shard, G-shard)
  • N операций жизненного цикла:
    • Генерация, раздача долей, подтверждение принятия
    • Смена телефона, переустановка ОС, миграция
    • Rewrap при смене guard pubkey
    • OOB-подтверждения, таймауты

Оценка (оптимистичная):

  • Вероятность потери/порчи одного объекта за 2–3 года: 10–15%
  • Вероятность ошибки на одной критической операции: 2–5%

Результат: Даже при небольших рисках долей и операций, для B2C 2-of-3 SSS даёт ≈7% вероятность полной потери за 2–3 года. Для массового продукта это слишком высокий риск.

4. Инженерные проблемы SSS

Фрагментация стандартов:

  • Отсутствуют единые константы (SLIP-39, Trezor SSS, другие реализации несовместимы).
  • Исторические уязвимости (интерполяция, восстановление с меньшим порогом): https://btcarmory.com/fragmented-backup-vuln/
  • Отсутствие единого "API" для SSS долей → lock-in на 5–10 лет.

Сложность реализации:

  • Требует команды опытных криптографов.
  • Огромная поверхность для багов: rewrap, ротация, guard lifecycle, OOB.
  • Бэкенд: оркестрация шардов, метаданные, guard registry, аудит, таймауты.

Вердикт: "Мы лечим кашель бегом под ледяным дождём."

5. Аргумент "для кого?"

Будь мы B2B-провайдером для миллионных сетей — SSS был бы необходимостью. Там другая ответственность, суммы, SLA.

Но мы делаем B2C consumer wallet. SSS+guard — это "технологичность ради технологичности", без целевого пользователя, проблему которого это решает.

Решение: Cloud Backup (Best Practice)

Как работает

Онбординг:

  1. "Создать кошелёк"
  2. "Сделать резервную копию"
  3. Выбрать способ:
    • Cloud backup: "Сохранить зашифрованную копию"
    • Вариант UX: "Придумай пароль восстановления" (только для шифрования; мы его не знаем)
    • И/или: passkey (локальная биометрия + платформенный keychain)
  4. Готово: в облаке пользователя лежит зашифрованный бэкап.

Восстановление (новое устройство):

  1. "Восстановить кошелёк"
  2. Войти в своё облако (iCloud/Google Drive)
  3. Ввести пароль восстановления
  4. Seed расшифровывается локально → кошелёк восстановлен

Технический флоу

  1. Seed создаётся на устройстве, никуда не уходит.
  2. При бэкапе: seed шифруется локально паролем восстановления (или passkey).
  3. Ciphertext сохраняется в iCloud / Google Drive пользователя (не на наш сервер).
  4. При восстановлении: пользователь входит в своё облако, скачивает ciphertext, вводит пароль → seed расшифровывается локально.

Преимущества

КритерийSSS + GuardCloud Backup (iCloud/GDrive)
Сложность бэкендаВысокая (шарды, rewrap, OOB, таймауты)Минимальная (мы ничего не храним)
UX для МаксатаНепонятен ("гвард? шард?")Понятен ("сохрани в облако, запомни пароль")
Регуляторный рискЕсть (храним ciphertext)Минимален (всё у юзера)
Точка отказаGuard недоступен / сервер лёг / rewrap-багТолько Apple/Google (стабильны)
ИндустрияПочти никто (enterprise MPC)MetaMask, Trust Wallet, Phantom, Rainbow

Что даёт:

  • Мы не храним ничего — ни ключей, ни шифртекстов. Регулятору нечего требовать.
  • Пользователь сам отвечает за своё облако и свой пароль. Это честный non-custodial.
  • Если забыл пароль — есть seed phrase (всегда). Это fallback, а не дыра.
  • Бэкенд минимальный: профиль, escrow-статусы, уведомления. Без криптографической оркестрации.

Ответственность = Некастодиальность

Подход "бэкап в облако юзера" — Best Practice:

  • Ответственен за свои решения пользователь.
  • Мы не можем восстановить его кошелёк, даже если захотим.
  • Это полностью совпадает с концепцией некастодиальности: "твои ключи — твоя ответственность".

Consequences

Positive:

  • Простота реализации и поддержки
  • Минимальный бэкенд (нет шардов, guard registry, rewrap)
  • Регуляторная чистота (не храним ничего)
  • Знакомый UX (как у MetaMask/Trust/Phantom)
  • Ресурсы команды → на escrow (основной продукт)

Negative:

  • Зависимость от облачных провайдеров пользователя (но они надёжнее нашего гипотетического guard-сервиса)
  • Нет "wow-фактора" для crypto-гиков (но это не наша аудитория)

Neutral:

  • Для enterprise/китов можно добавить SSS позже как опциональный "Advanced tier"

Implementation

MVP:

  1. Seed phrase generation + onboarding с проверкой бэкапа
  2. Encrypted cloud backup (iCloud/Google Drive)
  3. Recovery flow: download ciphertext → decrypt locally
  4. Fallback: seed phrase restore (всегда доступен)

Backend scope:

  • Никаких шардов, guard registry, rewrap
  • Только: профиль, escrow-статусы, уведомления, аудит

Mobile scope:

  • Платформенные API для iCloud/Google Drive
  • KDF (Argon2/scrypt) для пароля восстановления
  • Passkey support (опционально)
  • Supersedes: wallet/decisions/decision-2026-01-13-recovery-2of3.md
  • Related: escrow/decisions/decision-2026-01-14-kyc-provider-integration.md
  • Target users: Notion (см. контекст decision)

Next Steps

  • Прототип encrypted cloud backup (iOS/Android)
  • UX копирайт для онбординга ("пароль восстановления" vs "seed phrase")
  • Интеграция с iCloud/Google Drive API
  • Документация для пользователей: "Что делать, если забыл пароль?" → seed phrase
  • Отменить/архивировать SSS-related tasks

Вывод: Простота спасёт продукт. SSS — в архив.

Title: Adopt 2-of-3 recovery with guard + E2E server shard (supersedes KYC-SSS draft) Status: superseded Superseded by: decision-2026-01-17-cloud-backup-supersedes-sss.md Owner: Artur Date: 13.01.2026

Context:

  • 2026-01-13 рассматривался вариант «KYC-провайдер выдаёт пароль из лица» (KYC-SSS). Риски: центр доверия в KYC, deepfake/replay, "костыльное" (нестадартное) решение открывающее фронт для атак, сервер/провайдер может выпустить ключ без пользователя.
  • Нужен компромисс между UX и некастодиальностью: пользователь не теряет все средства при утере устройства, сервер не получает доступа к сид.

Decision:

  • Принят флоу 2/3 SSS: шард U у пользователя, шард S хранится на сервере только как шифртекст, шард G у гварда. Любые 2 из 3 восстанавливают сид.
  • Сервер не знает ключей расшифровки S-shard; S-shard шифруется E2E под pubkey восстановления пользователя и pubkey гварда (двойная обёртка/гибрид).
  • Recovery требует участия гварда (разблокировка G-shard под KDF+био/паскод) и сессионного ключа пользователя; KYC даёт только сигнал допуска, не ключ.
  • Таймауты, rate-limit, аудит событий recovery; OOB подтверждение гварда (тревога владельцу).

Safety rationale:

  • Нет приватных ключей/сид на сервере; шифртекст бесполезен без ключей U/G.
  • Сервер не может «тихо» запустить восстановление: нужен G-shard + сессионный ключ U, плюс OOB-подтверждение.
  • KYC не даёт доступ к секретам, только риск-сигнал; биометрия — локальный гейт, а не сетевой пароль.
  • Ротация/ревокация возможна: смена guard pubkey, rewrap S-shard, перевыпуск SSS.

Децентрализация:

  • UX: восстановление требует участия гварда; потеря и U-shard, и G-shard при недоступности S-shard = невосстановимо.
  • Server scope: хранит только шифртекст S-shard + метаданные (wallet_id, k/n, guard_id, статусы); обслуживает уведомления и аудит.
  • Guard responsibilities: хранить G-shard локально в защищённом хранилище; подтверждать запросы OOB; тревожить владельца.

Links:

  • Flow: wallet/flows/flow-wallet-recovery-2of3.md
  • Rules/invariants: .cursorrules

Прописать процедуру и решение rewrap при смене guard pubkey и при ротации recovery ключей.

Title: Wallet: SSS format — classic Shamir + share binding (versioned, authenticated); SLIP-39 optional Status: superseded Superseded by: decision-2026-01-17-cloud-backup-supersedes-sss.md Owner: Artur Date: 2026-01-14

Context:

  • В recovery 2-of-3 (U+S+G) нужно делить сид на шарды (k=2, n=3) так, чтобы:
    • нельзя было подменить/перемешать шарды между кошельками/профилями/сессиями незаметно;
    • формат был простым и предсказуемым для реализации и тест-векторов;
    • сервер оставался курьером шифртекста (non-custodial).
  • SLIP-39 удобен для ручного восстановления (мнемоники), но добавляет UX/форматную нагрузку; сейчас recovery — машинный (через приложения U/G).

Decision:

  • Принять classic Shamir Secret Sharing (Shamir over GF(256)/bytes) для split/merge seed (k=2, n=3).
  • Сделать обязательное binding + versioning для каждого шарда через аутентифицированную обёртку:
    • шард никогда не хранится/передаётся как “голые” байты Shamir, только как ciphertext в AEAD (AES-GCM / ChaCha20-Poly1305);
    • в AEAD AAD (или внутри зашифрованного заголовка) включаем минимум:
      • sss_format_version
      • wallet_id
      • sss_profile_id (идентификатор набора шардов/ротации)
      • k, n, share_index, shard_role (U/S/G)
      • опционально: guard_id, created_at
    • при попытке “скрестить” шарды между кошельками/профилями/ролями — AEAD аутентификация должна падать (детект до merge).
  • SLIP-39 оставить опцией, если появится требование ручного восстановления (человек вводит слова). Это отдельный UX-путь, не блокирующий текущий.

OOB (out-of-band) подтверждение — что это:

  • OOB подтверждение — проверка recovery-запроса гвардом по независимому каналу, не зависящему от сервера/приложения: звонок владельцу, заранее оговорённый код, личный контакт, второе устройство/мессенджер и т.п.
  • Цель: (1) удостовериться, что запрос инициировал владелец, (2) тревожить владельца при атаке, (3) снизить риск социального инжиниринга/компрометации канала push.

Consequences:

  • Формат шардов становится версируемым и проверяемым: легче мигрировать/ротировать и ловить ошибки интеграции.
  • Реализация обязана везде использовать AEAD с AAD (локально для U/G, на сервере для хранения S-shard), иначе binding теряется.
  • SLIP-39 не внедряем сейчас, чтобы не усложнять UX и крипто-спеку без требования.

Links:

  • Flow: wallet/flows/flow-wallet-recovery-2of3.md
  • Recovery decision (2-of-3): wallet/decisions/decision-2026-01-13-recovery-2of3.md

Next:

  • Зафиксировать точный binary layout заголовка (fields + sizes) и sss_format_version=1
  • Выбрать/проверить библиотеку Shamir (GF(256)) и добавить test vectors (split/merge + negative tests на подмену metadata)
  • Описать ротацию sss_profile_id (перевыпуск шардов, rewrap S-shard при смене guard pubkey)

Escrow service docs

Цель: держать решения и флоу для эскроу (P2P обмен, роли, антифрод) в одном месте.

Состав

  • flows/ — флоу эскроу (пример: flows/flow-exchange.md).
  • decisions/ — ADR/мини-решения.
  • problems/ — локальные инциденты/руткозы.
  • diagrams/ — Mermaid/PlantUML источники.

Как обновлять

  • Любое решение → мини-ADR: Context / Decision / Consequences / Links / Next.
  • Флоу храним здесь (для сервиса), сквозные — в корневом flows/.
  • Ссылки на код/контракты/tx, не копипаста.

Links: добавь адреса контрактов и репозиторий, когда будут.

Escrow / Flows

Title: Escrow exchange flow (buyer <-> seller via platform) Status: draft Owner: Artur Goal: Описать off/on-chain шаги обменки с ролями, включая диспут и вызовы релиза через HSM/tx-gateway. Инварианты: non-custodial, роли, дедлайн, события.

Roles:

  • Buyer — покупает крипту за фиат.

  • Seller (merchant) — продаёт крипту, ждёт фиат.

  • Platform (escrow) — блокирует/разрешает релиз по правилам, без доступа к средствам; роли через EXCHANGE_ROLE.

  • Non-custodial: контракт держит средства, сервер не имеет ключей пользователей.

  • Роли/доступ: EXCHANGE_ROLE на whitelisted адреса кидается; админ — multisig/owner.

  • Дедлайн: каждая сделка имеет deadline; по таймауту — auto-cancel/timeout path.

  • Events: все ключевые действия эмитят события для аудита/алертов.

  • Pausable/ReentrancyGuard паттерны; без прокси, если не обосновано.

  1. Offchain (юридика + тех):
  • Обменка/мерчант проходит KYC/KYB, подписывает договор.
  • Сообщает onchain-адреса работы: 0xA1... (main hot), 0xB2... (backup).
  • SLA/timeout фиксируются в договоре и в контрактном deadline.
  1. Onchain (настройка ролей):
  • Multisig/owner вызывает grantRole(EXCHANGE_ROLE, hot) и grantRole(EXCHANGE_ROLE, backup).
  • Модификатор:
modifier onlyExchange() {
    require(hasRole(EXCHANGE_ROLE, msg.sender), "not exchange");
    _;
}
  1. Создание сделки (обменкой):
createDeal(
  dealId,
  recipient, // адрес релизера/получателя
  amount,
  token,
  deadline   // SLA время
)
  • recipient — адрес того, кто получит релиз после подтверждения оплаты.
  • deadline — жёсткий таймер; по истечении — cancel/timeout.
  • События: DealCreated(dealId, recipient, token, amount, deadline).
  1. Диспут кейс (упрощённый поток):
  1. Buyer отправил фиат.
  2. Seller утверждает «деньги не дошли».
  3. Backend вызывает TX Gateway: openDispute(dealId, sides, paymentStatus, timestamp, log).
  4. Расследование offchain, сбор стейтментов/доказательств.
  5. Backend принимает решение release/refund/dispute-keep:
    • формирует callData для контракта (release или refund или отметка диспута);
    • передаёт в signer/HSM (см. "Release wallet" архитектуру).
  6. Контракт выполняет действие; эмитится событие DealReleased или DealRefunded/DealDisputed.
  1. Выходное состояние:
  • Сделка завершена (release или refund), комиссия списана.
  • События зафиксированы; по таймауту — auto-cancel/auto-refund (уточнить в коде).

Notes / Links:

  • Архитектура управления криптографическими ключами: .
  • Добавить адреса контрактов/tx, когда будут.

Next:

  • Формализовать timeout/cancel/partial release кейсы и события алертов.
  • Добавить Mermaid диаграмму (normal + dispute).
  • Пример tx hash в тестовой сети.

Escrow / Decisions

Title: Escrow design: minimal, auditable, multisig-controlled (Kleros only as future option) Status: draft Owner: Artur Date: 12.01.2026

Context:

  • Рассматривали механизм Kleros как опцию на будущее (внешний арбитраж).
  • Для MVP нужен максимально «тупой», формально проверяемый, изолированный контракт.
  • Цель: быстрый и предсказуемый escrow с минимальной поверхностью атаки, ясной ролью multisig.

Decision:

  • Для MVP: multisig/authorized release, без внешнего арбитража. Kleros — только как возможный плагин в будущем.
  • Контракт максимально простой: AccessControl (EXCHANGE_ROLE), ReentrancyGuard, SafeERC20, опционально Pausable; события на каждое действие.
  • Диспут offchain; onchain — вызов releaseByMultisig или refundByMultisig авторизованным адресом (multisig/tx-gateway).
  • Жёсткие инварианты и фиксированные адреса через constructor; без upgradeable proxy, без owner-settable адресов.

Security principles (обязательные):

  • Неизменяемый контракт (без прокси, адреса заданы в constructor).
  • Нет приватных ключей на бэкенде: бэкенд только создаёт сделки, читает события/view, не подписывает пользовательские tx.
  • Только 4 пути движения средств: deposit(), releaseByMultisig(), refundByMultisig(), ruleByArbitrator() (если будет), timeoutWithdraw().
  • ReentrancyGuard ✅; Checks-Effects-Interactions ✅; SafeERC20 ✅; Pausable ⚠ (опционально); Event логирование ✅; Hard limits ✅; invariant tests ✅; fuzz tests ✅.

Storage model (эскроу-хранилище):

  • Варианты: отдельный escrow на сделку или общий пул с internal accounting.
  • Для крупных объёмов — предпочтителен один контракт-хранилище + mapping(uint256 => Deal):
mapping(uint256 => Deal) public deals;

struct Deal {
    address buyer;
    address seller;
    uint256 amount;
    State state;
}

Consequences:

  • Проще аудит/формальная проверка; меньше зависимостей и газа.
  • Предсказуемый вывод: решение принимает whitelisted multisig/tx-gateway по SLA.
  • Нет публичного арбитража Kleros — компенсируется договором, логами, событиями onchain.

Links:

  • Flow: escrow/flows/flow-exchange.md
  • Rules/invariants: .cursorrules

Next:

  • Зафиксировать формальные SLA/timeout в контракте (auto-cancel/auto-refund).
  • Добавить тесты на роли, паузу, reentrancy, deadline.
  • Рассмотреть опциональный плагин внешнего арбитража как v2 (без Kleros-тяжести).

Title: Escrow: KYC/KYB integration & EXCHANGE_ROLE lifecycle (recovery uses risk signal only) Status: draft Owner: Artur Inspector Date: 2026-01-14

Context:

  • Mervey — некастодиальный кошелёк + P2P эскроу. Сервер не хранит ключи/сид.
  • Для escrow мерчанты должны быть проверены (KYC/KYB) и допущены к on-chain ролям (EXCHANGE_ROLE).
  • Для recovery KYC провайдер должен давать только risk-based сигнал допуска (не ключ/пароль), чтобы не становиться центром доверия.
  • Нужен санкционный скрининг + гео-ограничения (risk-based, ongoing monitoring).

Decision / Scope:

Backend (основная работа):

  • Интеграция API KYC-провайдера (Sumsub/Onfido/Veriff/др.)
  • Хранение статусов: pending | approved | rejected | expired
  • Webhooks от провайдера → обновление статусов и аудит
  • Санкционный скрининг (OFAC/EU/UN) + geo блок
  • Ongoing monitoring (перепроверка при изменениях в списках / risk events)

Blockchain / smart contracts (включая tx-gateway):

  • Escrow: после KYC_APPROVED для merchant → grantRole(EXCHANGE_ROLE, merchant_address) через multisig/tx-gateway/HSM
  • Escrow audit: emit MerchantWhitelisted(address indexed merchant, bytes32 kycHash) (без PII; хеш записи/аттестации)
  • Escrow revocation: при expired/rejectedrevokeRole(EXCHANGE_ROLE, merchant_address) (offchain триггер → onchain)
  • Recovery: KYC = только сигнал допуска перед стартом recovery session (backend ↔ KYC API), не ключ
  • Events: аудит-события для комплаенса (например KYCCheckPassed, RecoveryInitiated)

Backend ↔ blockchain wiring (целевое):

[Merchant] → [KYC Provider] → [Backend webhook]
                                 ↓
                         [DB status=approved]
                                 ↓
                 [TX Gateway / HSM signer] → [Contract: grantRole]
                                 ↓
                    [Event: MerchantWhitelisted]

Profile / gating (KYC-шнутый или нет?):

РольKYC обязателенЧто даёт
Обычный юзерНет (non-custodial)Кошелёк работает без KYC
RecoveryДа (сигнал)Допуск к recovery session, не ключ
Мерчант (escrow)Да (KYC/KYB)EXCHANGE_ROLE, доступ к P2P
Высокие лимитыВозможноTier-система, если будет

Consequences:

  • KYC не ломает базовый non-custodial продукт, но обязателен для мерчантов и risk-gated recovery.
  • On-chain enforcement делается через роли (EXCHANGE_ROLE), а не через хранение PII в контракте.
  • У комплаенса появляется auditable trail: webhook logs + on-chain events.

Questions (to backend/product/legal):

  • Какой провайдер выбран и какие обязательные поля/страны/лимиты?
  • Какие данные храним локально vs запрашиваем по API (минимизация данных)?
  • Tier-система лимитов или бинарный допуск?
  • Частота/триггеры ongoing monitoring?
  • Нужен ли onchain аудит через kycHash или достаточно событий + offchain хранилища?

Links:

  • Escrow flow: escrow/flows/flow-exchange.md
  • Wallet recovery flow: wallet/flows/flow-wallet-recovery-2of3.md
  • Recovery decision: wallet/decisions/decision-2026-01-13-recovery-2of3.md
  • Company snapshot: main/company.md

Next:

  • Уточнить провайдера и получить доступ к sandbox API
  • Согласовать формат MerchantWhitelisted и процесс ревокации роли
  • Прототипировать tx-gateway → grantRole/revokeRole флоу (HSM signer)
  • Интеграционные тесты: mock webhook → on-chain роль выставлена/снята

Title: Escrow observability: SLA monitor + dispute indexer as separate services Status: draft Owner: backend/ops Date: 2026-01-18

Context:

  • Эскроу требует детерминированных SLA таймеров и поискового слоя по спорам.
  • Нужны быстрые алерты и разбор спорных сделок без ручного просмотра логов.
  • Сервисы должны оставаться read-only к цепи, без приватных ключей.

Decision:

  • Заводим два микросервиса: on-chain SLA monitor и dispute event indexer, каждый как отдельный Deployment (1 pod) с managed Postgres.
  • Доступ к цепи только через rpc-gateway-rotator (кворум RPC, без прямых публичных endpoint).
  • Оба сервиса обязательны к прод-защите: /health, /ready, /metrics, алерты по ingest lag, retries с backoff.
  • Конфиги (chains/contracts/webhooks/slack) — только через файлы/секреты, без перекомпиляции; hot-reload допускается.
  • Стейт восстанавливаемый: реплей из цепи + БД; никаких локальных томов.

Consequences:

  • SLA-события и споры становятся наблюдаемыми и алертируемыми без изменений смарт-контрактов.
  • Один источник RPC-правды (gateway) снижает инконсисентность (флап рпс) обоих сервисов.
  • Managed БД упрощает бэкап и обновления, но требует сетевых правил к кластеру.
  • Операции получают /metrics для Grafana/alertmanager; саппорт — быстрый поиск и свежие таймеры.

Next:

  • Закрепить владельцев сервисов (oncall) и SLO: ingest lag, alert latency, search latency.
  • Задать дефолтные конфиги chains/contracts и мок-каналы алертов для dev.
  • Подготовить CI job: сборка образов + k8s deploy манифесты (см. deploy-sla-monitor-dispute-indexer.md).

Escrow / Problems

Расследования инцидентов, руткозы, "что пошло не так" в контексте escrow.

Проблема: кому верить — банку или чеку юзера?

Коротко, разговорно: спор в эскроу, фиат подтверждается криво. Юзер шлёт чек, банк/PSP даёт вебхук или выписку, мерчант говорит «не дошло», multisig должен решать. Ниже — больные вопросы без ответов (пока).

  1. Источник истины: чей сигнал важнее — webhook PSP, выписка мерчанта, или «скрин чека» от покупателя?
  2. Что делаем при chargeback/clawback спустя N дней, если мы уже сделали release? Кто несёт риск?
  3. Если банк задержал платёж, а SLA таймер сработал и мы сделали refund — как лечить двойные выплаты, есть ли «reopen»?
  4. Мерчант в блоке/hold у банка: держим ли крипту на таймере до разблокировки или сразу рефандим? Кто решает и на каких доказательствах?
  5. Кто имеет полномочия финального on-chain решения: multisig вслепую по флагу fiat_confirmed, или нужен второй фактор (аудитор)?
  6. Можно ли подделать чек/скрин? Есть ли обязательная верификация через API банка/PSP или запрещаем offchain доказательства без оракула?
  7. Reorg/L2 лаг: как синхронизировать on-chain release/refund с поздним fiat_confirmed, чтобы не словить рассинхрон?
  8. Как логируем и подписываем решения диспута, чтобы потом доказать, что multisig действовал по процессу (а не «на глаз»)?
  9. Если два разных PSP дают разные статусы по одному платежу, что выбираем: majority, приоритет «более сильного» PSP, или стоп-краник?
  10. Какой SLA на fiat_pending перед авто-refund? Что, если PSP не даёт финального статуса сутками?
  11. Кто платит комиссию/штрафы за холд/chargeback — мерчант, покупатель или платформа?
  12. Есть ли возможность «частичного релиза» при частичном зачислении фиата, или только binary release/refund?
  13. Как уведомляем стороны об отклонениях: алерты в Slack/webhook, нужен ли ручной ack?
  14. Нужен ли отдельный «arbiter role» (offchain) с правом блокировать решение multisig до выяснения?
  15. Как управляем ключами multisig/tx-gateway, чтобы компрометация не привела к массовым неправильным релизам?
  16. Если PSP сломан (нет webhook), используем ли «pull» проверку вручную/по API или ставим сделку в паузу?
  17. Какая политика при AML-флагах: auto-freeze on-chain? кто потом размораживает?
  18. Можно ли дать мерчанту self-service «апелляцию» без участия платформы, или это риск злоупотреблений?
  19. Что делаем с уже завершёнными сделками, если позже приходит банк-отзыв: ведём ли offchain долг мерчанта/покупателя?
  20. Требуем ли escrow балансы/депозиты от мерчантов как страховку под банковские риски?

Статус: Частично покрыто интеграцией с finic (решение от 25 января 2026).

Deploy: SLA monitor & dispute indexer (VPS + k3s, 1 pod each)

Коротко: k3s на VPS, один namespace, по одному Deployment, managed Postgres на облаке (отдельные БД). Ничего не хранится локально, только стейт в БД и метрики в Prometheus.

Пререквизиты

  • VPS c Ubuntu 22.04+, 2 vCPU/4GB+.
  • k3s без traefik (используем встроенный Service/Ingress по вкусу).
  • Managed Postgres x2 (sla_db, dispute_db), security group разрешает доступ с VPS.
  • Доступ к RPC через rpc-gateway-rotator URL.

Установка k3s (пример)

curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable traefik" sh -
kubectl create ns escrow-obs

Секреты (подставь URI)

kubectl -n escrow-obs create secret generic sla-secrets \
  --from-literal=DB_DSN=postgres://user:pass@host:5432/sla_db?sslmode=require \
  --from-literal=RPC_URL=https://rpc-gw.internal \
  --from-literal=WS_URL=wss://rpc-gw.internal/ws

kubectl -n escrow-obs create secret generic dispute-secrets \
  --from-literal=DB_DSN=postgres://user:pass@host:5432/dispute_db?sslmode=require \
  --from-literal=RPC_URL=https://rpc-gw.internal \
  --from-literal=WS_URL=wss://rpc-gw.internal/ws

Минимальный манифест (оба сервиса)

apiVersion: apps/v1
kind: Deployment
metadata: {name: sla-monitor, namespace: escrow-obs}
spec:
  replicas: 1
  selector: {matchLabels: {app: sla-monitor}}
  template:
    metadata: {labels: {app: sla-monitor}}
    spec:
      containers:
        - name: app
          image: registry/sla-monitor:TAG
          envFrom: [{secretRef: {name: sla-secrets}}]
          ports: [{containerPort: 8080, name: http}]
          readinessProbe: {httpGet: {path: /ready, port: http}, periodSeconds: 10}
          livenessProbe: {httpGet: {path: /health, port: http}, periodSeconds: 20}
          resources: {requests: {cpu: "200m", memory: "256Mi"}, limits: {cpu: "500m", memory: "512Mi"}}
---
apiVersion: v1
kind: Service
metadata: {name: sla-monitor, namespace: escrow-obs}
spec:
  selector: {app: sla-monitor}
  ports: [{port: 80, targetPort: http}]
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: dispute-indexer, namespace: escrow-obs}
spec:
  replicas: 1
  selector: {matchLabels: {app: dispute-indexer}}
  template:
    metadata: {labels: {app: dispute-indexer}}
    spec:
      containers:
        - name: app
          image: registry/dispute-indexer:TAG
          envFrom: [{secretRef: {name: dispute-secrets}}]
          ports: [{containerPort: 8080, name: http}]
          readinessProbe: {httpGet: {path: /ready, port: http}, periodSeconds: 10}
          livenessProbe: {httpGet: {path: /health, port: http}, periodSeconds: 20}
          resources: {requests: {cpu: "200m", memory: "256Mi"}, limits: {cpu: "500m", memory: "512Mi"}}
---
apiVersion: v1
kind: Service
metadata: {name: dispute-indexer, namespace: escrow-obs}
spec:
  selector: {app: dispute-indexer}
  ports: [{port: 80, targetPort: http}]

Экспонирование

  • Для приватки хватит kubectl port-forward или internal LB.
  • Для внешки: k3s ingress + TLS (Caddy/NGINX) → сервисы выше.

Наблюдаемость

  • /metrics в Prometheus; алерты: ingest_lag_blocks, alert_latency_seconds, rpc_errors_total.
  • /recent-violations и /events должны отвечать после старта (smoke).

Апдейт

kubectl -n escrow-obs set image deploy/sla-monitor app=registry/sla-monitor:NEW
kubectl -n escrow-obs set image deploy/dispute-indexer app=registry/dispute-indexer:NEW

Bridge service docs

Цель: держать флоу и решения по bridge (кросс-чейн свопы, интеграции) в одном месте.

Состав

  • flows/ — флоу bridge.
  • decisions/ — ADR/мини-решения.
  • problems/ — локальные инциденты/руткозы.
  • diagrams/ — Mermaid/PlantUML источники.

Как обновлять

  • Любое решение → мини-ADR: Context / Decision / Consequences / Links / Next.
  • Флоу храним здесь; сквозные темы — в корневом flows/.
  • Ссылки на код/контракты, без копипасты.

Next

  • Добавить первый флоу или ADR, когда появится реализация bridge.

Cross-service flows

Цель: собирать сквозные флоу, затрагивающие несколько сервисов (wallet/escrow/bridge).

Структура

  • Один файл — один сквозной флоу.
  • Формат: Goal → Actors → Steps (off/on-chain) → Events/Timers → Risks/Next.

Next

  • Добавить первый сквозной флоу (пример: onramp/offramp через escrow + wallet).