# Solidarity: receipt attestation

Status: draft 2 (2026-10-22). Part of the `solidarity` proposal, whose x402 extension is [solidarity.md](solidarity.md). A proposal: not part of x402 or any other protocol, and not reviewed or endorsed by their maintainers. Author: Craig Merry, craigmerry.com, written with AI assistance (Claude Code). No fund exists and no money moves.

This document is normative for logs. It specifies what a log publishes and how anyone checks it: lines, closed monthly batches (RFC 6962 Merkle trees), JCS statements signed with JWS and chained month to month, witness and fund cosignatures, receipts, keys and endpoints. It does not depend on the payment rail: a facilitator's log for x402 shares ([solidarity.md](solidarity.md)) and a processor's log for card and bank rails ([processor-mode.md](processor-mode.md)) publish exactly the same things. The last sections define the optional `batch-settlement` share kind for x402 logs and give test vectors.

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, RECOMMENDED and MAY are used as described in RFC 2119 and RFC 8174. EVM hex values (addresses, transaction hashes, channel ids) compare case-insensitively, and lines and settlement entries write them lowercase.

A log **accepts** a share when it records it for a period. An accepted share whose value never reaches the fund is **uncollected**: it is in no batch, total or settlement reference, and `/proofs/<id>` reports it as uncollected.

## Lines

Each recorded share is one line of printable ASCII, with fields separated by `|` and no field containing `|` or a space:

```
solidarity/2|<period>|<fund>|<asset>|<id>|<amount>
```

- `period`: `YYYY-MM`, the UTC month of the batch. For an x402 log, the month the log accepted the share; for a processor's log, the month the processor collected it.
- `fund`: a CAIP-10 account, or for a fund off chain, a URI listed in the log's descriptor.
- `asset`: the x402 asset string (x402 v2 §5.1.2): a token address, or an ISO 4217 code with amounts in minor units. EVM hex addresses in `fund` and `asset` are lowercase; other address formats are written as their network defines them.
- `id`: the share's id.
- `amount`: base-10 atomic units, greater than zero, with no leading zeros.

Lines carry no owner, no seller, no item and no exchange rate. A line's amount still shows how large the purchase was (amount divided by rate).

## Batches

There is one closed batch per `(log, period)`:

- Lines are sorted bytewise. A batch that repeats an `id` is invalid, whether or not the lines are identical.
- The root is the Merkle Tree Hash of RFC 6962 section 2.1: leaf hash `SHA-256(0x00 || line)`, node hash `SHA-256(0x01 || left || right)`. An empty batch has size 0 and the root `SHA-256("")`, and is still published.
- Uncollected shares are removed before the tree is built.
- A batch closes when its statement is issued, and is never extended or edited after that. Nothing that happens to a purchase later, including a refund or chargeback, changes a closed batch.

## Statements

A statement payload is a JSON object serialized with JCS (RFC 8785):

| Field | Meaning |
| --- | --- |
| `version` | `2`. |
| `log` | The log's URL. |
| `period` | `YYYY-MM`. |
| `size` | Number of lines. |
| `root` | Hex Merkle root. |
| `totals` | One row per `(fund, asset)` with any line: `{fund, asset, lines, amount}`, sorted bytewise by `fund`, then `asset`. `amount` is a decimal string. |
| `settlements` | Hex SHA-256 of the period's settlements file (see "Settlement references"). |
| `previous` | Hex SHA-256 of the previous period's payload bytes, or `null` only for the log's first period. |
| `issuedAt` | Unix time in seconds. |

Payload values are limited to strings, non-negative integers below 2^53, arrays, objects and `null`, so a minimal JCS implementation is exact. Amounts are always strings: at 18 decimals, 2^53 atomic units is less than 0.01 of a token. A verifier MUST reject a payload whose bytes are not the JCS form of its own content, and a payload with any other field or with a field of another type or form (for example a `period` that is not `YYYY-MM`, a hash that is not 64 lowercase hex characters, or a totals row that is out of order or not in the form lines use).

