# The quitanza format

**Format version 1**, issued by Quitanza API v0.3. This page is the normative specification: fields, canonicalization, the verification algorithm, and a worked example with real bytes you can verify without any Quitanza code.

A quitanza is a signed JSON document proving how an escrow closed: who paid whom, how much, against which terms, with what outcome, and the exact head of the evidence trail at the moment of issuance. Anyone holding the document and the issuer's public key can verify it offline.

## Fields

| Field | Type | Presence | Meaning |
|---|---|---|---|
| `id` | string | always | `qtz_` followed by 32 hex characters. |
| `escrowId` | string | always | The escrow this quitanza closes. |
| `outcome` | string | always | `released`, `refunded`, or `split`. |
| `payer` | hex(64) | always | Payer's raw 32-byte Ed25519 public key. |
| `payee` | hex(64) | always | Payee's raw 32-byte Ed25519 public key. |
| `amount` | string | always | Decimal string, e.g. `"25.00"`. Never a float. |
| `asset` | string | always | Asset symbol, e.g. `"USDC"`. |
| `termsHash` | hex(64) | always | SHA-256 of the canonical JSON of the escrow's Terms. |
| `trailHead` | hex(64) | always | Hash of the last evidence-trail entry at issuance. |
| `trailLength` | integer | always | Trail entry count at issuance. |
| `issuedAt` | string | always | ISO 8601 instant. |
| `deliveryHash` | hex(64) | when a delivery exists | SHA-256 of the canonical JSON of the delivered payload. |
| `verdictId` | string | clean settlements | The verdict that passed or failed the delivery. |
| `disputeId` | string | disputed settlements | The dispute whose ruling closed the matter. |
| `mandateHash` | hex(64) | mandated escrows | Content hash of the mandate the payer acted under. |
| `anchor` | object | chain-backed custody | Where the settlement moved funds on chain. See the Anchor addendum below. |
| `signature` | object | always | `{ "signer": hex(64), "signature": hex(128) }`, the issuer's Ed25519 signature. |

A quitanza born of a dispute carries `disputeId`; one born of a clean settlement carries `verdictId` and `deliveryHash`. A refund quitanza proves the matter closed in the payer's favor. Proof is symmetric.

## Canonicalization

Hashing and signing always operate on **canonical JSON**:

1. Object keys sorted lexicographically (code-unit order), at every nesting level.
2. No whitespace anywhere.
3. Keys whose value is `undefined` are omitted; `null` is kept.
4. Arrays keep their order; their elements are canonicalized recursively.
5. The result is UTF-8 encoded for hashing and signing.

Equivalent objects therefore produce identical bytes, so verification is byte-exact and implementation-independent.

## Verification algorithm

Given a quitanza document and the issuer's public key:

1. Remove the `signature` field; the remainder is the signed body.
2. Compute the canonical JSON of the body (rules above).
3. Check `signature.signer` equals the issuer public key you trust.
4. Verify `signature.signature` as an Ed25519 signature over the UTF-8 bytes of step 2, using that key.

To additionally bind the proof to history, fetch the evidence trail (`GET /v1/escrows/{escrowId}/trail`) and check: every entry's `hash` equals SHA-256 of the canonical JSON of `{index, prevHash, at, event, body}`, each `prevHash` matches the previous entry's `hash` (64 zeros for the genesis entry), and the entry at index `trailLength - 1` has hash `trailHead`.

## Worked example: real bytes

The document below was issued by a real engine run; every byte verifies.

