For organisations
Countersigning one entry
A person asks you to confirm one thing: a course they finished, a rent they paid, a job they held. You confirm that one entry with a key published at your own domain. Their record stays with them.
A countersignature counts only if its key is published at your own domain.
Four steps
- 1
The person shows you one entry
They show you the entry to be confirmed and its content hash. Not their record, and none of their other entries.
- 2
You countersign that hash
With a key you publish at
https://<your domain>/.well-known/3p-keys.json. The countersignature (3p.countersign/1) is over that entry's hash and this person's identity, and names your domain and the key's id. - 3
The person signs their card after
Your countersignature goes into the entry first. Their signature then covers it, so it cannot be removed or moved without breaking the card.
- 4
Anyone checks it
The verifier fetches your domain's key file and checks the key, its purpose and its dates. Then it says “countersigned by a key published at <your domain>”. That is the domain it fetched, never a name the card claims for itself.
On the verifier, choose the Countersigned sample. It is an invented course, countersigned by this site's demo key.
The key file
Format 3p.domain-keys/1, served at exactly /.well-known/3p-keys.json on your domain, over HTTPS, with Access-Control-Allow-Origin: * so a verifier in a browser can read it. Only the exact domain counts: not a subdomain, and not a redirect to another host.
keys: each with akid, the public key (jwk),purpose(it must includecountersign),valid_fromandvalid_until.retired: old keys, kept listed with their end date, so the countersignatures made in their time still count.revoked: the ids of keys that no longer count at all, for example a stolen one.
Demo This is the real file at 3rdpersona.com/.well-known/3p-keys.json. It identifies no institution and vouches for nothing real.
{
"format": "3p.domain-keys/1",
"domain": "3rdpersona.com",
"keys": [
{
"kid": "demo-2026",
"jwk": {
"crv": "P-256",
"kty": "EC",
"x": "Ug-XJSfB-wzSnaLLWM_UdAHJtcbvsPI9aoRNQxU6vuI",
"y": "wqF1mLltHUcCUJezV58_XPg0aYHRPIw-rva8xFqIM5M"
},
"purpose": [
"countersign"
],
"valid_from": "2026-10-01",
"valid_until": null,
"demo": true
}
],
"retired": [],
"revoked": [],
"note": "DEMO KEY. 3rdpersona.com publishes this key so anyone can try a countersigned packet in the verifier. It identifies no institution and vouches for nothing real."
}
The commands
From domain_keys.py in the 3P node. It needs Python 3 and openssl, and nothing else.
# 1. Make the key file (creates inst-key.pem if it does not exist; keep that file off the web server)
python3 domain_keys.py keyfile example.org inst-key.pem --kid 2026-registrar > 3p-keys.json
# then host it at https://example.org/.well-known/3p-keys.json, with Access-Control-Allow-Origin: *
# 2. Countersign entry f1 of a card the person has NOT signed yet
python3 domain_keys.py countersign packet.json f1 --domain example.org --kid 2026-registrar --key inst-key.pem
# 3. Check every countersignature in a card, online
python3 domain_keys.py check packet.json
Countersigning a card that is already signed is refused: the person's signature has to come after yours.
What does not count
- A key your domain does not publish. The signature may be valid, but the verifier says “example.org does not publish this key”. Test:
test_a_key_the_domain_does_not_publish_does_not_count - Another domain's key file. A file served from your domain that names another domain is refused. Test:
test_a_key_file_for_another_domain_does_not_count - A countersignature moved to another entry, or altered. It is over one entry's hash and no other. Test:
test_a_countersignature_moved_to_another_fact_or_altered_does_not_count
Also refused: a revoked key, a key not listed for countersign, and a date outside the key's validity. If your file cannot be fetched, the verifier says “claims example.org; not checked online”. It neither passes nor fails it.
What the check tells you
The verifier fetches your key file from the reader's own browser, each time it checks a card. So your server learns that a card claiming your countersignature is being checked, and when, and from which address. The request does not include the card.