Подписанные операции DID
Создание, изменение и деактивация DID Document принимаются только с подписью контроллера или владельца, указанных в документе. Каждая ревизия хранит подпись и звено хэш-цепочки, поэтому историю DID можно перепроверить: правка ревизии в базе обнаруживается.
Кто вправе подписать
Поле документа | Смысл |
| DID (строка или массив), которые управляют документом. Не задан — документом управляет сам DID |
| DID организации, от имени которой действует субъект (ИИ-агент, устройство, персона). Подписывает наравне с контроллером |
| ключи, которыми подписант может менять DID-документы. Если отношение у подписанта не задано, подходит ключ из |
Полномочия проверяются по текущей сохранённой версии документа (для создания — по создаваемому документу). Поэтому изменение не может само вписать нового контроллера и тут же им подписаться.
Согласие владельца. Владелец отвечает за субъекта, поэтому назначить owner (при создании или изменении) может только подпись самого этого владельца. Самоподписанный DID не может объявить организацию своим владельцем. Передача владения — в два шага: текущий контроллер или владелец добавляет новую организацию в controller, затем новая организация своей подписью устанавливает себя в owner (и при желании убирает себя из controller).
Ключ подписанта берётся из его DID Document: собственный — из того же документа, ключ контроллера или владельца — из реестра. Деактивированный DID подписывать не может.
Режимы
Переменная DID_OPERATION_PROOF:
Значение | Поведение |
| подписи не проверяются |
| подпись есть — проверяется строго; подписи нет — операция проходит, ревизия помечается как неподписанная |
| операция без действительной подписи отклоняется |
В режиме optional неподписанная операция не проверяется вовсе — в том числе на полномочия и на согласие владельца. Гарантии этого сценария действуют только для подписанных операций, полностью — в режиме required.
Формат подписи
Заголовок DID-Operation-Proof — compact JWS (RFC 7515):
protected header:
alg(EdDSA,ES256,ES384,ES512,RS256,PS256),kid— DID URL ключа подписанта (did:example:org#key-1),typ: did-operation+jws;payload:
{ "op": "update", "did": "did:ru:agent:z7iAK46JKsNfPgWkUErXXhtWXH7827MKHqRWeZnmA19C1", "prevVersionId": "1", "bodyDigest": "<base64url SHA-256 точных байт тела запроса>", "iat": 1792105200 }
Операция | HTTP |
|
|
создание |
|
| не передаётся |
замена |
|
| текущий |
изменение |
|
| текущий |
деактивация |
|
| текущий |
Подпись привязана к DID, к версии и к телу запроса. Повтор той же подписи после применения даёт 409 (версия уже другая), подпись старше 5 минут (iat) отклоняется.
Самосертифицируемые DID
DID метода did:ru (did:ru:agent|device|org:<id>, спецификация) всегда самосертифицируемые: <id> = z + base58btc от SHA-256 канонического JWK (RFC 7638) первого ключа capabilityInvocation начального документа. Такой DID нельзя занять, не имея ключа. У агента и устройства обязателен owner. Готовый DID печатает bin/didsign keygen -type agent.
То же правило можно включить для других префиксов: DID_SELF_CERTIFYING_PREFIXES (через запятую, например did:scada:device:; по умолчанию пусто).
Утилита didsign
go build -o bin/didsign ./cmd/didsign bin/didsign keygen # seed (секрет) и публичный JWK для verificationMethod bin/didsign keygen -type agent # + DID did:ru:agent:…, выведенный из ключа bin/didsign sign -seed <seed> -kid did:example:org#key-1 \ -op create -did $AGENT_DID -body agent.json # значение заголовка
Подписывается файл с телом ровно в том виде, в каком он уйдёт в запрос (curl --data-binary @file).
Сценарий: организация регистрирует ИИ-агента
Сервис запущен с DID_OPERATION_PROOF=required, B=http://localhost:8080/api/v1.
Ключи организации и агента:
bin/didsign keygen > org.key,bin/didsign keygen > agent.key.Документ организации
org.jsonс ключомdid:example:org#key-1вverificationMethod,authenticationиcapabilityInvocation.Без подписи:
curl -X POST $B/dids -H 'Content-Type: application/json' --data-binary @org.json # 401, WWW-Authenticate: DID-Operation-Proof
Организация подписывает свой документ:
P=$(bin/didsign sign -seed $ORG_SEED -kid did:example:org#key-1 -op create -did did:example:org -body org.json) curl -X POST $B/dids -H 'Content-Type: application/json' -H "DID-Operation-Proof: $P" --data-binary @org.json # 201
bin/didsign keygen -type agent > agent.key,AGENT_DID— строкаdid:из вывода. Документ агентаagent.jsonс"id": "$AGENT_DID","owner": "did:example:org"и собственным ключом вassertionMethodиcapabilityInvocation. Создание подписывает организация-владелец:
P=$(bin/didsign sign -seed $ORG_SEED -kid did:example:org#key-1 -op create -did $AGENT_DID -body agent.json) curl -X POST $B/dids -H 'Content-Type: application/json' -H "DID-Operation-Proof: $P" --data-binary @agent.json # 201
Агент добавляет себе service endpoint (
patch.json), подписывая своим ключом:
P=$(bin/didsign sign -seed $AGENT_SEED -kid $AGENT_DID#key-1 -op update -did $AGENT_DID -prev 1 -body patch.json) curl -X PATCH "$B/dids/$AGENT_DID" -H 'Content-Type: application/merge-patch+json' \ -H "DID-Operation-Proof: $P" --data-binary @patch.json # 200; повтор того же запроса — 409
Та же подпись, сделанная чужим ключом с kid организации, — 401 invalid did operation proof: invalid JWS signature.
Организация деактивирует агента (
deact.json={"didDocumentMetadata":{"deactivated":true}},-op deactivate -prev 2) —200.Проверка истории:
curl "$B/dids/$AGENT_DID/history/verify"
valid: true 1 create verified did:example:org 2 update verified $AGENT_DID 3 deactivate verified did:example:org
Если изменить ревизию 2 прямо в базе (
UPDATE did_revisions SET document = ...), проверка истории вернётvalid: falseи для ревизии 2 —brokenс причинамиdigest does not match the stored revisionиstored document differs from the signed request.
Проверка истории
GET /v1/dids/{did}/history/verify для каждой ревизии:
пересчитывает digest и сверяет звено
prevDigestс предыдущей ревизией;проверяет подпись ключом подписанта в той версии его документа, что действовала на момент операции (поздняя ротация ключа старые подписи не ломает);
восстанавливает документ из сохранённого подписанного тела запроса и сравнивает с сохранённым — ревизию не подделать, даже пересчитав digest.
Статус | Значение |
| digest, звено цепочки и подпись верны |
| цепочка верна, операция принята без подписи (режим |
| ревизия записана до появления цепочки |
| ревизия изменена после записи или подпись не проходит; |
headDigest — digest последней ревизии: значение для закрепления в HLF.
Чего проверка внутри сервиса не ловит: подмену неподписанных ревизий целиком и обрезку хвоста истории. Это закрывает закрепление headDigest в реестре HLF.
Коды ответов
Код | Когда |
| нет подписи в режиме |
| подписант не контроллер и не владелец; новый |
| подпись сделана для другой версии документа |
Identity-сервис
С IDENTITY_SYSTEM_KEY (base64url seed Ed25519, секрет) identity-сервис публикует этот ключ в системном DID, становится owner DID персон и подписывает их создание. Без переменной DID персон регистрируются без подписи — это работает только в режимах off и optional.
Алгоритмы ГОСТ
Подписи ГОСТ Р 34.10-2012 подключаются через didproof.Verifiers.Register с проверкой на стороне PKI-сервиса; в стандартной поставке их нет.
Журнал изменений
Дата | Перечень изменений | Автор |
2026-11-05 | Перенесён из архива DID-VC-итоги-2026-11-03 без изменения содержания. | Дис |