```json
{
  "id": "qtz_3cd1d5ba17904e2eb1437a1011afb02c",
  "escrowId": "esc_77a341165e084c0daadcae6be877c6ca",
  "outcome": "released",
  "payer": "145acbb3f6e695d4313e4f373630c5226154d2561162cf028e5b5926877bca93",
  "payee": "e0bf901f8fb87ae3f5890afc34e80e5e65eab05df47a2855363516d838bd12b2",
  "amount": "25.00",
  "asset": "USDC",
  "termsHash": "07c1aef495dd22528506ca3a7baf3122f8d5a730c867a26ede7a20901ce0c0db",
  "trailHead": "247ea9423f4c9bb4489ce19b2ce50c6beff6f811df5d97542cc9d715cfb52071",
  "trailLength": 4,
  "issuedAt": "2026-06-10T12:00:13.000Z",
  "deliveryHash": "9f45517c2cf50d8e72bb6f8f173e25f54dc0d5daf3b196b785ea4e22f4626ea2",
  "verdictId": "vrd_664601e9f96b4777bd48313c05057ec3",
  "signature": {
    "signer": "f327d5d6dcffe11a9c81f832a525a8b3936b7a5299f82e306c4cded8e1ea7b16",
    "signature": "d86329a4c26da2545c2b874c0e02ac44191fcefcca5a931d3a8b1d615eeafc9db86c4431a0e58fb2d2e7b019476fcdaef9f4fde88f85caf47b9bcc015ca3130f"
  }
}
```

The issuer's public key is `f327d5d6dcffe11a9c81f832a525a8b3936b7a5299f82e306c4cded8e1ea7b16`. The canonical JSON of the signed body, step 2 of the algorithm (one line, keys sorted, no whitespace), is:

```
{"amount":"25.00","asset":"USDC","deliveryHash":"9f45517c2cf50d8e72bb6f8f173e25f54dc0d5daf3b196b785ea4e22f4626ea2","escrowId":"esc_77a341165e084c0daadcae6be877c6ca","id":"qtz_3cd1d5ba17904e2eb1437a1011afb02c","issuedAt":"2026-06-10T12:00:13.000Z","outcome":"released","payee":"e0bf901f8fb87ae3f5890afc34e80e5e65eab05df47a2855363516d838bd12b2","payer":"145acbb3f6e695d4313e4f373630c5226154d2561162cf028e5b5926877bca93","termsHash":"07c1aef495dd22528506ca3a7baf3122f8d5a730c867a26ede7a20901ce0c0db","trailHead":"247ea9423f4c9bb4489ce19b2ce50c6beff6f811df5d97542cc9d715cfb52071","trailLength":4,"verdictId":"vrd_664601e9f96b4777bd48313c05057ec3"}
```

Verify it with nothing but Node's standard library, no Quitanza code:

```js
import { createPublicKey, verify } from "node:crypto";

const sortKeys = (v) =>
  Array.isArray(v) ? v.map(sortKeys)
  : v !== null && typeof v === "object"
    ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, sortKeys(v[k])]))
    : v;

const quitanza = JSON.parse(/* the document above */);
const { signature, ...body } = quitanza;
const canonical = JSON.stringify(sortKeys(body));

// Raw 32-byte Ed25519 key → DER SPKI by prefixing the fixed header.
const spki = Buffer.concat([
  Buffer.from("302a300506032b6570032100", "hex"),
  Buffer.from(signature.signer, "hex")
]);
const key = createPublicKey({ key: spki, format: "der", type: "spki" });

console.log(
  verify(null, Buffer.from(canonical), key, Buffer.from(signature.signature, "hex"))
); // true
```

Change any field and verification fails: the amount, the outcome, the trail head.

## What the trail head commits to

`trailHead` is the hash of the last entry in the escrow's evidence trail when the quitanza was issued, and `trailLength` is the entry count at that moment. Because each trail entry hashes its predecessor, the head transitively commits to every recorded event: creation, funding, the delivery's content hash, each verification check's result, every piece of dispute evidence, any timeout. The quitanza is small; what it pins down is not.

## Verifying against a live API

```
GET /v1/quitanzas/{id}/verify
→ { "signatureValid": true, "trailIntact": true, "trailHeadMatches": true, "issuer": "…" }
```

Or offline with `@quitanza/core`: `verifyQuitanza(quitanza)`.

## Verifying against a domain

A deployment publishes its issuer keys at `/.well-known/quitanza-issuer.json`. `verifyQuitanzaAgainstDomain(quitanza, "quitanza.com")` in `@quitanza/sdk` fetches that document, requires the quitanza's signing key to be among the published keys, and verifies the signature offline. The CLI form is `quitanza verify <id> --domain quitanza.com`.

## Addendum: the anchor block

When custody is chain-backed, the settlement moves funds in a contract
and the quitanza carries an optional `anchor` object inside the signed
body. Sandbox settlements omit it entirely, so every version 1 document
above remains valid and the verification algorithm is unchanged: the
anchor, when present, is just another signed field.