Totals are gross. A log MUST NOT deduct its costs from shares. Totals equal what reached each fund for the period, as the settlement references show.

The statement for period P SHOULD appear within 15 days after P ends and MUST appear before the statement for the next period.

## Settlement references

Each period has a settlements file: one entry per settlement, sorted bytewise with no entry repeated, each followed by one LF (an empty period has an empty file).

```
<fund>|<asset>|<ref>|<amount>
```

Entries are written like lines: printable ASCII, no field contains `|` or a space, `<fund>` and `<asset>` are written as in lines, and `<amount>` is base-10 atomic units, greater than zero, with no leading zeros. `<ref>` is one of the following, where `<transaction>` and `<channelId>` contain no `:`, and in EVM hex are `0x` and 64 lowercase hex digits:

| `ref` | Meaning |
| --- | --- |
| `exact:<transaction>` | An `exact` share's transfer ([solidarity.md](solidarity.md), "Share kinds"). |
| `claim:<transaction>:<channelId>` | The increase in that channel's `totalClaimed` made by that claim and attributed to this period (see "Optional share kind: `batch-settlement`"). One claim MAY be split across several periods; the parts never add up to more than the increase. |
| `payout:<reference>` | A processor's payment to the fund, with a reference that no other log paying that fund uses (for example, one that starts with the log's host) and that the fund can match in its own records ([processor-mode.md](processor-mode.md)). |

For each `(fund, asset)`, the amounts add up to the totals row. A verifier MUST reject a settlements file with an entry in any other form.

## Signatures and keys

