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:
- Object keys sorted lexicographically (code-unit order), at every nesting level.
- No whitespace anywhere.
- Keys whose value is
undefinedare omitted;nullis kept. - Arrays keep their order; their elements are canonicalized recursively.
- 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:
- Remove the
signaturefield; the remainder is the signed body. - Compute the canonical JSON of the body (rules above).
- Check
signature.signerequals the issuer public key you trust. - Verify
signature.signatureas 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.
{
"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:
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.
{
"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.