Eformogi
The Record: protocol draft 0.1 (CC BY 4.0; verifier Apache-2.0)

The reference verifier

Apache License 2.0. Part of The Record, protocol draft 0.1. Chapter 06. Verification is the normative text; this page is how you run it.

What it does

verify-offline.js is one Node file with no dependencies, no configuration, and no network access. Given a proof file downloaded from a verification page, it answers one question: is this receipt really in the signed, publicly anchored receipt log, and are the bytes the family sealed the bytes you are holding? It answers without asking Eformogi anything, which is the point. If the operator's servers went dark tomorrow, a proof file and this script would still verify.

It checks, in order:

  1. the artifact hash you computed yourself equals the sha256 recorded in the logged leaf, when you pass --sha256;
  2. leaf_hash equals SHA-256 of 0x00 followed by the leaf payload;
  3. the audit path folds from that leaf, at its index, in a tree of tree_size leaves, up to the root the signed tree head names;
  4. the signed tree head's Ed25519 signature verifies under the context eformogi-sth-v1: against the published organization key;
  5. the family's own signature over the leaf bytes verifies under the context eformogi-receipt-sig-v1: against the family public key carried in the proof, when the receipt was signed with a family key;
  6. that family key's key_id is the did:key you get by deriving one from the public key shipped alongside it, so a proof cannot name one key and hand you another.

Checks 1, 5 and 6 are skipped when their input is absent, and a skipped check is not a pass. Any failure exits non-zero and says which step failed. A proof whose leaf is newer than the latest signed tree head reports pending and exits non-zero rather than guessing.

It also prints one advisory note, which never changes the verdict: whether the family's key was established before or after the receipt was issued. Later is common and legitimate. issued_at is when the family sealed the record, so a record sealed before the family had an account, and every family's first signed receipt, will show a key that came afterwards. Treat it as context, not as a red flag.

What it does not check: whether the family later withdrew the receipt, which is only visible online today, and whether the signed tree head you hold is the one the rest of the world sees. For the second, compare the head against the public anchor repository yourself. That comparison is the whole reason the log is anchored outside our database.

Get a proof and run it

Every verification page at eformogi.com/verify/<receipt id> carries a Download proof (JSON) link under the verdict. Save that file, then:

# the whole check, offline, on the file you downloaded
node verify-offline.js tr_8e59bb9641e8-proof.json

# same thing, with the hash of the document you are actually holding
node verify-offline.js tr_8e59bb9641e8-proof.json --sha256 <64 hex characters>

# pin the organization key yourself instead of trusting the copy in the script
curl -s https://eformogi.com/.well-known/did.json > did.json
node verify-offline.js tr_8e59bb9641e8-proof.json --jwk did.json

You can also fetch a proof directly, without the page:

curl -s "https://eformogi.com/api/receipt-proof?id=tr_8e59bb9641e8" > proof.json
node verify-offline.js proof.json

Exit codes: 0 verified, 1 a step failed or the leaf is not yet covered by a head, 2 bad usage or an unreadable file. Node 18 or newer, because Ed25519 comes from node:crypto.

Run the conformance suite

run-vectors.sh feeds every vector in vectors/ through the verifier and exits non-zero if any computed verdict differs from the verdict the vector expects. Vectors that expect a failure pass when the verifier correctly refuses, which is the half of a conformance suite that is easy to skip and expensive to skip.

sh run-vectors.sh        # one line per vector
sh run-vectors.sh -v     # every step of every vector

Chapter 10. Conformance says what each vector covers and what passing them entitles you to claim. Short version: say "passes N of the M vectors in version X", name the version, and do not say "certified", because nobody issues that today.

Write your own

The specification, not this file, is normative. If your implementation and this verifier disagree, at least one of them is wrong, and the chapters decide which. Reuse the file under Apache-2.0, port it to your language, or read it as a worked example and throw it away. The vectors are the part that keeps everyone honest.

