3P فارسی Start writing

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. 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. 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. 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. 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.

Try a countersigned card

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.

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

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.

Start your record.

Start writing