Proof package specification tel050jp-communication-proof-v1

This document is the technical specification of the proof package issued by TEL050JP proof storage. It states the rules identified by tel050jp-communication-proof-v1 precisely enough for a third party to implement a verifier of their own. The ideas behind them are explained on the how it works page; other versions are listed on the versions page.

0. Identification

Protocol identifiertel050jp-communication-proof-v1
Package format identifiertel050jp-communication-proof-package-v1 (§10)
Statusfrozen: the rules of this version do not change (§14)
Frozen on2026-08-30
Successornone (current)
Where the identifier appearsthe schema field of communication.json, the protocol field of proof.json, LP(schema) in the leaf and in every signing input, the PROTOCOL constant of verify.html

1. Scope

Defined here: the structure and canonicalization of the communication record manifest, the communication record hash and the leaf, the Merkle tree and audit paths, the form of the message recorded on the Symbol blockchain, the issuer signatures (over proof.json and over certificate.pdf), the structure of the proof package proof.json, history.json, and the verification procedure with its result codes.

Not defined here: the encryption used in the archive, the purchase and extension workflow, the appearance of certificate.pdf, and how a Symbol node is contacted. These may change without affecting compatibility with this version.

2. Notation

SHA-256(x)
The FIPS 180-4 hash function. 32-byte output.
a ‖ b
Concatenation of byte strings.
LP(s)
The UTF-8 encoding of s, preceded by its length in bytes as an unsigned 16-bit big-endian integer. An s longer than 65,535 bytes is rejected.
UINT64_BE(n) / UINT32_BE(n)
Unsigned 64 / 32-bit big-endian integers.
hex
All hexadecimal is lowercase. Hashes are 64 digits, Ed25519 public keys 64 digits, Ed25519 signatures 128 digits.
timestamp
RFC 3339 in UTC with second precision only: YYYY-MM-DDTHH:MM:SSZ. epoch(t) is its Unix time in seconds.
UUID
8-4-4-4-12 lowercase hex. The third group starts with 1 to 8, the fourth with 8, 9, a or b.
0xNN
A one-byte constant (domain separation byte).

3. The communication record manifest (communication.json)

A JSON object describing one communication. It carries exactly the following fields; an unknown field and a missing one are both rejected.

FieldTypeConstraint
schemastringfixed to tel050jp-communication-proof-v1
proof_idstringUUID
tenant_idstringnot blank
object_uidstringnot blank; call:, voice_message: or fax: followed by the communication id
kindstringcall / voice_message / fax
directionstringinbound / outbound
started_atstringtimestamp
ended_atstring or nulltimestamp, not before started_at
duration_secondsinteger0 or more
dispositionstringnot blank
source_partystring or null—
destination_partystring or null—
artifactobjectsee below
retentionobjectsee below
previous_proof_leafstring or null64 hex digits; null when extension_sequence is 1, required when it is 2 or more
Field of artifactTypeConstraint
sha256string64 hex digits; SHA-256 of the original file's bytes
archive_ciphertext_sha256string64 hex digits; SHA-256 of the encrypted object in the archive
size_bytesinteger0 or more
media_typestringlowercase type/subtype
archive_version_idstringnot blank
Field of retentionTypeConstraint
extended_fromstringtimestamp
retain_untilstringtimestamp, after extended_from
extension_daysinteger1 or more
extension_sequenceinteger1 or more; which proof this is for the same communication

previous_proof_leaf must be null exactly when extension_sequence is 1, and must be the leaf (§6) of the previous version whenever it is 2 or more. A manifest that breaks this rule is rejected, so that a gap in the history cannot be hidden.

4. Canonicalization

  1. Normalize every string and every key to Unicode NFC.
  2. Sort JSON keys by their UTF-8 bytes, ascending (keys are ASCII only).
  3. No whitespace ({"a":1,"b":2}).
  4. Emit UTF-8 and do not escape non-ASCII characters.
  5. Do not escape /.
  6. Numbers are safe integers only (integers below 253). Floating point numbers and numbers written as strings are forbidden.
  7. null, "" and an absent field are three different things.
  8. Reject control characters (U+0000 to U+001F, U+007F to U+009F) in every string.