Files

FileWhat it is
verify-offline.jsThe reference verifier. One Node file, no dependencies, no network access.
run-vectors.shRuns every vector below through the verifier and exits non-zero on any mismatch.
LICENSE-Apache-2.0.txtApache License 2.0, the license of the verifier and the vectors.
LICENSE-CC-BY-4.0.txtCreative Commons Attribution 4.0, the license of the specification chapters.
NOTICE.txtCopyright holder and the two licenses, in a dozen lines.

Conformance vectors (22)

Every file below is served from this site and is covered by the Apache-2.0 license above. Fetch the files you need, or fetch one vector and run it on its own.

VectorChapterWhat it pinsExpected
v01-canonical-json.json02canonical JSON: sorted keys, no whitespace, arrays in order, unicode preservedmust verify
v02-leaf-payload-exact-bytes.json02leaf payload normalizes id and sha to lowercase, issued_at to ISO ms, keys sortedmust verify
v03-leaf-payload-rejects-ecr.json02leaf payload refuses a non-loggable prefix (ecr_ is institutional-only, never anchored)must refuse
v04-cr-derivation.json02cr_ id derives from {kind:course, student_name, course_title, term, grade, credits as posted string}must verify
v05-att-derivation-ws-target.json02att_ id derives from the public signer payload plus the lowercased email as email_hash_inputmust verify
v06-ws-derivation-v1.json02ws_ v1 id derives from {entry{kind,title,detail,evidence_url,at}, issuer, person_id, source_entry_id}; issued_at is outside the hashmust verify
v07-wn-derivation.json02wn_ id derives from {kind:weekly_note, author:kid, week_of, body}; author is fixed to kidmust verify
v08-tr-derivation.json02tr_ id derives from the whole issuance payload {student, courses, issued_at, issuer_user, person_id}must verify
v09-kb-binding-frozen.json04kb_ key binding: did:key derivation, frozen canonical payload, kb_ id and sha256must verify
v10-family-sig-valid.json04family Ed25519 signature over eformogi-receipt-sig-v1: + anchored leaf bytes verifies against the kb_ public keymust verify
v11-family-sig-context-stripped.json04a signature over the raw leaf without the context prefix MUST NOT verify (domain separation is load-bearing)must refuse
v12-org-countersig-valid.json03organization countersignature over eformogi-witness-countersig-v1: + the att_ leaf bytes verifies against the did:web keymust verify
v13-merkle-roots-frozen.json05RFC 6962 Merkle roots over 1, 2, 3, 7, 8 and 16 leaves (0x00 leaf domain, 0x01 node domain, split at largest power of two)must verify
v14-inclusion-valid.json05inclusion proof for leaf 3 of a 7-leaf tree folds to the root (RFC 9162 verification)must verify
v15-inclusion-wrong-index.json05the same audit path claimed for a different leaf index MUST NOT verifymust refuse
v16-sth-valid.json05signed tree head: Ed25519 over eformogi-sth-v1: + canonical JSON of the seven signed fieldsmust verify
v17-sth-tampered-tree-size.json05a tree head with a tampered tree_size MUST NOT verifymust refuse
v18-proof-four-steps-valid.json06the four-step inclusion-proof algorithm: artifact hash, leaf hash, inclusion, STH signaturemust verify
v19-proof-tampered-path.json06a proof with one flipped audit-path element MUST fail the inclusion stepmust refuse
v20-vc-eddsa-jcs-2022.json07W3C VC DataIntegrityProof / eddsa-jcs-2022: sha256(JCS(proof options)) || sha256(JCS(document)), signed raw with Ed25519must verify
v21-proof-with-family-signature.json06a real proof carrying issuer_signature: leaf hash, inclusion, tree head, family signature, and the did:key derivation of the family key idmust verify
v22-proof-family-signature-tampered.json06the same proof with one character of the family signature flipped MUST be refusedmust refuse