この文書は、TEL050JP の「証明付保管」が発行する証明パッケージの技術仕様です。識別子 tel050jp-communication-proof-v1 で示される規則を、第三者が独自に検証器を実装できる粒度で定めます。背景となる考え方は仕組みの解説を、他の版は版の一覧を参照してください。
0. 識別情報
| プロトコル識別子 | tel050jp-communication-proof-v1 |
|---|---|
| パッケージ形式識別子 | tel050jp-communication-proof-package-v1(§10) |
| 状態 | 固定(frozen)。本版の規則は変更しない(§14) |
| 固定日 | 2026-08-30 |
| 後継の版 | なし(現行) |
| 識別子が現れる場所 | communication.json の schema 欄、proof.json の protocol 欄、リーフと署名入力の LP(schema)、verify.html の PROTOCOL 定数 |
1. 適用範囲
定めるもの: 通信記録 Manifest の構造と正準化、通信記録ハッシュとリーフの計算、Merkle 木と監査経路、Symbol ブロックチェーンへ記録するメッセージの形式、発行者署名(proof.json と certificate.pdf)、証明パッケージ proof.json の構造、history.json、検証手順と判定コード。
定めないもの: 保管庫上の暗号化方式、購入・延長の業務フロー、certificate.pdf の見た目、Symbol ノードとの通信方法。これらは本版の互換性に影響しない範囲で変更されることがあります。
2. 記法
- SHA-256(x)
- FIPS 180-4 のハッシュ関数。出力は 32 バイト。
- a ‖ b
- バイト列の連結。
- LP(s)
- s を UTF-8 で符号化したバイト列の前に、そのバイト数を符号なし 16 ビット big-endian で置いたもの。65,535 バイトを超える s は拒否する。
- UINT64_BE(n) / UINT32_BE(n)
- 符号なし 64 / 32 ビット big-endian 整数。
- hex
- 16 進表記はすべて小文字。ハッシュ値は 64 桁、Ed25519 公開鍵は 64 桁、Ed25519 署名は 128 桁。
- 時刻
- RFC 3339 の UTC 秒精度
YYYY-MM-DDTHH:MM:SSZのみ。epoch(t) はその Unix 時刻(秒)。 - UUID
- 8-4-4-4-12 の小文字 hex。第 3 グループの先頭は 1〜8、第 4 グループの先頭は 8・9・a・b のいずれか。
- 0xNN
- 1 バイトの定数(ドメイン分離バイト)。
3. 通信記録 Manifest(communication.json)
1 件の通信を表す JSON オブジェクトで、次のフィールドを過不足なくもちます。未知のフィールドも欠損もすべて拒否します。
| フィールド | 型 | 制約 |
|---|---|---|
schema | string | tel050jp-communication-proof-v1 に固定 |
proof_id | string | UUID |
tenant_id | string | 空白でない |
object_uid | string | 空白でない。call:・voice_message:・fax: に通信 ID が続く |
kind | string | call / voice_message / fax |
direction | string | inbound / outbound |
started_at | string | 時刻 |
ended_at | string または null | 時刻。started_at 以降 |
duration_seconds | integer | 0 以上 |
disposition | string | 空白でない |
source_party | string または null | — |
destination_party | string または null | — |
artifact | object | 下表 |
retention | object | 下表 |
previous_proof_leaf | string または null | hex 64 桁。extension_sequence が 1 のとき null、2 以上のとき必須 |
artifact のフィールド | 型 | 制約 |
|---|---|---|
sha256 | string | hex 64 桁。原本ファイルのバイト列の SHA-256 |
archive_ciphertext_sha256 | string | hex 64 桁。保管庫上の暗号化済みオブジェクトの SHA-256 |
size_bytes | integer | 0 以上 |
media_type | string | 小文字の type/subtype |
archive_version_id | string | 空白でない |
retention のフィールド | 型 | 制約 |
|---|---|---|
extended_from | string | 時刻 |
retain_until | string | 時刻。extended_from より後 |
extension_days | integer | 1 以上 |
extension_sequence | integer | 1 以上。同じ通信に対する何回目の証明か |
previous_proof_leaf は、extension_sequence が 1 のとき必ず null、2 以上のとき必ず直前の版のリーフ(§6)です。この対応が崩れた Manifest は拒否します。履歴の切断を隠せないようにするためです。
4. 正準化
- すべての文字列とすべてのキーを Unicode NFC に正規化する。
- JSON のキーを UTF-8 バイト昇順に並べる(キーは ASCII のみ)。
- 空白を入れない(
{"a":1,"b":2})。 - UTF-8 で出力し、非 ASCII 文字をエスケープしない。
/をエスケープしない。- 数値は安全整数(253 未満の整数)のみ。浮動小数点数と、数値の文字列化を禁止する。
nullと""と欠損を区別する。- 制御文字(U+0000〜U+001F、U+007F〜U+009F)をすべての文字列で拒否する。
正準化した Manifest のバイト列を canonical_manifest と呼びます。証明パッケージに同梱される communication.json は、このバイト列そのものです。規則 8 は、制御文字が証明書上で不可視であり、JSON エンコーダごとにエスケープ表現が異なるため、同一 Manifest が実装間で別のハッシュになる経路を塞ぐためのものです。
5. 拒否条件
すべての実装は、次のいずれかに該当する Manifest を拒否します。
- 未知のフィールド、欠損したフィールド、
schemaの不一致(古い版として読み替えようとするものを含む)。 kind/directionの列挙外の値。- 大文字を含む hex、64 桁以外の hex。
- RFC 3339 UTC 秒精度以外の時刻(
+09:00、ミリ秒、存在しない日付)。 ended_at < started_at、retain_until <= extended_from。extension_sequence == 1とprevious_proof_leaf == nullの不一致。- 整数でない数値、文字列で送られた数値。
- 制御文字を含む文字列、UTF-8 として不正な文字列。
6. ハッシュとリーフ
communication_record_hash = SHA-256(canonical_manifest) artifact_hash = SHA-256(原本ファイルのバイト列そのもの) leaf = SHA-256( 0x00 ‖ LP(schema) ‖ LP(tenant_id) ‖ LP(object_uid) ‖ communication_record_hash[32] ‖ artifact_hash[32] ‖ UINT64_BE(epoch(retain_until)) ‖ UINT32_BE(extension_sequence) ‖ previous_proof_leaf[32](null のときは全ゼロの 32 バイト) )
LP の対象は NFC 正規化後の文字列で、canonical_manifest に入る文字列と同一でなければなりません。schema は本版では常に tel050jp-communication-proof-v1 です。リーフは Manifest 全体を知らなくても、proof.json がもつ 7 つの値(tenant_id、object_uid、communication_record_hash、artifact.sha256、retention.retain_until、retention.extension_sequence、retention.previous_proof_leaf)から再計算できます。
7. Merkle 木と監査経路
- バッチ内のリーフ順は
proof_idの昇順(バイト比較)に固定する。同一proof_idの重複と、空のバッチは拒否する。 - 親ノード = SHA-256(0x01 ‖ left[32] ‖ right[32])。
- 奇数規則: ある段の要素数が奇数のとき、余った最後の 1 つは複製せず、そのまま上の段に昇格する。末尾を複製する方式は、異なるリーフ集合が同じルートをもちうるため採用しない。
- リーフ 1 件のバッチのルートは、そのリーフ自身。
- 監査経路は
{"sibling": hex64, "position": "left" | "right"}の配列。昇格した段にはエントリがない。positionは sibling の側を示し、leftなら SHA-256(0x01 ‖ sibling ‖ current)、rightなら SHA-256(0x01 ‖ current ‖ sibling) で次の値を得る。
例: リーフ A、B、C(この順)のバッチでは、第 1 段が [H(A‖B), C]、ルートは H(H(A‖B) ‖ C) です。A の監査経路は [{B, right}, {C, right}]、C の監査経路は [{H(A‖B), left}] です。
8. Symbol へ記録するメッセージ
tel050jp:proof:v1 kind=communication root=sha256:<root> batch=<batch_id>
固定 156 文字。フィールド順は固定、区切りは半角スペース 1 個、末尾に空白なし、hex は小文字、未知フィールドの追加は不可です。パーサは次の正規表現に一致しないものを拒否します。
^tel050jp:proof:v1 kind=communication root=sha256:[0-9a-f]{64} batch=<UUID>$
このメッセージは、TEL050JP の Symbol 署名アカウントから所定の宛先アドレスへ送る転送トランザクションのメッセージ欄に置かれます。証明パッケージは、そのトランザクションを含むブロックの高さがファイナライズ済みの高さ以下になってから発行されます。
9. 発行者署名
署名方式は Ed25519(RFC 8032)です。署名鍵は TEL050JP の Manifest 署名鍵で、Symbol トランザクションに署名する鍵とは別のものです。
proof.json の署名
signing_input = 0x02 ‖ LP("tel050jp-communication-proof-v1")
‖ canonical_json(proof.json から signature を除いたもの)
canonical_json は §4 と同じ規則(NFC、キーの昇順、空白なし、UTF-8、スラッシュ非エスケープ)です。署名は proof.json の signature オブジェクトに入ります。
signature のフィールド | 値 |
|---|---|
algorithm | ed25519 |
public_key | hex 64 桁。署名した Ed25519 公開鍵 |
value | hex 128 桁。署名 |
署名入力の先頭 34 バイトは常に 02 001f 74656c3035306a702d636f6d6d756e69636174696f6e2d70726f6f662d7631(0x02、長さ 31、識別子の UTF-8)です。
certificate.pdf の署名
signing_input = 0x03 ‖ LP("tel050jp-communication-proof-v1") ‖ SHA-256(certificate.pdf のバイト列)[32]
certificate.pdf はダウンロード時に proof.json から描画されるため、proof.json の署名は PDF を対象にしません。代わりに、上の入力に同じ鍵で署名した certificate.sig.json を同梱します。
| certificate.sig.json のフィールド | 値 |
|---|---|
algorithm | ed25519 |
signs | certificate.pdf |
sha256 | hex 64 桁。署名時の PDF の SHA-256 |
public_key | hex 64 桁 |
value | hex 128 桁 |
ドメイン分離バイト
| バイト | 用途 |
|---|---|
0x00 | Merkle 木のリーフ(§6) |
0x01 | Merkle 木の内部ノード(§7) |
0x02 | proof.json の署名 |
0x03 | certificate.pdf の署名 |
10. 証明パッケージ proof.json(tel050jp-communication-proof-package-v1)
利用者が保持し、検証器が読む文書です。Manifest そのものは含まず、リーフの再計算に必要な値だけを含みます。
| フィールド | 型 | 内容 |
|---|---|---|
schema | string | tel050jp-communication-proof-package-v1 に固定 |
protocol | string | tel050jp-communication-proof-v1 に固定 |
proof_id | string | UUID。Manifest と同じ |
issued_at | string | 時刻。パッケージの発行時刻 |
tenant_id | string | Manifest と同じ |
object_uid | string | Manifest と同じ |
kind | string | Manifest と同じ |
artifact.sha256 | string | hex 64 桁。原本ファイルの SHA-256 |
artifact.size_bytes | integer | 原本ファイルのサイズ |
artifact.media_type | string | 原本ファイルのメディアタイプ |
communication_record_hash | string | hex 64 桁(§6) |
retention.retain_until | string | 時刻 |
retention.extension_sequence | integer | 1 以上 |
retention.previous_proof_leaf | string または null | hex 64 桁 |
merkle.leaf | string | hex 64 桁。この証明のリーフ |
merkle.audit_path | array | §7 の監査経路 |
merkle.root | string | hex 64 桁。バッチのルート |
merkle.batch_id | string | UUID |
symbol.network | string | 記録先の Symbol ネットワーク |
symbol.transaction_hash | string | 記録したトランザクションのハッシュ |
symbol.block_height | integer | そのトランザクションを含むブロックの高さ |
symbol.finalized_height | integer | 発行時点のファイナライズ済み高さ |
symbol.signer_public_key | string | 記録した Symbol アカウントの公開鍵 |
symbol.recipient_address | string | 転送先アドレス |
symbol.message | string | §8 のメッセージ |
signature | object | §9 の署名 |
proof.json(およびその symbol オブジェクト)に node_url フィールドがあってはなりません。証明が自分を検証するノードを指定することは禁止です。
ZIP の構成: proof.json、history.json(§11)、certificate.pdf、certificate.sig.json(§9)、verify.html(オフライン検証器)。通信記録つきの版では §4 の canonical_manifest そのものである communication.json を、原本つきの版では original.<拡張子> を追加します。
11. history.json
同じ object_uid に対する proof.json を retention.extension_sequence の昇順に並べた配列です。各要素の retention.previous_proof_leaf は直前の要素の merkle.leaf に等しく、先頭の要素だけが null です。sequence が飛ぶ履歴、鎖が切れた履歴は拒否します。
12. 検証手順と判定コード
検証器の入力は、proof.json、検証器自身の許可リスト(信頼する Ed25519 公開鍵の一覧、Symbol 署名者の公開鍵、宛先アドレス、ネットワーク)、任意で原本ファイル・communication.json・history.json・certificate.pdf と certificate.sig.json です。許可リストは証明の中の値ではなく、検証器に固定された値を使います。
schemaとprotocolが既知の識別子に厳密に一致すること。それ以外はunknown_schemaとし、以降の検査を行わない。node_urlがないこと。signature.algorithmがed25519、public_keyが許可リストにあり、§9 の入力に対して署名が検証できること。artifact.sha256が hex 64 桁で、原本ファイルがあればその SHA-256 と一致すること。- §6 の式で proof.json の 7 つの値からリーフを再計算し、
merkle.leafと一致すること。 merkle.audit_pathに沿ってリーフから積み上げた値がmerkle.rootと一致すること。symbol.messageが、merkle.rootとmerkle.batch_idから §8 の形式で組み立てた文字列と一致すること。symbol.signer_public_key・recipient_address・networkが許可リストと一致すること。symbol.block_height > 0かつsymbol.finalized_height >= symbol.block_heightであること。- (任意)信頼するノードに
transaction_hashを照会し、署名者・宛先・メッセージ・ブロック高が一致すること。 - communication.json があれば、そのバイト列の SHA-256 が
communication_record_hashと一致すること。 - history.json があれば、§11 の鎖が成立すること。
- certificate.pdf と certificate.sig.json があれば、PDF の SHA-256 が
sha256と一致し、§9 の入力に対して署名が検証できること。
| 判定コード | 意味 |
|---|---|
unknown_schema | 識別子が既知の版と一致しない |
proof_names_its_own_node | 証明が検証用ノードを指定している |
missing_signature | 署名がない、または方式が ed25519 でない |
untrusted_signing_key | 署名鍵が許可リストにない |
bad_signature | 署名が検証できない |
bad_artifact_hash | artifact.sha256 の形式が不正 |
artifact_does_not_match | 原本ファイルの SHA-256 が一致しない |
leaf_does_not_match_its_fields | 再計算したリーフが merkle.leaf と一致しない |
audit_path_does_not_reach_the_root | 監査経路を積み上げてもルートに一致しない |
malformed_audit_path | 監査経路の形式が不正 |
anchor_message_does_not_carry_this_root | 記録メッセージがこのルートとバッチ ID を表していない |
anchor_is_not_the_allowlisted_one | 署名者・宛先・ネットワークが許可リストと一致しない |
anchor_is_not_finalized | ブロック高がファイナライズ済み高さを超えている |
chain_disagrees_with_the_proof | ノードの回答が証明の記載と一致しない |
history_skips_a_version | 履歴の sequence が飛んでいる |
history_chain_is_broken | 履歴の前後のリーフがつながっていない |
13. テストベクタ
本版の 3 実装(PHP・TypeScript・Python)が共有するテストベクタの抜粋です。独自実装は、まずこれらの値を再現してください。
Manifest(call_first_extension)
{
"schema": "tel050jp-communication-proof-v1",
"proof_id": "3f2a1c64-6f5f-4d2e-9c1a-8b0d5e7a4411",
"tenant_id": "tel050jp",
"object_uid": "call:01JCX9K8T4V2QW3M5N7P8R9S0T",
"kind": "call",
"direction": "inbound",
"started_at": "2026-08-30T04:15:07Z",
"ended_at": "2026-08-30T04:18:22Z",
"duration_seconds": 195,
"disposition": "answered",
"source_party": "+815012345678",
"destination_party": "+815058109483",
"artifact": {
"sha256": "397c4d496b5be24cde8027f261de3adf418f1178fed7638e91d02cc90ccf0102",
"archive_ciphertext_sha256": "7e2573be8f2b70bb144640f08ed1db7744841bcee81c43b6d7ce0801151ef844",
"size_bytes": 320044,
"media_type": "audio/wav",
"archive_version_id": "3sL4kqtJlcpXroDTDmJ.rmSpXd3dIbrHY"
},
"retention": {
"extended_from": "2026-08-30T05:00:00Z",
"retain_until": "2027-08-30T05:00:00Z",
"extension_days": 365,
"extension_sequence": 1
},
"previous_proof_leaf": null
}
canonical_manifest:
{"artifact":{"archive_ciphertext_sha256":"7e2573be8f2b70bb144640f08ed1db7744841bcee81c43b6d7ce0801151ef844","archive_version_id":"3sL4kqtJlcpXroDTDmJ.rmSpXd3dIbrHY","media_type":"audio/wav","sha256":"397c4d496b5be24cde8027f261de3adf418f1178fed7638e91d02cc90ccf0102","size_bytes":320044},"destination_party":"+815058109483","direction":"inbound","disposition":"answered","duration_seconds":195,"ended_at":"2026-08-30T04:18:22Z","kind":"call","object_uid":"call:01JCX9K8T4V2QW3M5N7P8R9S0T","previous_proof_leaf":null,"proof_id":"3f2a1c64-6f5f-4d2e-9c1a-8b0d5e7a4411","retention":{"extended_from":"2026-08-30T05:00:00Z","extension_days":365,"extension_sequence":1,"retain_until":"2027-08-30T05:00:00Z"},"schema":"tel050jp-communication-proof-v1","source_party":"+815012345678","started_at":"2026-08-30T04:15:07Z","tenant_id":"tel050jp"}
| communication_record_hash | 6143dd3478a04f2b4fa108c4cef10dcf6f1ac2bda78965eaeb21e09475afa271 |
|---|---|
| leaf | 7fb734be444f24687d183f3118d6c9154645a0f4bc90ca393eb1666f698213e5 |
3 件のバッチ
| batch_id | 7b2f0c18-9d3e-4a51-b6c7-2e8f4a0d1b39 |
|---|---|
| proof_id の順(昇順) | 0a1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d 3f2a1c64-6f5f-4d2e-9c1a-8b0d5e7a4411 c7d8e9f0-1a2b-4c3d-8e4f-5a6b7c8d9e0f |
| リーフ(同じ順) | ede0abdbb444f6efd417b579ace33fd9bc51c941b2ff14f85e26d6f65070c7b3 7fb734be444f24687d183f3118d6c9154645a0f4bc90ca393eb1666f698213e5 8f78067d1eca3637c498647b4d991a61be214a8ab27d8a45c6ffc5b0eddd57a5 |
| root | 4a702235fe4922a434f675e424d971fa486ec6c97114e2034b212e64480e804c |
| Symbol メッセージ | tel050jp:proof:v1 kind=communication root=sha256:4a702235fe4922a434f675e424d971fa486ec6c97114e2034b212e64480e804c batch=7b2f0c18-9d3e-4a51-b6c7-2e8f4a0d1b39 |
リーフ 1 件のバッチでは、ルートはそのリーフ自身、監査経路は空の配列です。
14. 版の管理
- 本版は固定です。規則の変更が必要なときは新しい識別子(例:
tel050jp-communication-proof-v2)を定義し、版の一覧に追加します。 - 本版で発行された証明は、本版の規則のまま検証できる状態を維持します。
- 検証器は識別子を厳密一致で照合し、知らない版や、古い版として読み替えようとする証明を拒否します。
- §13 のテストベクタの値が変わる変更は、すべて新しい版を必要とします。
15. 参考規格
- National Institute of Standards and Technology. Secure Hash Standard (SHS). FIPS PUB 180-4, 2015.
- S. Josefsson and I. Liusvaara. Edwards-Curve Digital Signature Algorithm (EdDSA). RFC 8032, IETF, 2017.
- A. Rundgren, B. Jordan, and S. Erdtman. JSON Canonicalization Scheme (JCS). RFC 8785, IETF, 2020. 本仕様の正準化は JCS と同趣旨ですが、数値を安全整数に限定し、制御文字を拒否する点でより狭い規則です。
- G. Klyne and C. Newman. Date and Time on the Internet: Timestamps. RFC 3339, IETF, 2002.
- B. Laurie, A. Langley, and E. Kasper. Certificate Transparency. RFC 6962, IETF, 2013. リーフと内部ノードのドメイン分離の先例。
- NEM Group. Symbol Technical Reference. 2021. https://docs.symbol.dev/
本ページは技術仕様であり、法的効力や証拠としての評価について述べるものではありません。