# Run a witness This is the cheapest useful thing an outside party can contribute to a Contubernium deployment, and the only one that makes the rest of it mean anything. A **witness** watches an append-only log and cosigns what it sees — but only after refusing everything it should. It holds no measurements, makes no claims about electricity, and needs no relationship with any publisher. Two HTTP endpoints and a file on disk. ## Why anybody should bother The log in a Contubernium deployment is not trusted. It proves things about itself — that an entry is included, that a new history extends an old one — and a party running it could still show two different histories to two different readers, each internally perfect. That is the attack the whole structure is arranged around. Witnesses are the answer, and only if they are somebody else's. A reader declares whose cosignature it requires ([decision 0003](decisions/0003-witness-policy-is-the-readers.md)), and a checkpoint carrying a signature from a party the log operator does not control is evidence the log operator cannot manufacture. If the log operator also runs the witnesses, every check still passes and nothing has been established — which is exactly the state this project is in today, recorded as [decision 0004](decisions/0004-one-author-plays-every-role.md) with an expiry rather than a caveat. So: one witness run by one outside party is the difference between a demonstration and evidence. You do not have to trust the deployment, agree with it, or read the rest of these documents. ## What a witness must do Four refusals. Everything else is plumbing, and a witness that signs without them is worse than no witness at all — a reader's policy would report satisfaction it did not earn. 1. **Fetch, never accept.** A cosign request names a log and a tree size. It does *not* carry the checkpoint. You go to the log for the checkpoint yourself. A witness that signs what its caller handed over is a notary: the caller chose what got attested. 2. **Refuse a rollback.** A tree smaller than the one you last cosigned. 3. **Refuse an equivocation.** A second, different root at a size you have already signed. Two roots at one size cannot both be honest, and signing both manufactures the very evidence a reader relies on you to make impossible. Keep the conflicting checkpoint and report it. 4. **Refuse a rewritten history.** Growth for which no consistency proof from what you last cosigned verifies. This is where tampering is caught *before* publication rather than discovered in an audit afterwards. And one thing that is not a refusal but is just as load-bearing: **your state must be durable before you answer.** A witness that forgets what it cosigned across a restart will sign a rewritten history without noticing, which is precisely the attack it exists to prevent. [specification.md §13.4](specification.md) is the normative version, and §10.6–10.8 define the tree, the checkpoint, and the consistency proof. The signing input is §10.6. ## The short way: run the reference implementation ``` curl -sO https://contubernium.io/artifacts/contubernium-src.tar.gz tar xzf contubernium-src.tar.gz && cd contubernium-src pip install -e '.[ed25519]' python scripts/witness_key.py --name witness-acme --out ~/.contubernium/witness.key \ --base-url https://witness.acme.example:8801 --operator "Acme Metering" ``` That writes a 32-byte secret from your operating system's CSPRNG, mode 0600, and prints the public half as a JSON block. **The secret never leaves your machine and is never transported by any part of this system.** Do not use the demonstration keys for this. In the prototype deployment a node's secret is `sha256(node name)`, so anybody who knows a node is called `witness-1` can sign as `witness-1`. That is safe only because a demonstration protects nothing, and it is exactly the property your witness must not have. Then write a description naming the log you intend to follow and where your secret lives — `witness.json`: ```json { "log": "contubernium.prototype.log", "witnesses": ["witness-acme"], "publishers": [], "monitors": [], "required_witnesses": ["witness-acme"], "quorum": 1, "validity": {"start": "2025-01-01T00:00:00Z", "end": "2035-01-01T00:00:00Z"}, "demonstration": false, "ports": {"contubernium.prototype.log": 8899, "witness-acme": 8801}, "data_dir": "./witness-state", "secrets": {"witness-acme": "/home/you/.contubernium/witness.key"} } ``` `"demonstration": false` is the important line: it makes derived keys an error rather than a default, so a misconfiguration fails loudly instead of signing with a secret anybody could recompute. `secrets` holds *paths*, never key material. `data_dir` is where the last cosigned checkpoint is written before any signature is returned — put it somewhere that survives a restart. The log's entry in `ports` is where you expect *its* process to be listening. Then: ``` python -m contubernium.service.run --config witness.json --node witness-acme ``` ## The better way: write your own An independent implementation is worth more than a copy of ours running elsewhere, because a bug in ours is then a bug in both. The interface is small enough for an afternoon: | method | path | what it does | | --- | --- | --- | | `GET` | `/health` | `role`, `witness_id`, `follows` | | `POST` | `/cosign` | `{"log_id": "s:…", "tree_size": "i:…"}` → checkpoint + signature, or **409** | | `GET` | `/latest` | the last checkpoint you cosigned for a log, signed | You will need: the tagged wire encoding (§13.2 — every scalar declares its type, because a JSON number is a float in most parsers), the checkpoint signing input (§10.6), and RFC 6962 consistency proof verification (§10.8). `tests/vectors/` pins all of it case by case, so you can check your encoder and your proof verifier before you ever open a socket. Refusals are **409** with the reason in the body. Word them however you like — nothing checks your prose. ## Check yourself before anybody names you A witness is only worth naming if it actually refuses. So there is a harness that tries to make yours fail: ``` python scripts/check_witness.py --witness http://localhost:8801 \ --public-key --data-dir ./conformance ``` It stands up **its own log** on port 8899, under the log id `contubernium.conformance.log` (pass `--log-id` to change either). Point your witness at it and start the witness first — with the configuration above, that means setting the log's port to 8899 and its name to the harness's log id. The harness then drives that log through a rollback, an equivocation, and a rewritten prefix, plus the notary trap: it offers you a checkpoint in the request body and fails you if you sign it. Then: ``` # restart your witness, then: python scripts/check_witness.py --witness http://localhost:8801 \ --public-key --data-dir ./conformance --after-restart ``` The second run is the durability check, and it needs a restart because nothing inside a single run can tell a durable witness from a lucky one. The harness reports each property separately and marks the four that are the witness. It is designed to fail: `tests/test_witness_conformance.py` runs it against a rubber stamp, a credulous notary, an amnesiac, and a witness signing with the wrong key, and asserts that each is caught. A harness that only ever passes would hand out confidence rather than measure it. ## Joining a deployment Send the operator the block `witness_key.py` printed, **by a route they already trust**. Not through the log, not through this repository — a key that arrived over the channel it is supposed to secure establishes nothing. Email it, put it on your own website, exchange it in a contract, read it over the phone. ```json { "name": "witness-acme", "base_url": "https://witness.acme.example:8801", "public_key": "3b1f…", "algorithm": "ed25519", "operator": "Acme Metering" } ``` The operator adds it under `external_witnesses` in their deployment description. From that moment their publishers, notary, monitors, and readers all ask you alongside their own witnesses — and the deployment can no longer derive your key, start your node, or produce a cosignature in your name. A reader that then names you in its witness policy is requiring a signature nobody in the deployment can produce. `DeploymentSpec.independence()` says so in as many words, and says the opposite — that the deployment still establishes nothing an outside party checked — when you are named but not required. ## What you are signing up for, exactly **You are attesting: "the log at this identifier showed me this tree, at this size, with this root, at this time."** Nothing about electricity, nothing about whether any published condition is true, and nothing about the publishers. You are not endorsing the project. **You can stop whenever you like.** A witness that goes away stops answering; readers requiring you will report their policy unsatisfied, which is the correct and visible outcome. Nothing is stranded and nothing of yours is at stake — there is no bond, no token, and no staking anywhere near this role. **What it costs.** One small process, a few HTTP requests per epoch, and a file that must survive a restart. **What it does not give you.** No ability to censor (a publisher whose checkpoint you refuse can still publish; your refusal is information, not a veto), no access to any measurement, and no privileged view — you see exactly what the log shows you, which is the reason `GET /latest` exists for readers to compare witnesses against each other. ## Honest statement of where this stands Nobody outside this project currently runs a witness. The mechanism described here works and is tested; the arrangement it enables does not yet exist. Decision 0004 names three things that must happen before any deployment under licence — an independent implementer, an independent adversary, and witnesses the log operator does not run — and this document addresses the cost of the third, not the fact of it. Running one is what changes that, and no amount of tooling here can do it for you.