The bytes of the canonicalized manifest are called canonical_manifest. The communication.json bundled in a proof package is exactly these bytes. Rule 8 exists because a control character is invisible on a certificate and is escaped differently by every JSON encoder, which would be the one remaining way for the same manifest to hash differently in different implementations.

5. Rejection conditions

Every implementation rejects a manifest that meets any of the following.

  • An unknown field, a missing field, or a schema mismatch (including an attempt to pass the manifest off as an older version).
  • A kind or direction outside the enumerations.
  • Uppercase hex, or hex that is not 64 digits.
  • A timestamp that is not RFC 3339 UTC at second precision (+09:00, milliseconds, a date that does not exist).
  • ended_at < started_at, or retain_until <= extended_from.
  • extension_sequence == 1 disagreeing with previous_proof_leaf == null.
  • A non-integer number, or a number sent as a string.
  • A string containing a control character, or one that is not valid UTF-8.

6. Hashes and the leaf

communication_record_hash = SHA-256(canonical_manifest)
artifact_hash             = SHA-256(the exact bytes of the original file)

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] (32 zero bytes when null)
)

The strings passed to LP are the NFC-normalized values, identical to the strings inside canonical_manifest. In this version schema is always tel050jp-communication-proof-v1. The leaf can be recomputed without the manifest, from the seven values proof.json carries: tenant_id, object_uid, communication_record_hash, artifact.sha256, retention.retain_until, retention.extension_sequence and retention.previous_proof_leaf.

7. The Merkle tree and audit paths

  • Leaves in a batch are ordered by proof_id, ascending by bytes. A duplicate proof_id and an empty batch are rejected.
  • Parent node = SHA-256(0x01 ‖ left[32] ‖ right[32]).
  • Odd rule: when a level has an odd number of nodes, the last one is promoted to the next level unchanged, not duplicated. Duplicating the last node would let two different sets of leaves share one root.
  • The root of a one-leaf batch is that leaf.
  • An audit path is an array of {"sibling": hex64, "position": "left" | "right"}. A promoted level contributes no entry. position is the side of the sibling: left gives SHA-256(0x01 ‖ sibling ‖ current), right gives SHA-256(0x01 ‖ current ‖ sibling).

Example: for leaves A, B, C in that order, the first level is [H(A‖B), C] and the root is H(H(A‖B) ‖ C). The audit path of A is [{B, right}, {C, right}]; the audit path of C is [{H(A‖B), left}].

8. The message recorded on Symbol

tel050jp:proof:v1 kind=communication root=sha256:<root> batch=<batch_id>

Fixed length of 156 characters. Field order is fixed, fields are separated by exactly one space, there is no trailing whitespace, hex is lowercase, and no field may be added. A parser rejects anything that does not match the following expression.

^tel050jp:proof:v1 kind=communication root=sha256:[0-9a-f]{64} batch=<UUID>$

The message is placed in the message field of a transfer transaction sent from the TEL050JP Symbol signing account to a fixed recipient address. A proof package is issued only once the height of the block containing that transaction is at or below the finalized height.

9. Issuer signatures

The signature scheme is Ed25519 (RFC 8032). The signing key is the TEL050JP manifest signing key, which is a different key from the one that signs Symbol transactions.

The signature over proof.json

signing_input = 0x02 ‖ LP("tel050jp-communication-proof-v1")
                     ‖ canonical_json(proof.json without its signature field)

canonical_json follows the rules of §4 (NFC, sorted keys, no whitespace, UTF-8, unescaped slashes). The signature is stored in the signature object of proof.json.

Field of signatureValue
algorithmed25519
public_key64 hex digits; the Ed25519 public key that signed
value128 hex digits; the signature

