Хэш-цепочка SDK склеивает звенья в РАЗНЫХ регистрах (Crypto.sha256 → строчные
meta_hash/hash; doc_hash из фабрики → ПРОПИСНОЙ), а `.sig` хранит их в едином
(часто верхнем) регистре. Верификатор склеивал сырые строки .sig → C3/C4 падали
на валидных документах.
- C3: composite = SHA-256(meta_hash.toLowerCase + doc_hash.toUpperCase).
- C4: signed_hash = SHA-256(hash.toLowerCase + signed_at[без мс/Z]); SDK берёт
signed_at как toISOString().split('.')[0], а .sig кладёт с `.000`.
- C6: signed_at без таймзоны → роут с format:date-time отдавал HTTP 400; на фронте
помечаем `Z` (signed_at семантически UTC).
Регистры и формат signed_at выведены из исходников SDK (Document/Crypto) и
ДОКАЗАНЫ на реальном .sig: после фикса C1/C3/C4/C5 = PASS (крипто-подпись
verifyDigest валидна). C2 на legacy-доках — отдельный формат meta, не этот фикс.
Верификатор документов Coopenomics v1
Монорепа доказательного верификатора документов: проверка целостности, криптоподписи (secp256k1/SHA-256) и исторической привязки ключа к аккаунту в блокчейне Coopenomics.
Структура (pnpm workspace)
| Пакет | Назначение |
|---|---|
packages/shared (@verifier/shared) |
Контракт .sig v2.0 + чистые проверки C1–C5 (браузер + Node). Не публикуется. |
apps/api (@verifier/api) |
Fastify + Kysely + Postgres. Demux-индексер permissions (native-delta из @coopenomics/parser2) + REST /v1/active-permission/*. |
apps/spa (@verifier/spa) |
Vue 3 + Vite + Tailwind v4 + Reka UI. Загрузка PDF+.sig/ZIP, оркестрация C1–C8, вердикт. |
Проверки
| # | Проверка | Где |
|---|---|---|
| C1 | Целостность (SHA-256(pdf) == doc_hash) |
shared, локально |
| C2 | meta_hash (канонизация JCS) |
shared, локально |
| C3 | Композитный хэш | shared, локально |
| C4 | Подпись secp256k1 | shared, локально |
| C5 | Наличие сертификата | shared, локально |
| C6 | Историческая привязка ключа к аккаунту | api /v1/active-permission/check |
| C7 | Самоподписанность (⚠ предупреждение) | spa |
| C8 | Доказательство в цепи (опц.) | spa через публичный RPC |
Разработка
pnpm install
docker compose up -d # postgres:5433 + redis:6380
pnpm -r build
pnpm dev:api # :3030
pnpm dev:spa # :5173
Дизайн-язык SPA
Минималистичный пространственный дизайн на плоскостях, один ведущий цвет — бордо #9f1239
(бренд кооперативной экономики). Бордо только в chrome; вердикт — отдельная семантика
(✅ зелёный / ❌ красный / ⚠ amber). Reka UI + Tailwind v4 + Radix Colors. Эталоны: Linear, Vercel, Raycast, Resend.
Локальный бутстрап против цепи mono-ai-N
Индексация permissions с genesis для проверки C6 на живых документах.
Предусловие: поднята цепь mono (docker compose up в ~/mono-ai-N) — nodeos отдаёт
chain API на :8888 и state-history (SHiP) на :8070.
bash deploy/bootstrap/bootstrap-local.sh
Скрипт достаёт chain_id из get_info, поднимает изолированные postgres+redis,
parser2 Parser (SHiP→Redis) и verifier-api (consumer+REST, миграции авто), стримит логи.
- Parser читает SHiP с genesis → Redis Stream; verifier-api потребляет native-delta
permission. - Прогресс:
curl http://localhost:3030/v1/health(current_block/head_block/status). - Перезапуск безопасен — parser2 резюмится по позиции в Redis, upsert идемпотентен
(натуральный ключ),
sync_progressхранит прогресс. Репарсинга с нуля при рестарте нет. - Полный реиндекс (осознанно):
docker compose -f deploy/bootstrap/docker-compose.bootstrap.yaml down -v.
SPA направить на этот api: VITE_API_BASE=http://localhost:3030.
Деплой
Автономные пайплайны из одной репы: deploy-api.yml (docker image → ssh) и deploy-spa.yml
(vite build → nginx static). Деплой одного приложения не триггерит другое.