Реестр did:ru: контракт сканирования и выпуска документов

Версия контракта 0.2.0, состав полей — 2026-10-19-draft (черновик для согласования). Машиночитаемая спецификация — (OpenAPI 3.1). Состав полей документов реестр отдаёт сам: GET /api/v1/schemas — это источник истины, таблицы ниже — снимок.

1. Кто и что

Участник

Роль

Что делает в реестре

Орган по сертификации

certifier

выдаёт и отзывает сертификаты соответствия серий

Изготовитель

manufacturer

регистрирует приборы, выпускает паспорта, передаёт приборы

Покупатель

buyer

принимает приборы своей подписью, проверяет паспорта по метке

Объект

Идентификатор

Кто создаёт

Организация

did:ru:org:z…

сама организация

Прибор (экземпляр)

did:ru:device:z…

организация-владелец

Сертификат соответствия серии

urn:uuid:… (документ ConformityCertificateCredential)

орган по сертификации

Паспорт прибора

urn:uuid:… (документ InstrumentPassportCredential)

владелец прибора

Карточка организации

urn:uuid:… (документ OrganizationCredential)

сама организация

Идентификатор did:ru:… выводится из ключа (z + base58btc от SHA-256 отпечатка JWK по RFC 7638), поэтому занять или подделать чужой нельзя. Сертификат соответствия — это не сертификат ключа электронной подписи.

2. Метка

QR-код метки содержит ссылку:

<адрес реестра>/s/<DID прибора> пример: http://localhost:8081/s/did:ru:device:z9vYb8rKq3f2SxTn5Lm4Hc7Wd1Ae6Pj2Ug8Rk3Nx5Ty4Q
  • Камера телефона открывает по ней страницу проверки — приложение не нужно.

  • Сканер партнёра передаёт прочитанное как есть в GET /api/v1/scan?code=… или сам берёт DID из пути после /s/.

  • Принимается и голый DID в QR (did:ru:device:…).

  • Что печатать: GET /api/v1/labels/{did} (ссылка) и GET /api/v1/labels/{did}/qr.svg / qr.png (картинка, коррекция M).

Адрес реестра в метке задаётся при развёртывании и после печати меток не меняется. Нужно согласовать до печати.

3. Сканирование — один вызов

GET /api/v1/scan?code=http%3A%2F%2Flocalhost%3A8081%2Fs%2Fdid%3Aru%3Adevice%3Az9vY…

Публичный: без токена. Ответ (сокращён):

{ "result": { "did": "did:ru:device:z9vY…", "kind": "device", "verdict": "caveat", "caveats": ["certificate_revoked"], "owner": { "did": "did:ru:org:zEhB…", "name": "ОЙЛТИМ", "nameVerified": false }, "passport": { "id": "urn:uuid:e9ba…", "type": "InstrumentPassportCredential", "status": "active", "signed": true, "issuer": { "did": "did:ru:org:z84u…", "name": "Взлёт", "nameVerified": false }, "validFrom": "2026-10-19T15:42:32Z", "claims": { "serialId": "SN-0001", "nomenclature": "ЭМИС-ВИХРЬ 200", "manufacturedOn": "2026-10-01", "certificate": "urn:uuid:1699…" }, "verification": { "result": "registry_valid", "cryptographicallyVerified": true, "checks": { "proof": "valid", "status": "active", "time": "valid", "certificate": "revoked", "…": "…" } } }, "certificate": { "id": "urn:uuid:1699…", "status": "revoked", "claims": { "number": "RR-0042", "scope": "расходомеры серии 200" }, "…": "…" }, "labelUrl": "http://localhost:8081/s/did:ru:device:z9vY…", "checkedAt": "2026-10-19T15:45:36Z", "schemaVersion": "2026-10-19-draft" } }

Что показывать первым — verdict:

verdict

Значит

valid

паспорт подлинный и действует, сертификат соответствия серии действует

caveat

паспорт подлинный, но сертификат соответствия отозван, истёк или не найден

invalid

паспорту доверять нельзя (подпись не сходится, паспорт отозван или вне срока, издатель не найден)

no_passport

прибор зарегистрирован, паспорт не выпущен

deactivated

идентификатор прибора деактивирован (списан)

Почему не valid — caveats: passport_signature_invalid, passport_unsigned, passport_revoked, passport_expired, passport_issuer_unresolved, certificate_revoked, certificate_expired, certificate_missing. Отдельно, без влияния на verdict: transfer_in_progress — передача подписана, получатель ещё не подтвердил (кому — transferTo).

