主体を人から資料へ移す
ここまでは「この人は本学の所属者だ」でした。同じ道具立てで「この資料のこの版を、本学が確かに公開した」を言えます。
対象にしたのは IIIF(International Image Interoperability Framework、画像を機関を越えて相互利用するための枠組み)のマニフェストです。マニフェストは、資料のタイトル・所蔵者・権利表示・画像への参照などをまとめた JSON で、デジタルアーカイブでは事実上の標準になっています。
主体を人から資料に移すと、三者モデルが二者になります。
【人が主体のとき】
大学 ──発行──→ 中村 ──提示──→ 機関C
↑ 毎回 challenge に署名
【資料が主体のとき】
大学 ──署名──→ マニフェストの隣に置く ──→ 誰でも取得して検証
資料は秘密鍵を持てないので challenge に署名できません。提示という段が丸ごと消えます。 証明書はマニフェストの隣に静的ファイルとして置かれ、取得した者が誰でも検証します。
名宛人がいないので「盗んだ証明書の使い回し」を止める必要もありません。そもそも誰のものでもないからです。
人にはなかった問題が出る
代わりに、新しい問題が出ます。マニフェストは更新されます。 誤字の修正、記述の追加、権利表示の変更、画像の差し替え。日常的に起きます。
何に署名するかで、結果が変わります。
| 署名の対象 | 起きること |
|---|---|
| URI だけ | 中身を差し替えても署名が通る。無意味 |
| ハッシュだけ | 誤字ひとつ直すと無効。運用に耐えない |
| URI + ハッシュ + 版の日時 | 「この URI の、この版に対して保証した」と読める |
3点セットに署名する形を採りました。ファイルは資料の隣に置いてあります。
| パス | 公開 URL | |
|---|---|---|
| マニフェスト | docs/iiif/genji/manifest.json | https://nakamura196.github.io/did-log/iiif/genji/manifest.json |
| 証明書 | docs/iiif/genji/credential.jwt | 同 /credential.jwt |
| 凍結版 | docs/iiif/genji/manifest.2026-10-01.json | 同 /manifest.2026-10-01.json |
証明書の中身(credential.jwt の payload を展開したもの)はこうなります。
"credentialSubject": {
"id": "https://nakamura196.github.io/did-log/iiif/genji/manifest.json",
"type": "IIIFManifest",
"attestation": {
"ja": "本機関は、この URI において、下記のハッシュを持つ版を公開したことを表明する。"
},
"version": {
"digestAlgorithm": "SHA-256",
"canonicalization": "sorted-keys-json",
"digestValue": "942f87616e3558da30c5b6cbd867c491b9b43d8d86af860dc661f535fca64d68",
"capturedAt": "2026-10-01T00:00:00Z",
"archived": "https://…/iiif/genji/manifest.2026-10-01.json"
}
}
attestation に書いてある通り、主張しているのは「この版を確かに公開した」だけです。「内容が正しい」とは一言も言っていません。ここは意図的に弱くしてあります。
canonicalization を書き残しているのは、ハッシュを取る前の JSON の整え方を明示するためです。JSON はキーの順序や空白が自由なので、同じ内容でも書き方が違えばハッシュが変わります。何で正規化したかを書いておかないと、後から検証できません。
この書き方は決まっているのか
外側は規格通り、中身は独自です。 分けて見る必要があります。
| この試作 | 規格の定め | |
|---|---|---|
@context / type / issuer / issuanceDate | 規格通り | VC Data Model が定義 |
credentialSubject.id | 資料の URI | URI であればよい。DID である必要はない |
version / attestation / digestValue | 独自の語彙 | 定めなし |
credentialSubject.id に DID が入っていないのは、規格上まったく問題ありません。VC Data Model が要求しているのは「主体を指す URI」であって、DID はその一形態にすぎないからです。第2章では主体が人だったので DID を使いましたが、ここでの主体は資料で、資料には IIIF のマニフェスト URI という既存の識別子があります。わざわざ DID を新設する理由がありません。
一方、version の中身(digestValue / canonicalization / capturedAt / archived)は私が決めた名前です。ここに互換性はありません。他の実装が読んでも意味が分かりません。
標準側に何があるか
「参照先の資料が変わっていないことを証明書に埋める」という要求自体は、規格の側でも扱われています。
- VC Data Model 2.0(W3C 勧告、2025-05-15)の §5.3 が
relatedResourceを定めています。参照する資源のidと、そのダイジェスト(digestSRIまたはdigestMultibase)をまとめて持たせるための property で、目的はここでやっていることと同じです - 同じ2つのダイジェスト property は
relatedResourceの外でも使えます。idを持つ任意のオブジェクトに、どちらか一方を添えてよいと規定されています digestSRIは、Web の Subresource Integrity(SRI)のintegrity属性の文法をそのまま借用したものです。「似た技術が Web 側にもある」のではなく、SRI を直接持ち込んでいます(もう一方のdigestMultibaseは VC Data Integrity 1.0 §2.6 の定義を参照しています)
つまり 「URI + ハッシュ」までは標準の作法です。この試作が足しているのは残り2つで、
capturedAt(版の日時) — 標準の digest には「いつの版か」が入りません。更新をdriftedとして扱うには時点が要りますarchived(凍結版の URI) — どこが変わったかを後から言うために、署名した版そのものを残します
唯一の解ではありません。 digestMultibase だけで済ませて「一致しなければ無効」と割り切る実装も当然あり得ますし、そのほうが標準に忠実です。ここで3点にしたのは、資料は更新されるという前提を捨てられなかったからで、その判断の帰結が次に見る3値の判定です。
判定を3値にする
検証の結果を、受理か拒否かの2値ではなく3値にしました。
(この判定は、第6章で名簿を導入したところで4値になります。そこで valid の意味も変わるので、この章の valid は暫定的なものとして読んでください。)
| 判定 | 状態 |
|---|---|
valid | 署名が通り、手元のマニフェストが署名された版そのもの |
drifted | 署名は通るが、別の版。拒否しない |
invalid | 署名が通らない / 期限切れ / 失効 / 別の資料の証明書 |
drifted を用意するのが要点です。
- 拒否にすると、所蔵機関は誤字ひとつ直せなくなります
- 通してしまうと、署名の意味が消えます
第三の状態を置いて、判断を利用者に返します。更新され続ける資料に対する妥当な落とし所だと考えています。
実際の出力です。
31 検証 (資料版) — docs/iiif/genji/manifest.json (発行者を web で解決)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✔ 発行者の署名 did:web:nakamura196.github.io:did-log の鍵で検証OK
✔ 有効期限 (無期限)
✔ 失効リスト 該当なし
✔ 対象 URI の一致 https://…/iiif/genji/manifest.json
✔ 正規化方式の一致 sorted-keys-json
✔ 署名者の信用 名簿にある — 例示大学
✔ 内容ハッシュの一致 942f87616e3558da…
判定: valid 署名された版そのもの
資料と VC と発行者の鍵を一緒にアーカイブすれば、所蔵機関が消えた後でも検証できる。
署名者の信用 の行は、この章の話ではありません。第6章で扱う名簿の機能が先に出てしまっているだけなので、いまは読み飛ばしてください。
判定 の下の行が、この方式のいちばん大きな利点だと思います。証明書は 1,000 文字程度の文字列なので、資料と一緒に保存できます。 発行元が消えた後も、公開鍵さえどこかに残っていれば検証が成立します。
版が動いたときに何が起きるか
4つのシナリオを試しました。
| 何をするか | 判定 | |
|---|---|---|
| A | 誤字を直す(summary の1文字) | drifted |
| B | 権利表示を CC BY → CC BY-NC に書き換える | drifted |
| C | canvas が指す画像を差し替える | drifted |
| D | 別の資料のマニフェストに証明書を付け替える | invalid |
実行結果です。
[A] 誤字を直す — summary の文言を1文字直す
判定 drifted
変更 summary
[B] 権利表示を書き換える — rights を CC BY 4.0 → CC BY-NC 4.0
判定 drifted
変更 rights
[C] 画像を差し替える — canvas/1 が指す画像を別のものにする
判定 drifted
変更 items …/did-log/iiif/genji/canvas/1
[D] 別の資料に VC を付け替える — マニフェストの id だけを別の資料のものにする
判定 invalid
A と B が区別できない
これがこの方式の限界です。
誤字の修正も、権利表示の書き換えも、同じ drifted になります。 ハッシュは二値なので「どこが変わったか」までしか言えず、「それが重大か」は判断できません。
C も同じです。画像の差し替えは資料としては重大ですが、機構上は A と同格に扱われます。IIIF のマニフェストは記述と画像への参照を1つの文書に混ぜているので、「記述の更新」と「実体の差し替え」を分けたければ、署名の対象を分けるしかありません。たとえば rights だけを対象にしたライセンス証明を、別の証明書として発行する、といった設計です。
D だけが弾ける理由
対象の URI を証明書に書いてあるからです。ハッシュだけに署名していたら、これは検出できませんでした。URI とハッシュの両方が要る理由がここにあります。
凍結版を隣に置く
「どこが変わったか」を後から言うには、署名した版そのものが要ります。ただし証明書にマニフェストを丸ごと埋めると、肥大して配れなくなります。
そこで、署名した版を manifest.2026-10-01.json として凍結し、隣に置いて、証明書の version.archived から指す形にしました。
版を保存しておくのは、もともとアーカイブの仕事です。 分業として素直に収まりました。凍結版が手に入らない検証者は「違う」までしか言えませんが、valid / drifted / invalid の判定自体は変わりません。
循環しない工夫
マニフェストの seeAlso から証明書を指しています。
"seeAlso": [{
"id": "https://…/iiif/genji/credential.jwt",
"type": "Dataset",
"format": "application/jwt",
"profile": "https://www.w3.org/2018/credentials/v1"
}]
一見すると循環しそうですが、しません。URI は先に決まっていて中身に依存しないので、seeAlso が入った状態のマニフェストをそのままハッシュして署名できます。
循環するのは、証明書をマニフェスト本体に埋め込もうとしたときです。隣に別ファイルとして置く限り、この問題は起きません。
引き継いだ限界
ここまでで、資料の版に署名できるようになりました。ただし前章の問題はそのまま残っています。
公開鍵の履歴を機関が自分でホストしている以上、機関はその履歴を作り直せます。作り直せば、過去に存在しなかった版の証明書を、後から作れます。
「2026年10月にこの版を公開した」という主張の裏付けは、結局のところ機関の善意に依存したままです。
そしてもうひとつ、この章の検証には手前の穴が残っています。ここまでの valid が言っているのは「この証明書が名指した DID の鍵で署名が検証できた」ことだけで、その DID を信じてよいかは誰も言っていません。次章はそこから始めます。