# Contractor evidence and checkpoint format, version 1

All hashes below are lowercase hexadecimal SHA-256. Encoded strings marked
`base64url` use the URL-safe alphabet without padding. RFC 3161 responses use
standard base64 and contain DER `TimeStampResp`, not just the CMS token.

## Deterministic JSON

`C(value)` recursively emits compact JSON, sorting object keys with the
application's `localeCompare` order. The schema has fixed ASCII keys. Arrays
retain their order. Strings retain their exact Unicode characters and escaping
as emitted by `JSON.stringify`. The Python verifier implements the same key order
for this schema. Integers are JSON integers, timestamps are ISO UTC strings
with millisecond precision. This format is **not RFC 8785 JCS**. The manifest is
stored as its exact serialized string; verify its byte hash before reserializing.
`H(value)` below is SHA-256 over the UTF-8 bytes of `C(value)`.

## Evidence object

`format = "privcloud-contractor-issuance-evidence-v1"`. `generatedAt` and
`howToVerify` are informational and are not signed. `issuance` contains:

| Field | Meaning |
| --- | --- |
| `id` | Issuance identifier |
| `manifest` | Exact deterministic JSON string described below |
| `manifestHash` | SHA-256 of UTF-8 `manifest` bytes |
| `webauthn.credentialId` | base64url credential identifier |
| `webauthn.publicKeyCoseBase64Url` | base64url CBOR COSE public key |
| `webauthn.algorithm` | ES256 `-7` or RS256 `-257` supported by the verifier |
| `webauthn.authenticatorDataBase64Url` | base64url raw authenticatorData |
| `webauthn.clientDataJSONBase64Url` | base64url raw clientDataJSON |
| `webauthn.signatureBase64Url` | base64url raw WebAuthn signature |
| `webauthn.userHandleBase64Url` | Optional user handle, informational |
| `recordHash` | Hash of the record below |
| `rfc3161TimeStampRespBase64` | Timestamp over raw `recordHash` bytes, or null |

The manifest has `protocol = "privcloud-contractor-issuance-v1"`, `nonce`,
`issuedAt`, `team: {id,name}`, `issuer: {userId,email}`,
`contractor: {seatId,email,name}` (`name` may be null), `accessExpiresAt`, and:

```json
{
  "copy": {
    "previewId": "copy-id",
    "sourceFileId": "source-id",
    "sourceSha256": "64 lowercase hex characters",
    "markId": "32 lowercase hex characters",
    "pageCount": 1,
    "pages": [{
      "plainSha256": "64 lowercase hex characters",
      "cipherSha256": "64 lowercase hex characters",
      "width": 1920,
      "height": 1440
    }]
  }
}
```

The signed WebAuthn challenge equals `base64url(bytes(manifestHash))`.
`clientDataJSON.type` must equal `webauthn.get`. Verify the signature over
`authenticatorData || SHA256(rawClientDataJSON)`, UP/UV flags and the expected RP ID
hash. The default verifier uses the origin hostname as RP ID. Supply `--rp-id`
for a parent-domain RP ID or native origin, and `--expected-origin` to pin the
expected origin independently of the archive. A COSE key supplied by the archive
alone is not an independent identity certificate.

`recordHash` is `H` of this object, whose values are copied exactly:

```text
{
 protocol: "privcloud-contractor-issuance-v1",
 manifest: issuance.manifest,
 credentialId: issuance.webauthn.credentialId,
 credentialPublicKey: issuance.webauthn.publicKeyCoseBase64Url,
 credentialAlgorithm: issuance.webauthn.algorithm,
 authenticatorData: issuance.webauthn.authenticatorDataBase64Url,
 clientDataJSON: issuance.webauthn.clientDataJSONBase64Url,
 signature: issuance.webauthn.signatureBase64Url
}
```

## Journal

`journal.protocol = "privcloud-contractor-journal-v1"`; `journal.teamId` matches
the manifest's team. Each event has `seq`, `at`, `kind`, `previewId`, `seatId`,
`actorEmail`, `pageIndex`, `prevHash` and `hash`. Kinds currently used are `ISSUED`,
`OPENED`, `PAGE`, `DELETED`, `REVOKED` and `VAULT`. The last four content fields
may be null where the event allows it.

```text
contentHash = H({protocol,teamId,at,kind,previewId,seatId,actorEmail,pageIndex})
hash = H({protocol,seq,prevHash,contentHash})
```

The genesis `prevHash` is 64 zeros. Consecutive links have `seq = previous.seq + 1`
and `prevHash = previous.hash`. `journal.links` includes `{seq,prevHash,contentHash,hash}`
for the exported range, including opaque links for other copies. Detailed events
must match their corresponding links. Duplicate sequence numbers are rejected.
Retained ranges after deletion/retention can begin after genesis. The export does
not certify completeness of omitted event details.

`journal.anchors[]` contains `{seq,hash,rfc3161TimeStampRespBase64}`. A timestamp
imprint is the **32 decoded bytes** of the selected link hash. Verify the RFC 3161
response signature, imprint and certificate chain against an independently chosen
trust anchor. All prior links connected to that head are committed by it. A latest
unanchored tail is explicitly incomplete in strict mode.

The platform attempts hourly head sealing at minute 55 and retries pending issuance
timestamps. Failure leaves missing tokens visibly missing. No successful network
response or server-computed hash is substituted for a verified TSA response.

## Independently retained checkpoint

`checkpoint.json` has this minimal format:

```text
{
 format: "privcloud-contractor-checkpoint-v1",
 issuance: {recordHash,rfc3161TimeStampRespBase64},
 journal: {teamId,seq,hash,rfc3161TimeStampRespBase64}
}
```

Retain it outside the server before a suspected compromise. Verify both timestamp
responses and compare the retained `recordHash` and journal `(teamId,seq,hash)`
to the later archive. Missing or different retained head means the later range
cannot establish continuity with that checkpoint. Trusting the checkpoint received
with the later archive is insufficient to prove a prior state.

## Watermark

`MARK_TILE=128`, 32 x 32 chips, amplitude 3 RGB levels, detection threshold 8.
HMAC-SHA256 of UTF-8 `privcloud:forensic-mark:v1:<markId>:<block>` under the team's
32-byte audit key supplies 1024 pseudo-random chip signs over four blocks. The
smooth periodic tile is added to clamped RGB pixels; alpha is unchanged. Detection
estimates scale, folds pixels onto the tile and correlates candidate patterns.
Supported searched scales are 0.35 to 2.5. There is no rotation compensation.

The key is required to reconstruct an exact pattern. It is not required to erase
pixels destructively. Pixel recreation, thresholding and some transformations can
remove the mark. Pattern recognition is separate from file-byte integrity checks.
The benchmark uses a public demonstration key, never a production key.

Primary standards: [WebAuthn](https://www.w3.org/TR/webauthn-3/) and
[RFC 3161](https://www.rfc-editor.org/rfc/rfc3161).
