# Public verification of contractor copies

This directory runs without PrivCloud, a database, an account or a production
secret. It contains the watermark algorithm and evidence verifier used by the
application, a format specification and destructive tests with synthetic inputs.
It does not provide the private application's contractor service.

Use Node 24.15 or later, Python 3.8 or later, OpenSSL and Chrome/Chromium:

```sh
cd verification/contractor
npm ci
npm test
npm run benchmark -- results.local.json
```

For the downloaded ZIP, run these commands inside its extracted directory.

Set `CHROME_PATH` when Chrome is not `/usr/bin/google-chrome`. Tests fail if a
required tool is absent. They do not silently substitute a PNG round trip for a
screenshot. Screenshots load a local data document in Chrome without a network
server. API fixtures use HTTPS on loopback with an ephemeral certificate trusted
only by the CLI child process. Certificate and hostname checks remain enabled.
Signature and timestamp tests
run offline. npm needs network access only to install the pinned image library.

`npm test` creates an actual ES256 signature and RFC 3161 timestamp responses using
an ephemeral local test CA, verifies them with the production verifier, and
rejects altered originals, manifests, signatures, events, timestamps, rewritten
chains and truncated journals. The test CA is explicitly trusted by the test
only. It is not an independent time authority, a production authority, or evidence
of a qualified timestamp. No private signing key is stored in this directory.

`npm test` also reruns the destructive watermark benchmark. `results.json` is the
measured reference run. `RESULTS.md` explains its matrix. Each result identifies
the exact algorithm by SHA-256, its decoded pixel hash, dimensions, score, tool
versions and detection time. `WATERMARK_REPORT=/tmp/results.json npm test` saves
the newly measured matrix. Times depend on the machine and concurrent load.

A successful test checks specific examples. It does not prove resistance to all
images or attacks. Three fixtures and eight candidate copies do not calibrate a
real-world false attribution rate. The Gaussian estimate inside the detector is
not such a calibration. Rotation and deliberate erasure have documented failures.

## Verify a real evidence archive

Obtain the TSA root, expected WebAuthn origin/RP ID and a checkpoint from sources
you trust independently. Do not publish a team's audit key, real evidence or
contractor personal data. Retain the checkpoint outside the application **before**
a compromise. Then, with a later evidence ZIP and the original file:

```sh
python3 -I verifier.py evidence.zip \
  --strict --ca trusted-root.pem \
  --expected-origin https://your-application.example \
  --rp-id your-application.example \
  --original original.pdf --checkpoint independently-retained-checkpoint.json
```

Exit codes: 0 means the requested checks passed, 1 means failure, 2 means unreadable
or malformed input. Without `--strict`, missing timestamps and unknown TSA roots
remain explicit warnings and can still produce exit code 0. That mode is not a
complete anti-compromise verification.

The application includes `checkpoint.json` in its evidence ZIP once both the
issuance record and a journal head have timestamp tokens. It does not invent a
checkpoint when the TSA is unavailable. The hourly sealing job runs at minute 55.
Events after the latest successfully timestamped head are not yet anchored.
Downloading a new checkpoint after a compromise cannot establish the earlier
state. The timestamp proves that an imprint existed by its certified time; it
does not certify the precise occurrence time declared in each event.

## Scope of the claims

- Read-only access exposes encrypted rendered page copies to an authorized,
  unexpired contractor seat. It does not grant the original file, original
  decryption key or team membership. Enforcement belongs to server authorization
  tests in the private application. An offline verifier cannot prove deployment
  of those access checks. A displayed page can still be captured or photographed.
- SHA-256 comparison detects a changed original relative to the signed manifest.
  It does not decide whether the original was truthful or malicious when issued.
- A signature binds the manifest to a key. UP/UV are authenticator-reported flags.
  This archive alone does not prove hardware key storage, a physical person's
  identity, legal qualification or a non-exportable credential.
- A recognized watermark identifies an issued pattern. It does not prove who
  leaked the document. An absent watermark does not prove that a document never
  came from that copy. Team key holders can generate patterns themselves.
- Trusted timestamps and an independently retained checkpoint detect changes to
  the issuance record or the retained journal head, including a later rehash or
  truncation. A checkpoint kept only in a compromised database has no independent
  retention guarantee. A rewrite of an unanchored tail remains outside that proof.
- The chain proves continuity of the supplied link range. Other copies are
  represented by opaque content hashes. This privacy-preserving export cannot
  prove completeness of the detailed events attributed to one copy, or events
  that the server never recorded. `chainValidAtGeneration` is an informational
  server assertion, not evidence accepted instead of recomputing the hashes.

See [FORMAT.md](FORMAT.md) for exact fields and hashing rules. The source snapshots
are byte-checked against the application by its regression tests. The public
page is served by v2 at `/verification/contractor`. Rebuild the static assets
with `node scripts/build-public-verification.mjs` before deploying v2.
No GitHub synchronization is needed to serve these tools.

## Independently test deployed read-only access

`node read-only.mjs /private/path/test-tenant.json` runs actual HTTPS requests against
an existing test tenant, including an owner positive control on the original.
It verifies the contractor's rendered page ciphertext against a pinned hash,
refuses original bytes, direct-download URLs, original key grants, team keys and
team access, and checks revoked, expired and another seat's copies. It rejects
HTML/WAF responses and redirects. It does not create accounts or send invitations.
Opening preview pages can append ordinary consultation events to the tested tenant.

The private JSON configuration needs `origin` (HTTPS), `teamId`, `shareId`, `fileId`,
`previewId`, `revokedPreviewId`, `expiredPreviewId`, `otherPreviewId`,
`pageCipherSha256` (from the approved page manifest), and `ownerHeaders` /
`contractorHeaders` containing the respective `Cookie` or `Authorization` headers.
Use distinct dedicated test sessions and prepared, known-existing resources.
Keep this configuration outside the repository. The report contains only check
names, HTTP status and pass/fail; it excludes sessions, resource IDs and bodies.

The public unit tests verify this HTTPS runner against a loopback fixture, including
an intentionally open original route, altered ciphertext and WAF HTML. They also
reject cleartext origins, redirects, untrusted certificates and wrong hostnames. A fixture
passing is **not** a successful audit of the production deployment. No live SaaS
credentials are included and no production result is claimed here.