The first 34 bytes of the signing input are always 02 001f 74656c3035306a702d636f6d6d756e69636174696f6e2d70726f6f662d7631 (0x02, the length 31, the identifier in UTF-8).

The signature over certificate.pdf

signing_input = 0x03 ‖ LP("tel050jp-communication-proof-v1") ‖ SHA-256(the bytes of certificate.pdf)[32]

certificate.pdf is rendered from proof.json when the package is downloaded, so the signature over proof.json does not cover it. Instead the package carries certificate.sig.json, a signature by the same key over the input above.

Field of certificate.sig.jsonValue
algorithmed25519
signscertificate.pdf
sha25664 hex digits; SHA-256 of the PDF at signing time
public_key64 hex digits
value128 hex digits

Domain separation bytes

ByteUse
0x00Merkle leaf (§6)
0x01Merkle inner node (§7)
0x02signature over proof.json
0x03signature over certificate.pdf

10. The proof package proof.json (tel050jp-communication-proof-package-v1)

The document the customer keeps and the verifier reads. It does not contain the manifest itself, only the values needed to recompute the leaf.

FieldTypeContent
schemastringfixed to tel050jp-communication-proof-package-v1
protocolstringfixed to tel050jp-communication-proof-v1
proof_idstringUUID, as in the manifest
issued_atstringtimestamp; when the package was issued
tenant_idstringas in the manifest
object_uidstringas in the manifest
kindstringas in the manifest
artifact.sha256string64 hex digits; SHA-256 of the original file
artifact.size_bytesintegersize of the original file
artifact.media_typestringmedia type of the original file
communication_record_hashstring64 hex digits (§6)
retention.retain_untilstringtimestamp
retention.extension_sequenceinteger1 or more
retention.previous_proof_leafstring or null64 hex digits
merkle.leafstring64 hex digits; the leaf of this proof
merkle.audit_patharraythe audit path of §7
merkle.rootstring64 hex digits; the root of the batch
merkle.batch_idstringUUID
symbol.networkstringthe Symbol network written to
symbol.transaction_hashstringhash of the recording transaction
symbol.block_heightintegerheight of the block containing it
symbol.finalized_heightintegerfinalized height at issue time
symbol.signer_public_keystringpublic key of the recording Symbol account
symbol.recipient_addressstringthe transfer's recipient address
symbol.messagestringthe message of §8
signatureobjectthe signature of §9

proof.json (and its symbol object) must not carry a node_url field. A proof may not name the node that verifies it.

Contents of the ZIP: proof.json, history.json (§11), certificate.pdf, certificate.sig.json (§9) and verify.html (the offline verifier). The variant with the communication record adds communication.json, which is exactly the canonical_manifest of §4; the variant with the original adds original.<extension>.

11. history.json

An array of the proof.json documents for the same object_uid, ordered by retention.extension_sequence ascending. Each entry's retention.previous_proof_leaf equals the merkle.leaf of the entry before it, and only the first entry has null. A history that skips a sequence number or breaks the chain is rejected.

12. Verification procedure and result codes

A verifier takes proof.json, its own allowlist (the trusted Ed25519 public keys, the Symbol signer public key, the recipient address and the network), and optionally the original file, communication.json, history.json, and certificate.pdf with certificate.sig.json. The allowlist is fixed inside the verifier; it is never taken from the proof.

  1. schema and protocol match the known identifiers exactly. Otherwise the result is unknown_schema and no further check runs.
  2. There is no node_url.
  3. signature.algorithm is ed25519, public_key is on the allowlist, and the signature verifies over the input of §9.
  4. artifact.sha256 is 64 hex digits and, when the original file is given, equals its SHA-256.
  5. The leaf recomputed from the seven values of proof.json by the formula of §6 equals merkle.leaf.
  6. Folding the leaf up merkle.audit_path yields merkle.root.
  7. symbol.message equals the string built from merkle.root and merkle.batch_id in the form of §8.
  8. symbol.signer_public_key, recipient_address and network match the allowlist.
  9. symbol.block_height > 0 and symbol.finalized_height >= symbol.block_height.
  10. (Optional) A trusted node, asked about transaction_hash, reports the same signer, recipient, message and block height.
  11. When communication.json is given, the SHA-256 of its bytes equals communication_record_hash.
  12. When history.json is given, the chain of §11 holds.
  13. When certificate.pdf and certificate.sig.json are given, the SHA-256 of the PDF equals sha256 and the signature verifies over the input of §9.
