.cgfevidence バンドル形式

.cgfevidence ファイルは、単一・自己完結・署名済みのコンプライアンス成果物です。zstd 圧縮された tar アーカイブであり、固定の内部構成と Ed25519 + RFC 3161 暗号を持ちます。任意の CGF リーダーは、バンドル自身とトラスト・ストアだけでオフライン検証できます。

ディスク配置

dossier.cgfevidence            ← tar + zstd
├── manifest.json              ← 形式 / 版 / ハッシュ
├── graph.json                 ← 決定論的・内容アドレスのグラフ
├── claims.json                ← クレーム・パック結果(構造化)
├── narrative.md               ← グラフ + クレームからレンダリング
├── signatures/
│   ├── ed25519-ops@acme.sig   ← signed-payload.bin に対する分離署名
│   └── ed25519-cto@acme.sig
├── signed-payload.bin         ← 署名対象の正準バイト列
└── timestamp/
    └── freetsa.tsr            ← RFC 3161 TimeStampToken(任意)

signatures/timestamp/ を除くすべてが manifest.rootSha256 にハッシュされます。署名は signed-payload.bin を覆い、これは manifest.json の正準(キー整列・LF 終端)JSON 符号化です。これにより、署名すべきハッシュも検証すべきハッシュも一つで済みます。

manifest.json

{
  "format": "cgf",
  "version": "1.0",
  "rootSha256": "9f3a…d27e",
  "createdAt": "2026-04-12T09:14:22Z",
  "ingestor": {
    "name": "@daitai/cgf",
    "version": "1.0.0-rc.1"
  },
  "files": [
    { "path": "graph.json",    "sha256": "1e88…5a02", "bytes": 184211 },
    { "path": "claims.json",   "sha256": "73c0…b119", "bytes":  41098 },
    { "path": "narrative.md",  "sha256": "ab4f…0c7d", "bytes":  22440 }
  ],
  "claimPacks": [
    { "id": "eu-ai-act-high-risk", "version": "1.0.0", "result": "fail" },
    { "id": "gdpr-art-30",         "version": "1.0.0", "result": "pass" },
    { "id": "soc2-cc",             "version": "1.0.0", "result": "pass" }
  ]
}

rootSha256sha256(canonicalize({ files, claimPacks, ingestor, createdAt })) です。いずれかのファイル内の 1 バイトでも変われば、rootSha256 が変わり、すべての署名が失敗します。

署名スキーマ

署名は signed-payload.bin に対する Ed25519 です。ファイル名は情報目的のもので、束ねるのは埋め込まれた keyId です。

// signatures/ed25519-ops@acme.sig.json
{
  "alg":   "Ed25519",
  "keyId": "ops@acme",
  "pub":   "9c4d…ef10",          // 32 バイト生公開鍵, hex
  "sig":   "2c7b…f441",          // 64 バイト署名, hex
  "over":  "signed-payload.bin",
  "ts":    "2026-04-12T09:14:22Z"
}

複数署名者バンドルは複数の *.sig.json ファイルを含むだけです。何人必要か、各人がトラスト・ストアに在籍する必要があるかは、信頼ポリシーが決定します。

タイムスタンプ (RFC 3161)

--tsa <url> で構築された場合、タイムスタンプ局からの応答は DER 符号化された TimeStampToken としてそのまま保存されます:

timestamp/freetsa.tsr           ← 生の RFC 3161 TimeStampToken (DER)
timestamp/freetsa.json          ← 解析済みの補助コピー

解析済みコピー:

{
  "alg":  "RFC3161",
  "tsa":  "https://freetsa.org/tsr",
  "time": "2026-04-12T09:14:25Z",
  "hash": { "alg": "sha256", "value": "9f3a…d27e" }
}

hash.valuemanifest.rootSha256 と等しくなければなりません。これにより、TSA の「時刻 T 以前に存在した」証言がまさにこのバンドルに束ねられます。

検証アルゴリズム

準拠リーダーは次を行わなければなりません:

  1. バンドルをメモリ上で展開・解凍する。
  2. manifest.files の各エントリの sha256 を再計算し比較する。
  3. rootSha256 を再計算し manifest.rootSha256 と比較する。
  4. 各署名について、埋め込みの pub を用いて signed-payload.bin に対する Ed25519 検証を行う。
  5. timestamp/*.tsr が存在すれば解析し、TSA の CMS 署名を検証し、messageImprintrootSha256 と一致することを確認する。
  6. 有効な TrustPolicyトラスト・ストアとポリシー・プリセット 参照)を適用し、pass または構造化された違反の一覧を出力する。

ステップ 1–5 は暗号操作であり、どのマシンでも同一です。ステップ 6 はポリシーであり、検証側組織のリスク許容度を反映します。

例: 最小の有効バンドル

demo.cgfevidence
├── manifest.json
├── graph.json
├── claims.json
├── narrative.md
├── signed-payload.bin
└── signatures/
    └── ed25519-alice.sig.json

これだけ — TSA なし、署名者は一人。タイムスタンプを要求しない任意のポリシーに対して signed, untimestamped, 1/1 signers trusted として検証されます。

次に読むもの