| Field | Type | Meaning |
|---|---|---|
| `chain` | string | CAIP-2 chain id. Today always `eip155:31337`, a local anvil development node. |
| `contract` | string | Address of the escrow contract that held the funds. |
| `escrowRef` | hex(0x+64) | The escrow's id on that contract. |
| `txHash` | hex(0x+64) | The transaction that closed the matter on chain. |

Custody is local-only in this release: no public network, no real
funds. The anchor proves which transaction the issuer observed when it
signed; on a development chain that transaction exists only on the
node that produced it. The signature verifies offline either way.

### Worked example with an anchor: real bytes

Issued by a real engine run settling through QuitanzaEscrow on a local
anvil node. The same verification code as above accepts it unchanged.

```json
{
  "id": "qtz_23c712d99c8c4b6f9ce545cf45adaba7",
  "escrowId": "esc_5e39926e46904f2cb113b0675690a96b",
  "outcome": "released",
  "payer": "9f9c3a9ff4fbd30d8cd926d01c86e49756640a8680914e28f52689b29ecbea84",
  "payee": "44ad3dd06c8fd11768d9e025bf2bed0cf8769a6a37968d1ff444837b49d9c102",
  "amount": "25.00",
  "asset": "USDC",
  "termsHash": "1653b0e2c0441523d3afa70f7092433f22854004cc114967a1942ae3a67fe3e4",
  "trailHead": "be823b4b95836e18020aea0469e5472601dfbd39424a15ef7a075107bbe24124",
  "trailLength": 4,
  "issuedAt": "2026-06-10T22:30:13.000Z",
  "anchor": {
    "chain": "eip155:31337",
    "contract": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
    "escrowRef": "0x65c439bbdaab7fd6a50bba3be38e967a052c84a7bb059b525bc8e54d76229093",
    "txHash": "0x41ac0aa8893430112747b820d407d188c94a36d6862f85dcc4f696ffec070d33"
  },
  "deliveryHash": "3e68fb7b3c76277df0f62e842db9b3cef3c634caac234a9aeaf2d0aaa334688d",
  "verdictId": "vrd_fbe7431240014a1194210e4ea73a2fa6",
  "signature": {
    "signer": "85c3b227bd273d42bc375c62708f3d5bc759525d4f38a1be03d0eaa0b179b424",
    "signature": "a68ad13b2c98f5254e553adf31c261d49fabe5f815bb90fa150bd4f049ac3605c82064b06ad5043acd1a88666c12bb931eeadf85f0d6aabf10657af062d3400e"
  }
}
```

The canonical signed body, with the anchor in place (keys sorted, one
line, no whitespace):

```
{"amount":"25.00","anchor":{"chain":"eip155:31337","contract":"0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512","escrowRef":"0x65c439bbdaab7fd6a50bba3be38e967a052c84a7bb059b525bc8e54d76229093","txHash":"0x41ac0aa8893430112747b820d407d188c94a36d6862f85dcc4f696ffec070d33"},"asset":"USDC","deliveryHash":"3e68fb7b3c76277df0f62e842db9b3cef3c634caac234a9aeaf2d0aaa334688d","escrowId":"esc_5e39926e46904f2cb113b0675690a96b","id":"qtz_23c712d99c8c4b6f9ce545cf45adaba7","issuedAt":"2026-06-10T22:30:13.000Z","outcome":"released","payee":"44ad3dd06c8fd11768d9e025bf2bed0cf8769a6a37968d1ff444837b49d9c102","payer":"9f9c3a9ff4fbd30d8cd926d01c86e49756640a8680914e28f52689b29ecbea84","termsHash":"1653b0e2c0441523d3afa70f7092433f22854004cc114967a1942ae3a67fe3e4","trailHead":"be823b4b95836e18020aea0469e5472601dfbd39424a15ef7a075107bbe24124","trailLength":4,"verdictId":"vrd_fbe7431240014a1194210e4ea73a2fa6"}
```

## Etymology, briefly

A quitanza was the document a medieval creditor handed a debtor on payment: formal proof the obligation was discharged. First attested 1289, kin to English *acquit* and *quittance*. This format is that instrument, as cryptography.