Result codeMeaning
unknown_schemathe identifiers do not match a known version
proof_names_its_own_nodethe proof names a node to verify it with
missing_signatureno signature, or a scheme other than ed25519
untrusted_signing_keythe signing key is not on the allowlist
bad_signaturethe signature does not verify
bad_artifact_hashartifact.sha256 is malformed
artifact_does_not_matchthe original file's SHA-256 differs
leaf_does_not_match_its_fieldsthe recomputed leaf differs from merkle.leaf
audit_path_does_not_reach_the_rootfolding the audit path does not yield the root
malformed_audit_paththe audit path is malformed
anchor_message_does_not_carry_this_rootthe recorded message does not express this root and batch id
anchor_is_not_the_allowlisted_onesigner, recipient or network differ from the allowlist
anchor_is_not_finalizedthe block height is above the finalized height
chain_disagrees_with_the_proofthe node's answer differs from the proof
history_skips_a_versiona sequence number is missing from the history
history_chain_is_brokenconsecutive history entries are not linked by their leaves

13. Test vectors

An excerpt of the test vectors shared by the three implementations of this version (PHP, TypeScript and Python). An independent implementation should reproduce these values first.

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_hash6143dd3478a04f2b4fa108c4cef10dcf6f1ac2bda78965eaeb21e09475afa271
leaf7fb734be444f24687d183f3118d6c9154645a0f4bc90ca393eb1666f698213e5

A batch of three

batch_id7b2f0c18-9d3e-4a51-b6c7-2e8f4a0d1b39
proof_id order (ascending)
0a1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d
3f2a1c64-6f5f-4d2e-9c1a-8b0d5e7a4411
c7d8e9f0-1a2b-4c3d-8e4f-5a6b7c8d9e0f
leaves (same order)
ede0abdbb444f6efd417b579ace33fd9bc51c941b2ff14f85e26d6f65070c7b3
7fb734be444f24687d183f3118d6c9154645a0f4bc90ca393eb1666f698213e5
8f78067d1eca3637c498647b4d991a61be214a8ab27d8a45c6ffc5b0eddd57a5
root4a702235fe4922a434f675e424d971fa486ec6c97114e2034b212e64480e804c
Symbol messagetel050jp:proof:v1 kind=communication root=sha256:4a702235fe4922a434f675e424d971fa486ec6c97114e2034b212e64480e804c batch=7b2f0c18-9d3e-4a51-b6c7-2e8f4a0d1b39

In a one-leaf batch the root is the leaf itself and the audit path is an empty array.

14. Versioning

  • This version is frozen. When a rule has to change, a new identifier (for example tel050jp-communication-proof-v2) is defined and added to the versions page.
  • Proofs issued under this version remain verifiable under the rules of this version.
  • Verifiers compare identifiers for exact equality and refuse an unknown version, as well as a proof that tries to pass as an older one.
  • Any change that alters the test vectors of §13 requires a new version.

15. Normative references

  • 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. The canonicalization here has the same intent as JCS but is stricter: numbers are limited to safe integers and control characters are rejected.
  • 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. The precedent for separating leaves from inner nodes.
  • NEM Group. Symbol Technical Reference. 2021. https://docs.symbol.dev/

This page is a technical specification. It says nothing about legal effect or about how a proof is weighed as evidence.