A statement is signed with the envelope of the offer-and-receipt extension ([extension-offer-and-receipt.md](https://github.com/x402-foundation/x402/blob/main/specs/extensions/extension-offer-and-receipt.md) §3.1), `{"format": "jws", "signature": "<JWS compact serialization>"}`. The JWS header has `alg` (`EdDSA` or `ES256`) and `kid`, a DID URL (§3.3 of that document). The JWS payload is the statement's JCS bytes. Draft 2 defines JWS only. This is the envelope resource servers already use to sign offer-and-receipt receipts.

The log's current key is authorized as in that document's §4.5.1, with the log's URL in place of `resourceUrl`: a `did:web` document on the log's host, or a DNS TXT record at `_controllers.<host>`. DID documents and DNS show only current state (§4.5.1, last paragraph), so the log's descriptor lists every statement key it has used, each with its public key as a JWK (RFC 7517) and the periods it signed (see "Publication"). A verifier checks a statement's `kid` this way:

- A key the descriptor lists as current (`until` is `null`), or any key when the verifier has no descriptor: the verifier MUST check that it is authorized for the host of the statement's `log` in one of the two ways above and, with the descriptor, that the descriptor lists it for the statement's period.
- A key the descriptor lists as retired: the verifier takes the public key from the descriptor's `jwk`, and MUST accept it only for a statement whose period the descriptor lists for that key, and only when at least one cosignature the verifier requires (from a witness or the fund) is present and valid. Witnesses check the key while it is current, so their cosignatures are the time-stamped evidence that it was in use when the statement was issued.

## Witnesses and the fund

`cosignatures` is a list of `{"format": "jws", "signature"}` objects, each a JWS over the same payload bytes with the cosigner's own `kid`. Before cosigning, a witness MUST:

- verify the log's signature;
- check that `previous` is the hash of the payload for the period just before the statement's: the last payload it cosigned for this log, or one it reaches by walking the chain back to that payload one period at a time. A log's first statement has `previous` null and is for the `from` period of the earliest key in the log's descriptor;
- never cosign a second, different payload for the same `(log, period)`.

A witness MAY recompute `root`, `size`, `totals` and `settlements` from the published files.

A fund that cosigns a statement also attests, for every totals row that names it, that each settlement reference listed for it exists and is credited to it (an `exact` transfer to its address; a claim on a channel whose `receiver` is its address and whose `receiverAuthorizer` is this log's; a payout it received from this log), that across every statement it has cosigned, from any log and including this log's earlier periods, the amounts listed for any one reference never add up to more than that reference credited to it, and that the amounts add up to the row. This is the "money arrived" attestation.

Each verifier chooses which cosignatures it requires. RECOMMENDED: the fund plus one independent witness.

## Receipts

A receipt proves that one line is in one cosigned statement:

```
{version: 2, line, index, proof, statement: {format, signature}, cosignatures}
```

`index` is the line's 0-based position in the sorted batch (`leaf_index` in RFC 9162 section 2.1.3.2), and `proof` is the RFC 6962 section 2.1.1 audit path, as hex strings. To verify a receipt, decode the statement payload and check that it is in JCS form, verify the log's signature and the cosignatures the verifier requires, check that the line's `period` equals the statement's, and verify the audit path as in RFC 9162 section 2.1.3.2 against `root` and `size`.

## Consistency between months

Batches are closed and never extended, so RFC 6962 consistency proofs are not needed. Consistency comes from the `previous` hash chain and the witnesses' rule against cosigning two payloads for one period. Every period from the log's first has a statement, and witnesses check that each `previous` names the payload of the period just before, so a gap is visible. A verifier that walks the chain checks the same.

## Publication

All under the log's URL:

| Request | Returns |
| --- | --- |
| `GET <log>` | The descriptor (below). |
| `GET <log>/statements/<period>` | `{statement, cosignatures}`. |
| `GET <log>/lines/<period>` | The batch's lines, sorted, each followed by LF. |
| `GET <log>/settlements/<period>` | The settlements file. |
| `GET <log>/proofs/<id>` | `{"status": "included", period, line, index, proof}` (`index` and `proof` as in "Receipts"), `{"status": "queued"}` before the statement, `{"status": "pending"}` while the purchase is `settlement_pending`, `{"status": "uncollected"}`, or 404 for an id the log never accepted. |

<!-- example:descriptor -->
```json
{
  "version": 2,
  "log": "https://facilitator.example/solidarity",
  "funds": ["eip155:84532:0x000000000000000000000000000000000000f00d"],
  "accepts": [
    {
      "network": "eip155:84532",
      "asset": "0x036cbd53842c5426634e7929541ec2318f3dcf7e",
      "scheme": "batch-settlement",
      "maxTimeoutSeconds": 60,
      "extra": { "receiverAuthorizer": "0x3333333333333333333333333333333333333333", "withdrawDelay": 86400, "name": "USDC", "version": "2" }
    },
    {
      "network": "eip155:84532",
      "asset": "0x036cbd53842c5426634e7929541ec2318f3dcf7e",
      "scheme": "exact",
      "maxTimeoutSeconds": 60,
      "minAmount": "10000",
      "extra": { "name": "USDC", "version": "2" }
    }
  ],
  "keys": [
    {
      "kid": "did:web:facilitator.example#solidarity-1",
      "from": "2026-10",
      "until": null,
      "jwk": { "kty": "OKP", "crv": "Ed25519", "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" }
    }
  ],
  "witnesses": ["did:web:witness.example"]
}
```

- `funds` is the log's allow-list; it settles only to these. Who keeps a shared allow-list is an open question ([processor-mode.md](processor-mode.md), "Fund policy").
- `accepts` (x402 logs only) lists, per network and asset, the share kinds the log accepts, with the `scheme`, `maxTimeoutSeconds` and `extra` a share's `payment.accepted` must carry and, for `exact`, the `minAmount` ([solidarity.md](solidarity.md), "Share kinds"). A processor's log omits `accepts` and MAY publish `minCollection` ([processor-mode.md](processor-mode.md)).
- `keys` lists every statement key the log has used, with its public key as a JWK (`jwk`) and the first and last period it signed (`until` is `null` for the current key). A log never removes a key from `keys` or changes its `jwk` or `from`; it sets `until` when it retires the key. The earliest `from` is the log's first period. The `jwk` above is the RFC 8032 section 7.1 TEST 1 public key, a placeholder.
- `witnesses` names the witnesses the log expects to cosign its statements.

## Security considerations

- **Second preimages.** The leaf and node prefixes of RFC 6962 prevent presenting an inner node as a leaf. Do not use Merkle trees that duplicate an odd node.
- **Key rotation.** Retired keys stay listed in the descriptor with their public keys. A verifier accepts one only for the periods listed and with a cosignature it requires, because witnesses checked the key while it was current (see "Signatures and keys").
- **What lines reveal.** Lines carry random ids and no owner, seller or item, but their amounts reveal purchase sizes (amount divided by rate). Lines of `exact` and `batch-settlement` shares can be linked to the payer's address: the settlements file lists each `exact` transfer with the same amount as its line and names each claim's channel, and the chain shows who paid. Only a processor's log keeps owners unlinkable.
- **Replay.** A log records at most one line per `id`.

## Optional share kind: `batch-settlement`

This section applies only to x402 logs that list `batch-settlement` in their descriptor, and to clients that use it; everything above is rail-independent. A 0.1% share of a small purchase is smaller than one on-chain transfer fee, so `exact` shares need a `minAmount`. `batch-settlement` moves fees from per share to per owner per period: one deposit, cumulative vouchers checked by signature, then claims and one sweep. "The binding" is [the scheme's EVM binding](https://github.com/x402-foundation/x402/blob/main/specs/schemes/batch-settlement/scheme_batch_settlement_evm.md), in which the log plays the server role for the share's channel. Using the binding this way, with the fund as receiver, needs review by that scheme's maintainers ([solidarity.md](solidarity.md), "Open questions").

**Channel.** The channel's `receiver` is the fund (`payTo`), its `receiverAuthorizer` is the log's, and its `withdrawDelay` equals the descriptor entry's `extra.withdrawDelay` (the binding's verification rules 3 to 5). The channel is funded outside the extension: the client chooses the deposit and has it executed on chain before it sends shares on the channel. The deposit is money set aside for shares, not a cap. Because claims need the log's `receiverAuthorizer` or the channel's `receiver`, which is the fund, this kind may be used in the advertisement fallback ([solidarity.md](solidarity.md), "Advertisement").

**Vouchers.**

- Each share is a voucher. A share whose payload `type` is not `"voucher"` (for example, the binding's `deposit`) is skipped with `bad-payment`, and the log executes nothing for it.
- The voucher's `maxClaimableAmount` equals `chargedCumulativeAmount + accepted.amount`. The log MUST check this as the binding's server does, and skips with `cumulative-mismatch` when it fails.
- When it accepts the share, the log adds `accepted.amount`, the full amount the payer signed, to `chargedCumulativeAmount`, even when it records less (an `upto` purchase that settled below its maximum). Each voucher then backs exactly one share, and no signed headroom is left for another share, under another id, to spend. The log claims only recorded amounts. The difference is never claimed and stays in the payer's unspent deposit.
- A share on a `settlement_pending` purchase is skipped at once with `purchase-not-final` and charged nothing, because a held voucher would be signed on the same `chargedCumulativeAmount` as the client's next one. The client keeps its channel state as it was, and the share stays counted against the cap.
- The client MUST serialize shares per channel, or open one channel per concurrent lane (a distinct `salt`). The log MUST serialize processing per channel.

**Channel snapshot.** Every solidarity response for a `batch-settlement` share, skipped or not, carries `channel` whenever the log can identify the channel: a snapshot in the shape of the binding's `extra.channelState` (`channelId`, `balance`, `totalClaimed`, `withdrawRequestedAt`, `refundNonce`, `chargedCumulativeAmount`). A `cumulative-mismatch` skip, whose voucher signature verified for the channel, also carries `signedMaxClaimable` and `signature` from the last voucher the log holds, as the binding's Corrective 402 does. No other response carries them.

The snapshot is untrusted, as in the binding's client verification rules. The client's new state is its own previous state plus the share's `accepted.amount` when the outcome is `accepted` with an `amount` at most that, or plus 0 when the share was skipped or the entry is missing. It takes the new state only if any snapshot present has that `chargedCumulativeAmount`; otherwise it leaves its state unchanged. It MUST NOT copy the snapshot into its state, except to recover after a `cumulative-mismatch`: then it adopts `chargedCumulativeAmount` only if `signature` verifies as its own voucher signature over `signedMaxClaimable`, and `chargedCumulativeAmount` is at most `signedMaxClaimable`.

**Claims.**

- The log MUST claim outstanding share vouchers when it sees a withdrawal initiated, and in any case before the withdrawal delay elapses. It claims at once any share it accepts while a withdrawal is pending.
- Shares take consecutive ranges of `totalClaimed`, each as long as its recorded amount, in the order the log accepted them, starting from the channel's `totalClaimed` when the log first accepted a share on it. Each claim the log makes sets `totalClaimed` to the end of one of those ranges, so every increase can be attributed to periods. A claim that fails while the channel can still pay is retried, and its shares stay queued.
- The binding also lets the `receiver` call `claim` and `refund` directly. A fund MUST NOT claim or refund solidarity channels. An increase in `totalClaimed` that the log did not make is unattributed: it counts in no line, total or settlement reference, and the shares it covers are uncollected.
- A share the channel can no longer pay (for example, after a finalized withdrawal) is uncollected. The log then lowers `chargedCumulativeAmount` to what it can still collect, so the client's next voucher is a `cumulative-mismatch` and the client recovers from the snapshot as above.
- A log keeps retrying a failed claim until the statement for the share's period is due. A share still queued when the log issues that statement is uncollected, and so is every later share on that channel, because claims are cumulative. The log lowers `chargedCumulativeAmount` as for a channel that can no longer pay. One failing channel never holds back a statement.
- A share has reached the fund when it is claimed: claims are final accounting in the fund's favour, and `settle(receiver, token)` sweeps claimed funds for all logs at once and anyone may call it. Totals are therefore attested by claims, not by sweeps.

**Refunds.** A `batch-settlement` refund is not a purchase refund: it returns unspent deposit (`balance - totalClaimed`), never an amount already claimed. The payer recovers unspent deposit with the binding's timed withdrawal (`initiateWithdraw`), and the log claims outstanding share vouchers before the delay elapses. Draft 2 defines no way to ask the log for a cooperative refund, and a fund never refunds a share channel.

**Privacy and ids.** A voucher is cumulative, so it shows each seller the owner's running share total in that channel, a known leak not solved in draft 2. On chain, `ChannelCreated` names the payer with the fund as receiver, and each claim updates that channel's `totalClaimed`, so anyone can read an owner's running share total and, dividing by a guessable rate, estimate their agent spending across all sellers. A voucher has no free field, so nothing the payer signs binds a share's `id`, and a seller could rewrite it (an open question). `chargedCumulativeAmount` moves by what each share signed, so a voucher a seller relayed cannot be recorded a second time under another id, unless the log has since lowered `chargedCumulativeAmount` for a channel that could no longer pay.

## Test vectors

The Merkle test vectors of the Certificate Transparency reference implementation (RFC 6962 hashing) still apply: leaves `""`, `00`, `10`, `2021`, `3031`, `40414243`, `5051525354555657`, `606162636465666768696a6b6c6d6e6f`, and the root of all eight `5dc9da79a70659a9ad559cb701ded9a2ab9d823aad2f4960cfe370eff4604328`.

The draft 2 vectors below are generated by code, not typed. They are also in [`solidarity-vectors.json`](solidarity-vectors.json), and both reference checkers published with this document ([`merkle.js`](../merkle.js) and [`verify.py`](../verify.py), run as `python3 verify.py --selftest`) reproduce them, as does the reference implementation (https://github.com/craigm26/x402-solidarity). The signing keys are the published test keys of RFC 8032 section 7.1. They are for testing only and must never sign a real statement. Addresses, transaction hashes, channel ids and ids are placeholders.

<!-- vectors:begin (generated by scripts/solidarity-vectors.mjs; do not edit by hand) -->
Three lines in the order a log recorded them. The addresses were given in mixed case; `formatLine` writes them lowercase:

```
solidarity/2|2026-10|eip155:84532:0x000000000000000000000000000000000000f00d|0x036cbd53842c5426634e7929541ec2318f3dcf7e|dc13e8c42d0ba99ab65c4f8572486995|48000
solidarity/2|2026-10|eip155:84532:0x000000000000000000000000000000000000beef|0x036cbd53842c5426634e7929541ec2318f3dcf7e|dc597d2ef00f8d8d9369ace5607f8a8a|300000
solidarity/2|2026-10|eip155:84532:0x000000000000000000000000000000000000f00d|0x036cbd53842c5426634e7929541ec2318f3dcf7e|ed8425048b570cbbee42201f0b128fec|1250
```

Sorted bytewise, they form a batch of size 3 with root `ec6cb7dd91af90ccda9646000fbee7ebec31705637b8159a7381a58adf6e7f36` and these totals:

```json
[
  {
    "fund": "eip155:84532:0x000000000000000000000000000000000000beef",
    "asset": "0x036cbd53842c5426634e7929541ec2318f3dcf7e",
    "lines": 1,
    "amount": "300000"
  },
  {
    "fund": "eip155:84532:0x000000000000000000000000000000000000f00d",
    "asset": "0x036cbd53842c5426634e7929541ec2318f3dcf7e",
    "lines": 2,
    "amount": "49250"
  }
]
```

The settlements file (each entry ends with LF) has SHA-256 `7b4feff463c31223dc18c6ab3b02f137b3878cc79cf7683ed3095a3429251bfd`:

```
eip155:84532:0x000000000000000000000000000000000000beef|0x036cbd53842c5426634e7929541ec2318f3dcf7e|claim:0xabb98ffcdd426939b0995444babab4614d272f6e9eb2e20e47f25cc6d0cc9aac:0x73086a3409dc6fe96230fb9a82d1a36f7f948c45833efabf560a7ca487231870|300000
eip155:84532:0x000000000000000000000000000000000000f00d|0x036cbd53842c5426634e7929541ec2318f3dcf7e|claim:0xabb98ffcdd426939b0995444babab4614d272f6e9eb2e20e47f25cc6d0cc9aac:0x6b7963be289907125e9f84aed497bf7b437924bc86eaf20cdf88aa97c275b539|1250
eip155:84532:0x000000000000000000000000000000000000f00d|0x036cbd53842c5426634e7929541ec2318f3dcf7e|exact:0xf84ba932a6d28cb2c387241378164491630afb7f64a5d94040bbe0e20cee92f0|48000
```

An empty batch has size 0 and root `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`.

Statement for 2026-10 (the log's first period, so `previous` is null), as its exact JCS bytes. SHA-256: `f5a17de9204425442c039eebd5fa1c309f81045ab4f0fba7a15c12a88897a158`.

```json
{"issuedAt":1793836800,"log":"https://log.example/solidarity","period":"2026-10","previous":null,"root":"ec6cb7dd91af90ccda9646000fbee7ebec31705637b8159a7381a58adf6e7f36","settlements":"7b4feff463c31223dc18c6ab3b02f137b3878cc79cf7683ed3095a3429251bfd","size":3,"totals":[{"amount":"300000","asset":"0x036cbd53842c5426634e7929541ec2318f3dcf7e","fund":"eip155:84532:0x000000000000000000000000000000000000beef","lines":1},{"amount":"49250","asset":"0x036cbd53842c5426634e7929541ec2318f3dcf7e","fund":"eip155:84532:0x000000000000000000000000000000000000f00d","lines":2}],"version":2}
```

Statement for 2026-11 (empty), whose `previous` is the hash above. SHA-256: `a5f5f7a6e549206fbf2a589d0e3b85a305091644fe1d3484af25d9964d8fd263`.

```json
{"issuedAt":1796428800,"log":"https://log.example/solidarity","period":"2026-11","previous":"f5a17de9204425442c039eebd5fa1c309f81045ab4f0fba7a15c12a88897a158","root":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","settlements":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","size":0,"totals":[],"version":2}
```

The 2026-10 statement signed with the RFC 8032 section 7.1 TEST 1 key (public key `d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a`, kid `did:web:log.example#test-only-1`). Test only:

```
eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDp3ZWI6bG9nLmV4YW1wbGUjdGVzdC1vbmx5LTEifQ.eyJpc3N1ZWRBdCI6MTc5MzgzNjgwMCwibG9nIjoiaHR0cHM6Ly9sb2cuZXhhbXBsZS9zb2xpZGFyaXR5IiwicGVyaW9kIjoiMjAyNi0xMCIsInByZXZpb3VzIjpudWxsLCJyb290IjoiZWM2Y2I3ZGQ5MWFmOTBjY2RhOTY0NjAwMGZiZWU3ZWJlYzMxNzA1NjM3YjgxNTlhNzM4MWE1OGFkZjZlN2YzNiIsInNldHRsZW1lbnRzIjoiN2I0ZmVmZjQ2M2MzMTIyM2RjMThjNmFiM2IwMmYxMzdiMzg3OGNjNzljZjc2ODNlZDMwOTVhMzQyOTI1MWJmZCIsInNpemUiOjMsInRvdGFscyI6W3siYW1vdW50IjoiMzAwMDAwIiwiYXNzZXQiOiIweDAzNmNiZDUzODQyYzU0MjY2MzRlNzkyOTU0MWVjMjMxOGYzZGNmN2UiLCJmdW5kIjoiZWlwMTU1Ojg0NTMyOjB4MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYmVlZiIsImxpbmVzIjoxfSx7ImFtb3VudCI6IjQ5MjUwIiwiYXNzZXQiOiIweDAzNmNiZDUzODQyYzU0MjY2MzRlNzkyOTU0MWVjMjMxOGYzZGNmN2UiLCJmdW5kIjoiZWlwMTU1Ojg0NTMyOjB4MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwZjAwZCIsImxpbmVzIjoyfV0sInZlcnNpb24iOjJ9.ey5shRywBFp9S0q9C4HzoCcLepc3-DHBrp-c34GM2fdTDapRfahOg78n3vOIycogjxb_2I8C9yvHf5UsheS6BA
```

A receipt for the first input line, with a cosignature by the TEST 2 key (public key `3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c`):

```json
{
  "version": 2,
  "line": "solidarity/2|2026-10|eip155:84532:0x000000000000000000000000000000000000f00d|0x036cbd53842c5426634e7929541ec2318f3dcf7e|dc13e8c42d0ba99ab65c4f8572486995|48000",
  "index": 1,
  "proof": [
    "c149f6dc9f15d8419ed7113988c0f61a4ad0b2689b73abf1c64b4fc124ef1fcc",
    "2673be4d47dacd1f92bd7b442ee2a72d015cd2f6af5ddf31b555e8048728875f"
  ],
  "statement": {
    "format": "jws",
    "signature": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDp3ZWI6bG9nLmV4YW1wbGUjdGVzdC1vbmx5LTEifQ.eyJpc3N1ZWRBdCI6MTc5MzgzNjgwMCwibG9nIjoiaHR0cHM6Ly9sb2cuZXhhbXBsZS9zb2xpZGFyaXR5IiwicGVyaW9kIjoiMjAyNi0xMCIsInByZXZpb3VzIjpudWxsLCJyb290IjoiZWM2Y2I3ZGQ5MWFmOTBjY2RhOTY0NjAwMGZiZWU3ZWJlYzMxNzA1NjM3YjgxNTlhNzM4MWE1OGFkZjZlN2YzNiIsInNldHRsZW1lbnRzIjoiN2I0ZmVmZjQ2M2MzMTIyM2RjMThjNmFiM2IwMmYxMzdiMzg3OGNjNzljZjc2ODNlZDMwOTVhMzQyOTI1MWJmZCIsInNpemUiOjMsInRvdGFscyI6W3siYW1vdW50IjoiMzAwMDAwIiwiYXNzZXQiOiIweDAzNmNiZDUzODQyYzU0MjY2MzRlNzkyOTU0MWVjMjMxOGYzZGNmN2UiLCJmdW5kIjoiZWlwMTU1Ojg0NTMyOjB4MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYmVlZiIsImxpbmVzIjoxfSx7ImFtb3VudCI6IjQ5MjUwIiwiYXNzZXQiOiIweDAzNmNiZDUzODQyYzU0MjY2MzRlNzkyOTU0MWVjMjMxOGYzZGNmN2UiLCJmdW5kIjoiZWlwMTU1Ojg0NTMyOjB4MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwZjAwZCIsImxpbmVzIjoyfV0sInZlcnNpb24iOjJ9.ey5shRywBFp9S0q9C4HzoCcLepc3-DHBrp-c34GM2fdTDapRfahOg78n3vOIycogjxb_2I8C9yvHf5UsheS6BA"
  },
  "cosignatures": [
    {
      "format": "jws",
      "signature": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDp3ZWI6d2l0bmVzcy5leGFtcGxlI3Rlc3Qtb25seS0yIn0.eyJpc3N1ZWRBdCI6MTc5MzgzNjgwMCwibG9nIjoiaHR0cHM6Ly9sb2cuZXhhbXBsZS9zb2xpZGFyaXR5IiwicGVyaW9kIjoiMjAyNi0xMCIsInByZXZpb3VzIjpudWxsLCJyb290IjoiZWM2Y2I3ZGQ5MWFmOTBjY2RhOTY0NjAwMGZiZWU3ZWJlYzMxNzA1NjM3YjgxNTlhNzM4MWE1OGFkZjZlN2YzNiIsInNldHRsZW1lbnRzIjoiN2I0ZmVmZjQ2M2MzMTIyM2RjMThjNmFiM2IwMmYxMzdiMzg3OGNjNzljZjc2ODNlZDMwOTVhMzQyOTI1MWJmZCIsInNpemUiOjMsInRvdGFscyI6W3siYW1vdW50IjoiMzAwMDAwIiwiYXNzZXQiOiIweDAzNmNiZDUzODQyYzU0MjY2MzRlNzkyOTU0MWVjMjMxOGYzZGNmN2UiLCJmdW5kIjoiZWlwMTU1Ojg0NTMyOjB4MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYmVlZiIsImxpbmVzIjoxfSx7ImFtb3VudCI6IjQ5MjUwIiwiYXNzZXQiOiIweDAzNmNiZDUzODQyYzU0MjY2MzRlNzkyOTU0MWVjMjMxOGYzZGNmN2UiLCJmdW5kIjoiZWlwMTU1Ojg0NTMyOjB4MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwZjAwZCIsImxpbmVzIjoyfV0sInZlcnNpb24iOjJ9.W7MS_Rogc6unqghCbAQHQke0u23o_0dWWhbNldRvveDt4oVPikWDJueZ2Ia57vwnC9s55qVIVfWsyd_OM-_qAQ"
    }
  ]
}
```
<!-- vectors:end -->

## Open questions

1. **Statement signatures.** JWS only, or also EIP-712 as offer-and-receipt supports (which needs canonical types for the `totals` array)?
2. **Witnesses.** What default should verifiers require (the fund plus one independent witness is proposed), and who runs independent witnesses?
3. **Deadline.** Is 15 days after the period the right deadline for a statement?
