SettlementWitness Receipt Profiles v0.2
This page documents the signed-core field sets that Default Settlement's SettlementWitness service actually
issues today: settlement-witness-verified-v0.2 (generic) and
settlement-witness-verified-v0.2-counterparty-bound (its opt-in extension). It exists because
/integrate previously pointed verifiers here at /spec/sar-v0.1/
for "the full field set" — but that page documents a different, earlier protocol (the portable SAR v0.1 six-field
core) that SettlementWitness receipts do not use. This page is the one that is actually implementable against.
Not a new SAR protocol version
These are Default Settlement / SettlementWitness product receipt profiles, not a formal sar.v0.2
protocol release. The portable SAR protocol remains frozen at v0.1 unless a
separate, explicit protocol artifact says otherwise. A SettlementWitness receipt sets receipt_profile
to name itself explicitly — it never claims to be a sar.v0.1 receipt, and the two vocabularies
below (reason codes especially) are not interchangeable.
Profile identifiers
settlement-witness-verified-v0.2— the generic profile. Default when a caller omitsreceipt_profileon/settlement-witness/attest.settlement-witness-verified-v0.2-counterparty-bound— explicit opt-in extension of the generic profile that additionally binds a caller-suppliedcounterpartyidentifier into the signed core. Requesting it without a non-emptycounterpartyis refused (no receipt is issued); acounterpartysent without requesting this profile is also refused, never silently dropped.
receipt_version on both profiles is currently "0.2-candidate".
Generic signed-core field set — settlement-witness-verified-v0.2
Exactly these 9 fields form the signed core (field set is what matters; JCS sorts keys, so the order below is illustrative, not significant to the hash):
| Field | Type | Notes |
|---|---|---|
receipt_profile | string | Always "settlement-witness-verified-v0.2" on this profile. |
receipt_version | string | "0.2-candidate". |
sig_alg | string | Always "Ed25519". |
verdict | string | One of PASS, FAIL, INDETERMINATE. See Verdicts. |
reason_code | string | See Reason codes. |
properties | object | Property-level evaluation state, as computed by the evaluator. |
verification_basis | object | The complete evaluation basis (see below) — everything the verdict is grounded in. |
verifier_kid | string | Key id of the signing key; resolve via /.well-known/sar-keys.json. |
ts | string | Evaluation timestamp (verification_basis.evaluated_at). |
Counterparty-bound signed-core field set — settlement-witness-verified-v0.2-counterparty-bound
The same 9 fields above, plus exactly one more, required and non-empty:
| Field | Type | Notes |
|---|---|---|
counterparty | string | Caller-supplied identifier, required and non-empty on this profile. Cryptographically bound into the signed core: changing it after issuance invalidates both receipt_id and the signature. It is not an attestation that Default Settlement verified ownership, control, or identity of the identifier, and it is not a wallet-state check — it proves only that the identifier could not have been swapped after issuance. |
The verification_basis object
Exactly these 9 fields, all required, all non-empty strings (part of the signed core on both profiles above, nested under verification_basis):
| Field | Notes |
|---|---|
evaluator_id | Identity of the evaluator that produced the verdict. |
evaluator_version | Version of that evaluator. |
evaluation_profile | Which supported evaluation profile was used. |
policy_ref | Reference to the policy the evaluation was run under. |
condition_digest | sha256:<64 hex> — digest of the declared acceptance condition. |
evidence_profile | The evidence profile offered. Not restricted to a fixed allowlist here — an unsupported evidence profile is a signable INDETERMINATE verdict (see reason codes), not a request-shape error. |
evidence_digest | sha256:<64 hex> — digest of the evidence actually evaluated. |
request_id | Identifier of the originating request. |
evaluated_at | Timestamp of evaluation; duplicated at the top level as ts. |
Verdicts
Three verdicts are signable: PASS, FAIL, INDETERMINATE. A fourth internal state, EVALUATOR_TIMEOUT, exists in the evaluator's own vocabulary but is deliberately not signable — the signer refuses to issue a receipt for it (no receipt, no signature, rather than a receipt asserting a state its own basis can't justify).
Reason codes (current production)
CONDITION_SATISFIED— accompaniesPASS.CONDITION_CONTRADICTED— accompaniesFAIL. (NotCONDITION_NOT_SATISFIED— that string is not, and has never been, a live reason code in this system.)CONDITION_NOT_EVALUABLE— accompaniesINDETERMINATEwhen the declared acceptance condition itself could not be evaluated.EVIDENCE_PROFILE_UNSUPPORTED— accompaniesINDETERMINATEwhen the offered evidence profile is one this evaluator does not implement.
Different vocabulary from portable SAR v0.1
SPEC_MATCH / SPEC_MISMATCH and the rest of the reason-code list on the
portable SAR v0.1 reason-code registry are genuine, valid
codes for that separate, older, frozen protocol. They are not stale and were not rewritten. They are simply a
different vocabulary that SettlementWitness receipts (this page) do not use. Do not mix the two registries when
implementing a verifier — check which receipt_profile (or its absence, for legacy portable
receipts) you are looking at first.
receipt_id and canonicalization
- Take the signed-core object exactly as defined above for the receipt's
receipt_profile(9 fields generic, 10 counterparty-bound) — nothing more, nothing less. - Canonicalize it with JCS, RFC 8785 (recursive key-sorting, no whitespace).
- SHA-256 the canonical UTF-8 bytes.
receipt_idis"sha256:" + hex(digest).
Signature verification
- Algorithm: Ed25519 (
sig_algfield, always"Ed25519"currently). - The signature (
sig,"base64url:" + base64url-no-padding) is computed over the raw SHA-256 digest bytes from the previous step — not over the JCS-canonicalized bytes directly, and not a JOSE/JWS structure. - Resolve
verifier_kidagainst /.well-known/sar-keys.json to get the Ed25519 public key, then verify the signature over the same digest bytes. - An unrecognized
receipt_profilestring must be treated as unsupported and rejected — never guess a field set for it.
The unsigned _unsigned block
Receipts also carry receipt_id, sig, and a _unsigned object (currently just {"checks": [...]}, per-check detail) outside the signed core. _unsigned is inspectable but not part of what the signature attests — never verify it, and never treat its absence or alteration as invalidating the receipt.
Relationship to /attest's outer JWS envelope
/settlement-witness/attest's HTTP response wraps the receipt above in a separate outer response envelope that additionally carries a compact JWS (jws field, EdDSA, over the envelope payload minus _unsigned). This is a second, independent signature over different bytes than sig above. Verifying one does not verify the other. Full detail: /integrate.
Relationship to receipt lookup
GET /settlement-witness/receipt/{receipt_id} returns the flat receipt object described on this page (not the /attest response envelope, no jws field). Verify it exactly as described above. No auth required to look up a receipt by its id. Full detail: /integrate.