Подлинность паспорта и статус сертификата соответствия — разные проверки: после отзыва сертификата паспорт остаётся подлинным, verdict = caveat.

Названия организаций (name) указаны самими организациями (nameVerified: false) — подтверждение по сертификату ключа УКЭП ЮЛ (ИНН, ОГРН) будет следующим этапом.

4. Как класть: порядок шагов

Все изменения подписываются ключом стороны. Сейчас ключи — Ed25519 (alg: EdDSA), формат — компактный JWS.

Шаг

Кто подписывает

Вызов

1. Регистрация организации

сама организация

POST /did-api/api/v1/dids + DID-Operation-Proof

2. Карточка организации

сама организация

POST /api/v1/credentials (application/vc+jwt), OrganizationCredential

3. Сертификат соответствия

орган по сертификации

POST /api/v1/credentials, ConformityCertificateCredential, субъект — изготовитель

4. Регистрация прибора

изготовитель

POST /did-api/api/v1/dids с owner = DID изготовителя

5. Паспорт

владелец прибора

POST /api/v1/credentials, InstrumentPassportCredential, субъект — прибор, certificate — id действующего сертификата

6. Передача, шаг 1

владелец

PUT /did-api/api/v1/dids/{did}: owner прежний, controller: [<прибор>, <получатель>]

7. Передача, шаг 2

получатель

PUT /did-api/api/v1/dids/{did}: owner: <получатель>, controller: [<прибор>]

8. Отзыв

издатель документа

POST /api/v1/credentials/{id}/revoke + Credential-Operation-Proof

Подпись операции над DID (DID-Operation-Proof)

header = {"alg":"EdDSA","kid":"<DID подписывающего>#key-1","typ":"did-operation+jws"} payload = {"op":"create|update","did":"<DID документа>","prevVersionId":"<versionId — только для update>", "bodyDigest":"<base64url(SHA-256(тело запроса как отправлено))>","iat":<unix-время>} DID-Operation-Proof: base64url(header) . base64url(payload) . base64url(подпись)

prevVersionId берётся из didDocumentMetadata.versionId ответа GET /did-api/api/v1/dids/resolve. Изменение поверх устаревшей версии отклоняется (409, version_conflict). Подписать может сам DID, его контроллер или владелец. Стать владельцем можно только своей подписью.

Подпись документа (application/vc+jwt)

Тело запроса — JWS: {"alg":"EdDSA","kid":"<DID издателя>#key-1","typ":"vc+jwt"}. Полезная нагрузка — сам документ (W3C VC 2.0):

{ "@context": ["https://www.w3.org/ns/credentials/v2"], "id": "urn:uuid:3f0e…", "type": ["VerifiableCredential", "InstrumentPassportCredential"], "issuer": "did:ru:org:z84u…", "validFrom": "2026-10-19T15:42:32Z", "credentialSubject": { "id": "did:ru:device:z9vY…", "serialId": "SN-0001", "nomenclature": "ЭМИС-ВИХРЬ 200", "manufacturedOn": "2026-10-01", "certificate": "urn:uuid:1699…" } }

Отзыв: Credential-Operation-Proof — JWS typ: vc-operation+jws с полезной нагрузкой {"op":"revoke","credentialId":"<id>","iat":<unix-время>}.

5. Поля документов (черновик, ждут согласования)

Источник истины — GET /api/v1/schemas и JSON Schema каждого типа: GET /api/v1/schemas/{type}. Неописанные поля отклоняются (unknown_field), обязательные — проверяются (missing_field), типы и форматы — тоже (invalid_field). В ответе об ошибке — field.

InstrumentPassportCredential — паспорт прибора, субъект did:ru:device:…

Поле

Название

Тип

Обязательно

serialId

Заводской номер

строка ≤ 64

да

nomenclature

Модель

строка ≤ 256

да

manufacturedOn

Дата изготовления

дата YYYY-MM-DD

certificate

Сертификат соответствия серии

id ConformityCertificateCredential

да

replaces

Заменяет паспорт

id действующего InstrumentPassportCredential прибора

если он есть

ConformityCertificateCredential — сертификат соответствия, субъект did:ru:org:… (держатель)

Поле

Название

Тип

Обязательно

number

Номер сертификата

строка ≤ 64

да

scope

Продукция (область сертификации)

строка ≤ 512

да

standards

Нормативные документы

список строк ≤ 20

issuedOn

Дата выдачи

дата

Срок действия — validUntil документа.

OrganizationCredential — карточка организации, субъект did:ru:org:…, выпускает сама организация

Поле

Название

Тип

Обязательно

name

Название

строка ≤ 256

да

inn

ИНН

10 или 12 цифр

ogrn

ОГРН

13 или 15 цифр

role

Роль

certifier, manufacturer, buyer, operator

Документы стенда 5 (поля — GET /api/v1/schemas): ProductTypeCredential — тип прибора и набор его характеристик (обязательные проверяются у паспорта); IncomeInspectionCredential — акт входного контроля (выпускает получатель); BlockPassportCredential — паспорт блока с составом; CertificateCardCredential — карточка сертификата от агента.

Правила сверх полей:

  • паспорт выпускает только владелец или контроллер прибора (not_subject_authority);

  • паспорт ссылается только на действующий сертификат соответствия (certificate_not_in_force, unknown_reference, wrong_reference);

  • у прибора один действующий паспорт: новый указывает в replaces действующий, и тот отзывается при выпуске; без replaces — passport_exists, replaces не на действующий паспорт прибора — wrong_reference;

  • карточку организации выпускает только сама организация.

6. Доступ

Что

Как

Скан, метки, схемы, чтение и проверка документов, получение DID и его истории

публично

Выпуск, отзыв, изменение DID, поиск, ленты событий

токен партнёра Authorization: Bearer <token> (если реестр запущен с токенами) и подпись стороны

Токен отвечает на вопрос «какая система вызывает», подпись — «кто из участников это решил». Токен без подписи ничего не меняет. Токен партнёра — временная мера: целевой вход — через сервис аутентификации платформы.

Упрощение для стенда 5 — токен организации. Если организация заведена на платформе с ключом у неё (CUSTODY_FILE), её система вызывает /api/v1/custody/… токеном этой организации, а подписывает платформа её ключом: POST /custody/credentials {"type":[…],"credentialSubject":{…}}, POST /custody/devices, POST /custody/dids/{did}/transfer|accept|decline, PUT /custody/dids/{did}/services. Правила реестра те же, что для подписанных запросов. Это учебное упрощение, не юридическая равнозначность УКЭП. Сценарий целиком — docs/stand5/README.md и scripts/stand5-flow.mjs.

7. Ошибки

{ "error": "invalid credential: credentialSubject.serialId (Заводской номер) is required for InstrumentPassportCredential", "code": "missing_field", "field": "serialId" }

Разбирать по code и field, текст error — для разработчика.

HTTP

code

400

bad_request, invalid_did, invalid_label

401

unauthorized (нет токена), signature_required, invalid_signature

403

not_authorized (подписавший не владелец и не контроллер)

404

not_found

409

already_exists, version_conflict

410

deactivated

422

missing_field, invalid_field, unknown_field, invalid_subject, not_subject_authority, unknown_reference, wrong_reference, certificate_not_in_force, passport_exists, out_of_scope, missing_characteristic, not_accepted, self_certification, no_mandate, type_not_allowed, runtime_not_approved, invalid_document, invalid_credential

503

dependency_unavailable

8. Что ещё изменится

Что

Как повлияет на интеграцию

Состав полей паспорта и сертификата

меняется version в /api/v1/schemas; поля — по согласованию

Подписи ГОСТ Р 34.10-2012 и сертификаты ключей X.509 (УКЭП ЮЛ)

добавится алгоритм подписи и ключ с x5c в DID-документе организации; nameVerified станет true для подтверждённых

Вход через сервис аутентификации платформы

токен партнёра заменится токеном платформы

Запись истории DID в распределённый реестр

в истории появится идентификатор транзакции (anchorTxId)

Адрес реестра в метке

фиксируется до печати меток

Несовместимые изменения повышают мажорную версию контракта; добавление необязательных полей — нет.

9. Стенд

Адрес

Что

/portal

портал станций: изготовитель (типы, приборы, паспорта, сообщения), сертифицирующая организация (сертификаты, агент), покупатель (входной контроль, приёмка, блоки)

/p/<DID>

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

/scan, /s/<DID>

страница проверки: камера, фото метки или ссылка

/swagger

полный API VC-сервиса

/api/v1/schemas

состав полей

Журнал изменений

Дата

Перечень изменений

Автор

2026-11-05

Перенесён из архива DID-VC-итоги-2026-11-03 без изменения содержания.

Дис